diff --git a/DEPS b/DEPS
index ce63a9f..a5a1df9 100644
--- a/DEPS
+++ b/DEPS
@@ -138,11 +138,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': '08a8496135c5d3570e7d86f9fb902bd45f6e997f',
+  'skia_revision': 'd30e039d07f1c7d2090a9422e43c000fa55c5e45',
   # 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': '8588d1575543e17087081281e2b29c7b001db294',
+  'v8_revision': '4ffaaa6ad4ac090ef4d2a31cdc31af4ceab9fe0a',
   # 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.
@@ -150,15 +150,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '12f38c49d0ba6bec94808f072a3e29e0df85f081',
+  'angle_revision': 'bf826481548182905654cc71a76cd584561ab2e0',
   # 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': '26621d54a086fcf2775e9eed85992f22660fcef1',
+  'swiftshader_revision': 'd188b1ad9839bdbdc72248e75f2dc6b864eb0b5e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '92eba93b33c823f2df281aea5ff5204b5907383c',
+  'pdfium_revision': '89d1c7c0916ab66a8aec96853c6748e63f3f5ebc',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -201,7 +201,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'e502dbcca0143f5ea1a82b62ef81817d0f0c961f',
+  'catapult_revision': '2c7abe0de5aa57ec3937351805319d7d356c02d6',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -269,7 +269,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'shaderc_revision': 'c21dd10cf1969ae4721e3af7db621076de27bc17',
+  'shaderc_revision': '7b84ab7b2ffc31811518b03832a674c8eb1c5a76',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -807,7 +807,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '15e76240d795225e0198b888dcd005ae6aab726e',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'f496c5c1c52c9feebd202fb2c61d7ebe7d7062a0',
       'condition': 'checkout_linux',
   },
 
@@ -1058,7 +1058,7 @@
   },
 
   'src/third_party/libjpeg_turbo':
-    Var('chromium_git') + '/chromium/deps/libjpeg_turbo.git' + '@' + '2a34770be9715cfc1badff10fceba52dd393b094',
+    Var('chromium_git') + '/chromium/deps/libjpeg_turbo.git' + '@' + 'd78acdd58d99e49d9b7c1d97c347d4a9239c3f6b',
 
   'src/third_party/liblouis/src': {
       'url': Var('chromium_git') + '/external/liblouis-github.git' + '@' + '97ce1c67fccbd3668291b7e63c06161c095d49f2',
@@ -1187,7 +1187,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '74222959994106084c1d1f69566e631aab30806f',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  'f80e84c5cc7e3536e739644c42ab47c8de2640de',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1355,7 +1355,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '6f0b34abee8dba611c253738d955c59f703c147a',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '856ca198e21cab0b39933658f095762e0e5e1660',
+    Var('webrtc_git') + '/src.git' + '@' + 'e112bb84ef1fbf523f974a21a70ef0388b2070d2',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1396,7 +1396,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@f8393e0eb8714b07fa7f7a1c55fb6ff6952f86d6',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@231d2ee261e7ada45bcb5d50521e1cbe8da083ff',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/WATCHLISTS b/WATCHLISTS
index ace0367..4425981 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -1414,6 +1414,12 @@
                   '|ios/chrome/browser/ui/payments'\
                   '|ios/web/public/payments'
     },
+    'pdf': {
+      'filepath': 'components/pdf'\
+                  '|chrome/browser/pdf'\
+                  '|chrome/browser/ui/pdf'\
+                  '|pdf'
+    },
     'pepper_api': {
       'filepath': 'ppapi/api'\
                   '|ppapi/c',
@@ -2478,6 +2484,7 @@
                  'danyao+paymentswatch@chromium.org'],
     'payments_ios': ['mahmadi+paymentsioswatch@chromium.org',
                      'danyao+paymentswatch@chromium.org'],
+    'pdf': ['pdf-reviews@chromium.org'],
     'pepper_api': ['binji+watch@chromium.org',
                    'bradnelson+warch@chromium.org',
                    'ihf+watch@chromium.org',
diff --git a/ash/app_list/app_list_controller_impl.cc b/ash/app_list/app_list_controller_impl.cc
index f9fe6febd..c211c39 100644
--- a/ash/app_list/app_list_controller_impl.cc
+++ b/ash/app_list/app_list_controller_impl.cc
@@ -1420,6 +1420,11 @@
 void AppListControllerImpl::NotifyHomeLauncherAnimationTransition(
     AnimationTrigger trigger,
     bool launcher_will_show) {
+  // The AppListView may not exist if this is happening after tablet mode
+  // has started, but before the view is created.
+  if (!presenter_.GetView())
+    return;
+
   presenter_.GetView()->OnTabletModeAnimationTransitionNotified(
       CalculateAnimationTransitionForMetrics(trigger, launcher_will_show));
 }
diff --git a/ash/app_list/app_list_controller_impl_unittest.cc b/ash/app_list/app_list_controller_impl_unittest.cc
index f487772..8c9dd74 100644
--- a/ash/app_list/app_list_controller_impl_unittest.cc
+++ b/ash/app_list/app_list_controller_impl_unittest.cc
@@ -12,6 +12,8 @@
 #include "ash/app_list/views/search_box_view.h"
 #include "ash/home_screen/home_launcher_gesture_handler.h"
 #include "ash/home_screen/home_screen_controller.h"
+#include "ash/ime/ime_controller.h"
+#include "ash/ime/test_ime_controller_client.h"
 #include "ash/keyboard/ash_keyboard_controller.h"
 #include "ash/public/cpp/presentation_time_recorder.h"
 #include "ash/shelf/shelf.h"
@@ -28,6 +30,7 @@
 #include "ui/events/test/event_generator.h"
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/views/message_popup_view.h"
+#include "ui/views/controls/textfield/textfield_test_api.h"
 
 namespace ash {
 
@@ -180,6 +183,45 @@
       GetAppListView()->GetAppListBackgroundShieldForTest()->GetTransform());
 }
 
+// Verify that when the emoji panel shows and AppListView is in Peeking state,
+// AppListView's rounded corners should be hidden (see https://crbug.com/950468)
+TEST_F(AppListControllerImplTest, HideRoundingCornersWhenEmojiShows) {
+  // Set IME client. Otherwise the emoji panel is unable to show.
+  ImeController* ime_controller = Shell::Get()->ime_controller();
+  TestImeControllerClient client;
+  ime_controller->SetClient(client.CreateInterfacePtr());
+
+  // Show the app list view and right-click on the search box with mouse. So the
+  // text field's context menu shows.
+  ShowAppListNow();
+  app_list::SearchBoxView* search_box_view =
+      GetAppListView()->app_list_main_view()->search_box_view();
+  gfx::Point center_point = search_box_view->GetBoundsInScreen().CenterPoint();
+  ui::test::EventGenerator* event_generator = GetEventGenerator();
+  event_generator->MoveMouseTo(center_point);
+  event_generator->ClickRightButton();
+
+  // Expect that the first item in the context menu should be "Emoji". Show the
+  // emoji panel.
+  auto text_field_api =
+      std::make_unique<views::TextfieldTestApi>(search_box_view->search_box());
+  ASSERT_EQ("Emoji",
+            base::UTF16ToUTF8(
+                text_field_api->context_menu_contents()->GetLabelAt(0)));
+  text_field_api->context_menu_contents()->ActivatedAt(0);
+
+  // Wait for enough time. Then expect that AppListView is pushed up.
+  base::RunLoop().RunUntilIdle();
+  ASSERT_EQ(gfx::Point(0, 0), GetAppListView()->GetBoundsInScreen().origin());
+
+  // AppListBackgroundShield is translated to hide the rounded corners.
+  gfx::Transform expected_transform;
+  expected_transform.Translate(0, -app_list::kAppListBackgroundRadius);
+  EXPECT_EQ(
+      expected_transform,
+      GetAppListView()->GetAppListBackgroundShieldForTest()->GetTransform());
+}
+
 // Verifies that in clamshell mode the bounds of AppListView are correct when
 // the AppListView is in PEEKING state and the virtual keyboard is enabled (see
 // https://crbug.com/944233).
diff --git a/ash/app_list/app_list_presenter_delegate_unittest.cc b/ash/app_list/app_list_presenter_delegate_unittest.cc
index 370d05ba..ede47e9 100644
--- a/ash/app_list/app_list_presenter_delegate_unittest.cc
+++ b/ash/app_list/app_list_presenter_delegate_unittest.cc
@@ -153,19 +153,14 @@
 class AppListPresenterDelegateTest
     : public AppListPresenterDelegateZeroStateTest {
  public:
-  AppListPresenterDelegateTest() = default;
-  ~AppListPresenterDelegateTest() override = default;
-
-  // testing::Test:
-  void SetUp() override {
-    AppListPresenterDelegateZeroStateTest::SetUp();
-
+  AppListPresenterDelegateTest() {
     // Zeros state changes expected UI behavior. Most test cases in this suite
     // are the expected UI behavior with zero state being disabled.
     // TODO(jennyz): Add new test cases for zero state, crbug.com/925195.
     scoped_feature_list_.InitAndDisableFeature(
         app_list_features::kEnableZeroStateSuggestions);
   }
+  ~AppListPresenterDelegateTest() override = default;
 
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
@@ -1376,13 +1371,14 @@
 class AppListPresenterDelegateHomeLauncherTest
     : public AppListPresenterDelegateTest {
  public:
-  AppListPresenterDelegateHomeLauncherTest() = default;
+  AppListPresenterDelegateHomeLauncherTest() {
+    scoped_feature_list_.InitWithFeatures(
+        {app_list_features::kEnableBackgroundBlur}, {});
+  }
   ~AppListPresenterDelegateHomeLauncherTest() override = default;
 
   // testing::Test:
   void SetUp() override {
-    scoped_feature_list_.InitWithFeatures(
-        {app_list_features::kEnableBackgroundBlur}, {});
     AppListPresenterDelegateTest::SetUp();
     GetAppListTestHelper()->WaitUntilIdle();
   }
diff --git a/ash/app_list/views/app_list_view.cc b/ash/app_list/views/app_list_view.cc
index 26b7cd34..d2f5c6e 100644
--- a/ash/app_list/views/app_list_view.cc
+++ b/ash/app_list/views/app_list_view.cc
@@ -1881,15 +1881,8 @@
                                         ui::PropertyChangeReason reason) {
   DCHECK_EQ(GetWidget()->GetNativeView(), window);
 
-  // When the virtual keyboard shows, the AppListView is moved upward to avoid
-  // the overlapping area with the virtual keyboard. As a result, its bottom
-  // side may be on the display edge. Stop showing the rounded corners under
-  // this circumstance.
-  const bool hide_rounded_corners =
-      app_list_state_ == ash::AppListViewState::kHalf && new_bounds.y() == 0;
-
   gfx::Transform transform;
-  if (hide_rounded_corners)
+  if (ShouldHideRoundedCorners(new_bounds))
     transform.Translate(0, -kAppListBackgroundRadius);
 
   app_list_background_shield_->SetTransform(transform);
@@ -2130,13 +2123,10 @@
       transform.Translate(
           0, -kAppListBackgroundRadius * (app_list_transition_progress - 1));
     }
-  } else if (is_fullscreen() ||
-             (app_list_state_ == ash::AppListViewState::kHalf &&
-              GetBoundsInScreen().y() == 0)) {
+  } else if (is_fullscreen() || ShouldHideRoundedCorners(GetBoundsInScreen())) {
     // AppListView::Layout may be called after OnWindowBoundsChanged. It may
     // reset the transform of |app_list_background_shield_|. So hide the rounded
-    // corners when AppListView is in Half state and its bottom is on the
-    // display edge.
+    // corners here when ShouldHideRoundedCorners returns true.
     transform.Translate(0, -kAppListBackgroundRadius);
   }
   app_list_background_shield_->SetTransform(transform);
@@ -2165,6 +2155,16 @@
          GetPreferredWidgetYForState(target_state);
 }
 
+bool AppListView::ShouldHideRoundedCorners(const gfx::Rect& bounds) const {
+  // When the virtual keyboard shows, the AppListView is moved upward to avoid
+  // the overlapping area with the virtual keyboard. As a result, its bottom
+  // side may be on the display edge. Stop showing the rounded corners under
+  // this circumstance.
+  return (app_list_state_ == ash::AppListViewState::kPeeking ||
+          app_list_state_ == ash::AppListViewState::kHalf) &&
+         bounds.y() == 0;
+}
+
 void AppListView::OnStateTransitionAnimationCompleted() {
   delegate_->OnStateTransitionAnimationCompleted(app_list_state_);
 }
diff --git a/ash/app_list/views/app_list_view.h b/ash/app_list/views/app_list_view.h
index 3661178..1d60e60e 100644
--- a/ash/app_list/views/app_list_view.h
+++ b/ash/app_list/views/app_list_view.h
@@ -440,6 +440,10 @@
   bool ShouldUpdateChildViewsDuringAnimation(
       ash::AppListViewState target_state) const;
 
+  // Returns whether AppList's rounded corners should be hidden based on
+  // |bounds|.
+  bool ShouldHideRoundedCorners(const gfx::Rect& bounds) const;
+
   AppListViewDelegate* delegate_;    // Weak. Owned by AppListService.
   AppListModel* const model_;        // Not Owned.
   SearchModel* const search_model_;  // Not Owned.
diff --git a/ash/public/cpp/window_finder.h b/ash/public/cpp/window_finder.h
index d70139b8..ca154de4 100644
--- a/ash/public/cpp/window_finder.h
+++ b/ash/public/cpp/window_finder.h
@@ -20,15 +20,13 @@
 namespace ash {
 
 // Finds the topmost window at |screen_point| with ignoring |ignore|. If
-// |real_topmost| is not nullptr, it will be updated to the topmost visible
-// window regardless of |ignore|. If overview is active when this function is
-// called, the overview window that contains |screen_point| will be returned.
-// Note this overview window might not be visibile (e.g., it represents an aura
-// window whose window state is MINIMIZED).
+// overview is active when this function is called, the overview window that
+// contains |screen_point| will be returned. Note this overview window might not
+// be visibile (e.g., it represents an aura window whose window state is
+// MINIMIZED).
 ASH_EXPORT aura::Window* GetTopmostWindowAtPoint(
     const gfx::Point& screen_point,
-    const std::set<aura::Window*>& ignore,
-    aura::Window** real_topmost);
+    const std::set<aura::Window*>& ignore);
 
 }  // namespace ash
 
diff --git a/ash/system/bluetooth/tray_bluetooth_helper_legacy.cc b/ash/system/bluetooth/tray_bluetooth_helper_legacy.cc
index 7e769bb..180c157 100644
--- a/ash/system/bluetooth/tray_bluetooth_helper_legacy.cc
+++ b/ash/system/bluetooth/tray_bluetooth_helper_legacy.cc
@@ -18,6 +18,8 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
+#include "base/time/default_clock.h"
+#include "base/time/time.h"
 #include "device/bluetooth/bluetooth_adapter.h"
 #include "device/bluetooth/bluetooth_adapter_factory.h"
 #include "device/bluetooth/bluetooth_device.h"
@@ -187,6 +189,8 @@
 }
 
 void TrayBluetoothHelperLegacy::StartBluetoothDiscovering() {
+  discovery_start_timestamp_ = base::DefaultClock::GetInstance()->Now();
+
   if (HasBluetoothDiscoverySession()) {
     LOG(WARNING) << "Already have active Bluetooth device discovery session.";
     return;
@@ -200,6 +204,8 @@
 }
 
 void TrayBluetoothHelperLegacy::StopBluetoothDiscovering() {
+  discovery_start_timestamp_ = base::Time();
+
   should_run_discovery_ = false;
   if (!HasBluetoothDiscoverySession()) {
     LOG(WARNING) << "No active Bluetooth device discovery session.";
@@ -219,6 +225,14 @@
     return;
   }
 
+  if (!discovery_start_timestamp_.is_null()) {
+    device::RecordDeviceSelectionDuration(
+        base::DefaultClock::GetInstance()->Now() - discovery_start_timestamp_,
+        device::BluetoothUiSurface::kSystemTray, device->IsPaired(),
+        device->GetType());
+    discovery_start_timestamp_ = base::Time();
+  }
+
   // Extra consideration taken for already paired devices, for metrics
   // collection.
   if (device->IsPaired()) {
diff --git a/ash/system/bluetooth/tray_bluetooth_helper_legacy.h b/ash/system/bluetooth/tray_bluetooth_helper_legacy.h
index 119009e..a9aa123 100644
--- a/ash/system/bluetooth/tray_bluetooth_helper_legacy.h
+++ b/ash/system/bluetooth/tray_bluetooth_helper_legacy.h
@@ -17,9 +17,13 @@
 #include "device/bluetooth/bluetooth_adapter.h"
 #include "services/device/public/mojom/bluetooth_system.mojom.h"
 
+namespace base {
+class Time;
+}  // namespace base
+
 namespace device {
 class BluetoothDiscoverySession;
-}
+}  // namespace device
 
 namespace ash {
 
@@ -71,6 +75,11 @@
   device::mojom::BluetoothSystem::State last_state_ =
       device::mojom::BluetoothSystem::State::kUnavailable;
 
+  // The time at which discovery started, effectively when the user opened the
+  // System Tray Bluetooth options with Bluetooth on, or when Bluetooth turned
+  // on while the Bluetooth options were open.
+  base::Time discovery_start_timestamp_;
+
   // Object could be deleted during a prolonged Bluetooth operation.
   base::WeakPtrFactory<TrayBluetoothHelperLegacy> weak_ptr_factory_;
 
diff --git a/ash/wm/window_finder.cc b/ash/wm/window_finder.cc
index e173304..7413d5d3 100644
--- a/ash/wm/window_finder.cc
+++ b/ash/wm/window_finder.cc
@@ -51,8 +51,7 @@
     const gfx::Point& screen_point,
     aura::Window* window,
     aura::WindowTargeter* targeter,
-    const std::set<aura::Window*> ignore,
-    aura::Window** real_topmost) {
+    const std::set<aura::Window*> ignore) {
   if (!window->IsVisible())
     return nullptr;
 
@@ -62,11 +61,8 @@
     return nullptr;
 
   if (IsTopLevelWindow(window)) {
-    if (IsWindowTargeted(window, screen_point, targeter)) {
-      if (real_topmost && !(*real_topmost))
-        *real_topmost = window;
+    if (IsWindowTargeted(window, screen_point, targeter))
       return (ignore.find(window) == ignore.end()) ? window : nullptr;
-    }
     return nullptr;
   }
 
@@ -76,7 +72,7 @@
     aura::WindowTargeter* child_targeter =
         (*i)->targeter() ? (*i)->targeter() : targeter;
     aura::Window* result = GetTopmostWindowAtPointWithinWindow(
-        screen_point, *i, child_targeter, ignore, real_topmost);
+        screen_point, *i, child_targeter, ignore);
     if (result)
       return result;
   }
@@ -115,18 +111,14 @@
 namespace ash {
 
 aura::Window* GetTopmostWindowAtPoint(const gfx::Point& screen_point,
-                                      const std::set<aura::Window*>& ignore,
-                                      aura::Window** real_topmost) {
-  if (real_topmost)
-    *real_topmost = nullptr;
-  aura::Window* root = wm::GetRootWindowAt(screen_point);
-  // GetTopmostWindowAtPointWithinWindow() always needs to be called to update
-  // |real_topmost| correctly.
-  aura::Window* topmost_window = GetTopmostWindowAtPointWithinWindow(
-      screen_point, root, root->targeter(), ignore, real_topmost);
+                                      const std::set<aura::Window*>& ignore) {
   aura::Window* overview_window =
       GetToplevelWindowInOverviewAtPoint(screen_point, ignore);
-  return overview_window ? overview_window : topmost_window;
+  if (overview_window)
+    return overview_window;
+  aura::Window* root = wm::GetRootWindowAt(screen_point);
+  return GetTopmostWindowAtPointWithinWindow(screen_point, root,
+                                             root->targeter(), ignore);
 }
 
 }  // namespace ash
diff --git a/ash/wm/window_finder_unittest.cc b/ash/wm/window_finder_unittest.cc
index bfc5a3b2..f1fd8d9a 100644
--- a/ash/wm/window_finder_unittest.cc
+++ b/ash/wm/window_finder_unittest.cc
@@ -23,8 +23,7 @@
       CreateTestWindow(gfx::Rect(0, 0, 100, 100));
   std::set<aura::Window*> ignore;
 
-  EXPECT_EQ(window1.get(),
-            GetTopmostWindowAtPoint(gfx::Point(10, 10), ignore, nullptr));
+  EXPECT_EQ(window1.get(), GetTopmostWindowAtPoint(gfx::Point(10, 10), ignore));
 }
 
 TEST_F(WindowFinderTest, MultipleDisplays) {
@@ -37,12 +36,10 @@
   ASSERT_NE(window1->GetRootWindow(), window2->GetRootWindow());
 
   std::set<aura::Window*> ignore;
-  EXPECT_EQ(window1.get(),
-            GetTopmostWindowAtPoint(gfx::Point(10, 10), ignore, nullptr));
+  EXPECT_EQ(window1.get(), GetTopmostWindowAtPoint(gfx::Point(10, 10), ignore));
   EXPECT_EQ(window2.get(),
-            GetTopmostWindowAtPoint(gfx::Point(210, 10), ignore, nullptr));
-  EXPECT_EQ(nullptr,
-            GetTopmostWindowAtPoint(gfx::Point(10, 210), ignore, nullptr));
+            GetTopmostWindowAtPoint(gfx::Point(210, 10), ignore));
+  EXPECT_EQ(nullptr, GetTopmostWindowAtPoint(gfx::Point(10, 210), ignore));
 }
 
 TEST_F(WindowFinderTest, WindowTargeterWithHitTestRects) {
@@ -53,21 +50,14 @@
 
   std::set<aura::Window*> ignore;
 
-  aura::Window* real_topmost = nullptr;
-  EXPECT_EQ(window2.get(),
-            GetTopmostWindowAtPoint(gfx::Point(10, 10), ignore, &real_topmost));
-  EXPECT_EQ(window2.get(), real_topmost);
+  EXPECT_EQ(window2.get(), GetTopmostWindowAtPoint(gfx::Point(10, 10), ignore));
 
   auto targeter = std::make_unique<aura::WindowTargeter>();
   targeter->SetInsets(gfx::Insets(0, 50, 0, 0));
   window2->SetEventTargeter(std::move(targeter));
 
-  EXPECT_EQ(window1.get(),
-            GetTopmostWindowAtPoint(gfx::Point(10, 10), ignore, &real_topmost));
-  EXPECT_EQ(window1.get(), real_topmost);
-  EXPECT_EQ(window2.get(),
-            GetTopmostWindowAtPoint(gfx::Point(60, 10), ignore, &real_topmost));
-  EXPECT_EQ(window2.get(), real_topmost);
+  EXPECT_EQ(window1.get(), GetTopmostWindowAtPoint(gfx::Point(10, 10), ignore));
+  EXPECT_EQ(window2.get(), GetTopmostWindowAtPoint(gfx::Point(60, 10), ignore));
 }
 
 // Tests that when overview is active, GetTopmostWindowAtPoint() will return
@@ -94,15 +84,14 @@
       grid->GetOverviewItemContaining(window2.get())->target_bounds());
 
   std::set<aura::Window*> ignore;
-  aura::Window* real_topmost = nullptr;
-  EXPECT_EQ(window1.get(), GetTopmostWindowAtPoint(bounds1.CenterPoint(),
-                                                   ignore, &real_topmost));
-  EXPECT_EQ(window2.get(), GetTopmostWindowAtPoint(bounds2.CenterPoint(),
-                                                   ignore, &real_topmost));
+  EXPECT_EQ(window1.get(),
+            GetTopmostWindowAtPoint(bounds1.CenterPoint(), ignore));
+  EXPECT_EQ(window2.get(),
+            GetTopmostWindowAtPoint(bounds2.CenterPoint(), ignore));
 
   wm::GetWindowState(window1.get())->Minimize();
-  EXPECT_EQ(window1.get(), GetTopmostWindowAtPoint(bounds1.CenterPoint(),
-                                                   ignore, &real_topmost));
+  EXPECT_EQ(window1.get(),
+            GetTopmostWindowAtPoint(bounds1.CenterPoint(), ignore));
 }
 
 }  // namespace ash
diff --git a/ash/wm/window_resizer.cc b/ash/wm/window_resizer.cc
index c9ea3c8..879538d 100644
--- a/ash/wm/window_resizer.cc
+++ b/ash/wm/window_resizer.cc
@@ -13,7 +13,6 @@
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_delegate.h"
-#include "ui/aura/window_tracker.h"
 #include "ui/aura/window_tree_host.h"
 #include "ui/base/hit_test.h"
 #include "ui/base/ui_base_types.h"
@@ -269,12 +268,15 @@
 void WindowResizer::SetBoundsDuringResize(const gfx::Rect& bounds) {
   aura::Window* window = GetTarget();
   DCHECK(window);
+  auto ptr = weak_ptr_factory_.GetWeakPtr();
   const gfx::Rect original_bounds = window->bounds();
   window->SetBounds(bounds);
-  aura::WindowTracker tracker;
-  tracker.Add(window);
-  if (tracker.windows().empty())
-    return;  // Assume we've been destroyed.
+
+  // Resizer can be destroyed when a window is attached during tab dragging.
+  // crbug.com/970911.
+  if (!ptr)
+    return;
+
   if (bounds.size() == original_bounds.size())
     return;
   recorder_->RequestNext();
diff --git a/ash/wm/window_resizer.h b/ash/wm/window_resizer.h
index 35e9899..bd3495a1 100644
--- a/ash/wm/window_resizer.h
+++ b/ash/wm/window_resizer.h
@@ -12,6 +12,7 @@
 #include "ash/wm/drag_details.h"
 #include "ash/wm/window_state.h"
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "ui/wm/public/window_move_client.h"
 
 namespace aura {
@@ -113,6 +114,8 @@
 
   std::unique_ptr<PresentationTimeRecorder> recorder_;
 
+  base::WeakPtrFactory<WindowResizer> weak_ptr_factory_{this};
+
   DISALLOW_COPY_AND_ASSIGN(WindowResizer);
 };
 
diff --git a/base/feature_list.cc b/base/feature_list.cc
index 56aa03c..d602ce94 100644
--- a/base/feature_list.cc
+++ b/base/feature_list.cc
@@ -15,6 +15,7 @@
 #include "base/pickle.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
+#include "build/build_config.h"
 
 namespace base {
 
@@ -78,7 +79,7 @@
 
 #if defined(DCHECK_IS_CONFIGURABLE)
 const Feature kDCheckIsFatalFeature{"DcheckIsFatal",
-                                    base::FEATURE_DISABLED_BY_DEFAULT};
+                                    FEATURE_DISABLED_BY_DEFAULT};
 #endif  // defined(DCHECK_IS_CONFIGURABLE)
 
 FeatureList::FeatureList() = default;
@@ -214,8 +215,8 @@
 }
 
 // static
-std::vector<base::StringPiece> FeatureList::SplitFeatureListString(
-    base::StringPiece input) {
+std::vector<StringPiece> FeatureList::SplitFeatureListString(
+    StringPiece input) {
   return SplitStringPiece(input, ",", TRIM_WHITESPACE, SPLIT_WANT_NONEMPTY);
 }
 
@@ -244,9 +245,9 @@
     instance_existed_before = true;
   }
 
-  std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
+  std::unique_ptr<FeatureList> feature_list(new FeatureList);
   feature_list->InitializeFromCommandLine(enable_features, disable_features);
-  base::FeatureList::SetInstance(std::move(feature_list));
+  FeatureList::SetInstance(std::move(feature_list));
   return !instance_existed_before;
 }
 
@@ -268,8 +269,8 @@
   // DCHECK is also forced to be FATAL if we are running a death-test.
   // TODO(asvitkine): If we find other use-cases that need integrating here
   // then define a proper API/hook for the purpose.
-  if (base::FeatureList::IsEnabled(kDCheckIsFatalFeature) ||
-      base::CommandLine::ForCurrentProcess()->HasSwitch(
+  if (FeatureList::IsEnabled(kDCheckIsFatalFeature) ||
+      CommandLine::ForCurrentProcess()->HasSwitch(
           "gtest_internal_run_death_test")) {
     logging::LOG_DCHECK = logging::LOG_FATAL;
   } else {
@@ -283,7 +284,7 @@
   FeatureList* old_instance = g_feature_list_instance;
   g_feature_list_instance = nullptr;
   g_initialized_from_accessor = false;
-  return base::WrapUnique(old_instance);
+  return WrapUnique(old_instance);
 }
 
 // static
@@ -296,6 +297,8 @@
 
 void FeatureList::FinalizeInitialization() {
   DCHECK(!initialized_);
+  // Store the field trial list pointer for DCHECKing.
+  field_trial_list_ = FieldTrialList::GetInstance();
   initialized_ = true;
 }
 
@@ -341,14 +344,19 @@
     OverrideState overridden_state) {
   for (const auto& value : SplitFeatureListString(feature_list)) {
     StringPiece feature_name = value;
-    base::FieldTrial* trial = nullptr;
+    FieldTrial* trial = nullptr;
 
     // The entry may be of the form FeatureName<FieldTrialName - in which case,
     // this splits off the field trial name and associates it with the override.
     std::string::size_type pos = feature_name.find('<');
     if (pos != std::string::npos) {
       feature_name.set(value.data(), pos);
-      trial = base::FieldTrialList::Find(value.substr(pos + 1).as_string());
+      trial = FieldTrialList::Find(value.substr(pos + 1).as_string());
+#if !defined(OS_NACL)
+      // If the below DCHECK fires, it means a non-existent trial name was
+      // specified via the "Feature<Trial" command-line syntax.
+      DCHECK(trial) << "trial=" << value.substr(pos + 1);
+#endif  // !defined(OS_NACL)
     }
 
     RegisterOverride(feature_name, overridden_state, trial);
@@ -380,6 +388,13 @@
                                           bool command_line_only) {
   DCHECK(initialized_);
 
+  // Check that the FieldTrialList this is associated with, if any, is the
+  // active one. If not, it likely indicates that this FeatureList has override
+  // entries from a freed FieldTrial, which may be caused by an incorrect test
+  // set up.
+  if (field_trial_list_)
+    DCHECK_EQ(field_trial_list_, FieldTrialList::GetInstance());
+
   enable_overrides->clear();
   disable_overrides->clear();
 
diff --git a/base/feature_list.h b/base/feature_list.h
index 74e4e9a..883ac924 100644
--- a/base/feature_list.h
+++ b/base/feature_list.h
@@ -21,6 +21,7 @@
 namespace base {
 
 class FieldTrial;
+class FieldTrialList;
 
 // Specifies whether a given feature is enabled or disabled by default.
 enum FeatureState {
@@ -296,6 +297,12 @@
   Lock feature_identity_tracker_lock_;
   std::map<std::string, const Feature*> feature_identity_tracker_;
 
+  // Tracks the associated FieldTrialList for DCHECKs. This is used to catch
+  // the scenario where multiple FieldTrialList are used with the same
+  // FeatureList - which can lead to overrides pointing to invalid FieldTrial
+  // objects.
+  base::FieldTrialList* field_trial_list_ = nullptr;
+
   // Whether this object has been fully initialized. This gets set to true as a
   // result of FinalizeInitialization().
   bool initialized_ = false;
diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc
index 9236f44..b2335a7 100644
--- a/base/metrics/field_trial.cc
+++ b/base/metrics/field_trial.cc
@@ -179,17 +179,15 @@
 
 #if !defined(OS_NACL)
 // Returns whether the operation succeeded.
-bool DeserializeGUIDFromStringPieces(base::StringPiece first,
-                                     base::StringPiece second,
-                                     base::UnguessableToken* guid) {
+bool DeserializeGUIDFromStringPieces(StringPiece first,
+                                     StringPiece second,
+                                     UnguessableToken* guid) {
   uint64_t high = 0;
   uint64_t low = 0;
-  if (!base::StringToUint64(first, &high) ||
-      !base::StringToUint64(second, &low)) {
+  if (!StringToUint64(first, &high) || !StringToUint64(second, &low))
     return false;
-  }
 
-  *guid = base::UnguessableToken::Deserialize(high, low);
+  *guid = UnguessableToken::Deserialize(high, low);
   return true;
 }
 #endif  // !defined(OS_NACL)
@@ -456,6 +454,9 @@
     it->second->Release();
     registered_.erase(it->first);
   }
+  // Note: If this DCHECK fires in a test that uses ScopedFeatureList, it is
+  // likely caused by nested ScopedFeatureLists being destroyed in a different
+  // order than they are initialized.
   DCHECK_EQ(this, global_);
   global_ = nullptr;
 }
@@ -688,7 +689,7 @@
 
 // static
 void FieldTrialList::GetInitiallyActiveFieldTrials(
-    const base::CommandLine& command_line,
+    const CommandLine& command_line,
     FieldTrial::ActiveGroups* active_groups) {
   DCHECK(global_);
   DCHECK(global_->create_trials_from_command_line_called_);
@@ -796,7 +797,7 @@
 
 // static
 void FieldTrialList::CreateFeaturesFromCommandLine(
-    const base::CommandLine& command_line,
+    const CommandLine& command_line,
     const char* enable_features_switch,
     const char* disable_features_switch,
     FeatureList* feature_list) {
@@ -854,10 +855,10 @@
 #endif
 
 // static
-base::ReadOnlySharedMemoryRegion
+ReadOnlySharedMemoryRegion
 FieldTrialList::DuplicateFieldTrialSharedMemoryForTesting() {
   if (!global_)
-    return base::ReadOnlySharedMemoryRegion();
+    return ReadOnlySharedMemoryRegion();
 
   return global_->readonly_allocator_region_.Duplicate();
 }
@@ -987,8 +988,7 @@
 
   // Recording for stability debugging has to be done inline as a task posted
   // to an observer may not get executed before a crash.
-  base::debug::GlobalActivityTracker* tracker =
-      base::debug::GlobalActivityTracker::Get();
+  debug::GlobalActivityTracker* tracker = debug::GlobalActivityTracker::Get();
   if (tracker) {
     tracker->RecordFieldTrial(field_trial->trial_name(),
                               field_trial->group_name_internal());
@@ -1140,13 +1140,25 @@
 }
 
 // static
-bool FieldTrialList::IsGlobalSetForTesting() {
-  return global_ != nullptr;
+FieldTrialList* FieldTrialList::GetInstance() {
+  return global_;
+}
+
+// static
+FieldTrialList* FieldTrialList::BackupInstanceForTesting() {
+  FieldTrialList* instance = global_;
+  global_ = nullptr;
+  return instance;
+}
+
+// static
+void FieldTrialList::RestoreInstanceForTesting(FieldTrialList* instance) {
+  global_ = instance;
 }
 
 // static
 std::string FieldTrialList::SerializeSharedMemoryRegionMetadata(
-    const base::ReadOnlySharedMemoryRegion& shm) {
+    const ReadOnlySharedMemoryRegion& shm) {
   std::stringstream ss;
 #if defined(OS_WIN)
   // Tell the child process the name of the inherited HANDLE.
@@ -1163,7 +1175,7 @@
 #error Unsupported OS
 #endif
 
-  base::UnguessableToken guid = shm.GetGUID();
+  UnguessableToken guid = shm.GetGUID();
   ss << guid.GetHighForSerialization() << "," << guid.GetLowForSerialization();
   ss << "," << shm.GetSize();
   return ss.str();
@@ -1173,27 +1185,27 @@
     (defined(OS_MACOSX) && !defined(OS_IOS))
 
 // static
-base::ReadOnlySharedMemoryRegion
+ReadOnlySharedMemoryRegion
 FieldTrialList::DeserializeSharedMemoryRegionMetadata(
     const std::string& switch_value) {
-  std::vector<base::StringPiece> tokens = base::SplitStringPiece(
-      switch_value, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+  std::vector<StringPiece> tokens =
+      SplitStringPiece(switch_value, ",", KEEP_WHITESPACE, SPLIT_WANT_ALL);
 
   if (tokens.size() != 4)
-    return base::ReadOnlySharedMemoryRegion();
+    return ReadOnlySharedMemoryRegion();
 
   int field_trial_handle = 0;
-  if (!base::StringToInt(tokens[0], &field_trial_handle))
-    return base::ReadOnlySharedMemoryRegion();
+  if (!StringToInt(tokens[0], &field_trial_handle))
+    return ReadOnlySharedMemoryRegion();
 #if defined(OS_FUCHSIA)
   zx_handle_t handle = static_cast<zx_handle_t>(field_trial_handle);
   zx::vmo scoped_handle = zx::vmo(handle);
 #elif defined(OS_WIN)
   HANDLE handle = reinterpret_cast<HANDLE>(field_trial_handle);
-  if (base::IsCurrentProcessElevated()) {
-    // base::LaunchElevatedProcess doesn't have a way to duplicate the handle,
+  if (IsCurrentProcessElevated()) {
+    // LaunchElevatedProcess doesn't have a way to duplicate the handle,
     // but this process can since by definition it's not sandboxed.
-    base::ProcessId parent_pid = base::GetParentProcessId(GetCurrentProcess());
+    ProcessId parent_pid = GetParentProcessId(GetCurrentProcess());
     HANDLE parent_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, parent_pid);
     // TODO(https://crbug.com/916461): Duplicating the handle is known to fail
     // with ERROR_ACCESS_DENIED when the parent process is being torn down. This
@@ -1208,52 +1220,49 @@
   mac::ScopedMachSendRight scoped_handle =
       rendezvous->TakeSendRight(field_trial_handle);
   if (!scoped_handle.is_valid())
-    return base::ReadOnlySharedMemoryRegion();
+    return ReadOnlySharedMemoryRegion();
 #endif
 
-  base::UnguessableToken guid;
+  UnguessableToken guid;
   if (!DeserializeGUIDFromStringPieces(tokens[1], tokens[2], &guid))
-    return base::ReadOnlySharedMemoryRegion();
+    return ReadOnlySharedMemoryRegion();
 
   int size;
-  if (!base::StringToInt(tokens[3], &size))
-    return base::ReadOnlySharedMemoryRegion();
+  if (!StringToInt(tokens[3], &size))
+    return ReadOnlySharedMemoryRegion();
 
-  auto platform_handle = base::subtle::PlatformSharedMemoryRegion::Take(
+  auto platform_handle = subtle::PlatformSharedMemoryRegion::Take(
       std::move(scoped_handle),
-      base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly,
+      subtle::PlatformSharedMemoryRegion::Mode::kReadOnly,
       static_cast<size_t>(size), guid);
-  return base::ReadOnlySharedMemoryRegion::Deserialize(
-      std::move(platform_handle));
+  return ReadOnlySharedMemoryRegion::Deserialize(std::move(platform_handle));
 }
 
 #elif defined(OS_POSIX) && !defined(OS_NACL)
 
 // static
-base::ReadOnlySharedMemoryRegion
+ReadOnlySharedMemoryRegion
 FieldTrialList::DeserializeSharedMemoryRegionMetadata(
     int fd,
     const std::string& switch_value) {
-  std::vector<base::StringPiece> tokens = base::SplitStringPiece(
-      switch_value, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+  std::vector<StringPiece> tokens =
+      SplitStringPiece(switch_value, ",", KEEP_WHITESPACE, SPLIT_WANT_ALL);
 
   if (tokens.size() != 3)
     return ReadOnlySharedMemoryRegion();
 
-  base::UnguessableToken guid;
+  UnguessableToken guid;
   if (!DeserializeGUIDFromStringPieces(tokens[0], tokens[1], &guid))
     return ReadOnlySharedMemoryRegion();
 
   int size;
-  if (!base::StringToInt(tokens[2], &size))
+  if (!StringToInt(tokens[2], &size))
     return ReadOnlySharedMemoryRegion();
 
-  auto platform_region = base::subtle::PlatformSharedMemoryRegion::Take(
-      base::ScopedFD(fd),
-      base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly,
+  auto platform_region = subtle::PlatformSharedMemoryRegion::Take(
+      ScopedFD(fd), subtle::PlatformSharedMemoryRegion::Mode::kReadOnly,
       static_cast<size_t>(size), guid);
-  return base::ReadOnlySharedMemoryRegion::Deserialize(
-      std::move(platform_region));
+  return ReadOnlySharedMemoryRegion::Deserialize(std::move(platform_region));
 }
 
 #endif
@@ -1263,7 +1272,7 @@
 // static
 bool FieldTrialList::CreateTrialsFromSwitchValue(
     const std::string& switch_value) {
-  base::ReadOnlySharedMemoryRegion shm =
+  ReadOnlySharedMemoryRegion shm =
       DeserializeSharedMemoryRegionMetadata(switch_value);
   if (!shm.IsValid())
     return false;
@@ -1281,7 +1290,7 @@
   if (fd == -1)
     return false;
 
-  base::ReadOnlySharedMemoryRegion shm =
+  ReadOnlySharedMemoryRegion shm =
       DeserializeSharedMemoryRegionMetadata(fd, switch_value);
   if (!shm.IsValid())
     return false;
@@ -1294,8 +1303,8 @@
 
 // static
 bool FieldTrialList::CreateTrialsFromSharedMemoryRegion(
-    const base::ReadOnlySharedMemoryRegion& shm_region) {
-  base::ReadOnlySharedMemoryMapping shm_mapping =
+    const ReadOnlySharedMemoryRegion& shm_region) {
+  ReadOnlySharedMemoryMapping shm_mapping =
       shm_region.MapAt(0, kFieldTrialAllocationSize);
   if (!shm_mapping.IsValid())
     OnOutOfMemory(kFieldTrialAllocationSize);
@@ -1306,7 +1315,7 @@
 
 // static
 bool FieldTrialList::CreateTrialsFromSharedMemoryMapping(
-    base::ReadOnlySharedMemoryMapping shm_mapping) {
+    ReadOnlySharedMemoryMapping shm_mapping) {
   global_->field_trial_allocator_ =
       std::make_unique<ReadOnlySharedPersistentMemoryAllocator>(
           std::move(shm_mapping), 0, kAllocatorName);
@@ -1347,8 +1356,8 @@
   if (global_->field_trial_allocator_ != nullptr)
     return;
 
-  base::MappedReadOnlyRegion shm =
-      base::ReadOnlySharedMemoryRegion::Create(kFieldTrialAllocationSize);
+  MappedReadOnlyRegion shm =
+      ReadOnlySharedMemoryRegion::Create(kFieldTrialAllocationSize);
 
   if (!shm.IsValid())
     OnOutOfMemory(kFieldTrialAllocationSize);
diff --git a/base/metrics/field_trial.h b/base/metrics/field_trial.h
index 5834115..ae6068a3 100644
--- a/base/metrics/field_trial.h
+++ b/base/metrics/field_trial.h
@@ -534,7 +534,7 @@
   // holding field trial information.
   // Must be called only after a call to CreateTrialsFromCommandLine().
   static void GetInitiallyActiveFieldTrials(
-      const base::CommandLine& command_line,
+      const CommandLine& command_line,
       FieldTrial::ActiveGroups* active_groups);
 
   // Use a state string (re: StatesToString()) to augment the current list of
@@ -558,24 +558,22 @@
   // contain the shared memory handle that contains the field trial allocator.
   // We need the |field_trial_handle_switch| and |fd_key| arguments to be passed
   // in since base/ can't depend on content/.
-  static void CreateTrialsFromCommandLine(const base::CommandLine& cmd_line,
+  static void CreateTrialsFromCommandLine(const CommandLine& cmd_line,
                                           const char* field_trial_handle_switch,
                                           int fd_key);
 
   // Creates base::Feature overrides from the command line by first trying to
   // use shared memory and then falling back to the command line if it fails.
-  static void CreateFeaturesFromCommandLine(
-      const base::CommandLine& command_line,
-      const char* enable_features_switch,
-      const char* disable_features_switch,
-      FeatureList* feature_list);
+  static void CreateFeaturesFromCommandLine(const CommandLine& command_line,
+                                            const char* enable_features_switch,
+                                            const char* disable_features_switch,
+                                            FeatureList* feature_list);
 
 #if defined(OS_WIN)
   // On Windows, we need to explicitly pass down any handles to be inherited.
   // This function adds the shared memory handle to field trial state to the
   // list of handles to be inherited.
-  static void AppendFieldTrialHandleIfNeeded(
-      base::HandlesToInheritVector* handles);
+  static void AppendFieldTrialHandleIfNeeded(HandlesToInheritVector* handles);
 #elif defined(OS_FUCHSIA)
   // TODO(fuchsia): Implement shared-memory configuration (crbug.com/752368).
 #elif defined(OS_MACOSX) && !defined(OS_IOS)
@@ -590,8 +588,7 @@
   // descriptor.
   static int GetFieldTrialDescriptor();
 #endif
-  static base::ReadOnlySharedMemoryRegion
-  DuplicateFieldTrialSharedMemoryForTesting();
+  static ReadOnlySharedMemoryRegion DuplicateFieldTrialSharedMemoryForTesting();
 
   // Adds a switch to the command line containing the field trial state as a
   // string (if not using shared memory to share field trial state), or the
@@ -601,7 +598,7 @@
   static void CopyFieldTrialStateToFlags(const char* field_trial_handle_switch,
                                          const char* enable_features_switch,
                                          const char* disable_features_switch,
-                                         base::CommandLine* cmd_line);
+                                         CommandLine* cmd_line);
 
   // Create a FieldTrial with the given |name| and using 100% probability for
   // the FieldTrial, force FieldTrial to have the same group string as
@@ -667,8 +664,16 @@
   GetAllFieldTrialsFromPersistentAllocator(
       PersistentMemoryAllocator const& allocator);
 
-  // Returns true if a global field trial list is set. Only used for testing.
-  static bool IsGlobalSetForTesting();
+  // Returns a pointer to the global instance. This is exposed so that it can
+  // be used in a DCHECK in FeatureList and ScopedFeatureList test-only logic
+  // and is not intended to be used widely beyond those cases.
+  static FieldTrialList* GetInstance();
+
+  // For testing, sets the global instance to null and returns the previous one.
+  static FieldTrialList* BackupInstanceForTesting();
+
+  // For testing, sets the global instance to |instance|.
+  static void RestoreInstanceForTesting(FieldTrialList* instance);
 
  private:
   // Allow tests to access our innards for testing purposes.
@@ -688,13 +693,13 @@
   // a GUID. Serialization and deserialization doesn't actually transport the
   // underlying OS resource - that must be done by the Process launcher.
   static std::string SerializeSharedMemoryRegionMetadata(
-      const base::ReadOnlySharedMemoryRegion& shm);
+      const ReadOnlySharedMemoryRegion& shm);
 #if defined(OS_WIN) || defined(OS_FUCHSIA) || \
     (defined(OS_MACOSX) && !defined(OS_IOS))
-  static base::ReadOnlySharedMemoryRegion DeserializeSharedMemoryRegionMetadata(
+  static ReadOnlySharedMemoryRegion DeserializeSharedMemoryRegionMetadata(
       const std::string& switch_value);
 #elif defined(OS_POSIX) && !defined(OS_NACL)
-  static base::ReadOnlySharedMemoryRegion DeserializeSharedMemoryRegionMetadata(
+  static ReadOnlySharedMemoryRegion DeserializeSharedMemoryRegionMetadata(
       int fd,
       const std::string& switch_value);
 #endif
@@ -719,7 +724,7 @@
   // and creates field trials via CreateTrialsFromSharedMemoryMapping(). Returns
   // true if successful and false otherwise.
   static bool CreateTrialsFromSharedMemoryRegion(
-      const base::ReadOnlySharedMemoryRegion& shm_region);
+      const ReadOnlySharedMemoryRegion& shm_region);
 
   // Expects a mapped piece of shared memory |shm_mapping| that was created from
   // the browser process's field_trial_allocator and shared via the command
@@ -727,7 +732,7 @@
   // trials in it, and creates them via CreateFieldTrial(). Returns true if
   // successful and false otherwise.
   static bool CreateTrialsFromSharedMemoryMapping(
-      base::ReadOnlySharedMemoryMapping shm_mapping);
+      ReadOnlySharedMemoryMapping shm_mapping);
 
   // Instantiate the field trial allocator, add all existing field trials to it,
   // and duplicates its handle to a read-only handle, which gets stored in
@@ -794,7 +799,7 @@
   // Readonly copy of the region to the allocator. Needs to be a member variable
   // because it's needed from both CopyFieldTrialStateToFlags() and
   // AppendFieldTrialHandleIfNeeded().
-  base::ReadOnlySharedMemoryRegion readonly_allocator_region_;
+  ReadOnlySharedMemoryRegion readonly_allocator_region_;
 
   // Tracks whether CreateTrialsFromCommandLine() has been called.
   bool create_trials_from_command_line_called_ = false;
diff --git a/base/metrics/field_trial_params.cc b/base/metrics/field_trial_params.cc
index 0680d50..e8eb452 100644
--- a/base/metrics/field_trial_params.cc
+++ b/base/metrics/field_trial_params.cc
@@ -4,32 +4,91 @@
 
 #include "base/metrics/field_trial_params.h"
 
+#include <set>
+#include <utility>
+#include <vector>
+
 #include "base/feature_list.h"
 #include "base/metrics/field_trial.h"
 #include "base/metrics/field_trial_param_associator.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
 
 namespace base {
 
 bool AssociateFieldTrialParams(const std::string& trial_name,
                                const std::string& group_name,
                                const FieldTrialParams& params) {
-  return base::FieldTrialParamAssociator::GetInstance()
-      ->AssociateFieldTrialParams(trial_name, group_name, params);
+  return FieldTrialParamAssociator::GetInstance()->AssociateFieldTrialParams(
+      trial_name, group_name, params);
+}
+
+bool AssociateFieldTrialParamsFromString(
+    const std::string& params_string,
+    FieldTrialParamsDecodeStringFunc decode_data_func) {
+  // Format: Trial1.Group1:k1/v1/k2/v2,Trial2.Group2:k1/v1/k2/v2
+  std::set<std::pair<std::string, std::string>> trial_groups;
+  for (StringPiece experiment_group :
+       SplitStringPiece(params_string, ",", TRIM_WHITESPACE, SPLIT_WANT_ALL)) {
+    std::vector<StringPiece> experiment = SplitStringPiece(
+        experiment_group, ":", TRIM_WHITESPACE, SPLIT_WANT_ALL);
+    if (experiment.size() != 2) {
+      DLOG(ERROR) << "Experiment and params should be separated by ':'";
+      return false;
+    }
+
+    std::vector<std::string> group_parts =
+        SplitString(experiment[0], ".", TRIM_WHITESPACE, SPLIT_WANT_ALL);
+    if (group_parts.size() != 2) {
+      DLOG(ERROR) << "Trial and group name should be separated by '.'";
+      return false;
+    }
+
+    std::vector<std::string> key_values =
+        SplitString(experiment[1], "/", TRIM_WHITESPACE, SPLIT_WANT_ALL);
+    if (key_values.size() % 2 != 0) {
+      DLOG(ERROR) << "Param name and param value should be separated by '/'";
+      return false;
+    }
+    std::string trial = decode_data_func(group_parts[0]);
+    std::string group = decode_data_func(group_parts[1]);
+    auto trial_group = std::make_pair(trial, group);
+    if (trial_groups.find(trial_group) != trial_groups.end()) {
+      DLOG(ERROR) << StringPrintf(
+          "A (trial, group) pair listed more than once. (%s, %s)",
+          trial.c_str(), group.c_str());
+      return false;
+    }
+    trial_groups.insert(trial_group);
+    std::map<std::string, std::string> params;
+    for (size_t i = 0; i < key_values.size(); i += 2) {
+      std::string key = decode_data_func(key_values[i]);
+      std::string value = decode_data_func(key_values[i + 1]);
+      params[key] = value;
+    }
+    bool result = AssociateFieldTrialParams(trial, group, params);
+    if (!result) {
+      DLOG(ERROR) << "Failed to associate field trial params for group \""
+                  << group << "\" in trial \"" << trial << "\"";
+      return false;
+    }
+  }
+  return true;
 }
 
 bool GetFieldTrialParams(const std::string& trial_name,
                          FieldTrialParams* params) {
-  return base::FieldTrialParamAssociator::GetInstance()->GetFieldTrialParams(
+  return FieldTrialParamAssociator::GetInstance()->GetFieldTrialParams(
       trial_name, params);
 }
 
-bool GetFieldTrialParamsByFeature(const base::Feature& feature,
+bool GetFieldTrialParamsByFeature(const Feature& feature,
                                   FieldTrialParams* params) {
-  if (!base::FeatureList::IsEnabled(feature))
+  if (!FeatureList::IsEnabled(feature))
     return false;
 
-  base::FieldTrial* trial = base::FeatureList::GetFieldTrial(feature);
+  FieldTrial* trial = FeatureList::GetFieldTrial(feature);
   if (!trial)
     return false;
 
@@ -47,25 +106,25 @@
   return std::string();
 }
 
-std::string GetFieldTrialParamValueByFeature(const base::Feature& feature,
+std::string GetFieldTrialParamValueByFeature(const Feature& feature,
                                              const std::string& param_name) {
-  if (!base::FeatureList::IsEnabled(feature))
+  if (!FeatureList::IsEnabled(feature))
     return std::string();
 
-  base::FieldTrial* trial = base::FeatureList::GetFieldTrial(feature);
+  FieldTrial* trial = FeatureList::GetFieldTrial(feature);
   if (!trial)
     return std::string();
 
   return GetFieldTrialParamValue(trial->trial_name(), param_name);
 }
 
-int GetFieldTrialParamByFeatureAsInt(const base::Feature& feature,
+int GetFieldTrialParamByFeatureAsInt(const Feature& feature,
                                      const std::string& param_name,
                                      int default_value) {
   std::string value_as_string =
       GetFieldTrialParamValueByFeature(feature, param_name);
   int value_as_int = 0;
-  if (!base::StringToInt(value_as_string, &value_as_int)) {
+  if (!StringToInt(value_as_string, &value_as_int)) {
     if (!value_as_string.empty()) {
       DLOG(WARNING) << "Failed to parse field trial param " << param_name
                     << " with string value " << value_as_string
@@ -78,13 +137,13 @@
   return value_as_int;
 }
 
-double GetFieldTrialParamByFeatureAsDouble(const base::Feature& feature,
+double GetFieldTrialParamByFeatureAsDouble(const Feature& feature,
                                            const std::string& param_name,
                                            double default_value) {
   std::string value_as_string =
       GetFieldTrialParamValueByFeature(feature, param_name);
   double value_as_double = 0;
-  if (!base::StringToDouble(value_as_string, &value_as_double)) {
+  if (!StringToDouble(value_as_string, &value_as_double)) {
     if (!value_as_string.empty()) {
       DLOG(WARNING) << "Failed to parse field trial param " << param_name
                     << " with string value " << value_as_string
@@ -97,7 +156,7 @@
   return value_as_double;
 }
 
-bool GetFieldTrialParamByFeatureAsBool(const base::Feature& feature,
+bool GetFieldTrialParamByFeatureAsBool(const Feature& feature,
                                        const std::string& param_name,
                                        bool default_value) {
   std::string value_as_string =
@@ -134,7 +193,7 @@
   return GetFieldTrialParamByFeatureAsBool(*feature, name, default_value);
 }
 
-void LogInvalidEnumValue(const base::Feature& feature,
+void LogInvalidEnumValue(const Feature& feature,
                          const std::string& param_name,
                          const std::string& value_as_string,
                          int default_value_as_int) {
diff --git a/base/metrics/field_trial_params.h b/base/metrics/field_trial_params.h
index b2e838f..a9bd0c54 100644
--- a/base/metrics/field_trial_params.h
+++ b/base/metrics/field_trial_params.h
@@ -17,6 +17,9 @@
 // Key-value mapping type for field trial parameters.
 typedef std::map<std::string, std::string> FieldTrialParams;
 
+// Param string decoding function for AssociateFieldTrialParamsFromString().
+typedef std::string (*FieldTrialParamsDecodeStringFunc)(const std::string& str);
+
 // Associates the specified set of key-value |params| with the field trial
 // specified by |trial_name| and |group_name|. Fails and returns false if the
 // specified field trial already has params associated with it or the trial
@@ -25,6 +28,13 @@
                                            const std::string& group_name,
                                            const FieldTrialParams& params);
 
+// Provides a mechanism to associate multiple set of params to multiple groups
+// with a formatted string as returned by FieldTrialList::AllParamsToString().
+// |decode_data_func| allows specifying a custom decoding function.
+BASE_EXPORT bool AssociateFieldTrialParamsFromString(
+    const std::string& params_string,
+    FieldTrialParamsDecodeStringFunc decode_data_func);
+
 // Retrieves the set of key-value |params| for the specified field trial, based
 // on its selected group. If the field trial does not exist or its selected
 // group does not have any parameters associated with it, returns false and
diff --git a/base/test/scoped_feature_list.cc b/base/test/scoped_feature_list.cc
index 0cc910c..646f9fe 100644
--- a/base/test/scoped_feature_list.cc
+++ b/base/test/scoped_feature_list.cc
@@ -14,6 +14,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
+#include "base/test/mock_entropy_provider.h"
 
 namespace base {
 namespace test {
@@ -32,6 +33,17 @@
   return output;
 }
 
+std::vector<StringPiece> GetFeatureVectorFromFeaturesAndParams(
+    const std::vector<ScopedFeatureList::FeatureAndParams>&
+        features_and_params) {
+  std::vector<StringPiece> output;
+  for (const auto& entry : features_and_params) {
+    output.push_back(entry.feature.name);
+  }
+
+  return output;
+}
+
 // Extracts a feature name from a feature state string. For example, given
 // the input "*MyLovelyFeature<SomeFieldTrial", returns "MyLovelyFeature".
 StringPiece GetFeatureName(StringPiece feature) {
@@ -69,8 +81,9 @@
     StringPiece feature_name = GetFeatureName(feature);
 
     if (Contains(merged_features->enabled_feature_list, feature_name) ||
-        Contains(merged_features->disabled_feature_list, feature_name))
+        Contains(merged_features->disabled_feature_list, feature_name)) {
       continue;
+    }
 
     if (override_state == FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE) {
       merged_features->enabled_feature_list.push_back(feature);
@@ -82,25 +95,46 @@
   }
 }
 
+// Hex encode params so that special characters do not break formatting.
+std::string HexEncodeString(const std::string& input) {
+  return HexEncode(input.data(), input.size());
+}
+
+// Inverse of HexEncodeString().
+std::string HexDecodeString(const std::string& input) {
+  std::vector<uint8_t> bytes;
+  bool result = HexStringToBytes(input, &bytes);
+  DCHECK(result);
+  return std::string(reinterpret_cast<const char*>(&bytes[0]), bytes.size());
+}
+
 }  // namespace
 
 ScopedFeatureList::ScopedFeatureList() = default;
 
 ScopedFeatureList::~ScopedFeatureList() {
+  Reset();
+}
+
+void ScopedFeatureList::Reset() {
   // If one of the Init() functions was never called, don't reset anything.
   if (!init_called_)
     return;
 
-  auto* field_trial_param_associator = FieldTrialParamAssociator::GetInstance();
-  for (const auto& field_trial_override : field_trial_overrides_) {
-    if (field_trial_override) {
-      field_trial_param_associator->ClearParamsForTesting(
-          field_trial_override->trial_name(),
-          field_trial_override->group_name());
-    }
-  }
+  init_called_ = false;
 
   FeatureList::ClearInstanceForTesting();
+
+  if (field_trial_list_) {
+    field_trial_list_.reset();
+
+    // Restore params to how they were before.
+    FieldTrialParamAssociator::GetInstance()->ClearAllParamsForTesting();
+    AssociateFieldTrialParamsFromString(original_params_, &HexDecodeString);
+
+    FieldTrialList::RestoreInstanceForTesting(original_field_trial_list_);
+    original_field_trial_list_ = nullptr;
+  }
   if (original_feature_list_)
     FeatureList::RestoreInstanceForTesting(std::move(original_feature_list_));
 }
@@ -130,15 +164,15 @@
 void ScopedFeatureList::InitWithFeatures(
     const std::vector<Feature>& enabled_features,
     const std::vector<Feature>& disabled_features) {
-  InitWithFeaturesAndFieldTrials(enabled_features, {}, disabled_features);
+  InitWithFeaturesImpl(enabled_features, {}, disabled_features);
 }
 
 void ScopedFeatureList::InitAndEnableFeature(const Feature& feature) {
-  InitWithFeaturesAndFieldTrials({feature}, {}, {});
+  InitWithFeaturesImpl({feature}, {}, {});
 }
 
 void ScopedFeatureList::InitAndDisableFeature(const Feature& feature) {
-  InitWithFeaturesAndFieldTrials({}, {}, {feature});
+  InitWithFeaturesImpl({}, {}, {feature});
 }
 
 void ScopedFeatureList::InitWithFeatureState(const Feature& feature,
@@ -150,49 +184,78 @@
   }
 }
 
-void ScopedFeatureList::InitWithFeaturesAndFieldTrials(
+void ScopedFeatureList::InitWithFeaturesImpl(
     const std::vector<Feature>& enabled_features,
-    const std::vector<FieldTrial*>& trials_for_enabled_features,
+    const std::vector<FeatureAndParams>& enabled_features_and_params,
     const std::vector<Feature>& disabled_features) {
-  DCHECK_LE(trials_for_enabled_features.size(), enabled_features.size());
+  DCHECK(enabled_features.empty() || enabled_features_and_params.empty());
 
   Features merged_features;
-  merged_features.enabled_feature_list = GetFeatureVector(enabled_features);
+  if (!enabled_features_and_params.empty()) {
+    merged_features.enabled_feature_list =
+        GetFeatureVectorFromFeaturesAndParams(enabled_features_and_params);
+  } else {
+    merged_features.enabled_feature_list = GetFeatureVector(enabled_features);
+  }
   merged_features.disabled_feature_list = GetFeatureVector(disabled_features);
 
-  FeatureList* feature_list = FeatureList::GetInstance();
-
-  // |current_enabled_features| and |current_disabled_features| must declare out
-  // of if scope to avoid them out of scope before JoinString calls because
-  // |merged_features| may contains StringPiece which holding pointer points to
-  // |current_enabled_features| and |current_disabled_features|.
   std::string current_enabled_features;
   std::string current_disabled_features;
+  FeatureList* feature_list = FeatureList::GetInstance();
   if (feature_list) {
-    FeatureList::GetInstance()->GetFeatureOverrides(&current_enabled_features,
-                                                    &current_disabled_features);
-    OverrideFeatures(current_enabled_features,
-                     FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE,
-                     &merged_features);
-    OverrideFeatures(current_disabled_features,
-                     FeatureList::OverrideState::OVERRIDE_DISABLE_FEATURE,
-                     &merged_features);
+    feature_list->GetFeatureOverrides(&current_enabled_features,
+                                      &current_disabled_features);
   }
 
-  // Add the field trial overrides. This assumes that |enabled_features| are at
-  // the begining of |merged_features.enabled_feature_list|, in the same order.
-  auto trial_it = trials_for_enabled_features.begin();
+  // Save off the existing field trials and params.
+  std::string existing_trial_state;
+  FieldTrialList::AllStatesToString(&existing_trial_state, true);
+  original_params_ = FieldTrialList::AllParamsToString(true, &HexEncodeString);
+
+  // Back up the current field trial list, to be restored in Reset().
+  original_field_trial_list_ = FieldTrialList::BackupInstanceForTesting();
+
+  // Create a field trial list, to which we'll add trials corresponding to the
+  // features that have params, before restoring the field trial state from the
+  // previous instance, further down in this function.
+  field_trial_list_ =
+      std::make_unique<FieldTrialList>(std::make_unique<MockEntropyProvider>());
+
+  // Associate override params. This needs to be done before trial state gets
+  // restored, as that will activate trials, locking down param association.
+  auto* field_trial_param_associator = FieldTrialParamAssociator::GetInstance();
+  std::vector<std::string> features_with_trial;
   auto feature_it = merged_features.enabled_feature_list.begin();
-  std::vector<std::unique_ptr<std::string>> features_with_trial;
-  features_with_trial.reserve(trials_for_enabled_features.size());
-  while (trial_it != trials_for_enabled_features.end()) {
-    features_with_trial.push_back(std::make_unique<std::string>(
-        feature_it->as_string() + "<" + (*trial_it)->trial_name()));
-    // |features_with_trial| owns the string, and feature_it points to it.
-    *feature_it = *(features_with_trial.back());
-    ++trial_it;
+  for (const auto& enabled_feature : enabled_features_and_params) {
+    const std::string feature_name = enabled_feature.feature.name;
+    const std::string trial_name =
+        "scoped_feature_list_trial_for_" + feature_name;
+
+    scoped_refptr<FieldTrial> field_trial_override =
+        FieldTrialList::CreateFieldTrial(trial_name, kTrialGroup);
+    DCHECK(field_trial_override);
+
+    field_trial_param_associator->ClearParamsForTesting(trial_name,
+                                                        kTrialGroup);
+    bool success = field_trial_param_associator->AssociateFieldTrialParams(
+        trial_name, kTrialGroup, enabled_feature.params);
+    DCHECK(success);
+
+    features_with_trial.push_back(feature_name + "<" + trial_name);
+    *feature_it = features_with_trial.back();
     ++feature_it;
   }
+  // Restore other field trials. Note: We don't need to do anything for params
+  // here because the param associator already has the right state, which has
+  // been backed up via |original_params_| to be restored later.
+  FieldTrialList::CreateTrialsFromString(existing_trial_state, {});
+
+  OverrideFeatures(current_enabled_features,
+                   FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE,
+                   &merged_features);
+  OverrideFeatures(current_disabled_features,
+                   FeatureList::OverrideState::OVERRIDE_DISABLE_FEATURE,
+                   &merged_features);
 
   std::string enabled = JoinString(merged_features.enabled_feature_list, ",");
   std::string disabled = JoinString(merged_features.disabled_feature_list, ",");
@@ -201,47 +264,14 @@
 
 void ScopedFeatureList::InitAndEnableFeatureWithParameters(
     const Feature& feature,
-    const std::map<std::string, std::string>& feature_parameters) {
+    const FieldTrialParams& feature_parameters) {
   InitWithFeaturesAndParameters({{feature, feature_parameters}}, {});
 }
 
 void ScopedFeatureList::InitWithFeaturesAndParameters(
     const std::vector<FeatureAndParams>& enabled_features,
     const std::vector<Feature>& disabled_features) {
-  DCHECK(field_trial_overrides_.empty());
-
-  if (!FieldTrialList::IsGlobalSetForTesting()) {
-    field_trial_list_ = std::make_unique<base::FieldTrialList>(nullptr);
-  }
-
-  // Enabled features and field trials to use with
-  // InitWithFeaturesAndFieldTrials.
-  std::vector<Feature> features;
-  std::vector<FieldTrial*> field_trials;
-
-  auto* field_trial_param_associator = FieldTrialParamAssociator::GetInstance();
-
-  // TODO(crbug.com/794021) Remove this unique field trial name hack when there
-  // is a cleaner solution.
-  // Ensure that each call to this method uses a distinct field trial name.
-  // Otherwise, nested calls might fail due to the shared FieldTrialList
-  // already having the field trial registered.
-  static int num_calls = 0;
-  for (auto& enabled_feature : enabled_features) {
-    ++num_calls;
-    std::string trial_name =
-        "scoped_feature_list_trial_name" + base::NumberToString(num_calls);
-    scoped_refptr<FieldTrial> field_trial_override =
-        base::FieldTrialList::CreateFieldTrial(trial_name, kTrialGroup);
-    field_trial_overrides_.push_back(field_trial_override);
-    DCHECK(field_trial_overrides_.back());
-    field_trial_param_associator->AssociateFieldTrialParams(
-        trial_name, kTrialGroup, enabled_feature.params);
-    features.push_back(enabled_feature.feature);
-    field_trials.push_back(field_trial_override.get());
-  }
-
-  InitWithFeaturesAndFieldTrials(features, field_trials, disabled_features);
+  InitWithFeaturesImpl({}, enabled_features, disabled_features);
 }
 
 }  // namespace test
diff --git a/base/test/scoped_feature_list.h b/base/test/scoped_feature_list.h
index 5d965bc..5a637a6 100644
--- a/base/test/scoped_feature_list.h
+++ b/base/test/scoped_feature_list.h
@@ -13,25 +13,26 @@
 #include "base/feature_list.h"
 #include "base/memory/ref_counted.h"
 #include "base/metrics/field_trial.h"
+#include "base/metrics/field_trial_params.h"
 
 namespace base {
 namespace test {
 
 // ScopedFeatureList resets the global FeatureList instance to a new empty
-// instance and restores the original instance upon destruction.
+// instance and restores the original instance upon destruction. When using the
+// non-deprecated APIs, a corresponding FieldTrialList is also created.
+//
 // Note: Re-using the same object is not allowed. To reset the feature
 // list and initialize it anew, destroy an existing scoped list and init
 // a new one.
 //
-// ScopedFeatureList needs to be initialized (via one of Init... methods)
-// before running code that inspects the state of features.  In practice this
-// means:
-// - In browser tests, one of Init... methods should be called from the
-//   overriden ::testing::Test::SetUp method. For example:
-//     void SetUp() override {
-//       scoped_feature_list_.InitAndEnableFeature(features::kMyFeatureHere);
-//       InProcessBrowserTest::SetUp();
-//     }
+// ScopedFeatureList needs to be initialized (via one of Init*() methods)
+// before running code that inspects the state of features, such as in the
+// constructor of the test harness.
+//
+// If multiple instances of this class are used in a nested fashion, they
+// should be destroyed in the opposite order of their Init*() methods being
+// called.
 class ScopedFeatureList final {
  public:
   ScopedFeatureList();
@@ -39,9 +40,12 @@
 
   struct FeatureAndParams {
     const Feature& feature;
-    const std::map<std::string, std::string>& params;
+    const FieldTrialParams& params;
   };
 
+  // Resets the instance to a non-initialized state.
+  void Reset();
+
   // WARNING: This method will reset any globally configured features to their
   // default values, which can hide feature interaction bugs. Please use
   // sparingly.  https://crbug.com/713390
@@ -82,7 +86,7 @@
   // currently one.
   void InitAndEnableFeatureWithParameters(
       const Feature& feature,
-      const std::map<std::string, std::string>& feature_parameters);
+      const FieldTrialParams& feature_parameters);
 
   // Initializes and registers a FeatureList instance based on present
   // FeatureList and overridden with the given enabled features and the
@@ -108,13 +112,12 @@
   // Any feature overrides already present in the global FeatureList will
   // continue to apply, unless they conflict with the overrides passed into this
   // method.
-  // Field trials will apply to the enabled features, in the same order. The
-  // number of trials must be less (or equal) than the number of enabled
-  // features.
-  // Trials are expected to outlive the ScopedFeatureList.
-  void InitWithFeaturesAndFieldTrials(
+  // Features to enable may be specified through either |enabled_features| or
+  // |enabled_feature_and_params|, but not both (i.e. one of these must be
+  // empty).
+  void InitWithFeaturesImpl(
       const std::vector<Feature>& enabled_features,
-      const std::vector<FieldTrial*>& trials_for_enabled_features,
+      const std::vector<FeatureAndParams>& enabled_features_and_params,
       const std::vector<Feature>& disabled_features);
 
   // Initializes and registers a FeatureList instance based on present
@@ -126,7 +129,8 @@
 
   bool init_called_ = false;
   std::unique_ptr<FeatureList> original_feature_list_;
-  std::vector<scoped_refptr<FieldTrial>> field_trial_overrides_;
+  base::FieldTrialList* original_field_trial_list_;
+  std::string original_params_;
   std::unique_ptr<base::FieldTrialList> field_trial_list_;
 
   DISALLOW_COPY_AND_ASSIGN(ScopedFeatureList);
diff --git a/base/test/scoped_feature_list_unittest.cc b/base/test/scoped_feature_list_unittest.cc
index df21a07..7e251f6 100644
--- a/base/test/scoped_feature_list_unittest.cc
+++ b/base/test/scoped_feature_list_unittest.cc
@@ -167,7 +167,10 @@
     EXPECT_TRUE(FeatureList::IsEnabled(kTestFeature2));
     EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature1, kParam));
     EXPECT_EQ(kValue, GetFieldTrialParamValueByFeature(kTestFeature2, kParam));
-    EXPECT_EQ(trial.get(), FeatureList::GetFieldTrial(kTestFeature1));
+    EXPECT_EQ(trial.get()->trial_name(),
+              FeatureList::GetFieldTrial(kTestFeature1)->trial_name());
+    EXPECT_EQ(trial.get()->group_name(),
+              FeatureList::GetFieldTrial(kTestFeature1)->group_name());
     EXPECT_NE(nullptr, FeatureList::GetFieldTrial(kTestFeature2));
   }
 
@@ -240,6 +243,38 @@
   EXPECT_EQ("", GetFieldTrialParamValueByFeature(kTestFeature2, kParam));
 }
 
+TEST_F(ScopedFeatureListTest, ParamsWithSpecialCharsPreserved) {
+  // Check that special characters in param names and values are preserved.
+  const char kParam[] = ";_\\<:>/_!?";
+  const char kValue[] = ",;:/'!?";
+  FieldTrialParams params0 = {{kParam, kValue}};
+
+  test::ScopedFeatureList feature_list0;
+  feature_list0.InitWithFeaturesAndParameters({{kTestFeature1, params0}}, {});
+  EXPECT_EQ(kValue, GetFieldTrialParamValueByFeature(kTestFeature1, kParam));
+
+  {
+    const char kValue1[] = "normal";
+    FieldTrialParams params1 = {{kParam, kValue1}};
+    test::ScopedFeatureList feature_list1;
+    feature_list1.InitWithFeaturesAndParameters({{kTestFeature1, params1}}, {});
+
+    EXPECT_EQ(kValue1, GetFieldTrialParamValueByFeature(kTestFeature1, kParam));
+  }
+  EXPECT_EQ(kValue, GetFieldTrialParamValueByFeature(kTestFeature1, kParam));
+
+  {
+    const char kValue2[] = "[<(2)>]";
+    FieldTrialParams params2 = {{kParam, kValue2}};
+    test::ScopedFeatureList feature_list2;
+    feature_list2.InitWithFeaturesAndParameters({{kTestFeature2, params2}}, {});
+
+    EXPECT_EQ(kValue2, GetFieldTrialParamValueByFeature(kTestFeature2, kParam));
+    EXPECT_EQ(kValue, GetFieldTrialParamValueByFeature(kTestFeature1, kParam));
+  }
+  EXPECT_EQ(kValue, GetFieldTrialParamValueByFeature(kTestFeature1, kParam));
+}
+
 TEST_F(ScopedFeatureListTest, EnableFeatureOverrideDisable) {
   test::ScopedFeatureList feature_list1;
   feature_list1.InitWithFeatures({}, {kTestFeature1});
diff --git a/base/time/time_win.cc b/base/time/time_win.cc
index ffb4329..9bfdba08 100644
--- a/base/time/time_win.cc
+++ b/base/time/time_win.cc
@@ -31,6 +31,7 @@
 // will only increase the system-wide timer if we're not running on battery
 // power.
 
+#include "base/feature_list.h"
 #include "base/time/time.h"
 
 #include <windows.h>
@@ -84,12 +85,28 @@
   g_initial_time = CurrentWallclockMicroseconds();
 }
 
+const base::Feature kSlowDCTimerInterruptsFeature{
+    "SlowDCTimerInterrups", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // The two values that ActivateHighResolutionTimer uses to set the systemwide
 // timer interrupt frequency on Windows. It controls how precise timers are
 // but also has a big impact on battery life.
-const int kMinTimerIntervalHighResMs = 1;
-const int kMinTimerIntervalLowResMs = 4;
-// Track if kMinTimerIntervalHighResMs or kMinTimerIntervalLowResMs is active.
+// Used when running on AC power - plugged in - when a fast timer is wanted.
+UINT MinTimerIntervalHighResMs() {
+  return 1;
+}
+
+UINT MinTimerIntervalLowResMs() {
+  // Traditionally Chrome has used an interval of 4 ms when raising the timer
+  // interrupt frequency on battery power. However even 4 ms is too short an
+  // interval on modern CPUs - it wastes non-trivial power - so this experiment
+  // tests an interval of 8 ms, recommended by Intel.
+  static const UINT s_interval =
+      base::FeatureList::IsEnabled(kSlowDCTimerInterruptsFeature) ? 8 : 4;
+  return s_interval;
+}
+
+// Track if MinTimerIntervalHighResMs() or MinTimerIntervalLowResMs() is active.
 bool g_high_res_timer_enabled = false;
 // How many times the high resolution timer has been called.
 uint32_t g_high_res_timer_count = 0;
@@ -204,11 +221,11 @@
   // call timeEndPeriod with the same value used in timeBeginPeriod and
   // therefore undo the period effect.
   if (enable) {
-    timeEndPeriod(kMinTimerIntervalLowResMs);
-    timeBeginPeriod(kMinTimerIntervalHighResMs);
+    timeEndPeriod(MinTimerIntervalLowResMs());
+    timeBeginPeriod(MinTimerIntervalHighResMs());
   } else {
-    timeEndPeriod(kMinTimerIntervalHighResMs);
-    timeBeginPeriod(kMinTimerIntervalLowResMs);
+    timeEndPeriod(MinTimerIntervalHighResMs());
+    timeBeginPeriod(MinTimerIntervalLowResMs());
   }
 }
 
@@ -220,8 +237,8 @@
   const uint32_t max = std::numeric_limits<uint32_t>::max();
 
   AutoLock lock(*GetHighResLock());
-  UINT period = g_high_res_timer_enabled ? kMinTimerIntervalHighResMs
-                                         : kMinTimerIntervalLowResMs;
+  UINT period = g_high_res_timer_enabled ? MinTimerIntervalHighResMs()
+                                         : MinTimerIntervalLowResMs();
   if (activating) {
     DCHECK_NE(g_high_res_timer_count, max);
     ++g_high_res_timer_count;
@@ -238,7 +255,7 @@
       timeEndPeriod(period);
     }
   }
-  return (period == kMinTimerIntervalHighResMs);
+  return period == MinTimerIntervalHighResMs();
 }
 
 // static
diff --git a/base/trace_event/event_name_filter_unittest.cc b/base/trace_event/event_name_filter_unittest.cc
index d0b32d11..230e274 100644
--- a/base/trace_event/event_name_filter_unittest.cc
+++ b/base/trace_event/event_name_filter_unittest.cc
@@ -5,6 +5,7 @@
 #include "base/trace_event/event_name_filter.h"
 
 #include "base/memory/ptr_util.h"
+#include "base/trace_event/thread_instruction_count.h"
 #include "base/trace_event/trace_event_impl.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -13,8 +14,8 @@
 
 const TraceEvent& MakeTraceEvent(const char* name) {
   static TraceEvent event;
-  event.Reset(0, TimeTicks(), ThreadTicks(), 'b', nullptr, name, "", 0, 0,
-              nullptr, 0);
+  event.Reset(0, TimeTicks(), ThreadTicks(), ThreadInstructionCount(), 'b',
+              nullptr, name, "", 0, 0, nullptr, 0);
   return event;
 }
 
diff --git a/base/trace_event/trace_event.h b/base/trace_event/trace_event.h
index 50030ec..ac241e4 100644
--- a/base/trace_event/trace_event.h
+++ b/base/trace_event/trace_event.h
@@ -22,6 +22,7 @@
 #include "base/trace_event/builtin_categories.h"
 #include "base/trace_event/common/trace_event_common.h"
 #include "base/trace_event/heap_profiler.h"
+#include "base/trace_event/thread_instruction_count.h"
 #include "base/trace_event/trace_arguments.h"
 #include "base/trace_event/trace_category.h"
 #include "base/trace_event/trace_log.h"
@@ -377,28 +378,28 @@
 
 // Implementation detail: internal macro to create static category and add
 // event if the category is enabled.
-#define INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMPS(                \
-    category_group, name, id, thread_id, begin_timestamp, end_timestamp,    \
-    thread_end_timestamp, flags, ...)                                       \
-  do {                                                                      \
-    INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group);                 \
-    if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED()) {                    \
-      trace_event_internal::TraceID trace_event_trace_id((id));             \
-      unsigned int trace_event_flags =                                      \
-          flags | trace_event_trace_id.id_flags();                          \
-      const unsigned char* uid_category_group_enabled =                     \
-          INTERNAL_TRACE_EVENT_UID(category_group_enabled);                 \
-      auto handle =                                                         \
-          trace_event_internal::AddTraceEventWithThreadIdAndTimestamp(      \
-              TRACE_EVENT_PHASE_COMPLETE, uid_category_group_enabled, name, \
-              trace_event_trace_id.scope(), trace_event_trace_id.raw_id(),  \
-              thread_id, begin_timestamp,                                   \
-              trace_event_flags | TRACE_EVENT_FLAG_EXPLICIT_TIMESTAMP,      \
-              trace_event_internal::kNoId, ##__VA_ARGS__);                  \
-      TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION_EXPLICIT(                 \
-          uid_category_group_enabled, name, handle, end_timestamp,          \
-          thread_end_timestamp);                                            \
-    }                                                                       \
+#define INTERNAL_TRACE_EVENT_ADD_WITH_ID_TID_AND_TIMESTAMPS(                  \
+    category_group, name, id, thread_id, begin_timestamp, end_timestamp,      \
+    thread_end_timestamp, flags, ...)                                         \
+  do {                                                                        \
+    INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group);                   \
+    if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED()) {                      \
+      trace_event_internal::TraceID trace_event_trace_id((id));               \
+      unsigned int trace_event_flags =                                        \
+          flags | trace_event_trace_id.id_flags();                            \
+      const unsigned char* uid_category_group_enabled =                       \
+          INTERNAL_TRACE_EVENT_UID(category_group_enabled);                   \
+      auto handle =                                                           \
+          trace_event_internal::AddTraceEventWithThreadIdAndTimestamp(        \
+              TRACE_EVENT_PHASE_COMPLETE, uid_category_group_enabled, name,   \
+              trace_event_trace_id.scope(), trace_event_trace_id.raw_id(),    \
+              thread_id, begin_timestamp,                                     \
+              trace_event_flags | TRACE_EVENT_FLAG_EXPLICIT_TIMESTAMP,        \
+              trace_event_internal::kNoId, ##__VA_ARGS__);                    \
+      TRACE_EVENT_API_UPDATE_TRACE_EVENT_DURATION_EXPLICIT(                   \
+          uid_category_group_enabled, name, handle, end_timestamp,            \
+          thread_end_timestamp, base::trace_event::ThreadInstructionCount()); \
+    }                                                                         \
   } while (0)
 
 // The linked ID will not be mangled.
@@ -705,12 +706,13 @@
                          const char* name,
                          base::trace_event::TraceEventHandle handle);
 
-void BASE_EXPORT
-UpdateTraceEventDurationExplicit(const unsigned char* category_group_enabled,
-                                 const char* name,
-                                 base::trace_event::TraceEventHandle handle,
-                                 const base::TimeTicks& now,
-                                 const base::ThreadTicks& thread_now);
+void BASE_EXPORT UpdateTraceEventDurationExplicit(
+    const unsigned char* category_group_enabled,
+    const char* name,
+    base::trace_event::TraceEventHandle handle,
+    const base::TimeTicks& now,
+    const base::ThreadTicks& thread_now,
+    base::trace_event::ThreadInstructionCount thread_instruction_now);
 
 // These AddTraceEvent and AddTraceEventWithThreadIdAndTimestamp template
 // functions are defined here instead of in the macro, because the arg_values
diff --git a/base/trace_event/trace_event_impl.cc b/base/trace_event/trace_event_impl.cc
index ee3e21d..658aff3 100644
--- a/base/trace_event/trace_event_impl.cc
+++ b/base/trace_event/trace_event_impl.cc
@@ -35,6 +35,7 @@
 TraceEvent::TraceEvent(int thread_id,
                        TimeTicks timestamp,
                        ThreadTicks thread_timestamp,
+                       ThreadInstructionCount thread_instruction_count,
                        char phase,
                        const unsigned char* category_group_enabled,
                        const char* name,
@@ -45,6 +46,7 @@
                        unsigned int flags)
     : timestamp_(timestamp),
       thread_timestamp_(thread_timestamp),
+      thread_instruction_count_(thread_instruction_count),
       scope_(scope),
       id_(id),
       category_group_enabled_(category_group_enabled),
@@ -65,6 +67,7 @@
   // Only reset fields that won't be initialized in Reset(int, ...), or that may
   // hold references to other objects.
   duration_ = TimeDelta::FromInternalValue(-1);
+  thread_instruction_delta_ = ThreadInstructionDelta();
   args_.Reset();
   parameter_copy_storage_.Reset();
 }
@@ -72,6 +75,7 @@
 void TraceEvent::Reset(int thread_id,
                        TimeTicks timestamp,
                        ThreadTicks thread_timestamp,
+                       ThreadInstructionCount thread_instruction_count,
                        char phase,
                        const unsigned char* category_group_enabled,
                        const char* name,
@@ -90,6 +94,7 @@
   thread_id_ = thread_id;
   flags_ = flags;
   bind_id_ = bind_id;
+  thread_instruction_count_ = thread_instruction_count;
   phase_ = phase;
 
   InitArgs(args);
@@ -103,7 +108,8 @@
 }
 
 void TraceEvent::UpdateDuration(const TimeTicks& now,
-                                const ThreadTicks& thread_now) {
+                                const ThreadTicks& thread_now,
+                                ThreadInstructionCount thread_instruction_now) {
   DCHECK_EQ(duration_.ToInternalValue(), -1);
   duration_ = now - timestamp_;
 
@@ -111,6 +117,11 @@
   // initialized when it was recorded.
   if (thread_timestamp_ != ThreadTicks())
     thread_duration_ = thread_now - thread_timestamp_;
+
+  if (!thread_instruction_count_.is_null()) {
+    thread_instruction_delta_ =
+        thread_instruction_now - thread_instruction_count_;
+  }
 }
 
 void TraceEvent::EstimateTraceMemoryOverhead(
@@ -191,6 +202,10 @@
       if (thread_duration != -1)
         StringAppendF(out, ",\"tdur\":%" PRId64, thread_duration);
     }
+    if (!thread_instruction_count_.is_null()) {
+      int64_t thread_instructions = thread_instruction_delta_.ToInternalValue();
+      StringAppendF(out, ",\"tidelta\":%" PRId64, thread_instructions);
+    }
   }
 
   // Output tts if thread_timestamp is valid.
diff --git a/base/trace_event/trace_event_impl.h b/base/trace_event/trace_event_impl.h
index 97cd581..161cd6d 100644
--- a/base/trace_event/trace_event_impl.h
+++ b/base/trace_event/trace_event_impl.h
@@ -23,6 +23,7 @@
 #include "base/synchronization/lock.h"
 #include "base/threading/thread_local.h"
 #include "base/trace_event/common/trace_event_common.h"
+#include "base/trace_event/thread_instruction_count.h"
 #include "base/trace_event/trace_arguments.h"
 #include "base/trace_event/trace_event_memory_overhead.h"
 #include "build/build_config.h"
@@ -60,6 +61,7 @@
   TraceEvent(int thread_id,
              TimeTicks timestamp,
              ThreadTicks thread_timestamp,
+             ThreadInstructionCount thread_instruction_count,
              char phase,
              const unsigned char* category_group_enabled,
              const char* name,
@@ -88,6 +90,7 @@
   void Reset(int thread_id,
              TimeTicks timestamp,
              ThreadTicks thread_timestamp,
+             ThreadInstructionCount thread_instruction_count,
              char phase,
              const unsigned char* category_group_enabled,
              const char* name,
@@ -97,7 +100,9 @@
              TraceArguments* args,
              unsigned int flags);
 
-  void UpdateDuration(const TimeTicks& now, const ThreadTicks& thread_now);
+  void UpdateDuration(const TimeTicks& now,
+                      const ThreadTicks& thread_now,
+                      ThreadInstructionCount thread_instruction_now);
 
   void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead* overhead);
 
@@ -116,11 +121,17 @@
 
   TimeTicks timestamp() const { return timestamp_; }
   ThreadTicks thread_timestamp() const { return thread_timestamp_; }
+  ThreadInstructionCount thread_instruction_count() const {
+    return thread_instruction_count_;
+  }
   char phase() const { return phase_; }
   int thread_id() const { return thread_id_; }
   int process_id() const { return process_id_; }
   TimeDelta duration() const { return duration_; }
   TimeDelta thread_duration() const { return thread_duration_; }
+  ThreadInstructionDelta thread_instruction_delta() const {
+    return thread_instruction_delta_;
+  }
   const char* scope() const { return scope_; }
   unsigned long long id() const { return id_; }
   unsigned int flags() const { return flags_; }
@@ -162,6 +173,8 @@
   ThreadTicks thread_timestamp_ = ThreadTicks();
   TimeDelta duration_ = TimeDelta::FromInternalValue(-1);
   TimeDelta thread_duration_ = TimeDelta();
+  ThreadInstructionCount thread_instruction_count_ = ThreadInstructionCount();
+  ThreadInstructionDelta thread_instruction_delta_ = ThreadInstructionDelta();
   // scope_ and id_ can be used to store phase-specific data.
   // The following should be default-initialized to the expression
   // trace_event_internal::kGlobalScope, which is nullptr, but its definition
diff --git a/base/trace_event/trace_log.cc b/base/trace_event/trace_log.cc
index fe765a0..d5e9241 100644
--- a/base/trace_event/trace_log.cc
+++ b/base/trace_event/trace_log.cc
@@ -40,6 +40,7 @@
 #include "base/trace_event/memory_dump_manager.h"
 #include "base/trace_event/memory_dump_provider.h"
 #include "base/trace_event/process_memory_dump.h"
+#include "base/trace_event/thread_instruction_count.h"
 #include "base/trace_event/trace_buffer.h"
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
@@ -98,6 +99,11 @@
              : ThreadTicks();
 }
 
+ThreadInstructionCount ThreadInstructionNow() {
+  return ThreadInstructionCount::IsSupported() ? ThreadInstructionCount::Now()
+                                               : ThreadInstructionCount();
+}
+
 template <typename T>
 void InitializeMetadataEvent(TraceEvent* trace_event,
                              int thread_id,
@@ -108,13 +114,14 @@
     return;
 
   TraceArguments args(arg_name, value);
-  trace_event->Reset(
-      thread_id, TimeTicks(), ThreadTicks(), TRACE_EVENT_PHASE_METADATA,
-      CategoryRegistry::kCategoryMetadata->state_ptr(), metadata_name,
-      trace_event_internal::kGlobalScope,  // scope
-      trace_event_internal::kNoId,         // id
-      trace_event_internal::kNoId,         // bind_id
-      &args, TRACE_EVENT_FLAG_NONE);
+  trace_event->Reset(thread_id, TimeTicks(), ThreadTicks(),
+                     ThreadInstructionCount(), TRACE_EVENT_PHASE_METADATA,
+                     CategoryRegistry::kCategoryMetadata->state_ptr(),
+                     metadata_name,
+                     trace_event_internal::kGlobalScope,  // scope
+                     trace_event_internal::kNoId,         // id
+                     trace_event_internal::kNoId,         // bind_id
+                     &args, TRACE_EVENT_FLAG_NONE);
 }
 
 class AutoThreadLocalBoolean {
@@ -1178,6 +1185,7 @@
 
   TimeTicks offset_event_timestamp = OffsetTimestamp(timestamp);
   ThreadTicks thread_now = ThreadNow();
+  ThreadInstructionCount thread_instruction_now = ThreadInstructionNow();
 
   ThreadLocalEventBuffer* thread_local_event_buffer = nullptr;
   if (*category_group_enabled & RECORDING_MODE) {
@@ -1233,9 +1241,9 @@
     auto trace_event_override =
         add_trace_event_override_.load(std::memory_order_relaxed);
     if (trace_event_override) {
-      TraceEvent new_trace_event(thread_id, offset_event_timestamp, thread_now,
-                                 phase, category_group_enabled, name, scope, id,
-                                 bind_id, args, flags);
+      TraceEvent new_trace_event(
+          thread_id, offset_event_timestamp, thread_now, thread_instruction_now,
+          phase, category_group_enabled, name, scope, id, bind_id, args, flags);
 
       trace_event_override(
           &new_trace_event,
@@ -1249,8 +1257,8 @@
   bool disabled_by_filters = false;
   if (*category_group_enabled & TraceCategory::ENABLED_FOR_FILTERING) {
     auto new_trace_event = std::make_unique<TraceEvent>(
-        thread_id, offset_event_timestamp, thread_now, phase,
-        category_group_enabled, name, scope, id, bind_id, args, flags);
+        thread_id, offset_event_timestamp, thread_now, thread_instruction_now,
+        phase, category_group_enabled, name, scope, id, bind_id, args, flags);
 
     disabled_by_filters = true;
     ForEachCategoryFilter(
@@ -1281,7 +1289,8 @@
       if (filtered_trace_event) {
         *trace_event = std::move(*filtered_trace_event);
       } else {
-        trace_event->Reset(thread_id, offset_event_timestamp, thread_now, phase,
+        trace_event->Reset(thread_id, offset_event_timestamp, thread_now,
+                           thread_instruction_now, phase,
                            category_group_enabled, name, scope, id, bind_id,
                            args, flags);
       }
@@ -1312,10 +1321,11 @@
   int thread_id = static_cast<int>(base::PlatformThread::CurrentId());
   ThreadTicks thread_now = ThreadNow();
   TimeTicks now = OffsetNow();
+  ThreadInstructionCount thread_instruction_now = ThreadInstructionNow();
   AutoLock lock(lock_);
   auto trace_event = std::make_unique<TraceEvent>(
-      thread_id, now, thread_now, TRACE_EVENT_PHASE_METADATA,
-      category_group_enabled, name,
+      thread_id, now, thread_now, thread_instruction_now,
+      TRACE_EVENT_PHASE_METADATA, category_group_enabled, name,
       trace_event_internal::kGlobalScope,  // scope
       trace_event_internal::kNoId,         // id
       trace_event_internal::kNoId,         // bind_id
@@ -1394,7 +1404,8 @@
     return;
 
   UpdateTraceEventDurationExplicit(category_group_enabled, name, handle,
-                                   OffsetNow(), ThreadNow());
+                                   OffsetNow(), ThreadNow(),
+                                   ThreadInstructionNow());
 }
 
 void TraceLog::UpdateTraceEventDurationExplicit(
@@ -1402,7 +1413,8 @@
     const char* name,
     TraceEventHandle handle,
     const TimeTicks& now,
-    const ThreadTicks& thread_now) {
+    const ThreadTicks& thread_now,
+    ThreadInstructionCount thread_instruction_now) {
   char category_group_enabled_local = *category_group_enabled;
   if (!category_group_enabled_local)
     return;
@@ -1424,7 +1436,7 @@
     auto update_duration_callback =
         update_duration_callback_.load(std::memory_order_relaxed);
     if (update_duration_callback) {
-      update_duration_callback(handle, now, thread_now);
+      update_duration_callback(handle, now, thread_now, thread_instruction_now);
       return;
     }
   }
@@ -1437,7 +1449,7 @@
     if (trace_event) {
       DCHECK(trace_event->phase() == TRACE_EVENT_PHASE_COMPLETE);
 
-      trace_event->UpdateDuration(now, thread_now);
+      trace_event->UpdateDuration(now, thread_now, thread_instruction_now);
 #if defined(OS_ANDROID)
       trace_event->SendToATrace();
 #endif
@@ -1802,10 +1814,12 @@
     const char* name,
     base::trace_event::TraceEventHandle handle,
     const base::TimeTicks& now,
-    const base::ThreadTicks& thread_now) {
+    const base::ThreadTicks& thread_now,
+    base::trace_event::ThreadInstructionCount thread_instruction_now) {
   return base::trace_event::TraceLog::GetInstance()
       ->UpdateTraceEventDurationExplicit(category_group_enabled, name, handle,
-                                         now, thread_now);
+                                         now, thread_now,
+                                         thread_instruction_now);
 }
 
 ScopedTraceBinaryEfficient::ScopedTraceBinaryEfficient(
diff --git a/base/trace_event/trace_log.h b/base/trace_event/trace_log.h
index 360e1234..55f6825 100644
--- a/base/trace_event/trace_log.h
+++ b/base/trace_event/trace_log.h
@@ -200,9 +200,11 @@
                                                  bool thread_will_flush,
                                                  TraceEventHandle* handle);
   using OnFlushCallback = void (*)();
-  using UpdateDurationCallback = void (*)(TraceEventHandle handle,
-                                          const TimeTicks& now,
-                                          const ThreadTicks& thread_now);
+  using UpdateDurationCallback =
+      void (*)(TraceEventHandle handle,
+               const TimeTicks& now,
+               const ThreadTicks& thread_now,
+               ThreadInstructionCount thread_instruction_now);
   // The callbacks will be called up until the point where the flush is
   // finished, i.e. must be callable until OutputCallback is called with
   // has_more_events==false.
@@ -291,7 +293,8 @@
       const char* name,
       TraceEventHandle handle,
       const TimeTicks& now,
-      const ThreadTicks& thread_now);
+      const ThreadTicks& thread_now,
+      ThreadInstructionCount thread_instruction_now);
 
   void EndFilteredEvent(const unsigned char* category_group_enabled,
                         const char* name,
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 094f83a6..ad3ab3f5 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8910690471883428096
\ No newline at end of file
+8910661150919848928
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 6c574847..15175047 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-8910690961961271888
\ No newline at end of file
+8910657347225830192
\ No newline at end of file
diff --git a/cc/trees/layer_tree_host_pixeltest_readback.cc b/cc/trees/layer_tree_host_pixeltest_readback.cc
index 3c7c03f..76e0ab0 100644
--- a/cc/trees/layer_tree_host_pixeltest_readback.cc
+++ b/cc/trees/layer_tree_host_pixeltest_readback.cc
@@ -29,9 +29,6 @@
 };
 
 struct ReadbackTestConfig {
-  ReadbackTestConfig(LayerTreeTest::RendererType renderer_type_,
-                     ReadbackType readback_type_)
-      : renderer_type(renderer_type_), readback_type(readback_type_) {}
   LayerTreeTest::RendererType renderer_type;
   ReadbackType readback_type;
 };
@@ -191,7 +188,11 @@
       base::FilePath(FILE_PATH_LITERAL("green_small_with_blue_corner.png")));
 }
 
-TEST_P(LayerTreeHostReadbackPixelTest, ReadbackSubtreeSurroundsTargetLayer) {
+using LayerTreeHostReadbackPixelTestMaybeVulkan =
+    LayerTreeHostReadbackPixelTest;
+
+TEST_P(LayerTreeHostReadbackPixelTestMaybeVulkan,
+       ReadbackSubtreeSurroundsTargetLayer) {
   scoped_refptr<SolidColorLayer> background =
       CreateSolidColorLayer(gfx::Rect(0, 0, 200, 200), SK_ColorWHITE);
 
@@ -379,7 +380,7 @@
       base::FilePath(FILE_PATH_LITERAL("green_with_blue_corner.png")));
 }
 
-TEST_P(LayerTreeHostReadbackPixelTest, ReadbackNonRootOrFirstLayer) {
+TEST_P(LayerTreeHostReadbackPixelTestMaybeVulkan, ReadbackNonRootOrFirstLayer) {
   // This test has 3 render passes with the copy request on the render pass in
   // the middle. This test caught an issue where copy requests on non-root
   // non-first render passes were being treated differently from the first
@@ -415,15 +416,40 @@
       base::FilePath(FILE_PATH_LITERAL("green.png")));
 }
 
-INSTANTIATE_TEST_SUITE_P(
-    ,
-    LayerTreeHostReadbackPixelTest,
-    ::testing::Values(
-        ReadbackTestConfig(LayerTreeTest::RENDERER_SOFTWARE, READBACK_BITMAP),
-        ReadbackTestConfig(LayerTreeTest::RENDERER_GL, READBACK_TEXTURE),
-        ReadbackTestConfig(LayerTreeTest::RENDERER_GL, READBACK_BITMAP),
-        ReadbackTestConfig(LayerTreeTest::RENDERER_SKIA_GL, READBACK_BITMAP),
-        ReadbackTestConfig(LayerTreeTest::RENDERER_SKIA_GL, READBACK_TEXTURE)));
+// TODO(crbug.com/963446): Enable these tests for Skia Vulkan using texture
+// readback.
+ReadbackTestConfig const kTestConfigs[] = {
+    ReadbackTestConfig{LayerTreeTest::RENDERER_SOFTWARE, READBACK_BITMAP},
+    ReadbackTestConfig{LayerTreeTest::RENDERER_GL, READBACK_TEXTURE},
+    ReadbackTestConfig{LayerTreeTest::RENDERER_GL, READBACK_BITMAP},
+    ReadbackTestConfig{LayerTreeTest::RENDERER_SKIA_GL, READBACK_TEXTURE},
+    ReadbackTestConfig{LayerTreeTest::RENDERER_SKIA_GL, READBACK_BITMAP},
+#if defined(ENABLE_CC_VULKAN_TESTS)
+    ReadbackTestConfig{LayerTreeTest::RENDERER_SKIA_VK, READBACK_BITMAP},
+#endif
+};
+
+INSTANTIATE_TEST_SUITE_P(,
+                         LayerTreeHostReadbackPixelTest,
+                         ::testing::ValuesIn(kTestConfigs));
+
+// TODO(crbug.com/974283): These tests are crashing with vulkan when TSan or
+// MSan are used.
+ReadbackTestConfig const kMaybeVulkanTestConfigs[] = {
+    ReadbackTestConfig{LayerTreeTest::RENDERER_SOFTWARE, READBACK_BITMAP},
+    ReadbackTestConfig{LayerTreeTest::RENDERER_GL, READBACK_TEXTURE},
+    ReadbackTestConfig{LayerTreeTest::RENDERER_GL, READBACK_BITMAP},
+    ReadbackTestConfig{LayerTreeTest::RENDERER_SKIA_GL, READBACK_TEXTURE},
+    ReadbackTestConfig{LayerTreeTest::RENDERER_SKIA_GL, READBACK_BITMAP},
+#if defined(ENABLE_CC_VULKAN_TESTS) && !defined(THREAD_SANITIZER) && \
+    !defined(MEMORY_SANITIZER)
+    ReadbackTestConfig{LayerTreeTest::RENDERER_SKIA_VK, READBACK_BITMAP},
+#endif
+};
+
+INSTANTIATE_TEST_SUITE_P(,
+                         LayerTreeHostReadbackPixelTestMaybeVulkan,
+                         ::testing::ValuesIn(kMaybeVulkanTestConfigs));
 
 class LayerTreeHostReadbackDeviceScalePixelTest
     : public LayerTreeHostReadbackPixelTest {
@@ -510,15 +536,9 @@
       base::FilePath(FILE_PATH_LITERAL("green_small_with_blue_corner.png")));
 }
 
-INSTANTIATE_TEST_SUITE_P(
-    ,
-    LayerTreeHostReadbackDeviceScalePixelTest,
-    ::testing::Values(
-        ReadbackTestConfig(LayerTreeTest::RENDERER_SOFTWARE, READBACK_BITMAP),
-        ReadbackTestConfig(LayerTreeTest::RENDERER_GL, READBACK_TEXTURE),
-        ReadbackTestConfig(LayerTreeTest::RENDERER_GL, READBACK_BITMAP),
-        ReadbackTestConfig(LayerTreeTest::RENDERER_SKIA_GL, READBACK_BITMAP),
-        ReadbackTestConfig(LayerTreeTest::RENDERER_SKIA_GL, READBACK_TEXTURE)));
+INSTANTIATE_TEST_SUITE_P(,
+                         LayerTreeHostReadbackDeviceScalePixelTest,
+                         ::testing::ValuesIn(kTestConfigs));
 
 class LayerTreeHostReadbackColorSpacePixelTest
     : public LayerTreeHostReadbackPixelTest {
@@ -556,15 +576,9 @@
                base::FilePath(FILE_PATH_LITERAL("srgb_green_in_p3.png")));
 }
 
-INSTANTIATE_TEST_SUITE_P(
-    ,
-    LayerTreeHostReadbackColorSpacePixelTest,
-    ::testing::Values(
-        ReadbackTestConfig(LayerTreeTest::RENDERER_SOFTWARE, READBACK_BITMAP),
-        ReadbackTestConfig(LayerTreeTest::RENDERER_GL, READBACK_TEXTURE),
-        ReadbackTestConfig(LayerTreeTest::RENDERER_GL, READBACK_BITMAP),
-        ReadbackTestConfig(LayerTreeTest::RENDERER_SKIA_GL, READBACK_BITMAP),
-        ReadbackTestConfig(LayerTreeTest::RENDERER_SKIA_GL, READBACK_TEXTURE)));
+INSTANTIATE_TEST_SUITE_P(,
+                         LayerTreeHostReadbackColorSpacePixelTest,
+                         ::testing::ValuesIn(kTestConfigs));
 
 }  // namespace
 }  // namespace cc
diff --git a/cc/trees/layer_tree_host_pixeltest_scrollbars.cc b/cc/trees/layer_tree_host_pixeltest_scrollbars.cc
index ba7f209..1e6cc926 100644
--- a/cc/trees/layer_tree_host_pixeltest_scrollbars.cc
+++ b/cc/trees/layer_tree_host_pixeltest_scrollbars.cc
@@ -89,10 +89,17 @@
   gfx::Rect rect_;
 };
 
+LayerTreeTest::RendererType const kRendererTypes[] = {
+    LayerTreeTest::RENDERER_GL,
+    LayerTreeTest::RENDERER_SKIA_GL,
+#if defined(ENABLE_CC_VULKAN_TESTS)
+    LayerTreeTest::RENDERER_SKIA_VK,
+#endif
+};
+
 INSTANTIATE_TEST_SUITE_P(,
                          LayerTreeHostScrollbarsPixelTest,
-                         ::testing::Values(LayerTreeTest::RENDERER_GL,
-                                           LayerTreeTest::RENDERER_SKIA_GL));
+                         ::testing::ValuesIn(kRendererTypes));
 
 TEST_P(LayerTreeHostScrollbarsPixelTest, NoScale) {
   scoped_refptr<SolidColorLayer> background =
@@ -189,7 +196,8 @@
   scale_transform.Scale(scale, scale);
   layer->SetTransform(scale_transform);
 
-  if (renderer_type() == RENDERER_SKIA_GL)
+  if (renderer_type() == RENDERER_SKIA_GL ||
+      renderer_type() == RENDERER_SKIA_VK)
     pixel_comparator_ = std::make_unique<FuzzyPixelOffByOneComparator>(true);
 
   RunPixelTest(renderer_type(), background,
@@ -256,8 +264,7 @@
 
 INSTANTIATE_TEST_SUITE_P(,
                          LayerTreeHostOverlayScrollbarsPixelTest,
-                         ::testing::Values(LayerTreeTest::RENDERER_GL,
-                                           LayerTreeTest::RENDERER_SKIA_GL));
+                         ::testing::ValuesIn(kRendererTypes));
 
 // Simulate increasing the thickness of a painted overlay scrollbar. Ensure that
 // the scrollbar border remains crisp.
diff --git a/cc/trees/layer_tree_host_pixeltest_synchronous.cc b/cc/trees/layer_tree_host_pixeltest_synchronous.cc
index fac28b3..fd1c4cd 100644
--- a/cc/trees/layer_tree_host_pixeltest_synchronous.cc
+++ b/cc/trees/layer_tree_host_pixeltest_synchronous.cc
@@ -57,7 +57,12 @@
 INSTANTIATE_TEST_SUITE_P(,
                          LayerTreeHostSynchronousPixelTest,
                          ::testing::Values(LayerTreeTest::RENDERER_GL,
-                                           LayerTreeTest::RENDERER_SKIA_GL));
+                                           LayerTreeTest::RENDERER_SKIA_GL
+#if defined(ENABLE_CC_VULKAN_TESTS)
+                                           ,
+                                           LayerTreeTest::RENDERER_SKIA_VK
+#endif
+                                           ));
 
 TEST_P(LayerTreeHostSynchronousPixelTest, OneContentLayerZeroCopy) {
   use_zero_copy_ = true;
diff --git a/cc/trees/layer_tree_host_pixeltest_tiles.cc b/cc/trees/layer_tree_host_pixeltest_tiles.cc
index c9ab5ac..17ff346 100644
--- a/cc/trees/layer_tree_host_pixeltest_tiles.cc
+++ b/cc/trees/layer_tree_host_pixeltest_tiles.cc
@@ -172,6 +172,7 @@
   scoped_refptr<PictureLayer> picture_layer_;
 };
 
+// TODO(crbug.com/963446): Enable these tests for Vulkan.
 INSTANTIATE_TEST_SUITE_P(
     ,
     LayerTreeHostTilesTestPartialInvalidation,
@@ -198,6 +199,7 @@
 using LayerTreeHostTilesTestPartialInvalidationMultiThread =
     LayerTreeHostTilesTestPartialInvalidation;
 
+// TODO(crbug.com/963446): Enable these tests for Vulkan.
 INSTANTIATE_TEST_SUITE_P(
     ,
     LayerTreeHostTilesTestPartialInvalidationMultiThread,
@@ -227,6 +229,7 @@
 using LayerTreeHostTilesTestPartialInvalidationLowBitDepth =
     LayerTreeHostTilesTestPartialInvalidation;
 
+// TODO(crbug.com/963446): Enable these tests for Vulkan.
 INSTANTIATE_TEST_SUITE_P(
     ,
     LayerTreeHostTilesTestPartialInvalidationLowBitDepth,
diff --git a/chrome/android/features/tab_ui/java/res/layout/tab_grid_dialog_layout.xml b/chrome/android/features/tab_ui/java/res/layout/tab_grid_dialog_layout.xml
new file mode 100644
index 0000000..7e27743
--- /dev/null
+++ b/chrome/android/features/tab_ui/java/res/layout/tab_grid_dialog_layout.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2019 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/dialog_container_view"
+    android:background="@color/modern_primary_color">
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/bottom_sheet_peek_height"
+        android:id="@+id/dialog_ungroup_bar"
+        android:text="@string/tab_grid_dialog_remove_from_group"
+        android:textAppearance="@style/TextAppearance.WhiteHeadline"
+        android:background="@color/modern_blue_600"
+        android:gravity="center"
+        android:layout_alignParentBottom="true"
+        android:layout_centerHorizontal="true"
+        android:visibility="gone"/>
+</RelativeLayout>
+
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherCoordinator.java
index c0019425..ae387a8 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherCoordinator.java
@@ -99,7 +99,7 @@
         mTabGridCoordinator = new TabListCoordinator(TabListCoordinator.TabListMode.GRID, context,
                 tabModelSelector, mMultiThumbnailCardProvider, titleProvider, true,
                 mMediator::getCreateGroupButtonOnClickListener, gridCardOnClickListenerProvider,
-                compositorViewHolder, compositorViewHolder.getDynamicResourceLoader(), true,
+                null, compositorViewHolder, compositorViewHolder.getDynamicResourceLoader(), true,
                 org.chromium.chrome.tab_ui.R.layout.grid_tab_switcher_layout, COMPONENT_NAME);
 
         HistoryNavigationLayout navigation = compositorViewHolder.findViewById(
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
index 7745039..b274986 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
@@ -40,15 +40,15 @@
 
         mToolbarPropertyModel = new PropertyModel(TabGridSheetProperties.ALL_KEYS);
 
-        mTabListCoordinator = new TabListCoordinator(TabListCoordinator.TabListMode.GRID, context,
-                tabModelSelector, tabContentManager::getTabThumbnailWithCallback, null, false, null,
-                null, compositorViewHolder, null, false, R.layout.tab_list_recycler_view_layout,
-                COMPONENT_NAME);
-
         mMediator =
                 new TabGridDialogMediator(context, this::resetWithListOfTabs, mToolbarPropertyModel,
                         tabModelSelector, tabCreatorManager, resetHandler, animationOriginProvider);
 
+        mTabListCoordinator = new TabListCoordinator(TabListCoordinator.TabListMode.GRID, context,
+                tabModelSelector, tabContentManager::getTabThumbnailWithCallback, null, false, null,
+                null, mMediator.getTabGridDialogHandler(), compositorViewHolder, null, false,
+                R.layout.tab_list_recycler_view_layout, COMPONENT_NAME);
+
         mParentLayout = new TabGridDialogParent(context, compositorViewHolder);
     }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
index a12fd4f8..f2f907f 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
@@ -67,6 +67,7 @@
     private final TabGridDialogMediator.ResetHandler mDialogResetHandler;
     private final GridTabSwitcherMediator.ResetHandler mGridTabSwitcherResetHandler;
     private final AnimationOriginProvider mAnimationOriginProvider;
+    private final DialogHandler mTabGridDialogHandler;
     private int mCurrentTabId = Tab.INVALID_TAB_ID;
 
     TabGridDialogMediator(Context context, TabGridDialogMediator.ResetHandler dialogResetHandler,
@@ -81,6 +82,7 @@
         mDialogResetHandler = dialogResetHandler;
         mGridTabSwitcherResetHandler = gridTabSwitcherResetHandler;
         mAnimationOriginProvider = animationOriginProvider;
+        mTabGridDialogHandler = new DialogHandler();
 
         // Register for tab model.
         mTabModelObserver = new EmptyTabModelObserver() {
@@ -223,4 +225,25 @@
                 .getCurrentTabModelFilter()
                 .getRelatedTabList(tabId);
     }
+
+    TabListMediator.TabGridDialogHandler getTabGridDialogHandler() {
+        return mTabGridDialogHandler;
+    }
+
+    /**
+     * A handler that handles TabGridDialog related changes originated from {@link TabListMediator}
+     * and {@link TabGridItemTouchHelperCallback}.
+     */
+    class DialogHandler implements TabListMediator.TabGridDialogHandler {
+        @Override
+        public void updateUngroupBarStatus(@TabGridDialogParent.UngroupBarStatus int status) {
+            mModel.set(TabGridSheetProperties.UNGROUP_BAR_STATUS, status);
+        }
+
+        @Override
+        public void updateDialogContent(int tabId) {
+            mCurrentTabId = tabId;
+            updateDialog();
+        }
+    }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogParent.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogParent.java
index a0fc983..5efa703 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogParent.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogParent.java
@@ -12,21 +12,26 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.support.annotation.IntDef;
 import android.util.DisplayMetrics;
 import android.view.Gravity;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 import android.widget.PopupWindow;
 import android.widget.RelativeLayout;
+import android.widget.TextView;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.ContextUtils;
 import org.chromium.chrome.browser.widget.ScrimView;
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.ui.interpolators.BakedBezierInterpolator;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Parent for TabGridDialog component.
  * TODO(yuezhanggg): Add animations of card scales up to dialog and dialog scales down to card when
@@ -34,6 +39,15 @@
  */
 public class TabGridDialogParent {
     private static final int DIALOG_ANIMATION_DURATION = 300;
+    @IntDef({UngroupBarStatus.SHOW, UngroupBarStatus.HIDE, UngroupBarStatus.HOVERED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UngroupBarStatus {
+        int SHOW = 0;
+        int HIDE = 1;
+        int HOVERED = 2;
+        int NUM_ENTRIES = 3;
+    }
+
     private final ComponentCallbacks mComponentCallbacks;
     private final FrameLayout.LayoutParams mContainerParams;
     private final ViewGroup mParent;
@@ -56,6 +70,8 @@
     private int mDialogHeight;
     private int mSideMargin;
     private int mTopMargin;
+    private View mContentView;
+    private TextView mUngroupBar;
 
     TabGridDialogParent(Context context, ViewGroup parent) {
         mParent = parent;
@@ -79,23 +95,22 @@
             public void onLowMemory() {}
         };
         ContextUtils.getApplicationContext().registerComponentCallbacks(mComponentCallbacks);
-        setupDialogContent(context);
+        setupDialogContent(context, parent);
         prepareAnimation();
     }
 
-    private void setupDialogContent(Context context) {
+    private void setupDialogContent(Context context, ViewGroup parent) {
         FrameLayout backgroundView = new FrameLayout(context);
-        mDialogContainerView = new RelativeLayout(context);
+        mDialogContainerView = (RelativeLayout) LayoutInflater.from(context).inflate(
+                R.layout.tab_grid_dialog_layout, parent, false);
         mDialogContainerView.setLayoutParams(mContainerParams);
-        mDialogContainerView.setBackgroundColor(ApiCompatibilityUtils.getColor(
-                context.getResources(), org.chromium.chrome.R.color.modern_primary_color));
+        mUngroupBar = mDialogContainerView.findViewById(R.id.dialog_ungroup_bar);
+        backgroundView.addView(mDialogContainerView);
+
         mScrimView = new ScrimView(context, null, backgroundView);
         mPopupWindow = new PopupWindow(backgroundView, 0, 0);
         mBlockView = new View(context);
-        mBlockView.setLayoutParams(new RelativeLayout.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
         mBlockView.setOnClickListener(null);
-        backgroundView.addView(mDialogContainerView);
         updateDialogWithOrientation(context, context.getResources().getConfiguration().orientation);
     }
 
@@ -248,9 +263,11 @@
      * @param recyclerView The recyclerview to be added to dialog.
      */
     void resetDialog(View toolbarView, View recyclerView) {
+        mContentView = recyclerView;
         mDialogContainerView.removeAllViews();
         mDialogContainerView.addView(toolbarView);
-        mDialogContainerView.addView(recyclerView);
+        mDialogContainerView.addView(mContentView);
+        mDialogContainerView.addView(mUngroupBar);
         RelativeLayout.LayoutParams params =
                 (RelativeLayout.LayoutParams) recyclerView.getLayoutParams();
         params.setMargins(0, mToolbarHeight, 0, 0);
@@ -294,4 +311,23 @@
     public void destroy() {
         ContextUtils.getApplicationContext().unregisterComponentCallbacks(mComponentCallbacks);
     }
+
+    /**
+     * Update the ungroup bar based on {@code status}.
+     *
+     * @param status The status in {@link TabGridDialogParent.UngroupBarStatus} that the ungroup bar
+     *         should be updated to.
+     */
+    public void updateUngroupBar(int status) {
+        if (status == UngroupBarStatus.SHOW) {
+            mUngroupBar.setVisibility(View.VISIBLE);
+            mUngroupBar.setAlpha(1f);
+            mUngroupBar.bringToFront();
+        } else if (status == UngroupBarStatus.HIDE) {
+            mUngroupBar.setVisibility(View.INVISIBLE);
+        } else if (status == UngroupBarStatus.HOVERED) {
+            mUngroupBar.setAlpha(0.7f);
+            mContentView.bringToFront();
+        }
+    }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridItemTouchHelperCallback.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridItemTouchHelperCallback.java
index 6aadc51f..e59acb4 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridItemTouchHelperCallback.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridItemTouchHelperCallback.java
@@ -32,23 +32,28 @@
     private final TabModelSelector mTabModelSelector;
     private final TabListMediator.TabActionListener mTabClosedListener;
     private final String mComponentName;
+    private final TabListMediator.TabGridDialogHandler mTabGridDialogHandler;
     private float mSwipeToDismissThreshold;
     private float mMergeThreshold;
+    private float mUngroupThreshold;
     private boolean mActionsOnAllRelatedTabs;
     private int mDragFlags;
     private int mSelectedTabIndex = TabModel.INVALID_TAB_INDEX;
     private int mHoveredTabIndex = TabModel.INVALID_TAB_INDEX;
+    private int mUnGroupTabIndex = TabModel.INVALID_TAB_INDEX;
     private RecyclerView mRecyclerView;
 
     public TabGridItemTouchHelperCallback(TabListModel tabListModel,
             TabModelSelector tabModelSelector, TabListMediator.TabActionListener tabClosedListener,
-            String componentName, boolean actionsOnAllRelatedTabs) {
+            TabListMediator.TabGridDialogHandler tabGridDialogHandler, String componentName,
+            boolean actionsOnAllRelatedTabs) {
         super(0, 0);
         mModel = tabListModel;
         mTabModelSelector = tabModelSelector;
         mTabClosedListener = tabClosedListener;
         mComponentName = componentName;
         mActionsOnAllRelatedTabs = actionsOnAllRelatedTabs;
+        mTabGridDialogHandler = tabGridDialogHandler;
     }
 
     /**
@@ -59,9 +64,11 @@
      * @param mergeThreshold                   Defines the threshold of how much two items need to
      *         be overlapped in order to be considered as a merge operation.
      */
-    void setupCallback(float swipeToDismissThreshold, float mergeThreshold) {
+    void setupCallback(
+            float swipeToDismissThreshold, float mergeThreshold, float ungroupThreshold) {
         mSwipeToDismissThreshold = swipeToDismissThreshold;
         mMergeThreshold = mergeThreshold;
+        mUngroupThreshold = ungroupThreshold;
         boolean isTabGroupEnabled = FeatureUtilities.isTabGroupsAndroidEnabled();
         boolean isTabGroupUiImprovementEnabled =
                 FeatureUtilities.isTabGroupsAndroidUiImprovementsEnabled();
@@ -141,8 +148,20 @@
             if (mHoveredTabIndex == TabModel.INVALID_TAB_INDEX) {
                 mModel.updateSelectedTabForMergeToGroup(mSelectedTabIndex, false);
             }
+            if (mUnGroupTabIndex != TabModel.INVALID_TAB_INDEX) {
+                TabGroupModelFilter filter =
+                        (TabGroupModelFilter) mTabModelSelector.getTabModelFilterProvider()
+                                .getCurrentTabModelFilter();
+                filter.moveTabOutOfGroup(mModel.get(mUnGroupTabIndex).get(TabProperties.TAB_ID));
+                mRecyclerView.getAdapter().notifyDataSetChanged();
+            }
             mHoveredTabIndex = TabModel.INVALID_TAB_INDEX;
             mSelectedTabIndex = TabModel.INVALID_TAB_INDEX;
+            mUnGroupTabIndex = TabModel.INVALID_TAB_INDEX;
+            if (mTabGridDialogHandler != null) {
+                mTabGridDialogHandler.updateUngroupBarStatus(
+                        TabGridDialogParent.UngroupBarStatus.HIDE);
+            }
         }
     }
 
@@ -165,6 +184,19 @@
             if (prev_hovered != mHoveredTabIndex) {
                 mModel.updateHoveredTabForMergeToGroup(prev_hovered, false);
             }
+        } else if (actionState == ItemTouchHelper.ACTION_STATE_DRAG
+                && mTabGridDialogHandler != null) {
+            // Not allow ungrouping the last tab in group.
+            if (recyclerView.getAdapter().getItemCount() == 1) return;
+            boolean isHoveredOnUngroupBar = viewHolder.itemView.getBottom() + dY
+                    > recyclerView.getBottom() - mUngroupThreshold;
+            mUnGroupTabIndex = isHoveredOnUngroupBar ? viewHolder.getAdapterPosition()
+                                                     : TabModel.INVALID_TAB_INDEX;
+            mTabGridDialogHandler.updateUngroupBarStatus(isHoveredOnUngroupBar
+                            ? TabGridDialogParent.UngroupBarStatus.HOVERED
+                            : (mSelectedTabIndex == TabModel.INVALID_TAB_INDEX
+                                            ? TabGridDialogParent.UngroupBarStatus.HIDE
+                                            : TabGridDialogParent.UngroupBarStatus.SHOW));
         }
     }
 
@@ -202,4 +234,9 @@
     void setSelectedTabIndexForTest(int index) {
         mSelectedTabIndex = index;
     }
+
+    @VisibleForTesting
+    void setUnGroupTabIndexForTest(int index) {
+        mUnGroupTabIndex = index;
+    }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetCoordinator.java
index 219050e..bd7b6dfb 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetCoordinator.java
@@ -45,7 +45,7 @@
 
         mTabGridCoordinator = new TabListCoordinator(TabListCoordinator.TabListMode.GRID, context,
                 tabModelSelector, tabContentManager::getTabThumbnailWithCallback, null, false, null,
-                null, bottomSheetController.getBottomSheet(), null, false,
+                null, null, bottomSheetController.getBottomSheet(), null, false,
                 R.layout.tab_list_recycler_view_layout, COMPONENT_NAME);
 
         mMediator = new TabGridSheetMediator(mContext, bottomSheetController,
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetProperties.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetProperties.java
index e4370ea..87239a9 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetProperties.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetProperties.java
@@ -34,7 +34,9 @@
             new PropertyModel.WritableObjectPropertyKey<>();
     public static final PropertyModel.WritableObjectPropertyKey<Rect> ANIMATION_SOURCE_RECT =
             new PropertyModel.WritableObjectPropertyKey<>();
+    public static final PropertyModel.WritableIntPropertyKey UNGROUP_BAR_STATUS =
+            new PropertyModel.WritableIntPropertyKey();
     public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {COLLAPSE_CLICK_LISTENER,
             ADD_CLICK_LISTENER, HEADER_TITLE, CONTENT_TOP_MARGIN, PRIMARY_COLOR, TINT,
-            IS_DIALOG_VISIBLE, SCRIMVIEW_OBSERVER, ANIMATION_SOURCE_RECT};
+            IS_DIALOG_VISIBLE, SCRIMVIEW_OBSERVER, ANIMATION_SOURCE_RECT, UNGROUP_BAR_STATUS};
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetViewBinder.java
index 890007c6..3f7570c1 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetViewBinder.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetViewBinder.java
@@ -13,6 +13,7 @@
 import static org.chromium.chrome.browser.tasks.tab_management.TabGridSheetProperties.PRIMARY_COLOR;
 import static org.chromium.chrome.browser.tasks.tab_management.TabGridSheetProperties.SCRIMVIEW_OBSERVER;
 import static org.chromium.chrome.browser.tasks.tab_management.TabGridSheetProperties.TINT;
+import static org.chromium.chrome.browser.tasks.tab_management.TabGridSheetProperties.UNGROUP_BAR_STATUS;
 
 import android.support.annotation.Nullable;
 import android.view.View;
@@ -75,6 +76,8 @@
             }
         } else if (ANIMATION_SOURCE_RECT == propertyKey) {
             viewHolder.dialogView.setupDialogAnimation(model.get(ANIMATION_SOURCE_RECT));
+        } else if (UNGROUP_BAR_STATUS == propertyKey) {
+            viewHolder.dialogView.updateUngroupBar(model.get(UNGROUP_BAR_STATUS));
         }
     }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
index 72d51a7..bfced2e 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
@@ -77,7 +77,7 @@
         TabContentManager tabContentManager = activity.getTabContentManager();
 
         mTabStripCoordinator = new TabListCoordinator(TabListCoordinator.TabListMode.STRIP,
-                mContext, tabModelSelector, null, null, false, null, null,
+                mContext, tabModelSelector, null, null, false, null, null, null,
                 mTabStripToolbarCoordinator.getTabListContainerView(), null, true,
                 R.layout.tab_list_recycler_view_layout, COMPONENT_NAME);
 
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 ae45df0..dad0312 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
@@ -81,6 +81,7 @@
             @Nullable TabListMediator.CreateGroupButtonProvider createGroupButtonProvider,
             @Nullable TabListMediator
                     .GridCardOnClickListenerProvider gridCardOnClickListenerProvider,
+            @Nullable TabListMediator.TabGridDialogHandler dialogHandler,
             @NonNull ViewGroup parentView, @Nullable DynamicResourceLoader dynamicResourceLoader,
             boolean attachToParent, @LayoutRes int layoutId, String componentName) {
         TabListModel tabListModel = new TabListModel();
@@ -141,12 +142,17 @@
 
         mMediator = new TabListMediator(tabListModel, tabModelSelector, thumbnailProvider,
                 titleProvider, tabListFaviconProvider, actionOnRelatedTabs,
-                createGroupButtonProvider, null, gridCardOnClickListenerProvider, componentName);
+                createGroupButtonProvider, null, gridCardOnClickListenerProvider, dialogHandler,
+                componentName);
 
         if (mMode == TabListMode.GRID) {
             ItemTouchHelper touchHelper = new ItemTouchHelper(mMediator.getItemTouchHelperCallback(
                     context.getResources().getDimension(R.dimen.swipe_to_dismiss_threshold),
-                    context.getResources().getDimension(R.dimen.tab_grid_merge_threshold)));
+                    context.getResources().getDimension(R.dimen.tab_grid_merge_threshold),
+                    context.getResources().getDimension(R.dimen.bottom_sheet_peek_height)));
+            touchHelper.attachToRecyclerView(mRecyclerView);
+            mMediator.registerOrientationListener(
+                    (GridLayoutManager) mRecyclerView.getLayoutManager());
             touchHelper.attachToRecyclerView(mRecyclerView);
             mMediator.registerOrientationListener(
                     (GridLayoutManager) mRecyclerView.getLayoutManager());
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 ce75e7ae..e9aa6600 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
@@ -69,6 +69,26 @@
     public interface TitleProvider { String getTitle(Tab tab); }
 
     /**
+     * An interface to handle requests about updating TabGridDialog.
+     */
+    public interface TabGridDialogHandler {
+        /**
+         * This method updates the status of the ungroup bar in TabGridDialog.
+         *
+         * @param status The status in {@link TabGridDialogParent.UngroupBarStatus} that the ungroup
+         *         bar should be updated to.
+         */
+        void updateUngroupBarStatus(@TabGridDialogParent.UngroupBarStatus int status);
+
+        /**
+         * This method updates the content of the TabGridDialog.
+         *
+         * @param tabId The id of the {@link Tab} that is used to update TabGridDialog.
+         */
+        void updateDialogContent(int tabId);
+    }
+
+    /**
      * The object to set to TabProperties.THUMBNAIL_FETCHER for the TabGridViewBinder to obtain
      * the thumbnail asynchronously.
      */
@@ -163,6 +183,7 @@
     private final CreateGroupButtonProvider mCreateGroupButtonProvider;
     private final SelectionDelegateProvider mSelectionDelegateProvider;
     private final GridCardOnClickListenerProvider mGridCardOnClickListenerProvider;
+    private final TabGridDialogHandler mTabGridDialogHandler;
     private final String mComponentName;
     private boolean mActionsOnAllRelatedTabs;
     private ComponentCallbacks mComponentCallbacks;
@@ -279,7 +300,7 @@
             @Nullable CreateGroupButtonProvider createGroupButtonProvider,
             @Nullable SelectionDelegateProvider selectionDelegateProvider,
             @Nullable GridCardOnClickListenerProvider gridCardOnClickListenerProvider,
-            String componentName) {
+            @Nullable TabGridDialogHandler dialogHandler, String componentName) {
         mTabModelSelector = tabModelSelector;
         mThumbnailProvider = thumbnailProvider;
         mModel = model;
@@ -289,6 +310,7 @@
         mCreateGroupButtonProvider = createGroupButtonProvider;
         mSelectionDelegateProvider = selectionDelegateProvider;
         mGridCardOnClickListenerProvider = gridCardOnClickListenerProvider;
+        mTabGridDialogHandler = dialogHandler;
         mActionsOnAllRelatedTabs = actionOnRelatedTabs;
 
         mTabModelObserver = new EmptyTabModelObserver() {
@@ -375,6 +397,30 @@
                 }
 
                 @Override
+                public void didMoveTabOutOfGroup(Tab movedTab, int prevFilterIndex) {
+                    TabGroupModelFilter filter =
+                            (TabGroupModelFilter) mTabModelSelector.getTabModelFilterProvider()
+                                    .getCurrentTabModelFilter();
+                    if (mTabGridDialogHandler != null) {
+                        int curIndex = mModel.indexFromId(movedTab.getId());
+                        if (!isValidMovePosition(curIndex)) return;
+                        mModel.removeAt(curIndex);
+                        mTabGridDialogHandler.updateDialogContent(
+                                filter.getTabAt(prevFilterIndex).getId());
+                        return;
+                    }
+                    if (mActionsOnAllRelatedTabs) {
+                        Tab currentSelectedTab = mTabModelSelector.getCurrentTab();
+                        addTabInfoToModel(movedTab, mModel.size(),
+                                currentSelectedTab.getId() == movedTab.getId());
+                        boolean isSelected = mTabModelSelector.getCurrentTabId()
+                                == filter.getTabAt(prevFilterIndex).getId();
+                        updateTab(prevFilterIndex, filter.getTabAt(prevFilterIndex), isSelected,
+                                true, false);
+                    }
+                }
+
+                @Override
                 public void didMergeTabToGroup(Tab movedTab, int selectedTabIdInGroup) {
                     if (!mActionsOnAllRelatedTabs) return;
                     Pair<Integer, Integer> positions =
@@ -489,8 +535,9 @@
             }
         };
 
-        mTabGridItemTouchHelperCallback = new TabGridItemTouchHelperCallback(mModel,
-                mTabModelSelector, mTabClosedListener, mComponentName, mActionsOnAllRelatedTabs);
+        mTabGridItemTouchHelperCallback =
+                new TabGridItemTouchHelperCallback(mModel, mTabModelSelector, mTabClosedListener,
+                        mTabGridDialogHandler, mComponentName, mActionsOnAllRelatedTabs);
     }
 
     private void onTabClosedFrom(int tabId, String fromComponent) {
@@ -637,7 +684,6 @@
         mModel.get(index).set(TabProperties.IS_SELECTED, isSelected);
         mModel.get(index).set(TabProperties.TITLE, mTitleProvider.getTitle(tab));
         mModel.get(index).set(TabProperties.IS_HIDDEN, false);
-
         Callback<Drawable> faviconCallback = drawable -> {
             int modelIndex = mModel.indexFromId(tab.getId());
             if (modelIndex != Tab.INVALID_TAB_ID && drawable != null) {
@@ -656,9 +702,10 @@
     /**
      * @return The callback that hosts the logic for swipe and drag related actions.
      */
-    ItemTouchHelper.SimpleCallback getItemTouchHelperCallback(
-            final float swipeToDismissThreshold, final float mergeThreshold) {
-        mTabGridItemTouchHelperCallback.setupCallback(swipeToDismissThreshold, mergeThreshold);
+    ItemTouchHelper.SimpleCallback getItemTouchHelperCallback(final float swipeToDismissThreshold,
+            final float mergeThreshold, final float ungroupThreshold) {
+        mTabGridItemTouchHelperCallback.setupCallback(
+                swipeToDismissThreshold, mergeThreshold, ungroupThreshold);
         return mTabGridItemTouchHelperCallback;
     }
 
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 bd73e208..86477bf3 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
@@ -107,6 +107,10 @@
     TabGroupModelFilter mTabGroupModelFilter;
     @Mock
     EmptyTabModelFilter mEmptyTabModelFilter;
+    @Mock
+    TabListMediator.TabGridDialogHandler mTabGridDialogHandler;
+    @Mock
+    TabListMediator.CreateGroupButtonProvider mCreateGroupButtonProvider;
     @Captor
     ArgumentCaptor<TabModelObserver> mTabModelObserverCaptor;
     @Captor
@@ -150,6 +154,7 @@
         doReturn(mTabModelFilterProvider).when(mTabModelSelector).getTabModelFilterProvider();
         doReturn(mTabModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter();
         doReturn(mTab1).when(mTabModelSelector).getCurrentTab();
+        doReturn(TAB1_ID).when(mTabModelSelector).getCurrentTabId();
         doNothing()
                 .when(mTabModelFilterProvider)
                 .addTabModelFilterObserver(mTabModelObserverCaptor.capture());
@@ -175,7 +180,7 @@
         mModel = new TabListModel();
         mMediator = new TabListMediator(mModel, mTabModelSelector,
                 mTabContentManager::getTabThumbnailWithCallback, null, mTabListFaviconProvider,
-                false, null, null, null, getClass().getSimpleName());
+                false, null, null, null, null, getClass().getSimpleName());
     }
 
     @After
@@ -240,11 +245,10 @@
     public void sendsMoveTabSignalCorrectlyWithoutGroup() {
         initAndAssertAllProperties();
         FeatureUtilities.setTabGroupsAndroidEnabledForTesting(false);
-
+        TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback();
         doReturn(mEmptyTabModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter();
 
-        mMediator.getItemTouchHelperCallback(0f, 0f).onMove(
-                mRecyclerView, mViewHolder1, mViewHolder2);
+        itemTouchHelperCallback.onMove(mRecyclerView, mViewHolder1, mViewHolder2);
 
         verify(mTabModel).moveTab(eq(TAB1_ID), eq(2));
     }
@@ -255,8 +259,7 @@
     public void sendsMoveTabSignalCorrectlyWithGroup() {
         initAndAssertAllProperties();
         FeatureUtilities.setTabGroupsAndroidEnabledForTesting(true);
-        TabGridItemTouchHelperCallback itemTouchHelperCallback =
-                (TabGridItemTouchHelperCallback) mMediator.getItemTouchHelperCallback(0f, 0f);
+        TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback();
         itemTouchHelperCallback.setActionsOnAllRelatedTabsForTest(true);
 
         doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter();
@@ -275,8 +278,8 @@
 
         doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter();
 
-        mMediator.getItemTouchHelperCallback(0f, 0f).onMove(
-                mRecyclerView, mViewHolder1, mViewHolder2);
+        mMediator.getItemTouchHelperCallback(0f, 0f, 0f)
+                .onMove(mRecyclerView, mViewHolder1, mViewHolder2);
 
         verify(mTabModel).moveTab(eq(TAB1_ID), eq(2));
     }
@@ -288,8 +291,7 @@
         initAndAssertAllProperties();
         FeatureUtilities.setTabGroupsAndroidEnabledForTesting(true);
         mMediator.setActionOnAllRelatedTabsForTest(true);
-        TabGridItemTouchHelperCallback itemTouchHelperCallback =
-                (TabGridItemTouchHelperCallback) mMediator.getItemTouchHelperCallback(0f, 0f);
+        TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback();
         itemTouchHelperCallback.setActionsOnAllRelatedTabsForTest(true);
         itemTouchHelperCallback.setHoveredTabIndexForTest(POSITION1);
         itemTouchHelperCallback.setSelectedTabIndexForTest(POSITION2);
@@ -310,8 +312,7 @@
         initAndAssertAllProperties();
         FeatureUtilities.setTabGroupsAndroidEnabledForTesting(false);
         mMediator.setActionOnAllRelatedTabsForTest(true);
-        TabGridItemTouchHelperCallback itemTouchHelperCallback =
-                (TabGridItemTouchHelperCallback) mMediator.getItemTouchHelperCallback(0f, 0f);
+        TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback();
         itemTouchHelperCallback.setActionsOnAllRelatedTabsForTest(true);
         itemTouchHelperCallback.setHoveredTabIndexForTest(POSITION1);
         itemTouchHelperCallback.setSelectedTabIndexForTest(POSITION2);
@@ -333,8 +334,7 @@
         initAndAssertAllProperties();
         FeatureUtilities.setTabGroupsAndroidEnabledForTesting(true);
         mMediator.setActionOnAllRelatedTabsForTest(true);
-        TabGridItemTouchHelperCallback itemTouchHelperCallback =
-                (TabGridItemTouchHelperCallback) mMediator.getItemTouchHelperCallback(0f, 0f);
+        TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback();
         itemTouchHelperCallback.setActionsOnAllRelatedTabsForTest(true);
         itemTouchHelperCallback.setHoveredTabIndexForTest(POSITION1);
         itemTouchHelperCallback.setSelectedTabIndexForTest(POSITION2);
@@ -350,6 +350,29 @@
     }
 
     @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    public void
+    sendsUngroupSignalCorrectly() {
+        initAndAssertAllProperties();
+        setUpForTabGroupOperation();
+        FeatureUtilities.setTabGroupsAndroidEnabledForTesting(true);
+
+        TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback();
+        itemTouchHelperCallback.setActionsOnAllRelatedTabsForTest(false);
+        itemTouchHelperCallback.setUnGroupTabIndexForTest(POSITION1);
+        itemTouchHelperCallback.getMovementFlags(mRecyclerView, mViewHolder1);
+
+        doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter();
+        doReturn(mAdapter).when(mRecyclerView).getAdapter();
+
+        // Simulate the ungroup action.
+        itemTouchHelperCallback.onSelectedChanged(mViewHolder1, ItemTouchHelper.ACTION_STATE_IDLE);
+
+        verify(mTabGroupModelFilter).moveTabOutOfGroup(eq(TAB1_ID));
+    }
+
+    @Test
     public void tabClosure() {
         initAndAssertAllProperties();
 
@@ -497,7 +520,10 @@
     @Test
     public void tabMergeIntoGroup() {
         setUpForTabGroupOperation();
-        mMediator.setActionOnAllRelatedTabsForTest(true);
+        // Setup the mediator with a CreateGroupButtonProvider.
+        mMediator = new TabListMediator(mModel, mTabModelSelector,
+                mTabContentManager::getTabThumbnailWithCallback, null, mTabListFaviconProvider,
+                true, mCreateGroupButtonProvider, null, null, null, getClass().getSimpleName());
 
         // Assume that moveTab in TabModel is finished. Selected tab in the group becomes mTab1.
         doReturn(mTab1).when(mTabModel).getTabAt(POSITION2);
@@ -524,6 +550,90 @@
     }
 
     @Test
+    public void tabMoveOutOfGroup_GTS_Moved_Tab_Selected() {
+        setUpForTabGroupOperation();
+        // Setup the mediator with a CreateGroupButtonProvider.
+        mMediator = new TabListMediator(mModel, mTabModelSelector,
+                mTabContentManager::getTabThumbnailWithCallback, null, mTabListFaviconProvider,
+                true, mCreateGroupButtonProvider, null, null, null, getClass().getSimpleName());
+
+        // Assume that two tabs are in the same group before ungroup.
+        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab2));
+        mMediator.resetWithListOfTabs(tabs, false);
+
+        assertThat(mModel.size(), equalTo(1));
+        assertThat(mModel.get(0).get(TabProperties.TAB_ID), equalTo(TAB2_ID));
+        assertThat(mModel.get(0).get(TabProperties.TITLE), equalTo(TAB2_TITLE));
+
+        // Assume that TabGroupModelFilter is already updated.
+        doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION1);
+        doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION2);
+
+        mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab1, POSITION1);
+
+        assertThat(mModel.size(), equalTo(2));
+        assertThat(mModel.get(0).get(TabProperties.TAB_ID), equalTo(TAB2_ID));
+        assertThat(mModel.get(0).get(TabProperties.TITLE), equalTo(TAB2_TITLE));
+        assertThat(mModel.get(0).get(TabProperties.IS_SELECTED), equalTo(false));
+        assertThat(mModel.get(1).get(TabProperties.TAB_ID), equalTo(TAB1_ID));
+        assertThat(mModel.get(1).get(TabProperties.TITLE), equalTo(TAB1_TITLE));
+        assertThat(mModel.get(1).get(TabProperties.IS_SELECTED), equalTo(true));
+    }
+
+    @Test
+    public void tabMoveOutOfGroup_GTS_Origin_Tab_Selected() {
+        setUpForTabGroupOperation();
+        // Setup the mediator with a CreateGroupButtonProvider.
+        mMediator = new TabListMediator(mModel, mTabModelSelector,
+                mTabContentManager::getTabThumbnailWithCallback, null, mTabListFaviconProvider,
+                true, mCreateGroupButtonProvider, null, null, null, getClass().getSimpleName());
+
+        // Assume that two tabs are in the same group before ungroup.
+        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
+        mMediator.resetWithListOfTabs(tabs, false);
+
+        assertThat(mModel.size(), equalTo(1));
+        assertThat(mModel.get(0).get(TabProperties.TAB_ID), equalTo(TAB1_ID));
+        assertThat(mModel.get(0).get(TabProperties.TITLE), equalTo(TAB1_TITLE));
+
+        // Assume that TabGroupModelFilter is already updated.
+        doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
+        doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION2);
+
+        mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab2, POSITION1);
+
+        assertThat(mModel.size(), equalTo(2));
+        assertThat(mModel.get(0).get(TabProperties.TAB_ID), equalTo(TAB1_ID));
+        assertThat(mModel.get(0).get(TabProperties.TITLE), equalTo(TAB1_TITLE));
+        assertThat(mModel.get(0).get(TabProperties.IS_SELECTED), equalTo(true));
+        assertThat(mModel.get(1).get(TabProperties.TAB_ID), equalTo(TAB2_ID));
+        assertThat(mModel.get(1).get(TabProperties.TITLE), equalTo(TAB2_TITLE));
+        assertThat(mModel.get(1).get(TabProperties.IS_SELECTED), equalTo(false));
+    }
+
+    @Test
+    public void tabMoveOutOfGroup_Dialog() {
+        setUpForTabGroupOperation();
+
+        // Setup the mediator with a DialogHandler.
+        mMediator = new TabListMediator(mModel, mTabModelSelector,
+                mTabContentManager::getTabThumbnailWithCallback, null, mTabListFaviconProvider,
+                true, null, null, null, mTabGridDialogHandler, getClass().getSimpleName());
+
+        assertThat(mModel.size(), equalTo(2));
+        assertThat(mModel.get(0).get(TabProperties.TAB_ID), equalTo(TAB1_ID));
+        assertThat(mModel.get(0).get(TabProperties.TITLE), equalTo(TAB1_TITLE));
+        assertThat(mModel.get(1).get(TabProperties.TAB_ID), equalTo(TAB2_ID));
+        assertThat(mModel.get(1).get(TabProperties.TITLE), equalTo(TAB2_TITLE));
+
+        mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab1, POSITION1);
+
+        assertThat(mModel.size(), equalTo(1));
+        assertThat(mModel.get(0).get(TabProperties.TAB_ID), equalTo(TAB2_ID));
+        assertThat(mModel.get(0).get(TabProperties.TITLE), equalTo(TAB2_TITLE));
+    }
+
+    @Test
     public void tabMovementWithoutGroup_Forward() {
         initAndAssertAllProperties();
 
@@ -690,6 +800,7 @@
         doReturn(id).when(tab).getId();
         doReturn("").when(tab).getUrl();
         doReturn(title).when(tab).getTitle();
+        doReturn(true).when(tab).isIncognito();
         return tab;
     }
 
@@ -700,6 +811,10 @@
         return viewHolder;
     }
 
+    private TabGridItemTouchHelperCallback getItemTouchHelperCallback() {
+        return (TabGridItemTouchHelperCallback) mMediator.getItemTouchHelperCallback(0f, 0f, 0f);
+    }
+
     private void setUpForTabGroupOperation() {
         doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter();
         doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getTabModelFilter(true);
@@ -710,8 +825,8 @@
 
         mMediator = new TabListMediator(mModel, mTabModelSelector,
                 mTabContentManager::getTabThumbnailWithCallback, null, mTabListFaviconProvider,
-                true, null, null, null, getClass().getSimpleName());
+                true, null, null, null, null, getClass().getSimpleName());
 
         initAndAssertAllProperties();
     }
-}
\ No newline at end of file
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java b/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java
index 5e436d2..de8764a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/AppHooks.java
@@ -470,11 +470,6 @@
     }
 
     /**
-     * Starts monitoring network quality. Must be called after native initialization is complete.
-     */
-    public void startMonitoringNetworkQuality() {}
-
-    /**
      * Starts the observer for listening to system settings changes. Must be called on
      * ChromeActivity initialization.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
index 6fbff6b8..aa05e7bd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
@@ -1440,7 +1440,6 @@
             // TODO(yusufo): Unify initialization.
             initializeBottomSheet(true);
         }
-        AppHooks.get().startMonitoringNetworkQuality();
         AppHooks.get().startSystemSettingsObserver();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
index ca958f6b..45b95f0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
@@ -51,6 +51,7 @@
 import org.chromium.chrome.browser.webapps.WebappLauncherActivity;
 import org.chromium.content_public.browser.BrowserStartupController;
 import org.chromium.ui.widget.Toast;
+import org.chromium.webapk.lib.client.WebApkValidator;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -195,6 +196,19 @@
             return Action.FINISH_ACTIVITY;
         }
 
+        // Check if we should launch a WebApk to handle the intent.
+        // For NoTouchMode, prefer to launch PWAs instead of the browser on view intents.
+        if (FeatureUtilities.isNoTouchModeEnabled() && url != null
+                && Intent.ACTION_VIEW.equals(mIntent.getAction())) {
+            String packageName = WebApkValidator.queryFirstWebApkPackage(
+                    ContextUtils.getApplicationContext(), url);
+            if (packageName != null) {
+                mActivity.startActivity(WebApkValidator.createWebApkIntentForUrlAndOptionalPackage(
+                        url, packageName));
+                return Action.FINISH_ACTIVITY;
+            }
+        }
+
         // Check if we should push the user through First Run.
         if (FirstRunFlowSequencer.launch(mActivity, mIntent, false /* requiresBroadcast */,
                     false /* preferLightweightFre */)) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuCoordinator.java
index 57bfae8..5a0b441 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuCoordinator.java
@@ -69,7 +69,7 @@
         mHeaderCoordinator = new RevampedContextMenuHeaderCoordinator(
                 activity, params, params.getUrl(), params.isImage());
 
-        ModelListAdapter adapter = new ModelListAdapter(activity) {
+        ModelListAdapter adapter = new ModelListAdapter() {
             @Override
             public boolean areAllItemsEnabled() {
                 return false;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
index 1c75797..8799f76 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
@@ -76,6 +76,7 @@
 import org.chromium.chrome.browser.tabmodel.ChromeTabCreator;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorImpl;
+import org.chromium.chrome.browser.usage_stats.UsageStatsService;
 import org.chromium.chrome.browser.util.ColorUtils;
 import org.chromium.chrome.browser.util.IntentUtils;
 import org.chromium.content_public.browser.LoadUrlParams;
@@ -377,6 +378,10 @@
                     ApiCompatibilityUtils.getColor(getResources(), R.color.default_primary_color));
         }
 
+        if (isTaskRoot() && UsageStatsService.isEnabled()) {
+            UsageStatsService.getInstance().createPageViewObserver(getTabModelSelector(), this);
+        }
+
         super.finishNativeInitialization();
 
         // We start the Autofill Assistant after the call to super.finishNativeInitialization() as
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinatorImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinatorImpl.java
index e161a154..e28a5bb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinatorImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteCoordinatorImpl.java
@@ -101,7 +101,7 @@
                 // Start with visibility GONE to ensure that show() is called.
                 // http://crbug.com/517438
                 list.setVisibility(View.GONE);
-                ModelListAdapter adapter = new ModelListAdapter(context);
+                ModelListAdapter adapter = new ModelListAdapter();
                 list.setAdapter(adapter);
                 list.setClipToPadding(false);
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tabgroup/TabGroupModelFilter.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tabgroup/TabGroupModelFilter.java
index 425c2946b..ef9c7f9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tabgroup/TabGroupModelFilter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tabgroup/TabGroupModelFilter.java
@@ -73,6 +73,15 @@
          * @param tabModelNewIndex The new index of the {@code movedTab} in the {@link TabModel}.
          */
         void didMoveWithinGroup(Tab movedTab, int tabModelOldIndex, int tabModelNewIndex);
+
+        /**
+         * This method is called after a tab within a group is moved out of the group.
+         *
+         * @param movedTab The tab which has been moved.
+         * @param prevFilterIndex The index in {@link TabGroupModelFilter} of the group where {@code
+         *         moveTab} is in  before ungrouping.
+         */
+        void didMoveTabOutOfGroup(Tab movedTab, int prevFilterIndex);
     }
 
     /**
@@ -296,6 +305,47 @@
         }
     }
 
+    /**
+     * This method moves Tab with id as {@code sourceTabId} out of the group it belongs to.
+     *
+     * @param sourceTabId The id of the {@link Tab} to get the source group.
+     */
+    public void moveTabOutOfGroup(int sourceTabId) {
+        TabModel tabModel = getTabModel();
+        Tab tab = TabModelUtils.getTabById(tabModel, sourceTabId);
+        int curIndex = tabModel.indexOf(tab);
+        TabGroup curTabGroup = mGroupIdToGroupMap.get(tab.getRootId());
+        assert curTabGroup.size() > 1;
+
+        if (tab.getId() == tab.getRootId()) {
+            // If moving tab's id is the root id of the group, find a new root id.
+            int newRootId = Tab.INVALID_TAB_ID;
+            if (curIndex != 0 && tabModel.getTabAt(curIndex - 1).getRootId() == tab.getRootId()) {
+                newRootId = tabModel.getTabAt(curIndex - 1).getId();
+            } else if (curIndex != tabModel.getCount() - 1
+                    && tabModel.getTabAt(curIndex + 1).getRootId() == tab.getRootId()) {
+                newRootId = tabModel.getTabAt(curIndex + 1).getId();
+            }
+
+            assert newRootId != Tab.INVALID_TAB_ID;
+
+            for (int tabId : curTabGroup.getTabIdList()) {
+                TabModelUtils.getTabById(tabModel, tabId).setRootId(newRootId);
+            }
+            resetFilterState();
+        }
+        tab.setRootId(tab.getId());
+        // If moving tab is already the last tab in tab model, no move in tab model.
+        if (curIndex == tabModel.getCount() - 1) {
+            resetFilterState();
+            for (Observer observer : mGroupFilterObserver) {
+                observer.didMoveTabOutOfGroup(tab, getCount() - 2);
+            }
+            return;
+        }
+        tabModel.moveTab(tab.getId(), tabModel.getCount());
+    }
+
     private int getTabModelDestinationIndex(Tab destinationTab) {
         List<Integer> destinationGroupedTabIds =
                 mGroupIdToGroupMap.get(destinationTab.getRootId()).getTabIdList();
@@ -491,13 +541,21 @@
 
     @Override
     public void didMoveTab(Tab tab, int newIndex, int curIndex) {
-        // Need to cache the flag before resetting the internal data map.
+        // Need to cache the flags before resetting the internal data map.
         boolean isMergeTabToGroup = isMergeTabToGroup(tab);
-        int groupIdBeforeMove = getGroupIdBeforeMove(tab, isMergeTabToGroup);
+        boolean isMoveTabOutOfGroup = isMoveTabOutOfGroup(tab);
+        int groupIdBeforeMove = getGroupIdBeforeMove(tab, isMergeTabToGroup || isMoveTabOutOfGroup);
         assert groupIdBeforeMove != TabGroup.INVALID_GROUP_ID;
         TabGroup groupBeforeMove = mGroupIdToGroupMap.get(groupIdBeforeMove);
 
-        if (isMergeTabToGroup) {
+        if (isMoveTabOutOfGroup) {
+            resetFilterState();
+
+            int prevFilterIndex = mGroupIdToGroupIndexMap.get(groupIdBeforeMove);
+            for (Observer observer : mGroupFilterObserver) {
+                observer.didMoveTabOutOfGroup(tab, prevFilterIndex);
+            }
+        } else if (isMergeTabToGroup) {
             resetFilterState();
             if (groupBeforeMove != null && groupBeforeMove.size() != 1) return;
 
@@ -507,7 +565,6 @@
             }
         } else {
             reorder();
-
             if (isMoveWithinGroup(tab, curIndex, newIndex)) {
                 for (Observer observer : mGroupFilterObserver) {
                     observer.didMoveWithinGroup(tab, curIndex, newIndex);
@@ -523,7 +580,12 @@
         super.didMoveTab(tab, newIndex, curIndex);
     }
 
+    private boolean isMoveTabOutOfGroup(Tab movedTab) {
+        return !mGroupIdToGroupMap.containsKey(movedTab.getRootId());
+    }
+
     private boolean isMergeTabToGroup(Tab tab) {
+        if (!mGroupIdToGroupMap.containsKey(tab.getRootId())) return false;
         TabGroup tabGroup = mGroupIdToGroupMap.get(tab.getRootId());
         return !tabGroup.contains(tab.getId());
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/IdentityDiscController.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/IdentityDiscController.java
index 9361f6c..5106b603 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/IdentityDiscController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/IdentityDiscController.java
@@ -8,6 +8,7 @@
 import android.graphics.drawable.Drawable;
 import android.support.annotation.DimenRes;
 
+import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
@@ -123,6 +124,7 @@
     private void showIdentityDisc(String accountName) {
         Drawable profileImage = mProfileDataCache.getProfileDataOrDefault(accountName).getImage();
         mToolbarManager.enableExperimentalButton(view -> {
+            RecordUserAction.record("MobileToolbarIdentityDiscTap");
             PreferencesLauncher.launchSettingsPage(mContext, SyncAndServicesPreferences.class);
         }, profileImage, R.string.accessibility_toolbar_btn_identity_disc);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabCountProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabCountProvider.java
index 19b6f8a7..5a85cc99 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabCountProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/TabCountProvider.java
@@ -155,6 +155,11 @@
             @Override
             public void didMoveWithinGroup(
                     Tab movedTab, int tabModelOldIndex, int tabModelNewIndex) {}
+
+            @Override
+            public void didMoveTabOutOfGroup(Tab moveTab, int oldFilterIndex) {
+                updateTabCount();
+            }
         };
 
         if (mTabModelSelector.getTabModelFilterProvider().getCurrentTabModelFilter()
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/tasks/tabgroup/TabGroupModelFilterUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/tasks/tabgroup/TabGroupModelFilterUnitTest.java
index 14e0819..063f49b8 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/tasks/tabgroup/TabGroupModelFilterUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/tasks/tabgroup/TabGroupModelFilterUnitTest.java
@@ -4,7 +4,9 @@
 
 package org.chromium.chrome.browser.tasks.tabgroup;
 
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doAnswer;
@@ -224,6 +226,70 @@
     }
 
     @Test
+    public void moveTabOutOfGroup_No_Update_TabModel() {
+        List<Tab> expectedTabModel =
+                new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab3, mTab4, mTab5, mTab6));
+
+        mTabGroupModelFilter.moveTabOutOfGroup(TAB6_ID);
+
+        verify(mTabModel, never()).moveTab(anyInt(), anyInt());
+        assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
+        assertThat(mTab6.getRootId(), equalTo(TAB6_ID));
+    }
+
+    @Test
+    public void moveTabOutOfGroup_NonRoot_Tab() {
+        List<Tab> expectedTabModel =
+                new ArrayList<>(Arrays.asList(mTab1, mTab2, mTab4, mTab5, mTab6, mTab3));
+        assertThat(mTab3.getRootId(), equalTo(TAB2_ID));
+
+        mTabGroupModelFilter.moveTabOutOfGroup(TAB3_ID);
+
+        verify(mTabModel).moveTab(mTab3.getId(), mTabs.size());
+        verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab3, POSITION2);
+        assertThat(mTab3.getRootId(), equalTo(TAB3_ID));
+        assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
+    }
+
+    @Test
+    public void moveTabOutOfGroup_Root_Tab_First_Tab() {
+        List<Tab> expectedTabModel =
+                new ArrayList<>(Arrays.asList(mTab1, mTab3, mTab4, mTab5, mTab6, mTab2));
+        assertThat(mTab2.getRootId(), equalTo(TAB2_ID));
+        assertThat(mTab3.getRootId(), equalTo(TAB2_ID));
+
+        mTabGroupModelFilter.moveTabOutOfGroup(TAB2_ID);
+
+        verify(mTabModel).moveTab(mTab2.getId(), mTabs.size());
+        verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab2, POSITION2);
+        assertThat(mTab2.getRootId(), equalTo(TAB2_ID));
+        assertThat(mTab3.getRootId(), equalTo(TAB3_ID));
+        assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
+    }
+
+    @Test
+    public void moveTabOutOfGroup_Root_Tab_Not_First_Tab() {
+        // Move the root tab so that first tab in group is not the root tab.
+        mTabModel.moveTab(TAB2_ID, POSITION4);
+        List<Tab> tabModelBeforeUngroup =
+                new ArrayList<>(Arrays.asList(mTab1, mTab3, mTab2, mTab4, mTab5, mTab6));
+        assertArrayEquals(mTabs.toArray(), tabModelBeforeUngroup.toArray());
+
+        List<Tab> expectedTabModel =
+                new ArrayList<>(Arrays.asList(mTab1, mTab3, mTab4, mTab5, mTab6, mTab2));
+        assertThat(mTab2.getRootId(), equalTo(TAB2_ID));
+        assertThat(mTab3.getRootId(), equalTo(TAB2_ID));
+
+        mTabGroupModelFilter.moveTabOutOfGroup(TAB2_ID);
+
+        verify(mTabModel).moveTab(mTab2.getId(), mTabs.size());
+        verify(mTabGroupModelFilterObserver).didMoveTabOutOfGroup(mTab2, POSITION2);
+        assertThat(mTab2.getRootId(), equalTo(TAB2_ID));
+        assertThat(mTab3.getRootId(), equalTo(TAB3_ID));
+        assertArrayEquals(mTabs.toArray(), expectedTabModel.toArray());
+    }
+
+    @Test
     public void mergeTabToGroup_No_Update_TabModel() {
         List<Tab> expectedGroup = new ArrayList<>(Arrays.asList(mTab2, mTab3, mTab4));
 
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index 4184a57..71fd7ff 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-77.0.3824.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
+chromeos-chrome-amd64-77.0.3825.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogPresenter.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogPresenter.java
index e2ad00e..e641924 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogPresenter.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogPresenter.java
@@ -92,7 +92,7 @@
         });
         ViewGroup dialogView = (ViewGroup) LayoutInflater.from(mDialog.getContext())
                 .inflate(R.layout.touchless_dialog_view, null);
-        ModelListAdapter adapter = new ModelListAdapter(mActivity);
+        ModelListAdapter adapter = new ModelListAdapter();
         adapter.registerType(ListItemType.DEFAULT,
                 () -> LayoutInflater.from(mActivity).inflate(R.layout.dialog_list_item, null),
                 TouchlessDialogPresenter::bindListItem);
diff --git a/chrome/android/webapk/libs/client/src/org/chromium/webapk/lib/client/WebApkValidator.java b/chrome/android/webapk/libs/client/src/org/chromium/webapk/lib/client/WebApkValidator.java
index c74d78df..7882f23cd 100644
--- a/chrome/android/webapk/libs/client/src/org/chromium/webapk/lib/client/WebApkValidator.java
+++ b/chrome/android/webapk/libs/client/src/org/chromium/webapk/lib/client/WebApkValidator.java
@@ -130,24 +130,8 @@
      */
     private static List<ResolveInfo> resolveInfosForUrlAndOptionalPackage(
             Context context, String url, @Nullable String applicationPackage) {
-        Intent intent;
-        try {
-            intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
-        } catch (Exception e) {
-            return new LinkedList<>();
-        }
-
-        intent.addCategory(Intent.CATEGORY_BROWSABLE);
-        if (applicationPackage != null) {
-            intent.setPackage(applicationPackage);
-        } else {
-            intent.setComponent(null);
-        }
-        Intent selector = intent.getSelector();
-        if (selector != null) {
-            selector.addCategory(Intent.CATEGORY_BROWSABLE);
-            selector.setComponent(null);
-        }
+        Intent intent = createWebApkIntentForUrlAndOptionalPackage(url, applicationPackage);
+        if (intent == null) return new LinkedList<>();
 
         // StrictMode is relaxed due to https://crbug.com/843092.
         StrictMode.ThreadPolicy policy = StrictMode.allowThreadDiskReads();
@@ -228,6 +212,36 @@
         return verifyCommentSignedWebApk(packageInfo);
     }
 
+    /**
+     * @param url A Url that might launch a WebApk.
+     * @param applicationPackage The package of the WebApk to restrict the launch to.
+     * @return An intent that could launch a WebApk for the provided URL (and package), if such a
+     *         WebApk exists. If package isn't specified, the intent may create a disambiguation
+     *         dialog when started.
+     */
+    public static Intent createWebApkIntentForUrlAndOptionalPackage(
+            String url, @Nullable String applicationPackage) {
+        Intent intent;
+        try {
+            intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
+        } catch (Exception e) {
+            return null;
+        }
+
+        intent.addCategory(Intent.CATEGORY_BROWSABLE);
+        if (applicationPackage != null) {
+            intent.setPackage(applicationPackage);
+        } else {
+            intent.setComponent(null);
+        }
+        Intent selector = intent.getSelector();
+        if (selector != null) {
+            selector.addCategory(Intent.CATEGORY_BROWSABLE);
+            selector.setComponent(null);
+        }
+        return intent;
+    }
+
     /** Determine quickly whether this is definitely not a WebAPK */
     private static boolean isNotWebApkQuick(PackageInfo packageInfo) {
         if (packageInfo.applicationInfo == null || packageInfo.applicationInfo.metaData == null) {
diff --git a/chrome/app/builtin_service_manifests.cc b/chrome/app/builtin_service_manifests.cc
index 170afd5..9a8a88d 100644
--- a/chrome/app/builtin_service_manifests.cc
+++ b/chrome/app/builtin_service_manifests.cc
@@ -102,27 +102,6 @@
   return *manifest;
 }
 
-#if defined(OS_ANDROID)
-const service_manager::Manifest& GetAndroidDownloadManagerManifest() {
-  static base::NoDestructor<service_manager::Manifest> manifest{
-      service_manager::ManifestBuilder()
-          .WithServiceName("download_manager")
-          .WithDisplayName("Download Manager")
-          .WithOptions(
-              service_manager::ManifestOptionsBuilder()
-                  .WithExecutionMode(service_manager::Manifest::ExecutionMode::
-                                         kInProcessBuiltin)
-                  .WithInstanceSharingPolicy(
-                      service_manager::Manifest::InstanceSharingPolicy::
-                          kSingleton)
-                  .Build())
-          .RequireCapability("network", "network_service")
-          .RequireCapability("device", "device:wake_lock")
-          .Build()};
-  return *manifest;
-}
-#endif
-
 }  // namespace
 
 const std::vector<service_manager::Manifest>&
@@ -158,9 +137,7 @@
       GetUtilWinManifest(),
       GetWifiUtilWinManifest(),
 #endif
-#if defined(OS_ANDROID)
-      GetAndroidDownloadManagerManifest(),
-#else
+#if !defined(OS_ANDROID)
       mirroring::GetManifest(),
       GetProfileImportManifest(),
 #endif
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index b47d1556..0abb80d 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -607,8 +607,6 @@
     "invalidation/deprecated_profile_invalidation_provider_factory.h",
     "invalidation/profile_invalidation_provider_factory.cc",
     "invalidation/profile_invalidation_provider_factory.h",
-    "io_thread.cc",
-    "io_thread.h",
     "language/language_model_manager_factory.cc",
     "language/language_model_manager_factory.h",
     "language/translate_frame_binder.cc",
@@ -2885,6 +2883,8 @@
       "enterprise_reporting/prefs.h",
       "enterprise_reporting/profile_report_generator.cc",
       "enterprise_reporting/profile_report_generator.h",
+      "enterprise_reporting/report_generator.cc",
+      "enterprise_reporting/report_generator.h",
       "enterprise_reporting/report_scheduler.cc",
       "enterprise_reporting/report_scheduler.h",
       "enterprise_reporting/report_uploader.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index ec75847..63df476 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3007,6 +3007,10 @@
      flag_descriptions::kEnableBlinkGenPropertyTreesDescription, kOsAll,
      FEATURE_VALUE_TYPE(blink::features::kBlinkGenPropertyTrees)},
 
+    {"enable-backdrop-filter", flag_descriptions::kEnableCSSBackdropFilterName,
+     flag_descriptions::kEnableCSSBackdropFilterDescription, kOsAll,
+     FEATURE_VALUE_TYPE(blink::features::kCSSBackdropFilter)},
+
     {"enable-display-locking", flag_descriptions::kEnableDisplayLockingName,
      flag_descriptions::kEnableDisplayLockingDescription, kOsAll,
      FEATURE_VALUE_TYPE(blink::features::kDisplayLocking)},
@@ -3914,6 +3918,10 @@
      flag_descriptions::kCookieDeprecationMessagesDescription, kOsAll,
      FEATURE_VALUE_TYPE(features::kCookieDeprecationMessages)},
 
+    {"enable-caption-settings", flag_descriptions::kCaptionSettingsName,
+     flag_descriptions::kCaptionSettingsDescription, kOsAll,
+     FEATURE_VALUE_TYPE(features::kCaptionSettings)},
+
     {"ev-details-in-page-info", flag_descriptions::kEvDetailsInPageInfoName,
      flag_descriptions::kEvDetailsInPageInfoDescription, kOsDesktop,
      FEATURE_VALUE_TYPE(features::kEvDetailsInPageInfo)},
diff --git a/chrome/browser/android/download/download_manager_service.cc b/chrome/browser/android/download/download_manager_service.cc
index 396f6ea..ea7e740 100644
--- a/chrome/browser/android/download/download_manager_service.cc
+++ b/chrome/browser/android/download/download_manager_service.cc
@@ -42,6 +42,7 @@
 #include "content/public/browser/download_request_utils.h"
 #include "content/public/browser/network_service_instance.h"
 #include "content/public/browser/notification_service.h"
+#include "content/public/browser/system_connector.h"
 #include "jni/DownloadInfo_jni.h"
 #include "jni/DownloadItem_jni.h"
 #include "jni/DownloadManagerService_jni.h"
@@ -211,11 +212,6 @@
 
 DownloadManagerService::~DownloadManagerService() {}
 
-void DownloadManagerService::BindServiceRequest(
-    service_manager::mojom::ServiceRequest request) {
-  service_binding_.Bind(std::move(request));
-}
-
 void DownloadManagerService::Init(JNIEnv* env,
                                   jobject obj,
                                   bool is_full_browser_started) {
@@ -495,15 +491,6 @@
       content::DownloadItemUtils::GetBrowserContext(item)->IsOffTheRecord());
 }
 
-void DownloadManagerService::OnDisconnected() {
-  // Some unit tests recreate |ServiceManagerContext| inside
-  // |TestServiceManagerContext|. Closing |service_binding_| will prevent DCHECK
-  // in such tests, because |DownloadManagerService| starts automatically in
-  // |ServiceManagerContext|.
-  service_binding_.Close();
-  Terminate();
-}
-
 void DownloadManagerService::ResumeDownloadInternal(
     const std::string& download_guid,
     bool is_off_the_record,
@@ -699,7 +686,7 @@
     return;
   base::FilePath data_dir;
   base::android::GetDataDirectory(&data_dir);
-  service_manager::Connector* connector = service_binding_.GetConnector();
+  service_manager::Connector* connector = content::GetSystemConnector();
   in_progress_manager_ = std::make_unique<download::InProgressDownloadManager>(
       nullptr, data_dir.Append(chrome::kInitialProfile),
       base::BindRepeating(&IgnoreOriginSecurityCheck),
diff --git a/chrome/browser/android/download/download_manager_service.h b/chrome/browser/android/download/download_manager_service.h
index a9ae911..9b280f2e 100644
--- a/chrome/browser/android/download/download_manager_service.h
+++ b/chrome/browser/android/download/download_manager_service.h
@@ -19,9 +19,6 @@
 #include "content/public/browser/download_manager.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
-#include "services/service_manager/public/cpp/service.h"
-#include "services/service_manager/public/cpp/service_binding.h"
-#include "services/service_manager/public/mojom/service.mojom.h"
 
 using base::android::JavaParamRef;
 
@@ -36,8 +33,7 @@
 // Java object.
 class DownloadManagerService
     : public download::AllDownloadEventNotifier::Observer,
-      public content::NotificationObserver,
-      public service_manager::Service {
+      public content::NotificationObserver {
  public:
   static void CreateAutoResumptionHandler();
 
@@ -54,8 +50,6 @@
   DownloadManagerService();
   ~DownloadManagerService() override;
 
-  void BindServiceRequest(service_manager::mojom::ServiceRequest request);
-
   // Called to Initialize this object. If |is_full_browser_started| is false,
   // it means only the service manager is launched. OnFullBrowserStarted() will
   // be called later when browser process fully launches.
@@ -208,9 +202,6 @@
   friend class DownloadManagerServiceTest;
   friend struct base::DefaultSingletonTraits<DownloadManagerService>;
 
-  // service_manager::Service implementation.
-  void OnDisconnected() final;
-
   // Helper function to start the download resumption.
   void ResumeDownloadInternal(const std::string& download_guid,
                               bool is_off_the_record,
@@ -258,8 +249,6 @@
       download::SimpleDownloadManagerCoordinator* coordinator,
       bool is_off_the_record);
 
-  service_manager::ServiceBinding service_binding_{this};
-
   // Reference to the Java object.
   base::android::ScopedJavaGlobalRef<jobject> java_ref_;
 
diff --git a/chrome/browser/android/vr/arcore_device/arcore_impl.cc b/chrome/browser/android/vr/arcore_device/arcore_impl.cc
index 4b02fd4..8b99335 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_impl.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_impl.cc
@@ -219,12 +219,14 @@
       continue;
     }
 
-    if (DCHECK_IS_ON()) {
+#if DCHECK_IS_ON()
+    {
       ArTrackableType type;
       ArTrackable_getType(arcore_session_.get(), trackable.get(), &type);
       DCHECK(type == ArTrackableType::AR_TRACKABLE_PLANE)
           << "arcore_planes_ contains a trackable that is not an ArPlane!";
     }
+#endif
 
     ArPlane* ar_plane =
         ArAsPlane(trackable.get());  // Naked pointer is fine here, ArAsPlane
diff --git a/chrome/browser/browser_about_handler.cc b/chrome/browser/browser_about_handler.cc
index 55949cc..2b94c590 100644
--- a/chrome/browser/browser_about_handler.cc
+++ b/chrome/browser/browser_about_handler.cc
@@ -39,7 +39,8 @@
   FixupBrowserAboutURL(url, browser_context);
 
   // Check that about: URLs are fixed up to chrome: by url_formatter::FixupURL.
-  DCHECK((url->IsAboutBlank()) || !url->SchemeIs(url::kAboutScheme));
+  DCHECK(url->IsAboutBlank() || url->IsAboutSrcdoc() ||
+         !url->SchemeIs(url::kAboutScheme));
 
   // Only handle chrome://foo/, url_formatter::FixupURL translates about:foo.
   if (!url->SchemeIs(content::kChromeUIScheme))
diff --git a/chrome/browser/browser_process.h b/chrome/browser/browser_process.h
index e6442ab0..1d5ab8c4 100644
--- a/chrome/browser/browser_process.h
+++ b/chrome/browser/browser_process.h
@@ -28,7 +28,6 @@
 class GpuModeManager;
 class IconManager;
 class IntranetRedirectDetector;
-class IOThread;
 class MediaFileSystemRegistry;
 class NotificationPlatformBridge;
 class NotificationUIManager;
@@ -160,16 +159,6 @@
   virtual NotificationUIManager* notification_ui_manager() = 0;
   virtual NotificationPlatformBridge* notification_platform_bridge() = 0;
 
-  // Returns the state object for the thread that we perform I/O
-  // coordination on (network requests, communication with renderers,
-  // etc.
-  //
-  // Can be NULL close to startup and shutdown.
-  //
-  // NOTE: If you want to post a task to the IO thread, see
-  // browser_task_traits.h.
-  virtual IOThread* io_thread() = 0;
-
   // Replacement for IOThread (And ChromeNetLog). It owns and manages the
   // NetworkContext which will use the network service when the network service
   // is enabled. When the network service is not enabled, its NetworkContext is
diff --git a/chrome/browser/browser_process_impl.cc b/chrome/browser/browser_process_impl.cc
index edb060d..9b35070f 100644
--- a/chrome/browser/browser_process_impl.cc
+++ b/chrome/browser/browser_process_impl.cc
@@ -49,7 +49,6 @@
 #include "chrome/browser/gpu/gpu_mode_manager.h"
 #include "chrome/browser/icon_manager.h"
 #include "chrome/browser/intranet_redirect_detector.h"
-#include "chrome/browser/io_thread.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
 #include "chrome/browser/lifetime/switch_utils.h"
 #include "chrome/browser/media/webrtc/webrtc_event_log_manager.h"
@@ -373,12 +372,6 @@
   tearing_down_ = true;
   DCHECK(IsShuttingDown());
 
-  // We need to destroy the MetricsServicesManager, IntranetRedirectDetector,
-  // NetworkTimeTracker, and SafeBrowsing ClientSideDetectionService
-  // (owned by the SafeBrowsingService) before the io_thread_ gets destroyed,
-  // since their destructors can call the URLFetcher destructor, which does a
-  // PostDelayedTask operation on the IO thread. (The IO thread will handle
-  // that URLFetcher operation before going away.)
   metrics_services_manager_.reset();
   intranet_redirect_detector_.reset();
   if (safe_browsing_service_.get())
@@ -391,7 +384,7 @@
   system_notification_helper_.reset();
 
 #if !defined(OS_CHROMEOS)
-  // Need to clear the desktop notification balloons before the io_thread_ and
+  // Need to clear the desktop notification balloons before the IO thread and
   // before the profiles, since if there are any still showing we will access
   // those things during teardown.
   notification_ui_manager_.reset();
@@ -407,7 +400,7 @@
 
   battery_metrics_.reset();
 
-  // Need to clear profiles (download managers) before the io_thread_.
+  // Need to clear profiles (download managers) before the IO thread.
   {
     TRACE_EVENT0("shutdown",
                  "BrowserProcessImpl::StartTearDown:ProfileManager");
@@ -470,15 +463,6 @@
 
   // Must outlive the worker threads.
   webrtc_log_uploader_.reset();
-
-  // Reset associated state right after actual thread is stopped,
-  // as io_thread_.global_ cleanup happens in CleanUp on the IO
-  // thread, i.e. as the thread exits its message loop.
-  //
-  // This is important also because in various places, the
-  // IOThread object being NULL is considered synonymous with the
-  // IO thread having stopped.
-  io_thread_.reset();
 }
 #endif  // !defined(OS_ANDROID)
 
@@ -665,12 +649,6 @@
   return GetMetricsServicesManager()->GetRapporServiceImpl();
 }
 
-IOThread* BrowserProcessImpl::io_thread() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(io_thread_.get());
-  return io_thread_.get();
-}
-
 SystemNetworkContextManager*
 BrowserProcessImpl::system_network_context_manager() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -685,7 +663,6 @@
 
 network::NetworkQualityTracker* BrowserProcessImpl::network_quality_tracker() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(io_thread_);
   if (!network_quality_tracker_) {
     network_quality_tracker_ = std::make_unique<network::NetworkQualityTracker>(
         base::BindRepeating(&content::GetNetworkService));
@@ -1168,12 +1145,9 @@
         command_line.GetCommandLineString(), chrome::GetChannelName());
   }
 
-  // Must be created before the IOThread.
-  // TODO(mmenke): Once IOThread class is no longer needed (not the thread
-  // itself), this can be created on first use.
+  // TODO(mmenke): This can be created on first use.
   if (!SystemNetworkContextManager::GetInstance())
     SystemNetworkContextManager::CreateInstance(local_state());
-  io_thread_ = std::make_unique<IOThread>(net_log_.get());
 }
 
 void BrowserProcessImpl::PreMainMessageLoopRun() {
diff --git a/chrome/browser/browser_process_impl.h b/chrome/browser/browser_process_impl.h
index 7c28d1e5..0285bbdb 100644
--- a/chrome/browser/browser_process_impl.h
+++ b/chrome/browser/browser_process_impl.h
@@ -136,7 +136,6 @@
       override;
   metrics::MetricsService* metrics_service() override;
   rappor::RapporServiceImpl* rappor_service() override;
-  IOThread* io_thread() override;
   // TODO(qinmin): Remove this method as callers can retrieve the global
   // instance from SystemNetworkContextManager directly.
   SystemNetworkContextManager* system_network_context_manager() override;
@@ -246,8 +245,6 @@
   std::unique_ptr<metrics_services_manager::MetricsServicesManager>
       metrics_services_manager_;
 
-  std::unique_ptr<IOThread> io_thread_;
-
   bool created_watchdog_thread_ = false;
   std::unique_ptr<WatchDogThread> watchdog_thread_;
 
diff --git a/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc b/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
index 6db7804f..81ae7d8 100644
--- a/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
+++ b/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
@@ -297,7 +297,17 @@
 
 class BrowsingDataRemoverBrowserTest : public InProcessBrowserTest {
  public:
-  BrowsingDataRemoverBrowserTest() {}
+  BrowsingDataRemoverBrowserTest() {
+    std::vector<base::Feature> enabled_features = {
+        leveldb::kLevelDBRewriteFeature,
+        // Ensure that kOnionSoupDOMStorage is enabled because the old
+        // SessionStorage implementation causes flaky tests.
+        blink::features::kOnionSoupDOMStorage};
+#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
+    enabled_features.push_back(media::kExternalClearKeyForTesting);
+#endif
+    feature_list_.InitWithFeatures(enabled_features, {});
+  }
 
   // Call to use an Incognito browser rather than the default.
   void UseIncognitoBrowser() {
@@ -561,21 +571,11 @@
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
     InProcessBrowserTest::SetUpCommandLine(command_line);
-
-    std::vector<base::Feature> enabled_features = {
-        leveldb::kLevelDBRewriteFeature,
-        // Ensure that kOnionSoupDOMStorage is enabled because the old
-        // SessionStorage implementation causes flaky tests.
-        blink::features::kOnionSoupDOMStorage};
-
 #if BUILDFLAG(ENABLE_LIBRARY_CDMS)
     // Testing MediaLicenses requires additional command line parameters as
     // it uses the External Clear Key CDM.
     RegisterClearKeyCdm(command_line);
-    enabled_features.push_back(media::kExternalClearKeyForTesting);
 #endif
-
-    feature_list_.InitWithFeatures(enabled_features, {});
   }
 
   base::test::ScopedFeatureList feature_list_;
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
index 7300be2..67b333f 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
@@ -40,7 +40,6 @@
 #include "chrome/browser/external_protocol/external_protocol_handler.h"
 #include "chrome/browser/history/history_service_factory.h"
 #include "chrome/browser/history/web_history_service_factory.h"
-#include "chrome/browser/io_thread.h"
 #include "chrome/browser/language/url_language_histogram_factory.h"
 #include "chrome/browser/media/media_device_id_salt.h"
 #include "chrome/browser/media/media_engagement_service.h"
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 6a55db74..5a676dfa 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -435,7 +435,6 @@
 #include "chrome/browser/android/chrome_context_util.h"
 #include "chrome/browser/android/devtools_manager_delegate_android.h"
 #include "chrome/browser/android/download/available_offline_content_provider.h"
-#include "chrome/browser/android/download/download_manager_service.h"
 #include "chrome/browser/android/download/intercept_oma_download_navigation_throttle.h"
 #include "chrome/browser/android/ntp/new_tab_page_url_handler.h"
 #include "chrome/browser/android/service_tab_launcher.h"
@@ -955,9 +954,6 @@
   return ratio * (kMaxFSM - kMinFSM) + kMinFSM;
 }
 
-void StartDownloadManager(service_manager::mojom::ServiceRequest request) {
-  DownloadManagerService::GetInstance()->BindServiceRequest(std::move(request));
-}
 #endif  // defined(OS_ANDROID)
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
@@ -4029,11 +4025,6 @@
             std::move(*receiver)));
     return;
   }
-
-  if (identity.name() == "download_manager") {
-    StartDownloadManager(std::move(*receiver));
-    return;
-  }
 #endif
 
   if (identity.name() == heap_profiling::mojom::kServiceName) {
@@ -4071,14 +4062,6 @@
   return manifests;
 }
 
-std::vector<std::string> ChromeContentBrowserClient::GetStartupServices() {
-#if defined(OS_ANDROID)
-  return {"download_manager"};
-#else
-  return {};
-#endif
-}
-
 void ChromeContentBrowserClient::OpenURL(
     content::SiteInstance* site_instance,
     const content::OpenURLParams& params,
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 18c3804..d654bcc8 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -435,7 +435,6 @@
   base::Optional<service_manager::Manifest> GetServiceManifestOverlay(
       base::StringPiece name) override;
   std::vector<service_manager::Manifest> GetExtraServiceManifests() override;
-  std::vector<std::string> GetStartupServices() override;
   void OpenURL(
       content::SiteInstance* site_instance,
       const content::OpenURLParams& params,
diff --git a/chrome/browser/chrome_navigation_browsertest.cc b/chrome/browser/chrome_navigation_browsertest.cc
index 82dbc8b..d743651 100644
--- a/chrome/browser/chrome_navigation_browsertest.cc
+++ b/chrome/browser/chrome_navigation_browsertest.cc
@@ -62,13 +62,10 @@
 
 class ChromeNavigationBrowserTest : public InProcessBrowserTest {
  public:
-  ChromeNavigationBrowserTest() {}
-  ~ChromeNavigationBrowserTest() override {}
-
-  void SetUp() override {
+  ChromeNavigationBrowserTest() {
     scoped_feature_list_.InitAndEnableFeature(ukm::kUkmFeature);
-    InProcessBrowserTest::SetUp();
   }
+  ~ChromeNavigationBrowserTest() override {}
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
     // Backgrounded renderer processes run at a lower priority, causing the
@@ -1342,6 +1339,11 @@
 class SiteIsolationForPasswordSitesBrowserTest
     : public ChromeNavigationBrowserTest {
  public:
+  SiteIsolationForPasswordSitesBrowserTest() {
+    feature_list_.InitWithFeatures({features::kSiteIsolationForPasswordSites},
+                                   {features::kSitePerProcess});
+  }
+
   std::vector<std::string> GetSavedIsolatedSites() {
     return GetSavedIsolatedSites(browser()->profile());
   }
@@ -1385,12 +1387,6 @@
   const std::string kSyntheticTrialGroup = "Enabled";
 
  protected:
-  void SetUp() override {
-    feature_list_.InitWithFeatures({features::kSiteIsolationForPasswordSites},
-                                   {features::kSitePerProcess});
-    ChromeNavigationBrowserTest::SetUp();
-  }
-
   void SetUpCommandLine(base::CommandLine* command_line) override {
     ChromeNavigationBrowserTest::SetUpCommandLine(command_line);
 
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index afb72423..a2621d25 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -1077,6 +1077,8 @@
     "input_method/input_method_syncer.h",
     "kerberos/kerberos_credentials_manager.cc",
     "kerberos/kerberos_credentials_manager.h",
+    "kiosk_next/kiosk_next_browser_factory.cc",
+    "kiosk_next/kiosk_next_browser_factory.h",
     "kiosk_next_home/app_controller_service.cc",
     "kiosk_next_home/app_controller_service.h",
     "kiosk_next_home/app_controller_service_factory.cc",
diff --git a/chrome/browser/chromeos/kiosk_next/OWNERS b/chrome/browser/chromeos/kiosk_next/OWNERS
new file mode 100644
index 0000000..7fb73f6
--- /dev/null
+++ b/chrome/browser/chromeos/kiosk_next/OWNERS
@@ -0,0 +1,3 @@
+file://ash/kiosk_next/OWNERS
+
+# COMPONENT: UI>Shell>KioskNext
diff --git a/chrome/browser/chromeos/kiosk_next/kiosk_next_browser_factory.cc b/chrome/browser/chromeos/kiosk_next/kiosk_next_browser_factory.cc
new file mode 100644
index 0000000..fff341d
--- /dev/null
+++ b/chrome/browser/chromeos/kiosk_next/kiosk_next_browser_factory.cc
@@ -0,0 +1,180 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/kiosk_next/kiosk_next_browser_factory.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/no_destructor.h"
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/browser_navigator.h"
+#include "chrome/browser/ui/browser_navigator_params.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "components/user_manager/user_manager.h"
+#include "ui/base/page_transition_types.h"
+#include "ui/base/window_open_disposition.h"
+#include "url/gurl.h"
+
+namespace {
+
+constexpr size_t kMaxKioskNextBrowsersAllowed = 1;
+
+Profile* GetActiveUserProfile() {
+  auto* user = user_manager::UserManager::Get()->GetActiveUser();
+  Profile* profile = chromeos::ProfileHelper::Get()->GetProfileByUser(user);
+  DCHECK(profile);
+  return profile;
+}
+
+Browser* CreateKioskNextBrowser() {
+  Browser::CreateParams params(GetActiveUserProfile(), true /* user_gesture */);
+  params.type = Browser::Type::TYPE_POPUP; /* Not a tabbed experience. */
+  params.trusted_source = true;
+  params.initial_show_state = ui::SHOW_STATE_MAXIMIZED;
+  params.user_gesture = true;
+  Browser* browser = Browser::Create(params);
+  DCHECK(browser);
+  return browser;
+}
+
+void NavigateBrowser(Browser* browser, const GURL& url) {
+  NavigateParams params(browser, url, ui::PageTransition::PAGE_TRANSITION_LINK);
+  params.disposition = WindowOpenDisposition::CURRENT_TAB;
+  Navigate(&params);
+}
+
+}  // namespace
+
+KioskNextBrowserList::Entry::Entry(Browser* browser, const GURL& launch_url)
+    : browser(browser), launch_url(launch_url) {}
+
+// static
+const KioskNextBrowserList& KioskNextBrowserList::Get() {
+  return KioskNextBrowserFactory::Get()->kiosk_next_browser_list();
+}
+
+bool KioskNextBrowserList::CanAddBrowser() const {
+  return lru_browsers_.size() < max_browsers_allowed_;
+}
+
+bool KioskNextBrowserList::IsKioskNextBrowser(const Browser* browser) const {
+  return GetBrowserEntry(browser) != nullptr;
+}
+
+Browser* KioskNextBrowserList::GetBrowserForWebsite(const GURL& url) const {
+  for (auto& entry : lru_browsers_) {
+    if (entry->launch_url == url)
+      return entry->browser;
+  }
+  return nullptr;
+}
+
+const KioskNextBrowserList::Entry* KioskNextBrowserList::GetBrowserEntry(
+    const Browser* browser) const {
+  auto iterator = FindBrowser(browser);
+
+  if (iterator != lru_browsers_.end())
+    return (*iterator).get();
+  return nullptr;
+}
+
+void KioskNextBrowserList::OnBrowserRemoved(Browser* browser) {
+  auto iterator = FindBrowser(browser);
+  if (iterator == lru_browsers_.end())
+    return;
+  lru_browsers_.erase(iterator);
+}
+
+void KioskNextBrowserList::OnBrowserSetLastActive(Browser* browser) {
+  auto iterator = FindBrowser(browser);
+  if (iterator == lru_browsers_.end())
+    return;
+
+  if (browser == lru_browsers_.back()->browser)
+    return;
+
+  lru_browsers_.push_back(std::move(*iterator));
+  lru_browsers_.erase(iterator);
+}
+
+KioskNextBrowserList::KioskNextBrowserList(size_t max_browsers_allowed)
+    : max_browsers_allowed_(max_browsers_allowed) {
+  BrowserList::GetInstance()->AddObserver(this);
+}
+
+KioskNextBrowserList::~KioskNextBrowserList() {
+  if (BrowserList::GetInstance())
+    BrowserList::GetInstance()->RemoveObserver(this);
+}
+
+void KioskNextBrowserList::AddBrowser(Browser* browser, const GURL& url) {
+  DCHECK(CanAddBrowser());
+  lru_browsers_.push_back(
+      std::make_unique<KioskNextBrowserList::Entry>(browser, url));
+}
+
+void KioskNextBrowserList::RemoveLRUBrowser() {
+  if (lru_browsers_.size() > 0) {
+    // TODO(crbug.com/972880) Figure out how to handle the onbeforeunload
+    // handlers instead of just skipping them.
+    bool result = lru_browsers_.front()->browser->TryToCloseWindow(
+        true /* skip_beforeunload */, base::NullCallback());
+
+    DCHECK(!result);
+
+    lru_browsers_.pop_front();
+  }
+}
+
+KioskNextBrowserList::Iterator KioskNextBrowserList::FindBrowser(
+    const Browser* browser) {
+  return std::find_if(
+      lru_browsers_.begin(), lru_browsers_.end(),
+      [browser](const std::unique_ptr<KioskNextBrowserList::Entry>& entry) {
+        return browser == entry->browser;
+      });
+}
+
+KioskNextBrowserList::ConstIterator KioskNextBrowserList::FindBrowser(
+    const Browser* browser) const {
+  return std::find_if(
+      lru_browsers_.begin(), lru_browsers_.end(),
+      [browser](const std::unique_ptr<KioskNextBrowserList::Entry>& entry) {
+        return browser == entry->browser;
+      });
+}
+
+// static
+KioskNextBrowserFactory* KioskNextBrowserFactory::Get() {
+  static base::NoDestructor<KioskNextBrowserFactory> instance(
+      kMaxKioskNextBrowsersAllowed);
+  return instance.get();
+}
+
+KioskNextBrowserFactory::KioskNextBrowserFactory(size_t max_browsers)
+    : browser_list_(max_browsers) {}
+
+KioskNextBrowserFactory::~KioskNextBrowserFactory() = default;
+
+Browser* KioskNextBrowserFactory::CreateForWebsite(const GURL& url) {
+  Browser* browser = browser_list_.GetBrowserForWebsite(url);
+  if (browser) {
+    browser_list_.OnBrowserSetLastActive(browser);
+    browser->window()->Show();
+    return browser;
+  }
+
+  if (!browser_list_.CanAddBrowser())
+    browser_list_.RemoveLRUBrowser();
+
+  DCHECK(browser_list_.CanAddBrowser());
+  browser = CreateKioskNextBrowser();
+  browser_list_.AddBrowser(browser, url);
+
+  NavigateBrowser(browser, url);
+  browser->window()->Show();
+  return browser;
+}
diff --git a/chrome/browser/chromeos/kiosk_next/kiosk_next_browser_factory.h b/chrome/browser/chromeos/kiosk_next/kiosk_next_browser_factory.h
new file mode 100644
index 0000000..2c4f55b
--- /dev/null
+++ b/chrome/browser/chromeos/kiosk_next/kiosk_next_browser_factory.h
@@ -0,0 +1,107 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_KIOSK_NEXT_KIOSK_NEXT_BROWSER_FACTORY_H_
+#define CHROME_BROWSER_CHROMEOS_KIOSK_NEXT_KIOSK_NEXT_BROWSER_FACTORY_H_
+
+#include <list>
+#include <memory>
+
+#include "base/no_destructor.h"
+#include "chrome/browser/ui/browser_list_observer.h"
+#include "url/gurl.h"
+
+class Browser;
+
+// This class keeps a list of Browser instances along with their corresponding
+// launch URLs.
+class KioskNextBrowserList : public BrowserListObserver {
+ public:
+  // This struct keeps a browser instance and its associated launch url from
+  // kiosk next home.
+  struct Entry {
+    Entry(Browser* browser, const GURL& launch_url);
+
+    // Unowned. Owned by Views hierarchy (BrowserView).
+    Browser* const browser;
+
+    const GURL launch_url;
+  };
+
+  typedef std::list<std::unique_ptr<Entry>>::iterator Iterator;
+  typedef std::list<std::unique_ptr<Entry>>::const_iterator ConstIterator;
+
+  // Returns a pointer to the KioskNextBrowserList instance owned by
+  // KioskNextBrowserFactory.
+  static const KioskNextBrowserList& Get();
+
+  // This method is called by KioskNextBrowserFactory to check if the upper
+  // limit of number of Browsers has been reached.
+  bool CanAddBrowser() const;
+
+  // Checks if the browser instance is in the list of browsers created by
+  // KioskNextBrowserFactory.
+  bool IsKioskNextBrowser(const Browser* browser) const;
+
+  // Returns the corresponding browser if there exists a Browser instance
+  // that was launched with the particular URL.
+  Browser* GetBrowserForWebsite(const GURL& url) const;
+
+  // Returns the corresponding Entry if browser is a kiosk next browser.
+  // Otherwise, return an Entry with nullptr for its |browser|.
+  const Entry* GetBrowserEntry(const Browser* browser) const;
+
+  // BrowserListObserver:
+  void OnBrowserRemoved(Browser* browser) override;
+  void OnBrowserSetLastActive(Browser* browser) override;
+
+ private:
+  friend class KioskNextBrowserFactory;
+
+  explicit KioskNextBrowserList(size_t max_browsers_allowed);
+  ~KioskNextBrowserList() override;
+
+  // Called by KioskNextBrowserFactory to add a new browser instance along with
+  // its launch url.
+  void AddBrowser(Browser* browser, const GURL& url);
+
+  // Called by KioskNextBrowserFactory when CanAddBrowser() returns false. It
+  // removes the LRU browser to allow the factory class to continue creating.
+  void RemoveLRUBrowser();
+
+  Iterator FindBrowser(const Browser* browser);
+  ConstIterator FindBrowser(const Browser* browser) const;
+
+  size_t max_browsers_allowed_;
+  std::list<std::unique_ptr<Entry>> lru_browsers_;
+
+  DISALLOW_COPY_AND_ASSIGN(KioskNextBrowserList);
+};
+
+class KioskNextBrowserFactory {
+ public:
+  // Returns the instance already created for this singleton. Creates a new
+  // instance if one hasn't already been created yet.
+  static KioskNextBrowserFactory* Get();
+
+  ~KioskNextBrowserFactory();
+
+  Browser* CreateForWebsite(const GURL& url);
+
+  const KioskNextBrowserList& kiosk_next_browser_list() const {
+    return browser_list_;
+  }
+
+ private:
+  friend class KioskNextBrowserFactoryTest;
+  friend class base::NoDestructor<KioskNextBrowserFactory>;
+
+  explicit KioskNextBrowserFactory(size_t max_browsers_allowed);
+
+  KioskNextBrowserList browser_list_;
+
+  DISALLOW_COPY_AND_ASSIGN(KioskNextBrowserFactory);
+};
+
+#endif  // CHROME_BROWSER_CHROMEOS_KIOSK_NEXT_KIOSK_NEXT_BROWSER_FACTORY_H_
diff --git a/chrome/browser/chromeos/kiosk_next/kiosk_next_browser_factory_browsertest.cc b/chrome/browser/chromeos/kiosk_next/kiosk_next_browser_factory_browsertest.cc
new file mode 100644
index 0000000..bfa7a65
--- /dev/null
+++ b/chrome/browser/chromeos/kiosk_next/kiosk_next_browser_factory_browsertest.cc
@@ -0,0 +1,70 @@
+// 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 <memory>
+
+#include "chrome/browser/chromeos/kiosk_next/kiosk_next_browser_factory.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/test/browser_test.h"
+#include "ui/base/ui_base_types.h"
+#include "url/gurl.h"
+
+namespace {
+const GURL kUrl1("https://www.url1.com");
+const GURL kUrl2("https://www.url2.com");
+const GURL kUrl3("https://www.url3.com");
+}  // namespace
+
+class KioskNextBrowserFactoryTest : public InProcessBrowserTest {
+ public:
+  KioskNextBrowserFactoryTest() {}
+  ~KioskNextBrowserFactoryTest() override = default;
+
+  KioskNextBrowserList& GetBrowserList() {
+    return browser_factory_.browser_list_;
+  }
+
+ protected:
+  KioskNextBrowserFactory browser_factory_{2 /* max_browsers_allowed */};
+
+  DISALLOW_COPY_AND_ASSIGN(KioskNextBrowserFactoryTest);
+};
+
+IN_PROC_BROWSER_TEST_F(KioskNextBrowserFactoryTest, TestBrowserParams) {
+  // Adds a new KioskNextBrowser instance.
+  Browser* browser = browser_factory_.CreateForWebsite(kUrl1);
+  EXPECT_NE(browser, nullptr);
+  EXPECT_EQ(browser->initial_show_state(), ui::SHOW_STATE_MAXIMIZED);
+  EXPECT_FALSE(browser->is_type_tabbed());
+  EXPECT_TRUE(GetBrowserList().IsKioskNextBrowser(browser));
+  EXPECT_EQ(GetBrowserList().GetBrowserForWebsite(kUrl1), browser);
+}
+
+IN_PROC_BROWSER_TEST_F(KioskNextBrowserFactoryTest, TestFactoryCreateMethods) {
+  ASSERT_TRUE(GetBrowserList().CanAddBrowser());
+  EXPECT_EQ(GetBrowserList().GetBrowserForWebsite(kUrl1), nullptr);
+
+  // Adds a new KioskNextBrowser instance.
+  browser_factory_.CreateForWebsite(kUrl1);
+  EXPECT_TRUE(GetBrowserList().CanAddBrowser());
+  Browser* browser_2 = browser_factory_.CreateForWebsite(kUrl2);
+  EXPECT_NE(browser_2, nullptr);
+  EXPECT_EQ(GetBrowserList().GetBrowserEntry(browser_2)->browser, browser_2);
+
+  EXPECT_FALSE(GetBrowserList().CanAddBrowser());
+  Browser* browser_3 = browser_factory_.CreateForWebsite(kUrl3);
+  EXPECT_EQ(GetBrowserList().GetBrowserForWebsite(kUrl3), browser_3);
+
+  // The LRU browser has been removed.
+  EXPECT_EQ(GetBrowserList().GetBrowserForWebsite(kUrl1), nullptr);
+
+  // Make browser_3 is the lru browser.
+  GetBrowserList().OnBrowserSetLastActive(browser_2);
+
+  browser_factory_.CreateForWebsite(kUrl1);
+
+  // LRU browser has been removed.
+  EXPECT_EQ(GetBrowserList().GetBrowserForWebsite(kUrl3), nullptr);
+}
diff --git a/chrome/browser/chromeos/printing/printer_info.h b/chrome/browser/chromeos/printing/printer_info.h
index 35d4dcd7..b8cfd02e 100644
--- a/chrome/browser/chromeos/printing/printer_info.h
+++ b/chrome/browser/chromeos/printing/printer_info.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "base/callback_forward.h"
+#include "printing/backend/cups_jobs.h"
 
 namespace chromeos {
 
@@ -18,7 +19,7 @@
 // from the printer. |autoconf| indicates if we think we can compute the
 // printer capabilites without a PPD.
 using PrinterInfoCallback =
-    base::Callback<void(bool success,
+    base::Callback<void(::printing::PrinterQueryResult result,
                         const std::string& make,
                         const std::string& model,
                         const std::string& make_and_model,
diff --git a/chrome/browser/chromeos/printing/printer_info_cups.cc b/chrome/browser/chromeos/printing/printer_info_cups.cc
index d21d0172..431166f 100644
--- a/chrome/browser/chromeos/printing/printer_info_cups.cc
+++ b/chrome/browser/chromeos/printing/printer_info_cups.cc
@@ -16,7 +16,6 @@
 #include "base/task/task_traits.h"
 #include "base/task_runner_util.h"
 #include "base/version.h"
-#include "printing/backend/cups_jobs.h"
 
 namespace {
 
@@ -28,6 +27,13 @@
 const std::array<const char* const, 4> kMultiWordManufacturers{
     {"FUJI XEROX", "KODAK FUNAI", "KONICA MINOLTA", "TEXAS INSTRUMENTS"}};
 
+// Wraps a PrinterQueryResult and a PrinterInfo so that we can use
+// PostTaskAndResplyWithResult.
+struct QueryResult {
+  ::printing::PrinterQueryResult result;
+  ::printing::PrinterInfo printer_info;
+};
+
 // Returns the length of the portion of |make_and_model| representing the
 // manufacturer.  This is either a value from kMultiWordManufacaturers or the
 // first token.  If there is only one token or less, we assume that it does not
@@ -78,31 +84,34 @@
 
 // Dispatches an IPP request to |host| to retrieve printer information.  Returns
 // a nullptr if the request fails.
-std::unique_ptr<::printing::PrinterInfo> QueryPrinterImpl(
-    const std::string& host,
-    const int port,
-    const std::string& path,
-    bool encrypted) {
-  auto info = std::make_unique<::printing::PrinterInfo>();
-  if (!::printing::GetPrinterInfo(host, port, path, encrypted, info.get())) {
+QueryResult QueryPrinterImpl(const std::string& host,
+                             const int port,
+                             const std::string& path,
+                             bool encrypted) {
+  QueryResult result;
+  result.result = ::printing::GetPrinterInfo(host, port, path, encrypted,
+                                             &result.printer_info);
+  if (result.result != ::printing::PrinterQueryResult::SUCCESS) {
     LOG(ERROR) << "Could not retrieve printer info";
-    return nullptr;
   }
 
-  return info;
+  return result;
 }
 
 // Handles the request for |info|.  Parses make and model information before
 // calling |callback|.
 void OnPrinterQueried(const chromeos::PrinterInfoCallback& callback,
-                      std::unique_ptr<::printing::PrinterInfo> info) {
-  if (!info) {
+                      const QueryResult& query_result) {
+  const ::printing::PrinterQueryResult& result = query_result.result;
+  const ::printing::PrinterInfo& printer_info = query_result.printer_info;
+  if (result != ::printing::PrinterQueryResult::SUCCESS) {
     VLOG(1) << "Could not reach printer";
-    callback.Run(false, std::string(), std::string(), std::string(), {}, false);
+    callback.Run(result, std::string(), std::string(), std::string(), {},
+                 false);
     return;
   }
 
-  base::StringPiece make_and_model(info->make_and_model);
+  base::StringPiece make_and_model(printer_info.make_and_model);
   base::StringPiece make;
   base::StringPiece model;
 
@@ -115,8 +124,9 @@
     model = make_and_model;
   }
 
-  callback.Run(true, make.as_string(), model.as_string(), info->make_and_model,
-               info->document_formats, IsAutoconf(*info));
+  callback.Run(result, make.as_string(), model.as_string(),
+               printer_info.make_and_model, printer_info.document_formats,
+               IsAutoconf(printer_info));
 }
 
 }  // namespace
diff --git a/chrome/browser/chromeos/printing/printer_info_stub.cc b/chrome/browser/chromeos/printing/printer_info_stub.cc
index 66ccd18..bcd2caed 100644
--- a/chrome/browser/chromeos/printing/printer_info_stub.cc
+++ b/chrome/browser/chromeos/printing/printer_info_stub.cc
@@ -17,9 +17,11 @@
                      const PrinterInfoCallback& callback) {
   DCHECK(!host.empty());
 
-  base::PostTask(FROM_HERE,
-                 base::BindOnce(callback, false, "Foo", "Bar", "Foo Bar",
-                                std::vector<std::string>{}, false));
+  base::PostTask(
+      FROM_HERE,
+      base::BindOnce(callback, printing::PrinterQueryResult::UNKNOWN_FAILURE,
+                     "Foo", "Bar", "Foo Bar", std::vector<std::string>{},
+                     false));
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/usb/cros_usb_detector_unittest.cc b/chrome/browser/chromeos/usb/cros_usb_detector_unittest.cc
index 20337c8..0ed732b 100644
--- a/chrome/browser/chromeos/usb/cros_usb_detector_unittest.cc
+++ b/chrome/browser/chromeos/usb/cros_usb_detector_unittest.cc
@@ -155,6 +155,7 @@
   }
 
   void TearDown() override {
+    scoped_feature_list_.Reset();
     crostini_test_helper_.reset();
     BrowserWithTestWindowTest::TearDown();
   }
diff --git a/chrome/browser/client_hints/client_hints_browsertest.cc b/chrome/browser/client_hints/client_hints_browsertest.cc
index d53321e..bdabb5f 100644
--- a/chrome/browser/client_hints/client_hints_browsertest.cc
+++ b/chrome/browser/client_hints/client_hints_browsertest.cc
@@ -12,6 +12,7 @@
 #include "base/stl_util.h"
 #include "base/strings/string_util.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/mock_entropy_provider.h"
 #include "build/build_config.h"
 #include "chrome/browser/chrome_content_browser_client.h"
 #include "chrome/browser/content_settings/cookie_settings_factory.h"
@@ -136,7 +137,8 @@
                                public testing::WithParamInterface<bool> {
  public:
   ClientHintsBrowserTest()
-      : http_server_(net::EmbeddedTestServer::TYPE_HTTP),
+      : field_trial_list_(std::make_unique<base::MockEntropyProvider>()),
+        http_server_(net::EmbeddedTestServer::TYPE_HTTP),
         https_server_(net::EmbeddedTestServer::TYPE_HTTPS),
         https_cross_origin_server_(net::EmbeddedTestServer::TYPE_HTTPS),
         expect_client_hints_on_main_frame_(false),
@@ -410,6 +412,7 @@
     return main_frame_ua_observed_;
   }
 
+  base::FieldTrialList field_trial_list_;
   base::test::ScopedFeatureList scoped_feature_list_;
 
   std::string intercept_iframe_resource_;
@@ -1712,8 +1715,6 @@
 
   const net::EffectiveConnectionType web_effective_connection_type_override_ =
       net::EFFECTIVE_CONNECTION_TYPE_3G;
-
-  base::test::ScopedFeatureList scoped_feature_list_override_;
 };
 
 // Make sure that when NetInfo holdback experiment is enabled, the NetInfo APIs
diff --git a/chrome/browser/data_saver/data_saver_holdback_browsertest.cc b/chrome/browser/data_saver/data_saver_holdback_browsertest.cc
index 362c159..cf004b6 100644
--- a/chrome/browser/data_saver/data_saver_holdback_browsertest.cc
+++ b/chrome/browser/data_saver/data_saver_holdback_browsertest.cc
@@ -52,24 +52,10 @@
   }
 
   void ConfigureHoldbackExperiment() {
-    base::FieldTrialParamAssociator::GetInstance()->ClearAllParamsForTesting();
-    const std::string kTrialName = "TrialFoo";
-    const std::string kGroupName = "GroupFoo";  // Value not used
-
-    scoped_refptr<base::FieldTrial> trial =
-        base::FieldTrialList::CreateFieldTrial(kTrialName, kGroupName);
-
     std::map<std::string, std::string> params;
     params["holdback_web"] = GetParam() ? "true" : "false";
-    ASSERT_TRUE(
-        base::FieldTrialParamAssociator::GetInstance()
-            ->AssociateFieldTrialParams(kTrialName, kGroupName, params));
-
-    std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
-    feature_list->RegisterFieldTrialOverride(
-        features::kDataSaverHoldback.name,
-        base::FeatureList::OVERRIDE_ENABLE_FEATURE, trial.get());
-    scoped_feature_list_.InitWithFeatureList(std::move(feature_list));
+    scoped_feature_list_.InitWithFeaturesAndParameters(
+        {{features::kDataSaverHoldback, {params}}}, {});
   }
 
  private:
diff --git a/chrome/browser/enterprise_reporting/report_generator.cc b/chrome/browser/enterprise_reporting/report_generator.cc
new file mode 100644
index 0000000..9f1d61b
--- /dev/null
+++ b/chrome/browser/enterprise_reporting/report_generator.cc
@@ -0,0 +1,110 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/enterprise_reporting/report_generator.h"
+
+#include <utility>
+
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile_attributes_entry.h"
+#include "chrome/browser/profiles/profile_attributes_storage.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/common/channel_info.h"
+#include "components/policy/core/common/cloud/cloud_policy_util.h"
+#include "components/version_info/channel.h"
+#include "components/version_info/version_info.h"
+
+#if defined(OS_WIN)
+#include "base/win/wmi.h"
+#endif
+
+namespace em = enterprise_management;
+
+namespace enterprise_reporting {
+namespace {
+
+std::string GetChromePath() {
+  base::FilePath path;
+  base::PathService::Get(base::DIR_EXE, &path);
+  return path.AsUTF8Unsafe();
+}
+
+}  // namespace
+
+ReportGenerator::ReportGenerator() = default;
+ReportGenerator::~ReportGenerator() = default;
+
+std::vector<std::unique_ptr<em::ChromeDesktopReportRequest>>
+ReportGenerator::Generate() {
+  CreateBasicRequest();
+  requests_.push_back(
+      std::make_unique<em::ChromeDesktopReportRequest>(basic_request_));
+  return std::move(requests_);
+}
+
+void ReportGenerator::CreateBasicRequest() {
+  basic_request_.set_computer_name(this->GetMachineName());
+  basic_request_.set_os_user_name(GetOSUserName());
+  basic_request_.set_serial_number(GetSerialNumber());
+  basic_request_.set_allocated_os_report(GetOSReport().release());
+  basic_request_.set_allocated_browser_report(GetBrowserReport().release());
+  for (auto& profile : GetProfiles()) {
+    basic_request_.mutable_browser_report()->add_profile_info_list()->Swap(
+        profile.get());
+  }
+}
+
+std::unique_ptr<em::OSReport> ReportGenerator::GetOSReport() {
+  auto report = std::make_unique<em::OSReport>();
+  report->set_name(policy::GetOSPlatform());
+  report->set_arch(policy::GetOSArchitecture());
+  report->set_version(policy::GetOSVersion());
+  return report;
+}
+
+std::string ReportGenerator::GetMachineName() {
+  return policy::GetMachineName();
+}
+
+std::string ReportGenerator::GetOSUserName() {
+  return policy::GetOSUsername();
+}
+
+std::string ReportGenerator::GetSerialNumber() {
+#if defined(OS_WIN)
+  return base::UTF16ToUTF8(
+      base::win::WmiComputerSystemInfo::Get().serial_number());
+#else
+  return std::string();
+#endif
+}
+
+std::unique_ptr<em::BrowserReport> ReportGenerator::GetBrowserReport() {
+  auto report = std::make_unique<em::BrowserReport>();
+  report->set_browser_version(version_info::GetVersionNumber());
+  report->set_channel(policy::ConvertToProtoChannel(chrome::GetChannel()));
+  report->set_executable_path(GetChromePath());
+  return report;
+}
+
+std::vector<std::unique_ptr<em::ChromeUserProfileInfo>>
+ReportGenerator::GetProfiles() {
+  std::vector<std::unique_ptr<em::ChromeUserProfileInfo>> profiles;
+  for (auto* entry : g_browser_process->profile_manager()
+                         ->GetProfileAttributesStorage()
+                         .GetAllProfilesAttributes()) {
+    profiles.push_back(std::make_unique<em::ChromeUserProfileInfo>());
+    profiles.back()->set_id(entry->GetPath().AsUTF8Unsafe());
+    profiles.back()->set_name(base::UTF16ToUTF8(entry->GetName()));
+    profiles.back()->set_is_full_report(false);
+  }
+  return profiles;
+}
+
+}  // namespace enterprise_reporting
diff --git a/chrome/browser/enterprise_reporting/report_generator.h b/chrome/browser/enterprise_reporting/report_generator.h
new file mode 100644
index 0000000..1881c6f24
--- /dev/null
+++ b/chrome/browser/enterprise_reporting/report_generator.h
@@ -0,0 +1,63 @@
+// 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_ENTERPRISE_REPORTING_REPORT_GENERATOR_H_
+#define CHROME_BROWSER_ENTERPRISE_REPORTING_REPORT_GENERATOR_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "components/policy/proto/device_management_backend.pb.h"
+
+namespace em = enterprise_management;
+
+namespace enterprise_reporting {
+
+class ReportGenerator {
+ public:
+  ReportGenerator();
+  ~ReportGenerator();
+
+  std::vector<std::unique_ptr<em::ChromeDesktopReportRequest>> Generate();
+
+ protected:
+  // Creates a basic request that will be used by all Profiles.
+  void CreateBasicRequest();
+
+  // Returns an OS report contains basic OS information includes OS name, OS
+  // architecture and OS version.
+  virtual std::unique_ptr<em::OSReport> GetOSReport();
+
+  // Returns the name of computer.
+  virtual std::string GetMachineName();
+
+  // Returns the name of OS user.
+  virtual std::string GetOSUserName();
+
+  // Returns the Serial number of the device. It's Windows only field and empty
+  // on other platforms.
+  virtual std::string GetSerialNumber();
+
+  // Returns a browser report contains browser related information includes
+  // browser version, channel and executable path.
+  virtual std::unique_ptr<em::BrowserReport> GetBrowserReport();
+
+  // Returns the list of Profiles that is owned by current Browser instance. It
+  // only contains Profile's path and name.
+  std::vector<std::unique_ptr<em::ChromeUserProfileInfo>> GetProfiles();
+
+ private:
+  std::vector<std::unique_ptr<em::ChromeDesktopReportRequest>> requests_;
+
+  // Basic information that is shared among requests.
+  em::ChromeDesktopReportRequest basic_request_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReportGenerator);
+};
+
+}  // namespace enterprise_reporting
+
+#endif  // CHROME_BROWSER_ENTERPRISE_REPORTING_REPORT_GENERATOR_H_
diff --git a/chrome/browser/enterprise_reporting/report_generator_unittest.cc b/chrome/browser/enterprise_reporting/report_generator_unittest.cc
new file mode 100644
index 0000000..7a91250
--- /dev/null
+++ b/chrome/browser/enterprise_reporting/report_generator_unittest.cc
@@ -0,0 +1,131 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/enterprise_reporting/report_generator.h"
+
+#include <set>
+
+#include "base/logging.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+#include "chrome/browser/profiles/profile_attributes_storage.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "components/account_id/account_id.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace em = enterprise_management;
+
+namespace enterprise_reporting {
+namespace {
+#if !defined(OS_CHROMEOS)
+// We only upload serial number on Windows.
+void VerifySerialNumber(const std::string& serial_number) {
+#if defined(OS_WIN)
+  EXPECT_NE(std::string(), serial_number);
+#else
+  EXPECT_EQ(std::string(), serial_number);
+#endif
+}
+
+const char kProfile1[] = "Profile";
+#endif
+}  // namespace
+
+#if !defined(OS_CHROMEOS)
+class ReportGeneratorTest : public ::testing::Test {
+ public:
+  ReportGeneratorTest()
+      : profile_manager_(TestingBrowserProcess::GetGlobal()) {}
+  ~ReportGeneratorTest() override = default;
+
+  void SetUp() override { ASSERT_TRUE(profile_manager_.SetUp()); }
+
+  // Creates |number| of Profiles. Returns the set of their names. The profile
+  // names begin with Profile|start_index|. Profile instances are created if
+  // |is_active| is true. Otherwise, information is only put into
+  // ProfileAttributesStorage.
+  std::set<std::string> CreateProfiles(int number,
+                                       bool is_active,
+                                       int start_index = 0) {
+    std::set<std::string> profile_names;
+    for (int i = start_index; i < number; i++) {
+      std::string profile_name =
+          std::string(kProfile1) + base::NumberToString(i);
+      if (is_active) {
+        profile_manager_.CreateTestingProfile(profile_name);
+      } else {
+        profile_manager_.profile_attributes_storage()->AddProfile(
+            profile_manager()->profiles_dir().AppendASCII(profile_name),
+            base::ASCIIToUTF16(profile_name), std::string(), base::string16(),
+            0, std::string(), EmptyAccountId());
+      }
+      profile_names.insert(profile_name);
+    }
+    profile_manager_.CreateGuestProfile();
+    profile_manager_.CreateSystemProfile();
+    return profile_names;
+  }
+
+  TestingProfileManager* profile_manager() { return &profile_manager_; }
+
+ private:
+  content::TestBrowserThreadBundle thread_bundle_;
+  TestingProfileManager profile_manager_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReportGeneratorTest);
+};
+
+TEST_F(ReportGeneratorTest, GenerateBasicReport) {
+  int profile_number = 2;
+  auto profile_names = CreateProfiles(profile_number, /*is_active=*/false);
+
+  ReportGenerator generator;
+  auto requests = generator.Generate();
+  EXPECT_EQ(1u, requests.size());
+
+  auto* basic_request = requests[0].get();
+  EXPECT_NE(std::string(), basic_request->computer_name());
+  EXPECT_NE(std::string(), basic_request->os_user_name());
+  VerifySerialNumber(basic_request->serial_number());
+
+  EXPECT_TRUE(basic_request->has_os_report());
+  auto& os_report = basic_request->os_report();
+  EXPECT_NE(std::string(), os_report.name());
+  EXPECT_NE(std::string(), os_report.arch());
+  EXPECT_NE(std::string(), os_report.version());
+
+  EXPECT_TRUE(basic_request->has_browser_report());
+  auto& browser_report = basic_request->browser_report();
+  EXPECT_NE(std::string(), browser_report.browser_version());
+  EXPECT_NE(std::string(), browser_report.executable_path());
+  EXPECT_TRUE(browser_report.has_channel());
+
+  EXPECT_EQ(profile_number, browser_report.profile_info_list_size());
+  for (int i = 0; i < profile_number; i++) {
+    auto profile_info = browser_report.profile_info_list(i);
+    std::string profile_name = profile_info.name();
+
+    // Verify that the profile id is set as profile path.
+    EXPECT_EQ(profile_manager()
+                  ->profiles_dir()
+                  .AppendASCII(profile_name)
+                  .AsUTF8Unsafe(),
+              profile_info.id());
+
+    // Verify that the basic report does not contain any full Profile report.
+    EXPECT_FALSE(profile_info.is_full_report());
+
+    // Verify the profile name is one of the profiles that were created above.
+    auto it = profile_names.find(profile_name);
+    EXPECT_NE(profile_names.end(), it);
+    profile_names.erase(it);
+  }
+}
+#endif
+
+}  // namespace enterprise_reporting
diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chrome/browser/extensions/api/settings_private/prefs_util.cc
index 126cac8..cb018673 100644
--- a/chrome/browser/extensions/api/settings_private/prefs_util.cc
+++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc
@@ -128,6 +128,7 @@
       settings_api::PrefType::PREF_TYPE_BOOLEAN;
   (*s_whitelist)[bookmarks::prefs::kShowBookmarkBar] =
       settings_api::PrefType::PREF_TYPE_BOOLEAN;
+
 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
   (*s_whitelist)[::prefs::kUseCustomChromeFrame] =
       settings_api::PrefType::PREF_TYPE_BOOLEAN;
@@ -578,6 +579,8 @@
 #if defined(GOOGLE_CHROME_BUILD)
   (*s_whitelist)[::prefs::kMediaRouterEnableCloudServices] =
       settings_api::PrefType::PREF_TYPE_BOOLEAN;
+  (*s_whitelist)[::prefs::kUserFeedbackAllowed] =
+      settings_api::PrefType::PREF_TYPE_BOOLEAN;
 #endif  // defined(GOOGLE_CHROME_BUILD)
 
   // Media Remoting settings.
diff --git a/chrome/browser/extensions/app_data_migrator_unittest.cc b/chrome/browser/extensions/app_data_migrator_unittest.cc
index fba4794..e79ca1ef 100644
--- a/chrome/browser/extensions/app_data_migrator_unittest.cc
+++ b/chrome/browser/extensions/app_data_migrator_unittest.cc
@@ -145,8 +145,7 @@
                        const Extension* ext,
                        storage::FileSystemContext* fs_context,
                        Profile* profile) {
-  profile->GetExtensionSpecialStoragePolicy()->GrantRightsForExtension(ext,
-                                                                       profile);
+  profile->GetExtensionSpecialStoragePolicy()->GrantRightsForExtension(ext);
 
   base::FilePath path(FILE_PATH_LITERAL("test.txt"));
   GURL extension_url =
diff --git a/chrome/browser/extensions/extension_action_runner.cc b/chrome/browser/extensions/extension_action_runner.cc
index eb0d4128..ced1a58b 100644
--- a/chrome/browser/extensions/extension_action_runner.cc
+++ b/chrome/browser/extensions/extension_action_runner.cc
@@ -373,18 +373,18 @@
     const base::Callback<void(ToolbarActionsBarBubbleDelegate::CloseAction)>&
         callback) {
   Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
-  ToolbarActionsBar* toolbar_actions_bar =
-      browser ? browser->window()->GetToolbarActionsBar() : nullptr;
-  if (toolbar_actions_bar) {
-    if (default_bubble_close_action_for_testing_) {
-      base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE,
-          base::BindOnce(callback, *default_bubble_close_action_for_testing_));
-    } else {
-      toolbar_actions_bar->ShowToolbarActionBubble(
-          std::make_unique<BlockedActionBubbleDelegate>(callback,
-                                                        extension->id()));
-    }
+  ExtensionsContainer* const extensions_container =
+      browser ? browser->window()->GetExtensionsContainer() : nullptr;
+  if (!extensions_container)
+    return;
+  if (default_bubble_close_action_for_testing_) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::BindOnce(callback, *default_bubble_close_action_for_testing_));
+  } else {
+    extensions_container->ShowToolbarActionBubble(
+        std::make_unique<BlockedActionBubbleDelegate>(callback,
+                                                      extension->id()));
   }
 }
 
diff --git a/chrome/browser/extensions/extension_context_menu_model.cc b/chrome/browser/extensions/extension_context_menu_model.cc
index 5c168f5..0d2a26a 100644
--- a/chrome/browser/extensions/extension_context_menu_model.cc
+++ b/chrome/browser/extensions/extension_context_menu_model.cc
@@ -192,7 +192,8 @@
     const Extension* extension,
     Browser* browser,
     ButtonVisibility button_visibility,
-    PopupDelegate* delegate)
+    PopupDelegate* delegate,
+    bool can_show_icon_in_toolbar)
     : SimpleMenuModel(this),
       extension_id_(extension->id()),
       is_component_(Manifest::IsComponentLocation(extension->location())),
@@ -200,7 +201,8 @@
       profile_(browser->profile()),
       delegate_(delegate),
       action_type_(NO_ACTION),
-      button_visibility_(button_visibility) {
+      button_visibility_(button_visibility),
+      can_show_icon_in_toolbar_(can_show_icon_in_toolbar) {
   InitMenu(extension, button_visibility);
 }
 
@@ -231,7 +233,12 @@
     return extension_items_->IsCommandIdVisible(command_id);
   }
 
-  // Standard menu items are always visible.
+  // The command is hidden in app windows because they don't
+  // support showing extensions in the app window frame.
+  if (command_id == TOGGLE_VISIBILITY)
+    return can_show_icon_in_toolbar_;
+
+  // Standard menu items are visible.
   return true;
 }
 
diff --git a/chrome/browser/extensions/extension_context_menu_model.h b/chrome/browser/extensions/extension_context_menu_model.h
index 4ec65a1..93aee70 100644
--- a/chrome/browser/extensions/extension_context_menu_model.h
+++ b/chrome/browser/extensions/extension_context_menu_model.h
@@ -102,7 +102,8 @@
   ExtensionContextMenuModel(const Extension* extension,
                             Browser* browser,
                             ButtonVisibility visibility,
-                            PopupDelegate* delegate);
+                            PopupDelegate* delegate,
+                            bool can_show_icon_in_toolbar);
   ~ExtensionContextMenuModel() override;
 
   // SimpleMenuModel::Delegate:
@@ -166,6 +167,8 @@
   // The visibility of the button at the time the menu opened.
   ButtonVisibility button_visibility_;
 
+  const bool can_show_icon_in_toolbar_;
+
   // Menu matcher for context menu items specified by the extension.
   std::unique_ptr<ContextMenuMatcher> extension_items_;
 
diff --git a/chrome/browser/extensions/extension_context_menu_model_unittest.cc b/chrome/browser/extensions/extension_context_menu_model_unittest.cc
index c45d6c77..1f9c0f8 100644
--- a/chrome/browser/extensions/extension_context_menu_model_unittest.cc
+++ b/chrome/browser/extensions/extension_context_menu_model_unittest.cc
@@ -91,8 +91,8 @@
 
   std::unique_ptr<ExtensionContextMenuModel> BuildMenu() {
     return std::make_unique<ExtensionContextMenuModel>(
-        extension_.get(), browser_, ExtensionContextMenuModel::VISIBLE,
-        nullptr);
+        extension_.get(), browser_, ExtensionContextMenuModel::VISIBLE, nullptr,
+        true /* can_show_icon_in_toolbar */);
   }
 
   void AddContextItem(MenuItem::Context context) {
@@ -381,7 +381,8 @@
       "extension", manifest_keys::kPageAction, Manifest::EXTERNAL_POLICY);
 
   ExtensionContextMenuModel menu(extension, GetBrowser(),
-                                 ExtensionContextMenuModel::VISIBLE, nullptr);
+                                 ExtensionContextMenuModel::VISIBLE, nullptr,
+                                 true);
 
   ExtensionSystem* system = ExtensionSystem::Get(profile());
   system->management_policy()->UnregisterAllProviders();
@@ -433,7 +434,8 @@
     service()->AddExtension(extension.get());
 
     ExtensionContextMenuModel menu(extension.get(), GetBrowser(),
-                                   ExtensionContextMenuModel::VISIBLE, nullptr);
+                                   ExtensionContextMenuModel::VISIBLE, nullptr,
+                                   true);
 
     // A component extension's context menu should not include options for
     // managing extensions or removing it, and should only include an option for
@@ -462,7 +464,8 @@
             .SetLocation(Manifest::COMPONENT)
             .Build();
     ExtensionContextMenuModel menu(extension.get(), GetBrowser(),
-                                   ExtensionContextMenuModel::VISIBLE, nullptr);
+                                   ExtensionContextMenuModel::VISIBLE, nullptr,
+                                   true);
     service()->AddExtension(extension.get());
     EXPECT_TRUE(extensions::OptionsPageInfo::HasOptionsPage(extension.get()));
     EXPECT_NE(-1, menu.GetIndexOfCommandId(ExtensionContextMenuModel::OPTIONS));
@@ -614,7 +617,8 @@
       AddExtension("extension", manifest_keys::kPageAction, Manifest::INTERNAL);
 
   ExtensionContextMenuModel menu(extension, GetBrowser(),
-                                 ExtensionContextMenuModel::VISIBLE, nullptr);
+                                 ExtensionContextMenuModel::VISIBLE, nullptr,
+                                 true);
   EXPECT_TRUE(menu.IsCommandIdVisible(ExtensionContextMenuModel::HOME_PAGE));
   EXPECT_TRUE(menu.IsCommandIdVisible(ExtensionContextMenuModel::OPTIONS));
   EXPECT_TRUE(
@@ -660,7 +664,8 @@
   {
     // Even page actions should have a visibility option.
     ExtensionContextMenuModel menu(page_action, browser,
-                                   ExtensionContextMenuModel::VISIBLE, nullptr);
+                                   ExtensionContextMenuModel::VISIBLE, nullptr,
+                                   true);
     int index = menu.GetIndexOfCommandId(visibility_command);
     EXPECT_NE(-1, index);
     EXPECT_EQ(hide_string, menu.GetLabelAt(index));
@@ -668,7 +673,8 @@
 
   {
     ExtensionContextMenuModel menu(browser_action, browser,
-                                   ExtensionContextMenuModel::VISIBLE, nullptr);
+                                   ExtensionContextMenuModel::VISIBLE, nullptr,
+                                   true);
     int index = menu.GetIndexOfCommandId(visibility_command);
     EXPECT_NE(-1, index);
     EXPECT_EQ(hide_string, menu.GetLabelAt(index));
@@ -687,7 +693,7 @@
     // string.
     ExtensionContextMenuModel menu(browser_action, browser,
                                    ExtensionContextMenuModel::OVERFLOWED,
-                                   nullptr);
+                                   nullptr, true);
     int index = menu.GetIndexOfCommandId(visibility_command);
     EXPECT_NE(-1, index);
     EXPECT_EQ(show_string, menu.GetLabelAt(index));
@@ -698,7 +704,7 @@
     // popup, we should use a "Keep button in toolbar" string.
     ExtensionContextMenuModel menu(
         browser_action, browser,
-        ExtensionContextMenuModel::TRANSITIVELY_VISIBLE, nullptr);
+        ExtensionContextMenuModel::TRANSITIVELY_VISIBLE, nullptr, true);
     int index = menu.GetIndexOfCommandId(visibility_command);
     EXPECT_NE(-1, index);
     EXPECT_EQ(keep_string, menu.GetLabelAt(index));
@@ -720,7 +726,8 @@
     // reflects what normally happens (Chrome closes the menu when the uninstall
     // dialog shows up).
     ExtensionContextMenuModel menu(extension, GetBrowser(),
-                                   ExtensionContextMenuModel::VISIBLE, nullptr);
+                                   ExtensionContextMenuModel::VISIBLE, nullptr,
+                                   true);
     menu.ExecuteCommand(ExtensionContextMenuModel::UNINSTALL, 0);
   }
   uninstalled_observer.WaitForExtensionUninstalled();
@@ -756,7 +763,8 @@
       extension, UserScript::DOCUMENT_IDLE, increment_run_count);
 
   ExtensionContextMenuModel menu(extension, GetBrowser(),
-                                 ExtensionContextMenuModel::VISIBLE, nullptr);
+                                 ExtensionContextMenuModel::VISIBLE, nullptr,
+                                 true);
 
   EXPECT_NE(-1, menu.GetIndexOfCommandId(
                     ExtensionContextMenuModel::PAGE_ACCESS_SUBMENU));
@@ -869,7 +877,7 @@
       Manifest::INTERNAL, "http://www.example.com/*");
   ExtensionContextMenuModel single_host_menu(
       single_host_extension, GetBrowser(), ExtensionContextMenuModel::VISIBLE,
-      nullptr);
+      nullptr, true);
   EXPECT_NE(-1, single_host_menu.GetIndexOfCommandId(
                     ExtensionContextMenuModel::PAGE_ACCESS_SUBMENU));
 }
@@ -881,7 +889,8 @@
         "page_action", manifest_keys::kPageAction, Manifest::INTERNAL);
     ASSERT_TRUE(page_action);
     ExtensionContextMenuModel menu(page_action, GetBrowser(),
-                                   ExtensionContextMenuModel::VISIBLE, nullptr);
+                                   ExtensionContextMenuModel::VISIBLE, nullptr,
+                                   true);
     int inspect_popup_index =
         menu.GetIndexOfCommandId(ExtensionContextMenuModel::INSPECT_POPUP);
     EXPECT_GE(0, inspect_popup_index);
@@ -890,7 +899,8 @@
     const Extension* browser_action = AddExtension(
         "browser_action", manifest_keys::kBrowserAction, Manifest::INTERNAL);
     ExtensionContextMenuModel menu(browser_action, GetBrowser(),
-                                   ExtensionContextMenuModel::VISIBLE, nullptr);
+                                   ExtensionContextMenuModel::VISIBLE, nullptr,
+                                   true);
     int inspect_popup_index =
         menu.GetIndexOfCommandId(ExtensionContextMenuModel::INSPECT_POPUP);
     EXPECT_GE(0, inspect_popup_index);
@@ -901,7 +911,8 @@
     const Extension* no_action = AddExtension(
         "no_action", nullptr, Manifest::INTERNAL);
     ExtensionContextMenuModel menu(no_action, GetBrowser(),
-                                   ExtensionContextMenuModel::VISIBLE, nullptr);
+                                   ExtensionContextMenuModel::VISIBLE, nullptr,
+                                   true);
     int inspect_popup_index =
         menu.GetIndexOfCommandId(ExtensionContextMenuModel::INSPECT_POPUP);
     EXPECT_EQ(-1, inspect_popup_index);
@@ -1099,7 +1110,8 @@
     web_contents_tester->NavigateAndCommit(test_case.current_url);
 
     ExtensionContextMenuModel menu(extension.get(), GetBrowser(),
-                                   ExtensionContextMenuModel::VISIBLE, nullptr);
+                                   ExtensionContextMenuModel::VISIBLE, nullptr,
+                                   true);
 
     EXPECT_EQ(test_case.selected_entry.has_value(),
               !test_case.expected_entries.empty())
@@ -1186,7 +1198,8 @@
   AddTab(GURL("https://a.com"));
 
   ExtensionContextMenuModel menu(extension.get(), GetBrowser(),
-                                 ExtensionContextMenuModel::VISIBLE, nullptr);
+                                 ExtensionContextMenuModel::VISIBLE, nullptr,
+                                 true);
   EXPECT_EQ(CommandState::kEnabled, GetPageAccessCommandState(menu, kOnClick));
   EXPECT_EQ(CommandState::kDisabled, GetPageAccessCommandState(menu, kOnSite));
   EXPECT_EQ(CommandState::kDisabled,
@@ -1228,7 +1241,8 @@
 
   {
     ExtensionContextMenuModel menu(extension.get(), GetBrowser(),
-                                   ExtensionContextMenuModel::VISIBLE, nullptr);
+                                   ExtensionContextMenuModel::VISIBLE, nullptr,
+                                   true);
 
     // Without withholding host permissions, the menu should be visible on
     // a.com...
@@ -1256,7 +1270,8 @@
   {
     // ... but not on b.com, where it doesn't want to run.
     ExtensionContextMenuModel menu(extension.get(), GetBrowser(),
-                                   ExtensionContextMenuModel::VISIBLE, nullptr);
+                                   ExtensionContextMenuModel::VISIBLE, nullptr,
+                                   true);
     EXPECT_FALSE(HasPageAccessSubmenu(menu));
     EXPECT_TRUE(HasCantAccessPageEntry(menu));
   }
@@ -1270,7 +1285,8 @@
 
   {
     ExtensionContextMenuModel menu(extension.get(), GetBrowser(),
-                                   ExtensionContextMenuModel::VISIBLE, nullptr);
+                                   ExtensionContextMenuModel::VISIBLE, nullptr,
+                                   true);
     EXPECT_TRUE(HasPageAccessSubmenu(menu));
     EXPECT_FALSE(HasCantAccessPageEntry(menu));
     EXPECT_EQ(CommandState::kEnabled,
@@ -1296,7 +1312,8 @@
   }
 
   ExtensionContextMenuModel menu(extension.get(), GetBrowser(),
-                                 ExtensionContextMenuModel::VISIBLE, nullptr);
+                                 ExtensionContextMenuModel::VISIBLE, nullptr,
+                                 true);
   // Somewhat strangely, this also removes the access controls, because we don't
   // show it for sites the extension doesn't want to run on.
   EXPECT_FALSE(HasPageAccessSubmenu(menu));
@@ -1327,7 +1344,8 @@
   AddTab(a_com);
 
   ExtensionContextMenuModel menu(extension.get(), GetBrowser(),
-                                 ExtensionContextMenuModel::VISIBLE, nullptr);
+                                 ExtensionContextMenuModel::VISIBLE, nullptr,
+                                 true);
 
   EXPECT_EQ(CommandState::kEnabled, GetPageAccessCommandState(menu, kOnClick));
   EXPECT_EQ(CommandState::kEnabled, GetPageAccessCommandState(menu, kOnSite));
@@ -1368,7 +1386,8 @@
 
   Browser* browser = GetBrowser();
   ExtensionContextMenuModel menu(extension.get(), browser,
-                                 ExtensionContextMenuModel::VISIBLE, nullptr);
+                                 ExtensionContextMenuModel::VISIBLE, nullptr,
+                                 true);
 
   const ExtensionContextMenuModel::MenuEntries kLearnMore =
       ExtensionContextMenuModel::PAGE_ACCESS_LEARN_MORE;
@@ -1400,7 +1419,7 @@
       // The menu is constructed, but never shown.
       ExtensionContextMenuModel menu(extension.get(), GetBrowser(),
                                      ExtensionContextMenuModel::VISIBLE,
-                                     nullptr);
+                                     nullptr, true);
     }
     tester.ExpectTotalCount(kHistogramName, 0);
   }
@@ -1411,7 +1430,7 @@
       // The menu is constructed and shown, but no action is taken.
       ExtensionContextMenuModel menu(extension.get(), GetBrowser(),
                                      ExtensionContextMenuModel::VISIBLE,
-                                     nullptr);
+                                     nullptr, true);
       menu.OnMenuWillShow(&menu);
       menu.MenuClosed(&menu);
     }
@@ -1426,7 +1445,7 @@
       // The menu is constructed, shown, and an action taken.
       ExtensionContextMenuModel menu(extension.get(), GetBrowser(),
                                      ExtensionContextMenuModel::VISIBLE,
-                                     nullptr);
+                                     nullptr, true);
       menu.OnMenuWillShow(&menu);
       menu.ExecuteCommand(ExtensionContextMenuModel::MANAGE_EXTENSIONS, 0);
       menu.MenuClosed(&menu);
@@ -1474,4 +1493,25 @@
       1 /* expected_count */);
 }
 
+TEST_F(ExtensionContextMenuModelTest, HideToggleVisibility) {
+  InitializeEmptyExtensionService();
+  scoped_refptr<const Extension> extension =
+      ExtensionBuilder("extension").Build();
+  InitializeAndAddExtension(*extension);
+  {
+    ExtensionContextMenuModel menu(extension.get(), GetBrowser(),
+                                   ExtensionContextMenuModel::VISIBLE, nullptr,
+                                   true /* can_show_icon_in_toolbar */);
+    EXPECT_TRUE(
+        menu.IsCommandIdVisible(ExtensionContextMenuModel::TOGGLE_VISIBILITY));
+  }
+  {
+    ExtensionContextMenuModel menu(extension.get(), GetBrowser(),
+                                   ExtensionContextMenuModel::VISIBLE, nullptr,
+                                   false /* can_show_icon_in_toolbar */);
+    EXPECT_FALSE(
+        menu.IsCommandIdVisible(ExtensionContextMenuModel::TOGGLE_VISIBILITY));
+  }
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/extension_crash_recovery_browsertest.cc b/chrome/browser/extensions/extension_crash_recovery_browsertest.cc
index 8e36fd61c..ae366015 100644
--- a/chrome/browser/extensions/extension_crash_recovery_browsertest.cc
+++ b/chrome/browser/extensions/extension_crash_recovery_browsertest.cc
@@ -22,6 +22,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/result_codes.h"
 #include "content/public/common/url_constants.h"
+#include "content/public/test/no_renderer_crashes_assertion.h"
 #include "content/public/test/test_utils.h"
 #include "extensions/browser/extension_host.h"
 #include "extensions/browser/extension_registry.h"
@@ -142,6 +143,7 @@
   std::string first_extension_id_;
   std::string second_extension_id_;
   std::unique_ptr<NotificationDisplayServiceTester> display_service_;
+  content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes_;
 };
 
 IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest, Basic) {
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index b26a5be..33b54e1 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -890,7 +890,7 @@
   // TODO(kalman): Convert ExtensionSpecialStoragePolicy to a
   // BrowserContextKeyedService and use ExtensionRegistryObserver.
   profile_->GetExtensionSpecialStoragePolicy()->GrantRightsForExtension(
-      extension.get(), profile_);
+      extension.get());
 
   // TODO(kalman): This is broken. The crash reporter is process-wide so doesn't
   // work properly multi-profile. Besides which, it should be using
diff --git a/chrome/browser/extensions/extension_special_storage_policy.cc b/chrome/browser/extensions/extension_special_storage_policy.cc
index 71740d0..ceb0e41e 100644
--- a/chrome/browser/extensions/extension_special_storage_policy.cc
+++ b/chrome/browser/extensions/extension_special_storage_policy.cc
@@ -18,15 +18,12 @@
 #include "base/task/task_traits.h"
 #include "chrome/browser/content_settings/cookie_settings_factory.h"
 #include "chrome/common/chrome_switches.h"
-#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
 #include "chrome/common/url_constants.h"
 #include "components/content_settings/core/browser/cookie_settings.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/content_settings_types.h"
-#include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
-#include "content/public/browser/storage_partition.h"
 #include "content/public/common/url_constants.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
@@ -34,8 +31,6 @@
 #include "extensions/common/manifest_handlers/app_isolation_info.h"
 #include "extensions/common/manifest_handlers/content_capabilities_handler.h"
 #include "extensions/common/permissions/permissions_data.h"
-#include "storage/browser/quota/quota_manager.h"
-#include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
 #include "url/origin.h"
 
 using content::BrowserThread;
@@ -43,46 +38,6 @@
 using extensions::Extension;
 using storage::SpecialStoragePolicy;
 
-namespace {
-
-void ReportQuotaUsage(blink::mojom::QuotaStatusCode code,
-                      int64_t usage,
-                      int64_t quota) {
-  if (code == blink::mojom::QuotaStatusCode::kOk) {
-    // We're interested in the amount of space hosted apps are using. Record it
-    // when the extension is granted the unlimited storage permission (once per
-    // extension load, so on average once per run).
-    UMA_HISTOGRAM_MEMORY_KB("Extensions.HostedAppUnlimitedStorageUsage", usage);
-  }
-}
-
-// Log the usage for a hosted app with unlimited storage.
-void LogHostedAppUnlimitedStorageUsage(
-    scoped_refptr<const Extension> extension,
-    content::BrowserContext* browser_context) {
-  GURL launch_url =
-      extensions::AppLaunchInfo::GetLaunchWebURL(extension.get()).GetOrigin();
-  content::StoragePartition* partition =
-      browser_context ?  // |browser_context| can be NULL in unittests.
-      content::BrowserContext::GetStoragePartitionForSite(browser_context,
-                                                          launch_url) :
-      NULL;
-  if (partition) {
-    // We only have to query for kStorageTypePersistent data usage, because apps
-    // cannot ask for any more temporary storage, according to
-    // https://developers.google.com/chrome/whitepapers/storage.
-    base::PostTaskWithTraits(
-        FROM_HERE, {BrowserThread::IO, base::TaskPriority::BEST_EFFORT},
-        base::BindOnce(&storage::QuotaManager::GetUsageAndQuotaForWebApps,
-                       partition->GetQuotaManager(),
-                       url::Origin::Create(launch_url),
-                       blink::mojom::StorageType::kPersistent,
-                       base::Bind(&ReportQuotaUsage)));
-  }
-}
-
-}  // namespace
-
 ExtensionSpecialStoragePolicy::ExtensionSpecialStoragePolicy(
     content_settings::CookieSettings* cookie_settings)
     : cookie_settings_(cookie_settings) {
@@ -169,8 +124,7 @@
 }
 
 void ExtensionSpecialStoragePolicy::GrantRightsForExtension(
-    const extensions::Extension* extension,
-    content::BrowserContext* browser_context) {
+    const extensions::Extension* extension) {
   base::AutoLock locker(lock_);
   DCHECK(extension);
 
@@ -194,8 +148,6 @@
     if (extension->permissions_data()->HasAPIPermission(
             APIPermission::kUnlimitedStorage) &&
         unlimited_extensions_.Add(extension)) {
-      if (extension->is_hosted_app())
-        LogHostedAppUnlimitedStorageUsage(extension, browser_context);
       change_flags |= SpecialStoragePolicy::STORAGE_UNLIMITED;
     }
 
diff --git a/chrome/browser/extensions/extension_special_storage_policy.h b/chrome/browser/extensions/extension_special_storage_policy.h
index 59926272..c370b63 100644
--- a/chrome/browser/extensions/extension_special_storage_policy.h
+++ b/chrome/browser/extensions/extension_special_storage_policy.h
@@ -15,10 +15,6 @@
 #include "storage/browser/quota/special_storage_policy.h"
 #include "url/gurl.h"
 
-namespace content {
-class BrowserContext;
-}
-
 namespace content_settings {
 class CookieSettings;
 }
@@ -47,8 +43,7 @@
   CreateDeleteCookieOnExitPredicate() override;
 
   // Methods used by the ExtensionService to populate this class.
-  void GrantRightsForExtension(const extensions::Extension* extension,
-                               content::BrowserContext* browser_context);
+  void GrantRightsForExtension(const extensions::Extension* extension);
   void RevokeRightsForExtension(const extensions::Extension* extension);
   void RevokeRightsForAllExtensions();
 
diff --git a/chrome/browser/extensions/extension_special_storage_policy_unittest.cc b/chrome/browser/extensions/extension_special_storage_policy_unittest.cc
index 44d3fe55..23c0e153 100644
--- a/chrome/browser/extensions/extension_special_storage_policy_unittest.cc
+++ b/chrome/browser/extensions/extension_special_storage_policy_unittest.cc
@@ -204,7 +204,7 @@
 
 TEST_F(ExtensionSpecialStoragePolicyTest, AppWithProtectedStorage) {
   scoped_refptr<Extension> extension(CreateProtectedApp());
-  policy_->GrantRightsForExtension(extension.get(), NULL);
+  policy_->GrantRightsForExtension(extension.get());
   ExtensionSet protecting_extensions;
   protecting_extensions.Insert(extension);
   ExtensionSet empty_set;
@@ -225,7 +225,7 @@
 
 TEST_F(ExtensionSpecialStoragePolicyTest, AppWithUnlimitedStorage) {
   scoped_refptr<Extension> extension(CreateUnlimitedApp());
-  policy_->GrantRightsForExtension(extension.get(), NULL);
+  policy_->GrantRightsForExtension(extension.get());
   ExtensionSet protecting_extensions;
   protecting_extensions.Insert(extension);
   ExtensionSet empty_set;
@@ -257,7 +257,7 @@
   const GURL kHttpUrl("http://foo");
   const GURL kExtensionUrl("chrome-extension://bar");
   scoped_refptr<Extension> app(CreateRegularApp());
-  policy_->GrantRightsForExtension(app.get(), NULL);
+  policy_->GrantRightsForExtension(app.get());
 
   EXPECT_FALSE(policy_->HasIsolatedStorage(kHttpUrl));
   EXPECT_FALSE(policy_->HasIsolatedStorage(kExtensionUrl));
@@ -267,8 +267,8 @@
 TEST_F(ExtensionSpecialStoragePolicyTest, OverlappingApps) {
   scoped_refptr<Extension> protected_app(CreateProtectedApp());
   scoped_refptr<Extension> unlimited_app(CreateUnlimitedApp());
-  policy_->GrantRightsForExtension(protected_app.get(), NULL);
-  policy_->GrantRightsForExtension(unlimited_app.get(), NULL);
+  policy_->GrantRightsForExtension(protected_app.get());
+  policy_->GrantRightsForExtension(unlimited_app.get());
   ExtensionSet protecting_extensions;
   ExtensionSet empty_set;
   protecting_extensions.Insert(protected_app);
@@ -366,14 +366,14 @@
   for (size_t i = 0; i < base::size(apps); ++i) {
     SCOPED_TRACE(testing::Message() << "i: " << i);
     observer.ExpectGrant(apps[i]->id(), change_flags[i]);
-    policy_->GrantRightsForExtension(apps[i].get(), NULL);
+    policy_->GrantRightsForExtension(apps[i].get());
     base::RunLoop().RunUntilIdle();
     EXPECT_TRUE(observer.IsCompleted());
   }
 
   for (size_t i = 0; i < base::size(apps); ++i) {
     SCOPED_TRACE(testing::Message() << "i: " << i);
-    policy_->GrantRightsForExtension(apps[i].get(), NULL);
+    policy_->GrantRightsForExtension(apps[i].get());
     base::RunLoop().RunUntilIdle();
     EXPECT_TRUE(observer.IsCompleted());
   }
diff --git a/chrome/browser/extensions/navigation_observer_browsertest.cc b/chrome/browser/extensions/navigation_observer_browsertest.cc
index 39aafe2..290bbff 100644
--- a/chrome/browser/extensions/navigation_observer_browsertest.cc
+++ b/chrome/browser/extensions/navigation_observer_browsertest.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/navigation_observer.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "content/public/test/no_renderer_crashes_assertion.h"
 #include "extensions/browser/extension_dialog_auto_confirm.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_registry.h"
@@ -21,6 +22,11 @@
 // re-enable it.
 IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest,
                        PromptToReEnableExtensionsOnNavigation) {
+  // TODO(lukasza): https://crbug.com/970917: We should not terminate a renderer
+  // that hosts a disabled extension.  Once that is fixed, we should remove
+  // ScopedAllowRendererCrashes below.
+  content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes;
+
   NavigationObserver::SetAllowedRepeatedPromptingForTesting(true);
   base::ScopedClosureRunner reset_repeated_prompting(base::BindOnce([]() {
     NavigationObserver::SetAllowedRepeatedPromptingForTesting(false);
diff --git a/chrome/browser/feedback/show_feedback_page.cc b/chrome/browser/feedback/show_feedback_page.cc
index 89944c3..d850c27 100644
--- a/chrome/browser/feedback/show_feedback_page.cc
+++ b/chrome/browser/feedback/show_feedback_page.cc
@@ -12,7 +12,9 @@
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
 #include "components/feedback/feedback_util.h"
+#include "components/prefs/pref_service.h"
 #include "extensions/browser/api/feedback_private/feedback_private_api.h"
 
 #if defined(OS_CHROMEOS)
@@ -43,7 +45,6 @@
   }
 }
 #endif
-
 }  // namespace
 
 void ShowFeedbackPage(Browser* browser,
@@ -63,7 +64,9 @@
     LOG(ERROR) << "Cannot invoke feedback: No profile found!";
     return;
   }
-
+  if (!profile->GetPrefs()->GetBoolean(prefs::kUserFeedbackAllowed)) {
+    return;
+  }
   // Record an UMA histogram to know the most frequent feedback request source.
   UMA_HISTOGRAM_ENUMERATION("Feedback.RequestSource", source,
                             kFeedbackSourceCount);
diff --git a/chrome/browser/feedback/show_feedback_page_unittest.cc b/chrome/browser/feedback/show_feedback_page_unittest.cc
new file mode 100644
index 0000000..4deb6c1e
--- /dev/null
+++ b/chrome/browser/feedback/show_feedback_page_unittest.cc
@@ -0,0 +1,24 @@
+// 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/test/metrics/histogram_tester.h"
+#include "chrome/browser/ui/chrome_pages.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/browser_with_test_window_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ShowFeedbackPageTest = BrowserWithTestWindowTest;
+
+TEST_F(ShowFeedbackPageTest, UserFeedbackDisallowed) {
+  base::HistogramTester histogram_tester;
+  std::string unused;
+  chrome::ShowFeedbackPage(browser(), chrome::kFeedbackSourceBrowserCommand,
+                           unused, unused, unused, unused);
+  histogram_tester.ExpectTotalCount("Feedback.RequestSource", 1);
+  browser()->profile()->GetPrefs()->SetBoolean(prefs::kUserFeedbackAllowed,
+                                               false);
+  chrome::ShowFeedbackPage(browser(), chrome::kFeedbackSourceBrowserCommand,
+                           unused, unused, unused, unused);
+  histogram_tester.ExpectTotalCount("Feedback.RequestSource", 1);
+}
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 7dcbf9ec..7d41a4c2 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -934,6 +934,11 @@
     "expiry_milestone": 76
   },
   {
+    "name": "enable-caption-settings",
+    "owners": [ "evliu" ],
+    "expiry_milestone": 80
+  },
+  {
     "name": "enable-chrome-duet",
     "owners": [ "mdjones" ],
     "expiry_milestone": 79
@@ -1237,6 +1242,11 @@
     "expiry_milestone": 74
   },
   {
+    "name": "enable-backdrop-filter",
+    "owners": [ "masonfreed", "paint-dev@chromium.org" ],
+    "expiry_milestone": 78
+  },
+  {
     "name": "enable-layout-ng",
     "owners": [ "layout-dev@chromium.org" ],
     "expiry_milestone": 80
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index c6b89da..1d258bc 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -263,6 +263,10 @@
     "eligibility requirements for showing app banners, such as having a "
     "manifest, are met.";
 
+const char kCaptionSettingsName[] = "Caption Settings";
+const char kCaptionSettingsDescription[] =
+    "Enable the ability to customize captions.";
+
 const char kClickToOpenPDFName[] = "Click to open embedded PDFs";
 const char kClickToOpenPDFDescription[] =
     "When the PDF plugin is unavailable, show a click-to-open placeholder for "
@@ -660,6 +664,10 @@
     "Enable a new compositing mode where Blink generates the compositor "
     "property trees.";
 
+const char kEnableCSSBackdropFilterName[] = "Enable backdrop-filter";
+const char kEnableCSSBackdropFilterDescription[] =
+    "Enable a new CSS property called backdrop-filter.";
+
 const char kEnableDisplayLockingName[] = "Enable Display Locking";
 const char kEnableDisplayLockingDescription[] =
     "Enable Display Locking JavaScript API. The syntax and the APIs exposed "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 5c160602..d017453 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -167,6 +167,9 @@
 extern const char kBypassAppBannerEngagementChecksName[];
 extern const char kBypassAppBannerEngagementChecksDescription[];
 
+extern const char kCaptionSettingsName[];
+extern const char kCaptionSettingsDescription[];
+
 extern const char kClickToOpenPDFName[];
 extern const char kClickToOpenPDFDescription[];
 
@@ -399,6 +402,9 @@
 extern const char kEnableBlinkGenPropertyTreesName[];
 extern const char kEnableBlinkGenPropertyTreesDescription[];
 
+extern const char kEnableCSSBackdropFilterName[];
+extern const char kEnableCSSBackdropFilterDescription[];
+
 extern const char kEnableDisplayLockingName[];
 extern const char kEnableDisplayLockingDescription[];
 
diff --git a/chrome/browser/io_thread.cc b/chrome/browser/io_thread.cc
deleted file mode 100644
index 0acae565..0000000
--- a/chrome/browser/io_thread.cc
+++ /dev/null
@@ -1,76 +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.
-
-#include "chrome/browser/io_thread.h"
-
-#include "base/compiler_specific.h"
-#include "base/logging.h"
-#include "base/task/post_task.h"
-#include "base/time/time.h"
-#include "base/trace_event/trace_event.h"
-#include "build/build_config.h"
-#include "chrome/common/pref_names.h"
-#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_prefs.h"
-#include "components/net_log/chrome_net_log.h"
-#include "components/prefs/pref_service.h"
-#include "content/public/browser/browser_task_traits.h"
-#include "content/public/browser/browser_thread.h"
-#include "net/cert/cert_database.h"
-
-using content::BrowserThread;
-
-// The IOThread object must outlive any tasks posted to the IO thread before the
-// Quit task, so base::Bind() calls are not refcounted.
-
-namespace {
-
-#if defined(OS_MACOSX)
-void ObserveKeychainEvents() {
-  DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  net::CertDatabase::GetInstance()->StartListeningForKeychainEvents();
-}
-#endif
-
-}  // namespace
-
-// |local_state| is passed in explicitly in order to (1) reduce implicit
-// dependencies and (2) make IOThread more flexible for testing.
-IOThread::IOThread(net_log::ChromeNetLog* net_log) : net_log_(net_log) {
-  scoped_refptr<base::SingleThreadTaskRunner> io_thread_proxy =
-      base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::IO});
-
-  BrowserThread::SetIOThreadDelegate(this);
-}
-
-IOThread::~IOThread() {
-  // This isn't needed for production code, but in tests, IOThread may
-  // be multiply constructed.
-  BrowserThread::SetIOThreadDelegate(nullptr);
-}
-
-net_log::ChromeNetLog* IOThread::net_log() {
-  return net_log_;
-}
-
-void IOThread::Init() {
-  TRACE_EVENT0("startup", "IOThread::InitAsync");
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-
-#if defined(OS_MACOSX)
-  // Start observing Keychain events. This needs to be done on the UI thread,
-  // as Keychain services requires a CFRunLoop.
-  base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI},
-                           base::BindOnce(&ObserveKeychainEvents));
-#endif
-}
-
-void IOThread::CleanUp() {
-  if (net_log_)
-    net_log_->ShutDownBeforeThreadPool();
-}
-
-// static
-void IOThread::RegisterPrefs(PrefRegistrySimple* registry) {
-  data_reduction_proxy::RegisterPrefs(registry);
-}
diff --git a/chrome/browser/io_thread.h b/chrome/browser/io_thread.h
deleted file mode 100644
index c33614a7..0000000
--- a/chrome/browser/io_thread.h
+++ /dev/null
@@ -1,58 +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.
-
-#ifndef CHROME_BROWSER_IO_THREAD_H_
-#define CHROME_BROWSER_IO_THREAD_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <memory>
-#include <string>
-
-#include "base/compiler_specific.h"
-#include "base/macros.h"
-#include "build/build_config.h"
-#include "chrome/common/buildflags.h"
-#include "content/public/browser/browser_thread.h"
-#include "content/public/browser/browser_thread_delegate.h"
-
-class PrefRegistrySimple;
-
-namespace net_log {
-class ChromeNetLog;
-}
-
-// Contains state associated with, initialized and cleaned up on, and
-// primarily used on, the IO thread.
-//
-// If you are looking to interact with the IO thread (e.g. post tasks
-// to it or check if it is the current thread), see
-// content::BrowserThread.
-class IOThread : public content::BrowserThreadDelegate {
- public:
-  // |net_log| must either outlive the IOThread or be NULL.
-  explicit IOThread(net_log::ChromeNetLog* net_log);
-
-  ~IOThread() override;
-
-  static void RegisterPrefs(PrefRegistrySimple* registry);
-
-  net_log::ChromeNetLog* net_log();
-
- private:
-  // BrowserThreadDelegate implementation, runs on the IO thread.
-  // This handles initialization and destruction of state that must
-  // live on the IO thread.
-  void Init() override;
-  void CleanUp() override;
-
-  // The NetLog is owned by the browser process, to allow logging from other
-  // threads during shutdown, but is used most frequently on the IOThread.
-  net_log::ChromeNetLog* net_log_;
-
-  DISALLOW_COPY_AND_ASSIGN(IOThread);
-};
-
-#endif  // CHROME_BROWSER_IO_THREAD_H_
diff --git a/chrome/browser/io_thread_browsertest.cc b/chrome/browser/io_thread_browsertest.cc
index ab40906..d807c88 100644
--- a/chrome/browser/io_thread_browsertest.cc
+++ b/chrome/browser/io_thread_browsertest.cc
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/io_thread.h"
-
 #include <map>
 #include <memory>
 
diff --git a/chrome/browser/media/media_engagement_score_unittest.cc b/chrome/browser/media/media_engagement_score_unittest.cc
index 0e85003..e0220e3 100644
--- a/chrome/browser/media/media_engagement_score_unittest.cc
+++ b/chrome/browser/media/media_engagement_score_unittest.cc
@@ -161,8 +161,6 @@
   void OverrideFieldTrial(int min_visits,
                           double lower_threshold,
                           double upper_threshold) {
-    field_trial_list_.reset();
-    field_trial_list_ = std::make_unique<base::FieldTrialList>(nullptr);
     base::FieldTrialParamAssociator::GetInstance()->ClearAllParamsForTesting();
 
     std::map<std::string, std::string> params;
@@ -187,7 +185,6 @@
 
  private:
   std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
-  std::unique_ptr<base::FieldTrialList> field_trial_list_;
 };
 
 // Test Mojo serialization.
diff --git a/chrome/browser/media/unified_autoplay_browsertest.cc b/chrome/browser/media/unified_autoplay_browsertest.cc
index 3008f00..893b99b 100644
--- a/chrome/browser/media/unified_autoplay_browsertest.cc
+++ b/chrome/browser/media/unified_autoplay_browsertest.cc
@@ -419,10 +419,10 @@
   ~UnifiedAutoplaySettingBrowserTest() override = default;
 
   void SetUpOnMainThread() override {
+    UnifiedAutoplayBrowserTest::SetUpOnMainThread();
     scoped_feature_list_.InitWithFeatures(
         {media::kAutoplayDisableSettings, media::kAutoplayWhitelistSettings},
         {});
-    UnifiedAutoplayBrowserTest::SetUpOnMainThread();
   }
 
   bool AutoplayAllowed(const content::ToRenderFrameHost& adapter) {
diff --git a/chrome/browser/metrics/chrome_feature_list_creator.cc b/chrome/browser/metrics/chrome_feature_list_creator.cc
index 77738f3..23c9b9a 100644
--- a/chrome/browser/metrics/chrome_feature_list_creator.cc
+++ b/chrome/browser/metrics/chrome_feature_list_creator.cc
@@ -168,13 +168,18 @@
   browser_field_trials_ =
       std::make_unique<ChromeBrowserFieldTrials>(local_state_.get());
 
-  // Initialize FieldTrialList to support FieldTrials. This is intentionally
-  // leaked since it needs to live for the duration of the browser process and
-  // there's no benefit in cleaning it up at exit.
-  base::FieldTrialList* leaked_field_trial_list = new base::FieldTrialList(
-      metrics_services_manager_->CreateEntropyProvider());
-  ANNOTATE_LEAKING_OBJECT_PTR(leaked_field_trial_list);
-  ignore_result(leaked_field_trial_list);
+  // Initialize FieldTrialList to support FieldTrials. If an instance already
+  // exists, this is likely a test scenario with a ScopedFeatureList active,
+  // so use that one to apply any overrides.
+  if (!base::FieldTrialList::GetInstance()) {
+    // Note: This is intentionally leaked since it needs to live for the
+    // duration of the browser process and there's no benefit in cleaning it up
+    // at exit.
+    base::FieldTrialList* leaked_field_trial_list = new base::FieldTrialList(
+        metrics_services_manager_->CreateEntropyProvider());
+    ANNOTATE_LEAKING_OBJECT_PTR(leaked_field_trial_list);
+    ignore_result(leaked_field_trial_list);
+  }
 
   auto feature_list = std::make_unique<base::FeatureList>();
 
diff --git a/chrome/browser/metrics/ukm_browsertest.cc b/chrome/browser/metrics/ukm_browsertest.cc
index 61468a7..225eaeda 100644
--- a/chrome/browser/metrics/ukm_browsertest.cc
+++ b/chrome/browser/metrics/ukm_browsertest.cc
@@ -158,14 +158,10 @@
             is_unified_consent_enabled
                 ? unified_consent::UnifiedConsentFeatureState::kEnabled
                 : unified_consent::UnifiedConsentFeatureState::kDisabled) {
-  }
-
-  void SetUp() override {
     // Explicitly enable UKM and disable the MetricsReporting (which should
     // not affect UKM).
     scoped_feature_list_.InitWithFeatures({ukm::kUkmFeature},
                                           {internal::kMetricsReportingFeature});
-    SyncTest::SetUp();
   }
 
   bool ukm_enabled() const {
@@ -300,7 +296,7 @@
   ukm::UkmService* ukm_service() const {
     return g_browser_process->GetMetricsServicesManager()->GetUkmService();
   }
-  base::test::ScopedFeatureList scoped_feature_list_;
+
 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
   // ScopedAccountConsistencyDice is required for unified consent to be enabled.
   // Note that it uses forced field trials to enable DICE which disable metrics
@@ -309,6 +305,8 @@
   const std::unique_ptr<ScopedAccountConsistencyDice> scoped_dice_;
 #endif
   const unified_consent::ScopedUnifiedConsent scoped_unified_consent_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+
   DISALLOW_COPY_AND_ASSIGN(UkmBrowserTestBase);
 };
 
diff --git a/chrome/browser/net/dns_probe_browsertest.cc b/chrome/browser/net/dns_probe_browsertest.cc
index 726323ec..d1241af 100644
--- a/chrome/browser/net/dns_probe_browsertest.cc
+++ b/chrome/browser/net/dns_probe_browsertest.cc
@@ -12,7 +12,6 @@
 #include "base/threading/thread_restrictions.h"
 #include "base/time/default_tick_clock.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/io_thread.h"
 #include "chrome/browser/net/dns_probe_service_factory.h"
 #include "chrome/browser/net/dns_probe_test_util.h"
 #include "chrome/browser/net/net_error_tab_helper.h"
diff --git a/chrome/browser/net/system_network_context_manager.cc b/chrome/browser/net/system_network_context_manager.cc
index cd8e2608..315fe08 100644
--- a/chrome/browser/net/system_network_context_manager.cc
+++ b/chrome/browser/net/system_network_context_manager.cc
@@ -22,7 +22,6 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_content_browser_client.h"
 #include "chrome/browser/component_updater/crl_set_component_installer.h"
-#include "chrome/browser/io_thread.h"
 #include "chrome/browser/net/chrome_mojo_proxy_resolver_factory.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "chrome/browser/ssl/ssl_config_service_manager.h"
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 91b3b41..6fff588 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
@@ -872,8 +872,14 @@
       static_cast<int>(FrameData::MediaStatus::kNotPlayed));
 }
 
+// Flaky on Mac. http://crbug.com/972822
+#if defined(OS_MACOSX)
+#define MAYBE_AdFrameSizeInterventionMediaStatusPlayed DISABLED_AdFrameSizeInterventionMediaStatusPlayed
+#else
+#define MAYBE_AdFrameSizeInterventionMediaStatusPlayed AdFrameSizeInterventionMediaStatusPlayed
+#endif
 IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
-                       AdFrameSizeInterventionMediaStatusPlayed) {
+                       MAYBE_AdFrameSizeInterventionMediaStatusPlayed) {
   base::HistogramTester histogram_tester;
   ukm::TestAutoSetUkmRecorder ukm_recorder;
   embedded_test_server()->ServeFilesFromSourceDirectory(
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 21457c6..07848b7 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -1016,6 +1016,9 @@
   { key::kAllowPopupsDuringPageUnload,
     prefs::kAllowPopupsDuringPageUnload,
     base::Value::Type::BOOLEAN },
+  { key::kUserFeedbackAllowed,
+    prefs::kUserFeedbackAllowed,
+    base::Value::Type::BOOLEAN },
 
 #if defined(OS_WIN) || defined(OS_MACOSX) || \
     (defined(OS_LINUX) && !defined(OS_CHROMEOS))
diff --git a/chrome/browser/policy/policy_browsertest.cc b/chrome/browser/policy/policy_browsertest.cc
index c382b8f..57ac99a 100644
--- a/chrome/browser/policy/policy_browsertest.cc
+++ b/chrome/browser/policy/policy_browsertest.cc
@@ -70,7 +70,6 @@
 #include "chrome/browser/extensions/updater/extension_updater.h"
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/interstitials/security_interstitial_page_test_utils.h"
-#include "chrome/browser/io_thread.h"
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
 #include "chrome/browser/media/webrtc/media_stream_devices_controller.h"
 #include "chrome/browser/media/webrtc/webrtc_event_log_manager.h"
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 25d03118..4fba9d1 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -26,7 +26,6 @@
 #include "chrome/browser/first_run/first_run.h"
 #include "chrome/browser/gpu/gpu_mode_manager.h"
 #include "chrome/browser/intranet_redirect_detector.h"
-#include "chrome/browser/io_thread.h"
 #include "chrome/browser/lifetime/browser_shutdown.h"
 #include "chrome/browser/media/media_device_id_salt.h"
 #include "chrome/browser/media/media_engagement_service.h"
@@ -88,6 +87,7 @@
 #include "components/browsing_data/core/pref_names.h"
 #include "components/certificate_transparency/pref_names.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_prefs.h"
 #include "components/dom_distiller/core/distilled_page_prefs.h"
 #include "components/feature_engagement/buildflags.h"
 #include "components/flags_ui/pref_service_flags_storage.h"
@@ -521,6 +521,7 @@
 void RegisterLocalState(PrefRegistrySimple* registry) {
   // Please keep this list alphabetized.
   browser_shutdown::RegisterPrefs(registry);
+  data_reduction_proxy::RegisterPrefs(registry);
   BrowserProcessImpl::RegisterPrefs(registry);
   ChromeContentBrowserClient::RegisterLocalStatePrefs(registry);
   ChromeMetricsServiceClient::RegisterPrefs(registry);
@@ -531,7 +532,6 @@
   GpuModeManager::RegisterPrefs(registry);
   identity::IdentityManager::RegisterLocalStatePrefs(registry);
   IntranetRedirectDetector::RegisterPrefs(registry);
-  IOThread::RegisterPrefs(registry);
   language::GeoLanguageProvider::RegisterLocalStatePrefs(registry);
   language::UlpLanguageCodeLocator::RegisterLocalStatePrefs(registry);
   network_time::NetworkTimeTracker::RegisterPrefs(registry);
diff --git a/chrome/browser/previews/previews_lite_page_browsertest.cc b/chrome/browser/previews/previews_lite_page_browsertest.cc
index 1a706aee..01b3f3a 100644
--- a/chrome/browser/previews/previews_lite_page_browsertest.cc
+++ b/chrome/browser/previews/previews_lite_page_browsertest.cc
@@ -2023,8 +2023,8 @@
   ~CoinFlipHoldbackExperimentBrowserTest() override = default;
 
   void SetUp() override {
-    ukm_feature_list_.InitAndEnableFeature(ukm::kUkmFeature);
     PreviewsLitePageAndPageHintsBrowserTest::SetUp();
+    ukm_feature_list_.InitAndEnableFeature(ukm::kUkmFeature);
   }
 
   void SetUpCommandLine(base::CommandLine* cmd) override {
diff --git a/chrome/browser/previews/previews_offline_helper_unittest.cc b/chrome/browser/previews/previews_offline_helper_unittest.cc
index 1c70a773..f4334ab 100644
--- a/chrome/browser/previews/previews_offline_helper_unittest.cc
+++ b/chrome/browser/previews/previews_offline_helper_unittest.cc
@@ -376,7 +376,6 @@
 // we instead set the freshness duration to negative to make any newly saved
 // offline page stale.
 TEST_F(PreviewsOfflinePagesIntegrationTest, TestOfflinePagesDBQuery_Expired) {
-  base::FieldTrialList field_trial_list(nullptr);
   ASSERT_TRUE(base::AssociateFieldTrialParams(
       "ClientSidePreviews", "Enabled",
       {{"offline_preview_freshness_duration_in_days", "-1"}}));
diff --git a/chrome/browser/profiles/off_the_record_profile_impl.cc b/chrome/browser/profiles/off_the_record_profile_impl.cc
index abd5e5e..28ab69d 100644
--- a/chrome/browser/profiles/off_the_record_profile_impl.cc
+++ b/chrome/browser/profiles/off_the_record_profile_impl.cc
@@ -31,7 +31,6 @@
 #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/io_thread.h"
 #include "chrome/browser/permissions/permission_manager.h"
 #include "chrome/browser/permissions/permission_manager_factory.h"
 #include "chrome/browser/plugins/chrome_plugin_service_filter.h"
diff --git a/chrome/browser/profiles/off_the_record_profile_io_data.cc b/chrome/browser/profiles/off_the_record_profile_io_data.cc
index 9f53f36..3a70f5f 100644
--- a/chrome/browser/profiles/off_the_record_profile_io_data.cc
+++ b/chrome/browser/profiles/off_the_record_profile_io_data.cc
@@ -16,7 +16,6 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/custom_handlers/protocol_handler_registry.h"
 #include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h"
-#include "chrome/browser/io_thread.h"
 #include "chrome/browser/net/chrome_network_delegate.h"
 #include "chrome/browser/net/profile_network_context_service.h"
 #include "chrome/browser/net/profile_network_context_service_factory.h"
@@ -122,8 +121,8 @@
   content::CookieStoreConfig cookie_config;
   // Enable cookies for chrome-extension URLs.
   cookie_config.cookieable_schemes.push_back(extensions::kExtensionScheme);
-  extensions_cookie_store_ = content::CreateCookieStore(
-      cookie_config, profile_params->io_thread->net_log());
+  extensions_cookie_store_ =
+      content::CreateCookieStore(cookie_config, g_browser_process->net_log());
 }
 
 net::CookieStore* OffTheRecordProfileIOData::GetExtensionsCookieStore() const {
diff --git a/chrome/browser/profiles/profile_impl_io_data.cc b/chrome/browser/profiles/profile_impl_io_data.cc
index 4e66556..6fe886e 100644
--- a/chrome/browser/profiles/profile_impl_io_data.cc
+++ b/chrome/browser/profiles/profile_impl_io_data.cc
@@ -30,7 +30,6 @@
 #include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_io_data.h"
 #include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings.h"
 #include "chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings_factory.h"
-#include "chrome/browser/io_thread.h"
 #include "chrome/browser/net/chrome_network_delegate.h"
 #include "chrome/browser/net/profile_network_context_service.h"
 #include "chrome/browser/net/profile_network_context_service_factory.h"
@@ -226,8 +225,8 @@
   cookie_config.crypto_delegate = cookie_config::GetCookieCryptoDelegate();
   // Enable cookies for chrome-extension URLs.
   cookie_config.cookieable_schemes.push_back(extensions::kExtensionScheme);
-  extensions_cookie_store_ = content::CreateCookieStore(
-      cookie_config, profile_params->io_thread->net_log());
+  extensions_cookie_store_ =
+      content::CreateCookieStore(cookie_config, g_browser_process->net_log());
 }
 
 net::CookieStore* ProfileImplIOData::GetExtensionsCookieStore() const {
diff --git a/chrome/browser/profiles/profile_io_data.cc b/chrome/browser/profiles/profile_io_data.cc
index bad56f3..75d93e5 100644
--- a/chrome/browser/profiles/profile_io_data.cc
+++ b/chrome/browser/profiles/profile_io_data.cc
@@ -31,7 +31,6 @@
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/custom_handlers/protocol_handler_registry.h"
 #include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h"
-#include "chrome/browser/io_thread.h"
 #include "chrome/browser/net/chrome_network_delegate.h"
 #include "chrome/browser/net/profile_network_context_service.h"
 #include "chrome/browser/net/profile_network_context_service_factory.h"
@@ -252,8 +251,6 @@
   std::unique_ptr<ProfileParams> params(new ProfileParams);
   params->path = profile->GetPath();
 
-  params->io_thread = g_browser_process->io_thread();
-
   params->cookie_settings = CookieSettingsFactory::GetForProfile(profile);
   params->host_content_settings_map =
       HostContentSettingsMapFactory::GetForProfile(profile);
diff --git a/chrome/browser/profiles/profile_io_data.h b/chrome/browser/profiles/profile_io_data.h
index 6951921..f5f1545 100644
--- a/chrome/browser/profiles/profile_io_data.h
+++ b/chrome/browser/profiles/profile_io_data.h
@@ -20,7 +20,6 @@
 #include "base/synchronization/lock.h"
 #include "build/build_config.h"
 #include "chrome/browser/custom_handlers/protocol_handler_registry.h"
-#include "chrome/browser/io_thread.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/storage_partition_descriptor.h"
 #include "chrome/common/buildflags.h"
@@ -202,7 +201,6 @@
     ~ProfileParams();
 
     base::FilePath path;
-    IOThread* io_thread = nullptr;
 
     scoped_refptr<content_settings::CookieSettings> cookie_settings;
     scoped_refptr<HostContentSettingsMap> host_content_settings_map;
diff --git a/chrome/browser/profiles/profile_manager_unittest.cc b/chrome/browser/profiles/profile_manager_unittest.cc
index 8d8578f9..debc7ad 100644
--- a/chrome/browser/profiles/profile_manager_unittest.cc
+++ b/chrome/browser/profiles/profile_manager_unittest.cc
@@ -24,7 +24,6 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/history/history_service_factory.h"
-#include "chrome/browser/io_thread.h"
 #include "chrome/browser/prefs/browser_prefs.h"
 #include "chrome/browser/prefs/incognito_mode_prefs.h"
 #include "chrome/browser/profiles/profile.h"
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/log_store.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/log_store.js
index 230cfbbe..20f9dff 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/log_store.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/log_store.js
@@ -217,7 +217,7 @@
 /** @private @return {boolean} */
 LogStore.prototype.shouldSkipOutput_ = function() {
   var ChromeVoxState = chrome.extension.getBackgroundPage()['ChromeVoxState'];
-  if (ChromeVoxState.instance.currentRange &&
+  if (ChromeVoxState.instance && ChromeVoxState.instance.currentRange &&
       ChromeVoxState.instance.currentRange.start &&
       ChromeVoxState.instance.currentRange.start.node &&
       ChromeVoxState.instance.currentRange.start.node.root) {
diff --git a/chrome/browser/resources/chromeos/login/supervision_onboarding.js b/chrome/browser/resources/chromeos/login/supervision_onboarding.js
index 559765c..0303dcf 100644
--- a/chrome/browser/resources/chromeos/login/supervision_onboarding.js
+++ b/chrome/browser/resources/chromeos/login/supervision_onboarding.js
@@ -245,10 +245,13 @@
       /*
        * Properties to hide the main dialogs used to present the flow. Only one
        * of them can be shown at a time.
+       * Note: One of them needs to be visible when the element is being
+       * initialized, otherwise we risk having our width and height set to 0 by
+       * the display manager.
        */
-      hideContent_: {type: Boolean, value: true},
-      hideLoadingDialog_: {type: Boolean, value: true},
+      hideLoadingDialog_: {type: Boolean, value: false},
       hideRetryDialog_: {type: Boolean, value: true},
+      hideContent_: {type: Boolean, value: true},
 
       /*
        * Properties to hide the buttons that can trigger flow actions.
diff --git a/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_post_oobe.html b/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_post_oobe.html
index 64e541d..0d0a8da 100644
--- a/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_post_oobe.html
+++ b/chrome/browser/resources/chromeos/multidevice_setup/multidevice_setup_post_oobe.html
@@ -3,8 +3,8 @@
 <link rel="import" href="chrome://resources/html/cr.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup_shared_css.html">
 <link rel="import" href="chrome://resources/cr_components/chromeos/multidevice_setup/multidevice_setup.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
 <link rel="import" href="post_oobe_delegate.html">
 
 <dom-module id="multidevice-setup-post-oobe">
@@ -27,19 +27,19 @@
         forward-button-disabled="{{forwardButtonDisabled_}}"
         cancel-button-text-id="{{cancelButtonTextId_}}"
         backward-button-text-id="{{backwardButtonTextId_}}">
-      <paper-button id="backward-button"
+      <cr-button id="backward-button"
           slot="backward-button" class="cancel-button">
         [[i18n(backwardButtonTextId_)]]
-      </paper-button>
-      <paper-button id="cancel-button"
+      </cr-button>
+      <cr-button id="cancel-button"
           slot="cancel-button" class="cancel-button">
         [[i18n(cancelButtonTextId_)]]
-      </paper-button>
-      <paper-button id="forward-button"
+      </cr-button>
+      <cr-button id="forward-button"
           slot="forward-button" class="action-button"
           disabled$="[[forwardButtonDisabled_]]">
         [[i18n(forwardButtonTextId_)]]
-      </paper-button>
+      </cr-button>
     </multidevice-setup>
   </template>
   <script src="multidevice_setup_post_oobe.js">
diff --git a/chrome/browser/resources/local_ntp/customize.js b/chrome/browser/resources/local_ntp/customize.js
index f5d3811..878b4b0 100644
--- a/chrome/browser/resources/local_ntp/customize.js
+++ b/chrome/browser/resources/local_ntp/customize.js
@@ -95,6 +95,7 @@
   COLORS_DEFAULT: 'colors-default',
   COLORS_MENU: 'colors-menu',
   CUSTOMIZATION_MENU: 'customization-menu',
+  CUSTOM_BG: 'custom-bg',
   CUSTOM_LINKS_RESTORE_DEFAULT: 'custom-links-restore-default',
   CUSTOM_LINKS_RESTORE_DEFAULT_TEXT: 'custom-links-restore-default-text',
   DEFAULT_WALLPAPERS: 'edit-bg-default-wallpapers',
@@ -225,6 +226,12 @@
 customize.colorMenuLoaded = false;
 
 /**
+ * The original NTP background. Used to restore from image previews.
+ * @type {string}
+ */
+customize.originalBackground = '';
+
+/**
  * Sets the visibility of the settings menu and individual options depending on
  * their respective features.
  */
@@ -651,8 +658,32 @@
 };
 
 /**
- * Apply styling to a selected tile in the richer picker and enable the done
- * button.
+ * Preview an image as a custom backgrounds.
+ * @param {!Element} tile The tile that was selected.
+ */
+customize.richerPicker_previewImage = function(tile) {
+  customize.originalBackground =
+      $(customize.IDS.CUSTOM_BG).style.backgroundImage;
+
+  // TODO(crbug/971853): add browertests for previews.
+  // Set preview images at 720p by replacing the params in the url.
+  const re = /w\d+\-h\d+/;
+  $(customize.IDS.CUSTOM_BG).style.backgroundImage =
+      tile.style.backgroundImage.replace(re, 'w1280-h720');
+};
+
+/**
+ * Remove a preview image of a custom backgrounds.
+ * @param {!Element} tile The tile that was deselected.
+ */
+customize.richerPicker_unpreviewImage = function(tile) {
+  $(customize.IDS.CUSTOM_BG).style.backgroundImage =
+      customize.originalBackground;
+};
+
+/**
+ * Apply styling to a selected tile in the richer picker and enable the
+ * done button.
  * @param {?Element} tile The tile to apply styling to.
  */
 customize.richerPicker_selectTile = function(tile) {
@@ -671,6 +702,8 @@
   selectedCheck.classList.add(customize.CLASSES.SELECTED_CHECK);
   tile.appendChild(selectedCircle);
   tile.appendChild(selectedCheck);
+
+  customize.richerPicker_previewImage(tile);
 };
 
 /**
@@ -696,6 +729,8 @@
       --i;
     }
   }
+
+  customize.richerPicker_unpreviewImage(tile);
 };
 
 /**
diff --git a/chrome/browser/resources/settings/about_page/about_page.html b/chrome/browser/resources/settings/about_page/about_page.html
index 82136b5..9656740c 100644
--- a/chrome/browser/resources/settings/about_page/about_page.html
+++ b/chrome/browser/resources/settings/about_page/about_page.html
@@ -217,6 +217,7 @@
               label="$i18n{aboutGetHelpUsingChrome}" external></cr-link-row>
 <if expr="_google_chrome">
           <cr-link-row class="hr" id="reportIssue" on-click="onReportIssueTap_"
+              hidden="[[!prefs.feedback_allowed.value]]"
               label="$i18n{aboutReportAnIssue}"></cr-link-row>
 </if>
 <if expr="chromeos">
diff --git a/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.js b/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.js
index f22bdd00..7e2c35f3 100644
--- a/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.js
+++ b/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.js
@@ -143,6 +143,18 @@
       type: Number,
       value: 1000,
     },
+
+    /**
+     * The time in milliseconds at which discovery was started attempt (when the
+     * page was opened with Bluetooth on, or when Bluetooth turned on while the
+     * page was active).
+     * @private {?number}
+     */
+    discoveryStartTimestampMs_: {
+      type: Number,
+      value: null,
+    },
+
   },
 
   observers: [
@@ -392,6 +404,10 @@
       this.openDialog_();
     }
 
+    if (isPaired !== undefined && device.transport !== undefined) {
+      this.recordDeviceSelectionDuration_(isPaired, device.transport);
+    }
+
     const address = device.address;
     this.bluetoothPrivate.connect(address, result => {
       if (isPaired) {
@@ -492,10 +508,12 @@
       this.updateTimerId_ =
         window.setInterval(this.refreshBluetoothList_.bind(this),
                            this.listUpdateFrequencyMs);
+      this.discoveryStartTimestampMs_ = Date.now();
       return;
     }
     window.clearInterval(this.updateTimerId_);
     this.updateTimerId_ = undefined;
+    this.discoveryStartTimestampMs_ = null;
     this.deviceList_ = [];
   },
 
@@ -542,5 +560,31 @@
     }
 
     chrome.bluetoothPrivate.recordReconnection(success);
-  }
+  },
+
+  /**
+   * Record metrics for how long it took between when discovery started on the
+   * Settings page, and the user selected the device they wanted to connect to.
+   * @param {!boolean} wasPaired If the selected device was already
+   *     paired.
+   * @param {!chrome.bluetooth.Transport} transport The transport type
+   *     of the device.
+   * @private
+   */
+  recordDeviceSelectionDuration_: function(wasPaired, transport) {
+    if (!this.discoveryStartTimestampMs_) {
+      // It's not necessarily an error that |discoveryStartTimestampMs_| isn't
+      // present; it's intentionally cleared after the first device selection
+      // (see further on in this method). Recording subsequent device selections
+      // after the first would provide inflated durations that don't truly
+      // reflect how long it took for the user to find the device they're
+      // looking for.
+      return;
+    }
+
+    chrome.bluetoothPrivate.recordDeviceSelection(
+        Date.now() - this.discoveryStartTimestampMs_, wasPaired, transport);
+
+    this.discoveryStartTimestampMs_ = null;
+  },
 });
diff --git a/chrome/browser/resources/settings/languages_page/edit_dictionary_page.html b/chrome/browser/resources/settings/languages_page/edit_dictionary_page.html
index 5c35676..a712eee 100644
--- a/chrome/browser/resources/settings/languages_page/edit_dictionary_page.html
+++ b/chrome/browser/resources/settings/languages_page/edit_dictionary_page.html
@@ -34,14 +34,11 @@
           on-keys-pressed="onKeysPress_"></iron-a11y-keys>
       <cr-input id="newWord" value="{{newWordValue_}}"
           placeholder="$i18n{addDictionaryWordLabel}"
-          invalid="[[isWordInvalid_(newWordValue_)]]"
-          error-message="[[isWordInvalid_(newWordValue_,
-              '$i18nPolymer{addDictionaryWordDuplicateError}',
-              '$i18nPolymer{addDictionaryWordLengthError}')]]"
+          invalid="[[isWordInvalid_(newWordValue_, words_.*)]]"
+          error-message="[[getErrorMessage_(newWordValue_, words_.*)]]"
           spellcheck="false">
-        <cr-button on-click="onAddWordTap_"
-            disabled="[[disableAddButton_(newWordValue_)]]" id="addWord"
-            slot="suffix">
+        <cr-button on-click="onAddWordTap_" id="addWord" slot="suffix"
+            disabled$="[[disableAddButton_(newWordValue_, words_.*)]]">
           $i18n{addDictionaryWordButton}
         </cr-button>
       </cr-input>
diff --git a/chrome/browser/resources/settings/languages_page/edit_dictionary_page.js b/chrome/browser/resources/settings/languages_page/edit_dictionary_page.js
index 6417661..cb66b2e 100644
--- a/chrome/browser/resources/settings/languages_page/edit_dictionary_page.js
+++ b/chrome/browser/resources/settings/languages_page/edit_dictionary_page.js
@@ -18,7 +18,10 @@
 
   properties: {
     /** @private {string} */
-    newWordValue_: String,
+    newWordValue_: {
+      type: String,
+      value: '',
+    },
 
     /**
      * Needed by GlobalScrollTargetBehavior.
@@ -66,33 +69,81 @@
   },
 
   /**
+   * Adds the word in the new-word input to the dictionary.
+   * @private
+   */
+  addWordFromInput_: function() {
+    // Spaces are allowed, but removing leading and trailing whitespace.
+    const word = this.getTrimmedNewWord_();
+    this.newWordValue_ = '';
+    if (word) {
+      this.languageSettingsPrivate.addSpellcheckWord(word);
+    }
+  },
+
+  /**
    * Check if the field is empty or invalid.
-   * @param {string} word
    * @return {boolean}
    * @private
    */
-  disableAddButton_: function(word) {
-    return word.trim().length == 0 || this.isWordInvalid_(word);
+  disableAddButton_: function() {
+    return this.getTrimmedNewWord_().length == 0 || this.isWordInvalid_();
+  },
+
+  /**
+   * @return {string}
+   * @private
+   */
+  getErrorMessage_: function() {
+    if (this.newWordIsTooLong_()) {
+      return loadTimeData.getString('addDictionaryWordLengthError');
+    }
+    if (this.newWordAlreadyAdded_()) {
+      return loadTimeData.getString('addDictionaryWordDuplicateError');
+    }
+    return '';
+  },
+
+  /**
+   * @return {string}
+   * @private
+   */
+  getTrimmedNewWord_: function() {
+    return this.newWordValue_.trim();
   },
 
   /**
    * If the word is invalid, returns true (or a message if one is provided).
    * Otherwise returns false.
-   * @param {string} word
-   * @param {string} duplicateError
-   * @param {string} lengthError
-   * @return {string|boolean}
+   * @return {boolean}
    * @private
    */
-  isWordInvalid_: function(word, duplicateError, lengthError) {
-    const trimmedWord = word.trim();
-    if (this.words_.indexOf(trimmedWord) != -1) {
-      return duplicateError || true;
-    } else if (trimmedWord.length > MAX_CUSTOM_DICTIONARY_WORD_BYTES) {
-      return lengthError || true;
-    }
+  isWordInvalid_: function() {
+    return this.newWordAlreadyAdded_() || this.newWordIsTooLong_();
+  },
 
-    return false;
+  /**
+   * @return {boolean}
+   * @private
+   */
+  newWordAlreadyAdded_: function() {
+    return this.words_.includes(this.getTrimmedNewWord_());
+  },
+
+  /**
+   * @return {boolean}
+   * @private
+   */
+  newWordIsTooLong_: function() {
+    return this.getTrimmedNewWord_().length > MAX_CUSTOM_DICTIONARY_WORD_BYTES;
+  },
+
+  /**
+   * Handles tapping on the Add Word button.
+   */
+  onAddWordTap_: function(e) {
+    this.addWordFromInput_();
+    this.$.newWord.focus();
   },
 
   /**
@@ -120,7 +171,7 @@
     }
 
     for (const word of added) {
-      if (this.words_.indexOf(word) == -1) {
+      if (!this.words_.includes(word)) {
         this.unshift('words_', word);
       }
     }
@@ -134,12 +185,6 @@
       Polymer.dom.flush();
       this.$$('#list').notifyResize();
     }
-
-    // Update input enable to reflect new additions/removals.
-    // TODO(hsuregan): Remove hack when notifyPath() or notifySplices()
-    // is successful at creating DOM changes when applied to words_ (when
-    // attached to input newWord), OR when array changes are registered.
-    this.$.addWord.disabled = !this.$.newWord.validate();
   },
 
   /**
@@ -147,8 +192,7 @@
    * @param {!CustomEvent<!{key: string}>} e
    */
   onKeysPress_: function(e) {
-    if (e.detail.key == 'enter' &&
-        !this.disableAddButton_(this.newWordValue_)) {
+    if (e.detail.key == 'enter' && !this.disableAddButton_()) {
       this.addWordFromInput_();
     } else if (e.detail.key == 'esc') {
       e.detail.keyboardEvent.target.value = '';
@@ -156,32 +200,10 @@
   },
 
   /**
-   * Handles tapping on the Add Word button.
-   */
-  onAddWordTap_: function(e) {
-    this.addWordFromInput_();
-    this.$.newWord.focus();
-  },
-
-  /**
    * Handles tapping on a "Remove word" icon button.
    * @param {!{model: !{item: string}}} e
    */
   onRemoveWordTap_: function(e) {
     this.languageSettingsPrivate.removeSpellcheckWord(e.model.item);
   },
-
-  /**
-   * Adds the word in the new-word input to the dictionary.
-   */
-  addWordFromInput_: function() {
-    // Spaces are allowed, but removing leading and trailing whitespace.
-    const word = this.newWordValue_.trim();
-    this.newWordValue_ = '';
-    if (!word) {
-      return;
-    }
-
-    this.languageSettingsPrivate.addSpellcheckWord(word);
-  },
 });
diff --git a/chrome/browser/resources/settings/multidevice_page/BUILD.gn b/chrome/browser/resources/settings/multidevice_page/BUILD.gn
index fb06950..f00e9cb 100644
--- a/chrome/browser/resources/settings/multidevice_page/BUILD.gn
+++ b/chrome/browser/resources/settings/multidevice_page/BUILD.gn
@@ -4,18 +4,25 @@
 
 import("//third_party/closure_compiler/compile_js.gni")
 
+# Note: This file is not chromeos only because multidevice_browser_proxy is
+# required by site_settings:site_list.
+
 js_type_check("closure_compile") {
   deps = [
     ":multidevice_browser_proxy",
     ":multidevice_constants",
-    ":multidevice_feature_behavior",
-    ":multidevice_feature_item",
-    ":multidevice_feature_toggle",
-    ":multidevice_page",
-    ":multidevice_smartlock_subpage",
-    ":multidevice_subpage",
-    ":multidevice_tether_item",
   ]
+  if (is_chromeos) {
+    deps += [
+      ":multidevice_feature_behavior",
+      ":multidevice_feature_item",
+      ":multidevice_feature_toggle",
+      ":multidevice_page",
+      ":multidevice_smartlock_subpage",
+      ":multidevice_subpage",
+      ":multidevice_tether_item",
+    ]
+  }
 }
 
 js_library("multidevice_browser_proxy") {
@@ -31,77 +38,79 @@
   ]
 }
 
-js_library("multidevice_feature_behavior") {
-  deps = [
-    ":multidevice_constants",
-    "//ui/webui/resources/js:cr",
-    "//ui/webui/resources/js:i18n_behavior",
-  ]
-}
+if (is_chromeos) {
+  js_library("multidevice_feature_behavior") {
+    deps = [
+      ":multidevice_constants",
+      "//ui/webui/resources/js:cr",
+      "//ui/webui/resources/js:i18n_behavior",
+    ]
+  }
 
-js_library("multidevice_feature_item") {
-  deps = [
-    ":multidevice_constants",
-    ":multidevice_feature_behavior",
-    "..:route",
-    "//ui/webui/resources/js:cr",
-  ]
-}
+  js_library("multidevice_feature_item") {
+    deps = [
+      ":multidevice_constants",
+      ":multidevice_feature_behavior",
+      "..:route",
+      "//ui/webui/resources/js:cr",
+    ]
+  }
 
-js_library("multidevice_feature_toggle") {
-  deps = [
-    ":multidevice_constants",
-    ":multidevice_feature_behavior",
-  ]
-}
+  js_library("multidevice_feature_toggle") {
+    deps = [
+      ":multidevice_constants",
+      ":multidevice_feature_behavior",
+    ]
+  }
 
-js_library("multidevice_page") {
-  deps = [
-    ":multidevice_browser_proxy",
-    ":multidevice_constants",
-    ":multidevice_feature_behavior",
-    "..:route",
-    "../controls:password_prompt_dialog",
-    "//ui/webui/resources/js:cr",
-    "//ui/webui/resources/js:web_ui_listener_behavior",
-  ]
-}
+  js_library("multidevice_page") {
+    deps = [
+      ":multidevice_browser_proxy",
+      ":multidevice_constants",
+      ":multidevice_feature_behavior",
+      "..:route",
+      "../controls:password_prompt_dialog",
+      "//ui/webui/resources/js:cr",
+      "//ui/webui/resources/js:web_ui_listener_behavior",
+    ]
+  }
 
-js_library("multidevice_radio_button") {
-  deps = [
-    "//third_party/polymer/v1_0/components-chromium/iron-a11y-keys-behavior:iron-a11y-keys-behavior-extracted",
-    "//ui/webui/resources/cr_elements/cr_radio_button:cr_radio_button_behavior",
-    "//ui/webui/resources/cr_elements/policy:cr_policy_indicator",
-  ]
-}
+  js_library("multidevice_radio_button") {
+    deps = [
+      "//third_party/polymer/v1_0/components-chromium/iron-a11y-keys-behavior:iron-a11y-keys-behavior-extracted",
+      "//ui/webui/resources/cr_elements/cr_radio_button:cr_radio_button_behavior",
+      "//ui/webui/resources/cr_elements/policy:cr_policy_indicator",
+    ]
+  }
 
-js_library("multidevice_smartlock_subpage") {
-  deps = [
-    ":multidevice_constants",
-    ":multidevice_feature_behavior",
-    "../prefs:prefs_behavior",
-    "//ui/webui/resources/cr_elements/cr_radio_button:cr_radio_button",
-    "//ui/webui/resources/js:cr",
-  ]
-}
+  js_library("multidevice_smartlock_subpage") {
+    deps = [
+      ":multidevice_constants",
+      ":multidevice_feature_behavior",
+      "../prefs:prefs_behavior",
+      "//ui/webui/resources/cr_elements/cr_radio_button:cr_radio_button",
+      "//ui/webui/resources/js:cr",
+    ]
+  }
 
-js_library("multidevice_subpage") {
-  deps = [
-    ":multidevice_constants",
-    ":multidevice_feature_behavior",
-    "..:route",
-    "//ui/webui/resources/cr_elements/chromeos/network:cr_network_listener_behavior",
-  ]
-  externs_list = [ "$externs_path/networking_private.js" ]
-  extra_sources = [ "$interfaces_path/networking_private_interface.js" ]
-}
+  js_library("multidevice_subpage") {
+    deps = [
+      ":multidevice_constants",
+      ":multidevice_feature_behavior",
+      "..:route",
+      "//ui/webui/resources/cr_elements/chromeos/network:cr_network_listener_behavior",
+    ]
+    externs_list = [ "$externs_path/networking_private.js" ]
+    extra_sources = [ "$interfaces_path/networking_private_interface.js" ]
+  }
 
-js_library("multidevice_tether_item") {
-  deps = [
-    ":multidevice_feature_behavior",
-    "..:route",
-    "//ui/webui/resources/cr_elements/chromeos/network:cr_onc_types",
-  ]
-  externs_list = [ "$externs_path/networking_private.js" ]
-  extra_sources = [ "$interfaces_path/networking_private_interface.js" ]
+  js_library("multidevice_tether_item") {
+    deps = [
+      ":multidevice_feature_behavior",
+      "..:route",
+      "//ui/webui/resources/cr_elements/chromeos/network:cr_onc_types",
+    ]
+    externs_list = [ "$externs_path/networking_private.js" ]
+    extra_sources = [ "$interfaces_path/networking_private_interface.js" ]
+  }
 }
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win_unittest.cc b/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win_unittest.cc
index 3fbcb03a..6166e88 100644
--- a/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win_unittest.cc
+++ b/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win_unittest.cc
@@ -7,6 +7,7 @@
 #include <map>
 #include <string>
 
+#include "base/metrics/field_trial.h"
 #include "base/win/windows_version.h"
 #include "components/variations/variations_params_manager.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/chrome/browser/safe_browsing/settings_reset_prompt/settings_reset_prompt_config_unittest.cc b/chrome/browser/safe_browsing/settings_reset_prompt/settings_reset_prompt_config_unittest.cc
index 6e25b28..c5bf5083 100644
--- a/chrome/browser/safe_browsing/settings_reset_prompt/settings_reset_prompt_config_unittest.cc
+++ b/chrome/browser/safe_browsing/settings_reset_prompt/settings_reset_prompt_config_unittest.cc
@@ -10,14 +10,12 @@
 #include "base/strings/stringprintf.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
-#include "components/variations/variations_params_manager.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
 namespace safe_browsing {
 
-const char kTrialName[] = "trial";
 // A SHA256 hash for "mydomain.com".
 const char kDomainHash[] =
     "0a79eaf6adb7b1e60d3fa548aa63105f525a00448efbb59ee965b9351a90ac31";
@@ -30,16 +28,14 @@
 // start off with all features disabled.
 class SettingsResetPromptConfigTest : public ::testing::Test {
  protected:
-  typedef std::map<std::string, std::string> Parameters;
+  typedef base::FieldTrialParams Parameters;
 
   // Sets the settings reset prompt feature parameters, which has the
   // side-effect of also enabling the feature.
   void SetFeatureParams(const Parameters& params) {
-    static std::set<std::string> features = {kSettingsResetPrompt.name};
-
-    params_manager_.ClearAllVariationParams();
-    params_manager_.SetVariationParamsWithFeatureAssociations(kTrialName,
-                                                              params, features);
+    scoped_feature_list_.Reset();
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        kSettingsResetPrompt, params);
   }
 
   Parameters GetDefaultFeatureParams() {
@@ -50,7 +46,6 @@
         {"time_between_prompts_seconds", "3600"}};
   }
 
-  variations::testing::VariationParamsManager params_manager_;
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 376c5c6..0f4a3d7 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1123,6 +1123,7 @@
       "toolbar/recent_tabs_sub_menu_model.cc",
       "toolbar/recent_tabs_sub_menu_model.h",
       "toolbar/toolbar_action_view_controller.h",
+      "toolbar/toolbar_action_view_delegate.cc",
       "toolbar/toolbar_action_view_delegate.h",
       "toolbar/toolbar_actions_bar.cc",
       "toolbar/toolbar_actions_bar.h",
diff --git a/chrome/browser/ui/ash/kiosk_next_shell_client_browsertest.cc b/chrome/browser/ui/ash/kiosk_next_shell_client_browsertest.cc
index 866beffc..4b91f7b 100644
--- a/chrome/browser/ui/ash/kiosk_next_shell_client_browsertest.cc
+++ b/chrome/browser/ui/ash/kiosk_next_shell_client_browsertest.cc
@@ -37,10 +37,8 @@
 
 class KioskNextShellClientTest : public OobeBaseTest {
  public:
-  // OobeBaseTest:
-  void SetUp() override {
+  KioskNextShellClientTest() : OobeBaseTest() {
     feature_list_.InitAndEnableFeature(ash::features::kKioskNextShell);
-    OobeBaseTest::SetUp();
   }
 
   void Login(const std::string& username) {
@@ -143,11 +141,8 @@
 // to the KioskNextShellLaunch test.
 class KioskNextShellClientMashDisabledTest : public KioskNextShellClientTest {
  public:
-  // KioskNextShellClientTest:
-  void SetUp() override {
+  KioskNextShellClientMashDisabledTest() : KioskNextShellClientTest() {
     feature_list_.InitAndDisableFeature(features::kSingleProcessMash);
-
-    KioskNextShellClientTest::SetUp();
   }
 
  private:
diff --git a/chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.cc b/chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.cc
index b7ffef0..43f2426 100644
--- a/chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.cc
+++ b/chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.cc
@@ -410,7 +410,7 @@
 void BrowserShortcutLauncherItemController::OnBrowserClosing(Browser* browser) {
   DCHECK(browser);
   // Reset pointers to the closed browser, but leave menu indices intact.
-  for (auto it : app_menu_items_) {
+  for (auto& it : app_menu_items_) {
     if (it.first == browser)
       it.first = nullptr;
   }
diff --git a/chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller_browsertest.cc b/chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller_browsertest.cc
new file mode 100644
index 0000000..c178158
--- /dev/null
+++ b/chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller_browsertest.cc
@@ -0,0 +1,58 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/ash/launcher/browser_shortcut_launcher_item_controller.h"
+
+#include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+
+using BrowserShortcutLauncherItemControllerTest = InProcessBrowserTest;
+IN_PROC_BROWSER_TEST_F(BrowserShortcutLauncherItemControllerTest, AppMenu) {
+  BrowserShortcutLauncherItemController* controller =
+      ChromeLauncherController::instance()
+          ->GetBrowserShortcutLauncherItemController();
+  ASSERT_TRUE(controller);
+
+  BrowserList* browser_list = BrowserList::GetInstance();
+  EXPECT_EQ(browser_list->size(), 1U);
+
+  Browser* browser1 = CreateBrowser(browser()->profile());
+  EXPECT_EQ(browser_list->size(), 2U);
+
+  ui_test_utils::NavigateToURL(browser(),
+                               GURL("data:text/html,<title>0</title>"));
+  ui_test_utils::NavigateToURL(browser1,
+                               GURL("data:text/html,<title>1</title>"));
+  auto items = controller->GetAppMenuItems(ui::EF_NONE);
+  ASSERT_EQ(items.size(), 2U);
+  EXPECT_EQ(base::ASCIIToUTF16("0"), items[0].first);
+  EXPECT_EQ(base::ASCIIToUTF16("1"), items[1].first);
+
+  // Close the window and wait for all asynchronous window teardown.
+  browser1->window()->Close();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(browser_list->size(), 1U);
+  // Selecting the app menu item for the closed browser window should not crash.
+  controller->ExecuteCommand(/*from_context_menu=*/false, /*command_id=*/1,
+                             ui::EF_NONE, display::kInvalidDisplayId);
+
+  // Create and close a window, but don't allow asynchronous teardown to occur.
+  browser1 = CreateBrowser(browser()->profile());
+  EXPECT_EQ(browser_list->size(), 2U);
+  browser1->window()->Close();
+  EXPECT_EQ(browser_list->size(), 2U);
+  // The app menu should not list the browser window while it is closing.
+  items = controller->GetAppMenuItems(ui::EF_NONE);
+  EXPECT_EQ(items.size(), 1U);
+  // Now, allow the asynchronous teardown to occur.
+  EXPECT_EQ(browser_list->size(), 2U);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(browser_list->size(), 1U);
+}
diff --git a/chrome/browser/ui/browser_ui_prefs.cc b/chrome/browser/ui/browser_ui_prefs.cc
index 699daff..6d26c7f 100644
--- a/chrome/browser/ui/browser_ui_prefs.cc
+++ b/chrome/browser/ui/browser_ui_prefs.cc
@@ -124,4 +124,5 @@
   registry->RegisterBooleanPref(prefs::kEnterpriseHardwarePlatformAPIEnabled,
                                 false);
   registry->RegisterBooleanPref(prefs::kAllowPopupsDuringPageUnload, false);
+  registry->RegisterBooleanPref(prefs::kUserFeedbackAllowed, true);
 }
diff --git a/chrome/browser/ui/browser_window.h b/chrome/browser/ui/browser_window.h
index 3acdf53..f0c296f 100644
--- a/chrome/browser/ui/browser_window.h
+++ b/chrome/browser/ui/browser_window.h
@@ -35,6 +35,7 @@
 class Browser;
 class DownloadShelf;
 class ExclusiveAccessContext;
+class ExtensionsContainer;
 class FindBar;
 class GURL;
 class LocationBar;
@@ -276,8 +277,13 @@
   virtual void FocusToolbar() = 0;
 
   // Returns the ToolbarActionsBar associated with the window, if any.
+  // TODO(pbos): Replace usages of |GetToolbarActionsBar| with
+  // |GetExtensionsContainer|.
   virtual ToolbarActionsBar* GetToolbarActionsBar() = 0;
 
+  // Returns the ExtensionsContainer associated with the window, if any.
+  virtual ExtensionsContainer* GetExtensionsContainer() = 0;
+
   // Called from toolbar subviews during their show/hide animations.
   virtual void ToolbarSizeChanged(bool is_animating) = 0;
 
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller.cc b/chrome/browser/ui/extensions/extension_action_view_controller.cc
index 2d664ea..085f424 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller.cc
+++ b/chrome/browser/ui/extensions/extension_action_view_controller.cc
@@ -216,7 +216,8 @@
 
   // Reconstruct the menu every time because the menu's contents are dynamic.
   context_menu_model_.reset(new extensions::ExtensionContextMenuModel(
-      extension(), browser_, visibility, this));
+      extension(), browser_, visibility, this,
+      view_delegate_->CanShowIconInToolbar()));
   return context_menu_model_.get();
 }
 
diff --git a/chrome/browser/ui/extensions/extensions_container.h b/chrome/browser/ui/extensions/extensions_container.h
index 1f54184e0..46d643e 100644
--- a/chrome/browser/ui/extensions/extensions_container.h
+++ b/chrome/browser/ui/extensions/extensions_container.h
@@ -10,6 +10,7 @@
 #include "base/callback_forward.h"
 
 class ToolbarActionViewController;
+class ToolbarActionsBarBubbleDelegate;
 
 // An interface for containers in the toolbar that host extensions.
 class ExtensionsContainer {
@@ -48,6 +49,14 @@
   virtual void PopOutAction(ToolbarActionViewController* action,
                             bool is_sticky,
                             const base::Closure& closure) = 0;
+
+  // Displays the given |bubble| once the toolbar is no longer animating.
+  virtual void ShowToolbarActionBubble(
+      std::unique_ptr<ToolbarActionsBarBubbleDelegate> bubble) = 0;
+
+  // Same as above, but uses PostTask() in all cases.
+  virtual void ShowToolbarActionBubbleAsync(
+      std::unique_ptr<ToolbarActionsBarBubbleDelegate> bubble) = 0;
 };
 
 #endif  // CHROME_BROWSER_UI_EXTENSIONS_EXTENSIONS_CONTAINER_H_
diff --git a/chrome/browser/ui/extensions/hosted_app_browsertest.cc b/chrome/browser/ui/extensions/hosted_app_browsertest.cc
index 1a8d194..ac96d4b 100644
--- a/chrome/browser/ui/extensions/hosted_app_browsertest.cc
+++ b/chrome/browser/ui/extensions/hosted_app_browsertest.cc
@@ -286,15 +286,16 @@
   HostedAppTest()
       : app_browser_(nullptr),
         app_(nullptr),
-        https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+        https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
+    scoped_feature_list_.InitWithFeatures(
+        {}, {predictors::kSpeculativePreconnectFeature});
+  }
   ~HostedAppTest() override {}
 
   void SetUp() override {
     https_server_.AddDefaultHandlers(GetChromeTestDataDir());
 
     app_type_ = GetParam();
-    scoped_feature_list_.InitWithFeatures(
-        {}, {predictors::kSpeculativePreconnectFeature});
 
     extensions::ExtensionBrowserTest::SetUp();
   }
diff --git a/chrome/browser/ui/page_info/page_info_unittest.cc b/chrome/browser/ui/page_info/page_info_unittest.cc
index 83ca105..545296d 100644
--- a/chrome/browser/ui/page_info/page_info_unittest.cc
+++ b/chrome/browser/ui/page_info/page_info_unittest.cc
@@ -18,6 +18,8 @@
 #include "build/build_config.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/infobars/mock_infobar_service.h"
+#include "chrome/browser/ssl/chrome_ssl_host_state_delegate.h"
+#include "chrome/browser/ssl/chrome_ssl_host_state_delegate_factory.h"
 #include "chrome/browser/ui/page_info/page_info_ui.h"
 #include "chrome/browser/usb/usb_chooser_context.h"
 #include "chrome/browser/usb/usb_chooser_context_factory.h"
@@ -931,9 +933,9 @@
 }
 #endif
 
-// Tests that metrics for the "Re-Enable Warnings" button on PageInfo are being
-// logged correctly.
-TEST_F(PageInfoTest, ReEnableWarningsMetrics) {
+// Tests that "Re-Enable Warnings" button on PageInfo both removes certificate
+// exceptions and logs metrics correctly.
+TEST_F(PageInfoTest, ReEnableWarnings) {
   struct TestCase {
     const std::string url;
     const bool button_visible;
@@ -949,25 +951,22 @@
       "interstitial.ssl.did_user_revoke_decisions2";
   for (const auto& test : kTestCases) {
     base::HistogramTester histograms;
+    ChromeSSLHostStateDelegate* ssl_state =
+        ChromeSSLHostStateDelegateFactory::GetForProfile(profile());
+    const std::string host = GURL(test.url).host();
+
+    ssl_state->RevokeUserAllowExceptionsHard(host);
     ResetMockUI();
     SetURL(test.url);
     if (test.button_visible) {
       // In the case where the button should be visible, add an exception to
       // the profile settings for the site (since the exception is what
       // will make the button visible).
-      HostContentSettingsMap* content_settings =
-          HostContentSettingsMapFactory::GetForProfile(profile());
-      std::unique_ptr<base::DictionaryValue> dict =
-          std::unique_ptr<base::DictionaryValue>(new base::DictionaryValue());
-      dict->SetKey(
-          "testkey",
-          base::Value(content::SSLHostStateDelegate::CertJudgment::ALLOWED));
-      content_settings->SetWebsiteSettingDefaultScope(
-          url(), GURL(), CONTENT_SETTINGS_TYPE_SSL_CERT_DECISIONS,
-          std::string(), std::move(dict));
+      ssl_state->AllowCert(host, *cert(), net::ERR_CERT_DATE_INVALID);
       page_info();
       if (test.button_clicked) {
         page_info()->OnRevokeSSLErrorBypassButtonPressed();
+        EXPECT_FALSE(ssl_state->HasAllowException(host));
         ClearPageInfo();
         histograms.ExpectTotalCount(kGenericHistogram, 1);
         histograms.ExpectBucketCount(
@@ -977,6 +976,7 @@
             1);
       } else {  // Case where button is visible but not clicked.
         ClearPageInfo();
+        EXPECT_TRUE(ssl_state->HasAllowException(host));
         histograms.ExpectTotalCount(kGenericHistogram, 1);
         histograms.ExpectBucketCount(
             kGenericHistogram,
@@ -987,6 +987,7 @@
     } else {
       page_info();
       ClearPageInfo();
+      EXPECT_FALSE(ssl_state->HasAllowException(host));
       // Button is not visible, so check histogram is empty after opening and
       // closing page info.
       histograms.ExpectTotalCount(kGenericHistogram, 0);
diff --git a/chrome/browser/ui/toolbar/app_menu_model.cc b/chrome/browser/ui/toolbar/app_menu_model.cc
index a8864bc..a111149a 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model.cc
@@ -178,7 +178,8 @@
       SetIcon(GetIndexOfCommandId(IDC_HELP_PAGE_VIA_MENU),
               rb.GetNativeImageNamed(IDR_HELP_MENU));
     }
-    AddItemWithStringId(IDC_FEEDBACK, IDS_FEEDBACK);
+    if (browser->profile()->GetPrefs()->GetBoolean(prefs::kUserFeedbackAllowed))
+      AddItemWithStringId(IDC_FEEDBACK, IDS_FEEDBACK);
   }
 
   DISALLOW_COPY_AND_ASSIGN(HelpMenuModel);
diff --git a/chrome/browser/ui/toolbar/media_router_contextual_menu.cc b/chrome/browser/ui/toolbar/media_router_contextual_menu.cc
index 46f5d650..89bc9fda 100644
--- a/chrome/browser/ui/toolbar/media_router_contextual_menu.cc
+++ b/chrome/browser/ui/toolbar/media_router_contextual_menu.cc
@@ -76,8 +76,12 @@
     menu_model_->AddCheckItemWithStringId(
         IDC_MEDIA_ROUTER_CLOUD_SERVICES_TOGGLE,
         IDS_MEDIA_ROUTER_CLOUD_SERVICES_TOGGLE);
-    menu_model_->AddItemWithStringId(IDC_MEDIA_ROUTER_REPORT_ISSUE,
-                                     IDS_MEDIA_ROUTER_REPORT_ISSUE);
+
+    if (browser->profile()->GetPrefs()->GetBoolean(
+            prefs::kUserFeedbackAllowed)) {
+      menu_model_->AddItemWithStringId(IDC_MEDIA_ROUTER_REPORT_ISSUE,
+                                       IDS_MEDIA_ROUTER_REPORT_ISSUE);
+    }
   }
 }
 
diff --git a/chrome/browser/ui/toolbar/toolbar_action_view_delegate.cc b/chrome/browser/ui/toolbar/toolbar_action_view_delegate.cc
new file mode 100644
index 0000000..35647a5
--- /dev/null
+++ b/chrome/browser/ui/toolbar/toolbar_action_view_delegate.cc
@@ -0,0 +1,9 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/toolbar/toolbar_action_view_delegate.h"
+
+bool ToolbarActionViewDelegate::CanShowIconInToolbar() const {
+  return true;
+}
diff --git a/chrome/browser/ui/toolbar/toolbar_action_view_delegate.h b/chrome/browser/ui/toolbar/toolbar_action_view_delegate.h
index 74b99f59..f64cfef6 100644
--- a/chrome/browser/ui/toolbar/toolbar_action_view_delegate.h
+++ b/chrome/browser/ui/toolbar/toolbar_action_view_delegate.h
@@ -24,6 +24,9 @@
   // Returns true if a context menu is running.
   virtual bool IsMenuRunning() const = 0;
 
+  // Whether the container supports showing extensions outside of the menu.
+  virtual bool CanShowIconInToolbar() const;
+
   // Called when a popup is shown. If |by_user| is true, then this was through
   // a direct user action (as oppposed to, e.g., an API call).
   virtual void OnPopupShown(bool by_user) {}
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_bar.h b/chrome/browser/ui/toolbar/toolbar_actions_bar.h
index e46bf85..8ceb350c 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_bar.h
+++ b/chrome/browser/ui/toolbar/toolbar_actions_bar.h
@@ -193,13 +193,6 @@
   void AddObserver(ToolbarActionsBarObserver* observer);
   void RemoveObserver(ToolbarActionsBarObserver* observer);
 
-  // Displays the given |bubble| once the toolbar is no longer animating.
-  void ShowToolbarActionBubble(
-      std::unique_ptr<ToolbarActionsBarBubbleDelegate> bubble);
-  // Same as above, but uses PostTask() in all cases.
-  void ShowToolbarActionBubbleAsync(
-      std::unique_ptr<ToolbarActionsBarBubbleDelegate> bubble);
-
   // Returns the underlying toolbar actions, but does not order them. Primarily
   // for use in testing.
   const ToolbarActions& toolbar_actions_unordered() const {
@@ -248,6 +241,10 @@
   void PopOutAction(ToolbarActionViewController* action,
                     bool is_sticky,
                     const base::Closure& closure) override;
+  void ShowToolbarActionBubble(
+      std::unique_ptr<ToolbarActionsBarBubbleDelegate> bubble) override;
+  void ShowToolbarActionBubbleAsync(
+      std::unique_ptr<ToolbarActionsBarBubbleDelegate> bubble) override;
 
  private:
   // Returns the insets by which the icon area bounds (See GetIconAreaRect())
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_view_browsertest.cc b/chrome/browser/ui/views/extensions/extensions_menu_view_browsertest.cc
index 64368f8..50c92f8 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_view_browsertest.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_view_browsertest.cc
@@ -9,17 +9,24 @@
 #include "base/path_service.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/extensions/chrome_test_extension_loader.h"
+#include "chrome/browser/extensions/extension_action_runner.h"
+#include "chrome/browser/extensions/scripting_permissions_modifier.h"
 #include "chrome/browser/ui/test/test_browser_dialog.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/extensions/extensions_menu_button.h"
 #include "chrome/browser/ui/views/extensions/extensions_toolbar_button.h"
 #include "chrome/browser/ui/views/extensions/extensions_toolbar_container.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/webui_url_constants.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/dns/mock_host_resolver.h"
 #include "ui/views/test/button_test_api.h"
 #include "ui/views/test/widget_test.h"
+#include "ui/views/window/dialog_client_view.h"
 
 class ExtensionsMenuViewBrowserTest : public DialogBrowserTest {
  protected:
@@ -36,6 +43,11 @@
     DialogBrowserTest::SetUp();
   }
 
+  void SetUpOnMainThread() override {
+    DialogBrowserTest::SetUpOnMainThread();
+    host_resolver()->AddRule("*", "127.0.0.1");
+  }
+
   void ShowUi(const std::string& name) override {
     ui::MouseEvent click_event(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
                                base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, 0);
@@ -130,7 +142,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(ExtensionsMenuViewBrowserTest,
-                       TriggeringExtensionClosesMenu) {
+                       ActivationWithReloadNeeded_Accept) {
   LoadTestExtension("extensions/trigger_actions/browser_action");
   ShowUi("");
   VerifyUi();
@@ -186,5 +198,68 @@
       browser()->tab_strip_model()->GetActiveWebContents()->GetVisibleURL());
 }
 
+class ActivateWithReloadExtensionsMenuBrowserTest
+    : public ExtensionsMenuViewBrowserTest,
+      public ::testing::WithParamInterface<bool> {};
+
+IN_PROC_BROWSER_TEST_P(ActivateWithReloadExtensionsMenuBrowserTest,
+                       ActivateWithReload) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  LoadTestExtension("extensions/blocked_actions/content_scripts");
+  auto extension = extensions_.back();
+  extensions::ScriptingPermissionsModifier modifier(browser()->profile(),
+                                                    extension);
+  modifier.SetWithholdHostPermissions(true);
+
+  ui_test_utils::NavigateToURL(
+      browser(), embedded_test_server()->GetURL("example.com", "/empty.html"));
+
+  ShowUi("");
+  VerifyUi();
+
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+
+  extensions::ExtensionActionRunner* action_runner =
+      extensions::ExtensionActionRunner::GetForWebContents(web_contents);
+
+  EXPECT_TRUE(action_runner->WantsToRun(extension.get()));
+
+  TriggerSingleExtensionButton();
+
+  auto* const action_bubble = BrowserView::GetBrowserViewForBrowser(browser())
+                                  ->toolbar()
+                                  ->extensions_container()
+                                  ->action_bubble_public_for_testing();
+  ASSERT_TRUE(action_bubble);
+
+  views::DialogClientView* const dialog_client_view =
+      action_bubble->GetDialogClientView();
+
+  const bool accept_reload_dialog = GetParam();
+  if (accept_reload_dialog) {
+    content::TestNavigationObserver observer(web_contents);
+    dialog_client_view->AcceptWindow();
+    EXPECT_TRUE(web_contents->IsLoading());
+    // Wait for reload to finish.
+    observer.WaitForNavigationFinished();
+    EXPECT_TRUE(observer.last_navigation_succeeded());
+    // After reload the extension should be allowed to run.
+    EXPECT_FALSE(action_runner->WantsToRun(extension.get()));
+  } else {
+    dialog_client_view->CancelWindow();
+    EXPECT_FALSE(web_contents->IsLoading());
+    EXPECT_TRUE(action_runner->WantsToRun(extension.get()));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(AcceptDialog,
+                         ActivateWithReloadExtensionsMenuBrowserTest,
+                         testing::Values(true));
+
+INSTANTIATE_TEST_SUITE_P(CancelDialog,
+                         ActivateWithReloadExtensionsMenuBrowserTest,
+                         testing::Values(false));
+
 // TODO(pbos): Add test coverage that makes sure removing popped-out extensions
 // properly disposes of the popup.
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
index b8676d1..d963686 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
@@ -9,6 +9,8 @@
 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
 #include "chrome/browser/ui/views/extensions/extensions_menu_view.h"
 #include "chrome/browser/ui/views/extensions/extensions_toolbar_button.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views.h"
+#include "ui/views/widget/widget_observer.h"
 
 ExtensionsToolbarContainer::ExtensionsToolbarContainer(Browser* browser)
     : ToolbarIconContainerView(/*uses_highlight=*/true),
@@ -21,7 +23,13 @@
   CreateActions();
 }
 
-ExtensionsToolbarContainer::~ExtensionsToolbarContainer() = default;
+ExtensionsToolbarContainer::~ExtensionsToolbarContainer() {
+  if (active_bubble_)
+    active_bubble_->GetWidget()->Close();
+  // We should synchronously receive the OnWidgetClosing() event, so we should
+  // always have cleared the active bubble by now.
+  DCHECK(!active_bubble_);
+}
 
 void ExtensionsToolbarContainer::UpdateAllIcons() {
   extensions_button_->UpdateIcon();
@@ -44,7 +52,9 @@
 bool ExtensionsToolbarContainer::IsActionVisibleOnToolbar(
     const ToolbarActionViewController* action) const {
   return model_->IsActionPinned(action->GetId()) ||
-         action == popped_out_action_;
+         action == popped_out_action_ ||
+         (active_bubble_ &&
+          action->GetId() == active_bubble_->GetAnchorActionId());
 }
 
 void ExtensionsToolbarContainer::UndoPopOut() {
@@ -92,6 +102,34 @@
   closure.Run();
 }
 
+void ExtensionsToolbarContainer::ShowToolbarActionBubble(
+    std::unique_ptr<ToolbarActionsBarBubbleDelegate> controller) {
+  // TODO(pbos): Make sure we finish animations before showing the bubble.
+
+  auto iter = icons_.find(controller->GetAnchorActionId());
+
+  views::View* const anchor_view = iter != icons_.end()
+                                       ? static_cast<View*>(iter->second.get())
+                                       : extensions_button_;
+
+  anchor_view->SetVisible(true);
+
+  active_bubble_ = new ToolbarActionsBarBubbleViews(
+      anchor_view, gfx::Point(), anchor_view != extensions_button_,
+      std::move(controller));
+  views::BubbleDialogDelegateView::CreateBubble(active_bubble_)
+      ->AddObserver(this);
+  active_bubble_->Show();
+}
+
+void ExtensionsToolbarContainer::ShowToolbarActionBubbleAsync(
+    std::unique_ptr<ToolbarActionsBarBubbleDelegate> bubble) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&ExtensionsToolbarContainer::ShowToolbarActionBubble,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(bubble)));
+}
+
 void ExtensionsToolbarContainer::OnToolbarActionAdded(
     const ToolbarActionsModel::ActionId& action_id,
     int index) {
@@ -219,3 +257,27 @@
   // TODO(pbos): Implement
   return false;
 }
+
+void ExtensionsToolbarContainer::OnWidgetClosing(views::Widget* widget) {
+  ClearActiveBubble(widget);
+}
+
+void ExtensionsToolbarContainer::OnWidgetDestroying(views::Widget* widget) {
+  ClearActiveBubble(widget);
+}
+
+void ExtensionsToolbarContainer::ClearActiveBubble(views::Widget* widget) {
+  DCHECK(active_bubble_);
+  DCHECK_EQ(active_bubble_->GetWidget(), widget);
+  ToolbarActionViewController* const action =
+      GetActionForId(active_bubble_->GetAnchorActionId());
+  // TODO(pbos): Note that this crashes if a bubble anchors to the menu and not
+  // to an extension that gets popped out. This should be fixed, but a test
+  // should first be added to make sure that it's covered.
+  CHECK(action);
+  active_bubble_ = nullptr;
+  widget->RemoveObserver(this);
+  // Note that we only hide this view if it's not visible for other reasons
+  // than displaying the bubble.
+  icons_[action->GetId()]->SetVisible(IsActionVisibleOnToolbar(action));
+}
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.h b/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
index 919e980..3998230 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
@@ -9,6 +9,7 @@
 #include <memory>
 #include <vector>
 
+#include "base/optional.h"
 #include "chrome/browser/ui/extensions/extensions_container.h"
 #include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_action_view.h"
@@ -17,6 +18,7 @@
 class Browser;
 class ExtensionsToolbarButton;
 class ToolbarActionViewController;
+class ToolbarActionsBarBubbleViews;
 
 // Container for extensions shown in the toolbar. These include pinned
 // extensions and extensions that are 'popped out' transitively to show dialogs
@@ -29,7 +31,8 @@
 class ExtensionsToolbarContainer : public ToolbarIconContainerView,
                                    public ExtensionsContainer,
                                    public ToolbarActionsModel::Observer,
-                                   public ToolbarActionView::Delegate {
+                                   public ToolbarActionView::Delegate,
+                                   public views::WidgetObserver {
  public:
   explicit ExtensionsToolbarContainer(Browser* browser);
   ~ExtensionsToolbarContainer() override;
@@ -38,6 +41,10 @@
     return extensions_button_;
   }
 
+  ToolbarActionsBarBubbleViews* action_bubble_public_for_testing() {
+    return active_bubble_;
+  }
+
   // ToolbarIconContainerView:
   void UpdateAllIcons() override;
 
@@ -54,6 +61,9 @@
   // popped out actions, extensions button).
   void ReorderViews();
 
+  // Clears the |active_bubble_|, and unregisters the container as an observer.
+  void ClearActiveBubble(views::Widget* widget);
+
   // ExtensionsContainer:
   ToolbarActionViewController* GetActionForId(
       const std::string& action_id) override;
@@ -67,6 +77,10 @@
   void PopOutAction(ToolbarActionViewController* action,
                     bool is_sticky,
                     const base::Closure& closure) override;
+  void ShowToolbarActionBubble(
+      std::unique_ptr<ToolbarActionsBarBubbleDelegate> bubble) override;
+  void ShowToolbarActionBubbleAsync(
+      std::unique_ptr<ToolbarActionsBarBubbleDelegate> bubble) override;
 
   // ToolbarActionsModel::Observer:
   void OnToolbarActionAdded(const ToolbarActionsModel::ActionId& action_id,
@@ -97,6 +111,10 @@
                            const gfx::Point& press_pt,
                            const gfx::Point& p) override;
 
+  // views::WidgetObserver:
+  void OnWidgetClosing(views::Widget* widget) override;
+  void OnWidgetDestroying(views::Widget* widget) override;
+
   Browser* const browser_;
   ToolbarActionsModel* const model_;
   ScopedObserver<ToolbarActionsModel, ToolbarActionsModel::Observer>
@@ -116,6 +134,11 @@
   // The action that triggered the current popup, if any.
   ToolbarActionViewController* popup_owner_ = nullptr;
 
+  // The extension bubble that is actively showing, if any.
+  ToolbarActionsBarBubbleViews* active_bubble_ = nullptr;
+
+  base::WeakPtrFactory<ExtensionsToolbarContainer> weak_ptr_factory_{this};
+
   DISALLOW_COPY_AND_ASSIGN(ExtensionsToolbarContainer);
 };
 
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index c90d55c..77939df 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -83,6 +83,7 @@
 #include "chrome/browser/ui/views/download/download_shelf_view.h"
 #include "chrome/browser/ui/views/exclusive_access_bubble_views.h"
 #include "chrome/browser/ui/views/extensions/extension_keybinding_registry_views.h"
+#include "chrome/browser/ui/views/extensions/extensions_toolbar_container.h"
 #include "chrome/browser/ui/views/find_bar_host.h"
 #include "chrome/browser/ui/views/frame/app_menu_button.h"
 #include "chrome/browser/ui/views/frame/browser_view_layout.h"
@@ -1167,6 +1168,12 @@
   return container ? container->toolbar_actions_bar() : nullptr;
 }
 
+ExtensionsContainer* BrowserView::GetExtensionsContainer() {
+  if (toolbar_ && toolbar_->extensions_container())
+    return toolbar_->extensions_container();
+  return GetToolbarActionsBar();
+}
+
 void BrowserView::ToolbarSizeChanged(bool is_animating) {
   if (is_animating)
     contents_web_view_->SetFastResize(true);
diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h
index fcfaa4fa..b2ddd63 100644
--- a/chrome/browser/ui/views/frame/browser_view.h
+++ b/chrome/browser/ui/views/frame/browser_view.h
@@ -347,6 +347,7 @@
   void ResetToolbarTabState(content::WebContents* contents) override;
   void FocusToolbar() override;
   ToolbarActionsBar* GetToolbarActionsBar() override;
+  ExtensionsContainer* GetExtensionsContainer() override;
   void ToolbarSizeChanged(bool is_animating) override;
   void TabDraggingStatusChanged(bool is_dragging) override;
   void FocusAppMenu() override;
diff --git a/chrome/browser/ui/views/frame/hosted_app_button_container.cc b/chrome/browser/ui/views/frame/hosted_app_button_container.cc
index 86b16a7..a997957 100644
--- a/chrome/browser/ui/views/frame/hosted_app_button_container.cc
+++ b/chrome/browser/ui/views/frame/hosted_app_button_container.cc
@@ -309,6 +309,10 @@
   return base::Optional<int>();
 }
 
+bool HostedAppButtonContainer::CanShowIconInToolbar() const {
+  return false;
+}
+
 std::unique_ptr<ToolbarActionsBar>
 HostedAppButtonContainer::CreateToolbarActionsBar(
     ToolbarActionsBarDelegate* delegate,
diff --git a/chrome/browser/ui/views/frame/hosted_app_button_container.h b/chrome/browser/ui/views/frame/hosted_app_button_container.h
index 90f18cb..f499ac9c 100644
--- a/chrome/browser/ui/views/frame/hosted_app_button_container.h
+++ b/chrome/browser/ui/views/frame/hosted_app_button_container.h
@@ -91,6 +91,7 @@
   // BrowserActionsContainer::Delegate:
   views::LabelButton* GetOverflowReferenceView() override;
   base::Optional<int> GetMaxBrowserActionsWidth() const override;
+  bool CanShowIconInToolbar() const override;
   std::unique_ptr<ToolbarActionsBar> CreateToolbarActionsBar(
       ToolbarActionsBarDelegate* delegate,
       Browser* browser,
diff --git a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_device_button.cc b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_device_button.cc
index 029b90d..32636250 100644
--- a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_device_button.cc
+++ b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_device_button.cc
@@ -31,6 +31,13 @@
   TOTAL_COUNT = 2  // Add new types above this line.
 };
 
+SkColor GetColorfromTheme() {
+  const ui::NativeTheme* native_theme =
+      ui::NativeTheme::GetInstanceForNativeUi();
+  return native_theme->GetSystemColor(
+      ui::NativeTheme::kColorId_DefaultIconColor);
+}
+
 gfx::ImageSkia CreateDeviceIcon(DeviceIconType icon_type) {
   const gfx::VectorIcon* vector_icon;
   switch (icon_type) {
@@ -43,8 +50,9 @@
     default:
       vector_icon = &kSendTabToSelfIcon;
   }
-  SkColor icon_color = ui::NativeTheme::kColorId_DefaultIconColor;
-  return gfx::CreateVectorIcon(*vector_icon, kPrimaryIconSize, icon_color);
+
+  return gfx::CreateVectorIcon(*vector_icon, kPrimaryIconSize,
+                               GetColorfromTheme());
 }
 
 std::unique_ptr<views::ImageView> CreateIconView(
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 38b857f8..5beddf2b 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -2237,8 +2237,8 @@
         &tabs_, animator_->GetCurrentTabStates(), available_width,
         controller_->GetActiveIndex());
 
-    new_tab_button_bounds_.set_origin(
-        gfx::Point(trailing_x + TabToNewTabButtonSpacing(), 0));
+    new_tab_button_bounds_.set_origin(gfx::Point(
+        std::min(available_width, trailing_x) + TabToNewTabButtonSpacing(), 0));
   }
   new_tab_button_->SetBoundsRect(new_tab_button_bounds_);
 
diff --git a/chrome/browser/ui/views/tabs/window_finder_chromeos.cc b/chrome/browser/ui/views/tabs/window_finder_chromeos.cc
index 41b6284..0745fb8 100644
--- a/chrome/browser/ui/views/tabs/window_finder_chromeos.cc
+++ b/chrome/browser/ui/views/tabs/window_finder_chromeos.cc
@@ -9,5 +9,5 @@
 gfx::NativeWindow WindowFinder::GetLocalProcessWindowAtPoint(
     const gfx::Point& screen_point,
     const std::set<gfx::NativeWindow>& ignore) {
-  return ash::GetTopmostWindowAtPoint(screen_point, ignore, nullptr);
+  return ash::GetTopmostWindowAtPoint(screen_point, ignore);
 }
diff --git a/chrome/browser/ui/views/toolbar/browser_actions_container.cc b/chrome/browser/ui/views/toolbar/browser_actions_container.cc
index 602247c..d6cb4c61 100644
--- a/chrome/browser/ui/views/toolbar/browser_actions_container.cc
+++ b/chrome/browser/ui/views/toolbar/browser_actions_container.cc
@@ -49,6 +49,13 @@
 #include "ui/views/widget/widget.h"
 
 ////////////////////////////////////////////////////////////////////////////////
+// BrowserActionsContainer::Delegate
+
+bool BrowserActionsContainer::Delegate::CanShowIconInToolbar() const {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // BrowserActionsContainer::DropPosition
 
 struct BrowserActionsContainer::DropPosition {
@@ -149,6 +156,10 @@
   return main_container_ != nullptr;
 }
 
+bool BrowserActionsContainer::CanShowIconInToolbar() const {
+  return delegate_->CanShowIconInToolbar();
+}
+
 void BrowserActionsContainer::OnToolbarActionViewDragDone() {
   toolbar_actions_bar_->OnDragEnded();
 }
diff --git a/chrome/browser/ui/views/toolbar/browser_actions_container.h b/chrome/browser/ui/views/toolbar/browser_actions_container.h
index 0258578..cba1af2 100644
--- a/chrome/browser/ui/views/toolbar/browser_actions_container.h
+++ b/chrome/browser/ui/views/toolbar/browser_actions_container.h
@@ -124,6 +124,9 @@
     // empty value means there is no maximum.
     virtual base::Optional<int> GetMaxBrowserActionsWidth() const = 0;
 
+    // Whether the container supports showing extensions outside of the menu.
+    virtual bool CanShowIconInToolbar() const;
+
     // Creates a ToolbarActionsBar for the BrowserActionsContainer to use.
     virtual std::unique_ptr<ToolbarActionsBar> CreateToolbarActionsBar(
         ToolbarActionsBarDelegate* delegate,
@@ -225,6 +228,7 @@
   // Overridden from ToolbarActionView::Delegate:
   content::WebContents* GetCurrentWebContents() override;
   bool ShownInsideMenu() const override;
+  bool CanShowIconInToolbar() const override;
   void OnToolbarActionViewDragDone() override;
   views::LabelButton* GetOverflowReferenceView() override;
   gfx::Size GetToolbarActionSize() override;
diff --git a/chrome/browser/ui/views/toolbar/toolbar_action_view.cc b/chrome/browser/ui/views/toolbar/toolbar_action_view.cc
index e14c087..73dabfe 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_action_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_action_view.cc
@@ -47,6 +47,13 @@
 }  // namespace
 
 ////////////////////////////////////////////////////////////////////////////////
+// ToolbarActionView::Delegate
+
+bool ToolbarActionView::Delegate::CanShowIconInToolbar() const {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // ToolbarActionView
 
 const char ToolbarActionView::kClassName[] = "ToolbarActionView";
@@ -298,6 +305,10 @@
   return menu_ != nullptr;
 }
 
+bool ToolbarActionView::CanShowIconInToolbar() const {
+  return delegate_->CanShowIconInToolbar();
+}
+
 void ToolbarActionView::OnPopupShown(bool by_user) {
   // If this was through direct user action, we press the menu button.
   if (by_user) {
diff --git a/chrome/browser/ui/views/toolbar/toolbar_action_view.h b/chrome/browser/ui/views/toolbar/toolbar_action_view.h
index 2b6db8a0..58d7491 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_action_view.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_action_view.h
@@ -41,6 +41,9 @@
     // Whether the container for this button is shown inside a menu.
     virtual bool ShownInsideMenu() const = 0;
 
+    // Whether the container supports showing extensions outside of the menu.
+    virtual bool CanShowIconInToolbar() const;
+
     // Notifies that a drag completed.
     virtual void OnToolbarActionViewDragDone() = 0;
 
@@ -115,6 +118,7 @@
   views::FocusManager* GetFocusManagerForAccelerator() override;
   views::Button* GetReferenceButtonForPopup() override;
   bool IsMenuRunning() const override;
+  bool CanShowIconInToolbar() const override;
   void OnPopupShown(bool by_user) override;
   void OnPopupClosed() override;
 
diff --git a/chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views.cc b/chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views.cc
index f6e7ff6..741dc25 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views.cc
@@ -54,6 +54,10 @@
   GetWidget()->Show();
 }
 
+std::string ToolbarActionsBarBubbleViews::GetAnchorActionId() {
+  return delegate_->GetAnchorActionId();
+}
+
 std::unique_ptr<views::View> ToolbarActionsBarBubbleViews::CreateExtraView() {
   std::unique_ptr<ToolbarActionsBarBubbleDelegate::ExtraViewInfo>
       extra_view_info = delegate_->GetExtraViewInfo();
diff --git a/chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views.h b/chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views.h
index 89cb1c8..1e105f7 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_actions_bar_bubble_views.h
@@ -32,6 +32,7 @@
   ~ToolbarActionsBarBubbleViews() override;
 
   void Show();
+  std::string GetAnchorActionId();
 
   const views::Label* body_text() const { return body_text_; }
   const views::Label* item_list() const { return item_list_; }
diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
index 71aabddc..0e60e35 100644
--- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc
@@ -59,7 +59,6 @@
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
 #include "chrome/browser/chromeos/system/system_clock.h"
-#include "chrome/browser/io_thread.h"
 #include "chrome/browser/lifetime/browser_shutdown.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_metrics.h"
diff --git a/chrome/browser/ui/webui/interventions_internals/interventions_internals_page_handler_unittest.cc b/chrome/browser/ui/webui/interventions_internals/interventions_internals_page_handler_unittest.cc
index 1743e638..b594b454 100644
--- a/chrome/browser/ui/webui/interventions_internals/interventions_internals_page_handler_unittest.cc
+++ b/chrome/browser/ui/webui/interventions_internals/interventions_internals_page_handler_unittest.cc
@@ -511,7 +511,6 @@
 }
 
 TEST_F(InterventionsInternalsPageHandlerTest, GetFlagsEctForceFieldtrialValue) {
-  base::FieldTrialList field_trial_list_(nullptr);
   const std::string trial_name = "NetworkQualityEstimator";
   const std::string group_name = "Enabled";
   const std::string expected_ect = "Slow-2G";
diff --git a/chrome/browser/ui/webui/net_export_ui.cc b/chrome/browser/ui/webui/net_export_ui.cc
index 1b9b3d8..35f21ae 100644
--- a/chrome/browser/ui/webui/net_export_ui.cc
+++ b/chrome/browser/ui/webui/net_export_ui.cc
@@ -22,7 +22,6 @@
 #include "base/values.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/download/download_prefs.h"
-#include "chrome/browser/io_thread.h"
 #include "chrome/browser/net/net_export_helper.h"
 #include "chrome/browser/net/system_network_context_manager.h"
 #include "chrome/browser/platform_util.h"
diff --git a/chrome/browser/ui/webui/net_internals/net_internals_ui_browsertest.cc b/chrome/browser/ui/webui/net_internals/net_internals_ui_browsertest.cc
index 92f899d7..3a017464 100644
--- a/chrome/browser/ui/webui/net_internals/net_internals_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/net_internals/net_internals_ui_browsertest.cc
@@ -20,7 +20,6 @@
 #include "base/threading/thread_restrictions.h"
 #include "base/values.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/io_thread.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
diff --git a/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc b/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
index 60c15ce..c5914fac 100644
--- a/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
@@ -59,6 +59,8 @@
 
 namespace {
 
+using printing::PrinterQueryResult;
+
 // These values are written to logs.  New enum values can be added, but existing
 // enums must never be renumbered or deleted and reused.
 enum PpdSourceForHistogram { kUser = 0, kScs = 1, kPpdSourceMax };
@@ -106,7 +108,7 @@
   // Behavior for querying a non-IPP uri is undefined and disallowed.
   if (!IsIppUri(printer_uri) || !optional.has_value()) {
     PRINTER_LOG(ERROR) << "Printer uri is invalid: " << printer_uri;
-    callback.Run(false, "", "", "", {}, false);
+    callback.Run(PrinterQueryResult::UNKNOWN_FAILURE, "", "", "", {}, false);
     return;
   }
 
@@ -459,7 +461,8 @@
 
   if (printer_address.empty()) {
     // Run the failure callback.
-    OnAutoconfQueried(callback_id, false, "", "", "", {}, false);
+    OnAutoconfQueried(callback_id, PrinterQueryResult::UNKNOWN_FAILURE, "", "",
+                      "", {}, false);
     return;
   }
 
@@ -484,12 +487,13 @@
 
 void CupsPrintersHandler::OnAutoconfQueriedDiscovered(
     Printer printer,
-    bool success,
+    PrinterQueryResult result,
     const std::string& make,
     const std::string& model,
     const std::string& make_and_model,
     const std::vector<std::string>& document_formats,
     bool ipp_everywhere) {
+  const bool success = result == PrinterQueryResult::SUCCESS;
   RecordIppQuerySuccess(success);
 
   if (success) {
@@ -528,14 +532,23 @@
 
 void CupsPrintersHandler::OnAutoconfQueried(
     const std::string& callback_id,
-    bool success,
+    PrinterQueryResult result,
     const std::string& make,
     const std::string& model,
     const std::string& make_and_model,
     const std::vector<std::string>& document_formats,
     bool ipp_everywhere) {
+  const bool success = result == PrinterQueryResult::SUCCESS;
   RecordIppQuerySuccess(success);
 
+  if (result == PrinterQueryResult::UNREACHABLE) {
+    PRINTER_LOG(DEBUG) << "Could not reach printer";
+    base::DictionaryValue reject;
+    reject.SetString("message", "Unable to reach printer");
+    RejectJavascriptCallback(base::Value(callback_id), reject);
+    return;
+  }
+
   if (!success) {
     PRINTER_LOG(DEBUG) << "Could not query printer";
     base::DictionaryValue reject;
diff --git a/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.h b/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.h
index f790bfc..42c17f4 100644
--- a/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.h
+++ b/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.h
@@ -20,6 +20,7 @@
 #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
 #include "chromeos/printing/ppd_provider.h"
 #include "chromeos/printing/printer_configuration.h"
+#include "printing/backend/cups_jobs.h"
 #include "ui/shell_dialogs/select_file_dialog.h"
 
 namespace base {
@@ -79,7 +80,7 @@
   // Everywhere driver should be attempted. If |success| is false, the values of
   // |make|, |model|, |make_and_model|, and |ipp_everywhere| are not specified.
   void OnAutoconfQueried(const std::string& callback_id,
-                         bool success,
+                         printing::PrinterQueryResult result,
                          const std::string& make,
                          const std::string& model,
                          const std::string& make_and_model,
@@ -89,7 +90,7 @@
   // Handles the callback for HandleGetPrinterInfo for a discovered printer.
   void OnAutoconfQueriedDiscovered(
       Printer printer,
-      bool success,
+      printing::PrinterQueryResult result,
       const std::string& make,
       const std::string& model,
       const std::string& make_and_model,
diff --git a/chrome/browser/usb/usb_blocklist_unittest.cc b/chrome/browser/usb/usb_blocklist_unittest.cc
index e5e9665..bcea7606 100644
--- a/chrome/browser/usb/usb_blocklist_unittest.cc
+++ b/chrome/browser/usb/usb_blocklist_unittest.cc
@@ -3,6 +3,8 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/usb/usb_blocklist.h"
+
+#include "base/strings/string_piece.h"
 #include "components/variations/variations_params_manager.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/chrome/browser/vr/test/fake_xr_session_request_consent_manager.cc b/chrome/browser/vr/test/fake_xr_session_request_consent_manager.cc
index 6eac1495..4f8f769 100644
--- a/chrome/browser/vr/test/fake_xr_session_request_consent_manager.cc
+++ b/chrome/browser/vr/test/fake_xr_session_request_consent_manager.cc
@@ -49,6 +49,15 @@
       confirm_dialog->CloseDialog();
       break;
   }
+
+  // Allow the dialog to close cleanly.
+  base::RunLoop dialog_clean_close_loop(
+      base::RunLoop::Type::kNestableTasksAllowed);
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE, dialog_clean_close_loop.QuitWhenIdleClosure(),
+      kConsentDialogDisplayingTime);
+  dialog_clean_close_loop.Run();
+
   return confirm_dialog;
 }
 
diff --git a/chrome/browser/vr/test/xr_browser_test.cc b/chrome/browser/vr/test/xr_browser_test.cc
index 7f22461..54ddc20e 100644
--- a/chrome/browser/vr/test/xr_browser_test.cc
+++ b/chrome/browser/vr/test/xr_browser_test.cc
@@ -243,7 +243,7 @@
   }
 
   bool result;
-  DLOG(ERROR) << "Run JavaScript: " << js_expression;
+  DLOG(INFO) << "Run JavaScript: " << js_expression;
   EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
       web_contents,
       "window.domAutomationController.send(" + js_expression + ")", &result))
diff --git a/chrome/browser/vr/webxr_vr_consent_dialog_browser_test.cc b/chrome/browser/vr/webxr_vr_consent_dialog_browser_test.cc
index b3a1079..f1ee1c3 100644
--- a/chrome/browser/vr/webxr_vr_consent_dialog_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_consent_dialog_browser_test.cc
@@ -47,7 +47,7 @@
 
 IN_PROC_BROWSER_TEST_F(
     WebXrVrConsentDialogBrowserTest,
-    DISABLED_TestWebXrVrSucceedsWhenUserClicksConsentDialogAllowButton) {
+    TestWebXrVrSucceedsWhenUserClicksConsentDialogAllowButton) {
   SetupFakeConsentManager(
       FakeXRSessionRequestConsentManager::UserResponse::kClickAllowButton);
 
@@ -70,24 +70,24 @@
 
 IN_PROC_BROWSER_TEST_F(
     WebXrVrConsentDialogBrowserTest,
-    DISABLED_TestWebXrVrFailsWhenUserClicksConsentDialogCancelButton) {
+    TestWebXrVrFailsWhenUserClicksConsentDialogCancelButton) {
   SetupFakeConsentManager(
       FakeXRSessionRequestConsentManager::UserResponse::kClickCancelButton);
 
   LoadUrlAndAwaitInitialization(GetFileUrlForHtmlTestFile(
-      "webxr_test_presentation_promise_rejected_if_don_canceled"));
-  ExecuteStepAndWait("onImmersiveRequestWithDon()");
+      "webxr_test_presentation_promise_rejected_if_consent_not_granted"));
+  ExecuteStepAndWait("onImmersiveRequestWithConsent()");
   EndTest();
 }
 
 IN_PROC_BROWSER_TEST_F(WebXrVrConsentDialogBrowserTest,
-                       DISABLED_TestWebXrVrFailsWhenUserClosesConsentDialog) {
+                       TestWebXrVrFailsWhenUserClosesConsentDialog) {
   SetupFakeConsentManager(
       FakeXRSessionRequestConsentManager::UserResponse::kCloseDialog);
 
   LoadUrlAndAwaitInitialization(GetFileUrlForHtmlTestFile(
-      "webxr_test_presentation_promise_rejected_if_don_canceled"));
-  ExecuteStepAndWait("onImmersiveRequestWithDon()");
+      "webxr_test_presentation_promise_rejected_if_consent_not_granted"));
+  ExecuteStepAndWait("onImmersiveRequestWithConsent()");
   EndTest();
 }
 
diff --git a/chrome/browser/webauth_interactive_uitest.cc b/chrome/browser/webauth_interactive_uitest.cc
index 2320aca..821b33b86 100644
--- a/chrome/browser/webauth_interactive_uitest.cc
+++ b/chrome/browser/webauth_interactive_uitest.cc
@@ -133,8 +133,12 @@
   // tab/window, and have the user believe that they are interacting with that
   // trusted site.
   virtual_device_factory->mutable_state()->simulate_press_callback =
-      base::BindRepeating([](Browser* browser) { chrome::NewTab(browser); },
-                          browser());
+      base::BindRepeating(
+          [](Browser* browser, device::VirtualFidoDevice* device) {
+            chrome::NewTab(browser);
+            return true;
+          },
+          browser());
   ASSERT_TRUE(content::ExecuteScriptAndExtractString(initial_web_contents,
                                                      register_script, &result));
   EXPECT_THAT(result, ::testing::HasSubstr(kFocusErrorSubstring));
@@ -176,12 +180,13 @@
 
   // Requesting "direct" attestation will trigger a permissions prompt.
   virtual_device_factory->mutable_state()->simulate_press_callback =
-      base::BindLambdaForTesting([&]() {
+      base::BindLambdaForTesting([&](device::VirtualFidoDevice* device) {
         dialog_model_ =
             AuthenticatorRequestScheduler::GetRequestDelegateForTest(
                 initial_web_contents)
                 ->WeakDialogModelForTesting();
         dialog_model_->AddObserver(this);
+        return true;
       });
 
   const std::string get_assertion_with_attestation_script =
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index b32338b..a97bcd1 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -134,6 +134,10 @@
 const base::Feature kBundledConnectionHelpFeature{
     "BundledConnectionHelp", base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Enables the UI to configure caption settings.
+const base::Feature kCaptionSettings{"CaptionSettings",
+                                     base::FEATURE_DISABLED_BY_DEFAULT};
+
 #if !defined(OS_ANDROID)
 // Enables logging UKMs for background tab activity by TabActivityWatcher.
 const base::Feature kTabMetricsLogging{"TabMetricsLogging",
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 099548f..4c06713 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -84,6 +84,9 @@
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kBundledConnectionHelpFeature;
 
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kCaptionSettings;
+
 #if (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_MACOSX)
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kCertDualVerificationTrialFeature;
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 358090b..d93967a 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -126,6 +126,9 @@
 // are only restored on startup if kRestoreOnStartup is 4.
 const char kURLsToRestoreOnStartup[] = "session.startup_urls";
 
+// Boolean that is true when user feedback to Google is allowed.
+const char kUserFeedbackAllowed[] = "feedback_allowed";
+
 // Stores the email address associated with the google account of the custodian
 // of the supervised user, set when the supervised user is created.
 const char kSupervisedUserCustodianEmail[] = "profile.managed.custodian_email";
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index a44dedb..e344fca 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -59,6 +59,7 @@
 extern const char kSupervisedUserSharedSettings[];
 extern const char kSupervisedUserWhitelists[];
 extern const char kURLsToRestoreOnStartup[];
+extern const char kUserFeedbackAllowed[];
 
 #if BUILDFLAG(ENABLE_RLZ)
 extern const char kRlzPingDelaySeconds[];
diff --git a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
index 71b495b..413b525d 100644
--- a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
+++ b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
@@ -42,6 +42,10 @@
 #include "third_party/blink/public/web/web_widget.h"
 #include "ui/events/keycodes/keyboard_codes.h"
 
+#if defined(OS_WIN)
+#include "third_party/blink/public/web/win/web_font_rendering.h"
+#endif
+
 using autofill::FillingStatus;
 using autofill::FormTracker;
 using autofill::PasswordForm;
@@ -291,6 +295,13 @@
   void SetUp() override {
     ChromeRenderViewTest::SetUp();
 
+#if defined(OS_WIN)
+    // Autofill uses the system font to render suggestion previews. On Windows
+    // an extra step is required to ensure that the system font is configured.
+    blink::WebFontRendering::SetMenuFontMetrics(
+        base::ASCIIToUTF16("Arial").c_str(), 12);
+#endif
+
     // TODO(crbug/862989): Remove workaround preventing non-test classes to bind
     // fake_driver_ or fake_pw_client_.
     password_autofill_agent_->GetPasswordManagerDriver();
diff --git a/chrome/renderer/safe_browsing/threat_dom_details_browsertest.cc b/chrome/renderer/safe_browsing/threat_dom_details_browsertest.cc
index 5f3875af..c07409e 100644
--- a/chrome/renderer/safe_browsing/threat_dom_details_browsertest.cc
+++ b/chrome/renderer/safe_browsing/threat_dom_details_browsertest.cc
@@ -25,21 +25,12 @@
   std::map<std::string, std::string> feature_params;
   feature_params[std::string(safe_browsing::kTagAndAttributeParamName)] =
       "div,foo,div,baz,div,attr2,div,attr3,div,longattr4,div,attr5,div,attr6";
-  variations::AssociateVariationParams(
-      safe_browsing::kThreatDomDetailsTagAndAttributeFeature.name, "Group",
-      feature_params);
-  base::FieldTrial* trial = base::FieldTrialList::CreateFieldTrial(
-      safe_browsing::kThreatDomDetailsTagAndAttributeFeature.name, "Group");
-  std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
-  feature_list->InitializeFromCommandLine(
-      safe_browsing::kThreatDomDetailsTagAndAttributeFeature.name,
-      std::string());
-  feature_list->AssociateReportingFieldTrial(
-      safe_browsing::kThreatDomDetailsTagAndAttributeFeature.name,
-      base::FeatureList::OVERRIDE_ENABLE_FEATURE, trial);
   std::unique_ptr<base::test::ScopedFeatureList> scoped_list(
       new base::test::ScopedFeatureList);
-  scoped_list->InitWithFeatureList(std::move(feature_list));
+  scoped_list->InitWithFeaturesAndParameters(
+      {{safe_browsing::kThreatDomDetailsTagAndAttributeFeature,
+        feature_params}},
+      {});
   return scoped_list;
 }
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 8a88340..89448fc 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2138,6 +2138,7 @@
         "../browser/ui/ash/keyboard/keyboard_controller_browsertest.cc",
         "../browser/ui/ash/keyboard/keyboard_end_to_end_browsertest.cc",
         "../browser/ui/ash/launcher/arc_app_launcher_browsertest.cc",
+        "../browser/ui/ash/launcher/browser_shortcut_launcher_item_controller_browsertest.cc",
         "../browser/ui/ash/launcher/chrome_launcher_controller_browsertest.cc",
         "../browser/ui/ash/launcher/chrome_launcher_controller_test_util.cc",
         "../browser/ui/ash/launcher/chrome_launcher_controller_test_util.h",
@@ -2194,6 +2195,7 @@
       }
       if (enable_kiosk_next) {
         sources += [
+          "../browser/chromeos/kiosk_next/kiosk_next_browser_factory_browsertest.cc",
           "../browser/chromeos/kiosk_next_home/kiosk_next_home_browsertest.cc",
           "../browser/ui/ash/kiosk_next_shell_client_browsertest.cc",
         ]
@@ -3466,6 +3468,7 @@
       "../browser/download/download_commands_unittest.cc",
       "../browser/download/download_shelf_unittest.cc",
       "../browser/enterprise_reporting/profile_report_generator_unittest.cc",
+      "../browser/enterprise_reporting/report_generator_unittest.cc",
       "../browser/enterprise_reporting/report_scheduler_unittest.cc",
       "../browser/enterprise_reporting/report_uploader_unittest.cc",
       "../browser/enterprise_reporting/request_timer_unittest.cc",
@@ -3713,6 +3716,9 @@
       "../utility/importer/firefox_importer_unittest_utils.h",
       "../utility/importer/firefox_importer_unittest_utils_mac.cc",
       "../utility/importer/safari_importer_unittest.mm",
+
+      # Android uses different way of showing feedback page
+      "../browser/feedback/show_feedback_page_unittest.cc",
     ]
     if (is_posix || is_fuchsia) {
       sources += [ "../browser/process_singleton_posix_unittest.cc" ]
diff --git a/chrome/test/base/chrome_unit_test_suite.cc b/chrome/test/base/chrome_unit_test_suite.cc
index 0918712..e28d9c0 100644
--- a/chrome/test/base/chrome_unit_test_suite.cc
+++ b/chrome/test/base/chrome_unit_test_suite.cc
@@ -43,6 +43,30 @@
 
 namespace {
 
+class ChromeContentBrowserClientWithoutNetworkServiceInitialization
+    : public ChromeContentBrowserClient {
+ public:
+  // content::ContentBrowserClient:
+  // Skip some production Network Service code that doesn't work in unit tests.
+  void OnNetworkServiceCreated(
+      network::mojom::NetworkService* network_service) override {}
+  // Overridden to skip a call to ProfileIOData::FromResourceContext downstream
+  // of ProxyingURLLoaderFactory, which assumes the ResourceContext is a
+  // ProfileIOData::ResourceContext, but in unit tests it's a mock.
+  bool WillCreateURLLoaderFactory(
+      content::BrowserContext* browser_context,
+      content::RenderFrameHost* frame,
+      int render_process_id,
+      bool is_navigation,
+      bool is_download,
+      const url::Origin& request_initiator,
+      network::mojom::URLLoaderFactoryRequest* factory_request,
+      network::mojom::TrustedURLLoaderHeaderClientPtrInfo* header_client,
+      bool* bypass_redirect_checks) override {
+    return false;
+  }
+};
+
 // Creates a TestingBrowserProcess for each test.
 class ChromeUnitTestSuiteInitializer : public testing::EmptyTestEventListener {
  public:
@@ -53,7 +77,8 @@
     content_client_.reset(new ChromeContentClient);
     content::SetContentClient(content_client_.get());
 
-    browser_content_client_.reset(new ChromeContentBrowserClient());
+    browser_content_client_.reset(
+        new ChromeContentBrowserClientWithoutNetworkServiceInitialization());
     content::SetBrowserClientForTesting(browser_content_client_.get());
     utility_content_client_.reset(new ChromeContentUtilityClient());
     content::SetUtilityClientForTesting(utility_content_client_.get());
diff --git a/chrome/test/base/run_all_unittests.cc b/chrome/test/base/run_all_unittests.cc
index 80ffb261..1c48bb0 100644
--- a/chrome/test/base/run_all_unittests.cc
+++ b/chrome/test/base/run_all_unittests.cc
@@ -11,7 +11,6 @@
 #include "chrome/test/base/chrome_unit_test_suite.h"
 #include "content/public/test/unittest_test_suite.h"
 #include "mojo/core/embedder/scoped_ipc_support.h"
-#include "services/network/public/cpp/features.h"
 
 #if defined(OS_WIN)
 #include "chrome/install_static/test/scoped_install_details.h"
@@ -22,9 +21,7 @@
 
   // unit_tests don't currently work with the Network Service enabled.
   // https://crbug.com/966633.
-  content::UnitTestTestSuite test_suite(
-      new ChromeUnitTestSuite(argc, argv),
-      network::features::kNetworkServiceFeatureName);
+  content::UnitTestTestSuite test_suite(new ChromeUnitTestSuite(argc, argv));
 
   base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart);
   mojo::core::ScopedIPCSupport ipc_support(
diff --git a/chrome/test/base/test_browser_window.cc b/chrome/test/base/test_browser_window.cc
index 9adf436..357352a 100644
--- a/chrome/test/base/test_browser_window.cc
+++ b/chrome/test/base/test_browser_window.cc
@@ -152,6 +152,10 @@
   return nullptr;
 }
 
+ExtensionsContainer* TestBrowserWindow::GetExtensionsContainer() {
+  return nullptr;
+}
+
 content::KeyboardEventProcessingResult
 TestBrowserWindow::PreHandleKeyboardEvent(
     const content::NativeWebKeyboardEvent& event) {
diff --git a/chrome/test/base/test_browser_window.h b/chrome/test/base/test_browser_window.h
index 919853e..6499a88 100644
--- a/chrome/test/base/test_browser_window.h
+++ b/chrome/test/base/test_browser_window.h
@@ -103,6 +103,7 @@
   void ResetToolbarTabState(content::WebContents* contents) override {}
   void FocusToolbar() override {}
   ToolbarActionsBar* GetToolbarActionsBar() override;
+  ExtensionsContainer* GetExtensionsContainer() override;
   void ToolbarSizeChanged(bool is_animating) override {}
   void TabDraggingStatusChanged(bool is_dragging) override {}
   void FocusAppMenu() override {}
diff --git a/chrome/test/base/testing_browser_process.cc b/chrome/test/base/testing_browser_process.cc
index f055e01..6fe5698 100644
--- a/chrome/test/base/testing_browser_process.cc
+++ b/chrome/test/base/testing_browser_process.cc
@@ -86,7 +86,6 @@
       app_locale_("en"),
       is_shutting_down_(false),
       local_state_(nullptr),
-      io_thread_(nullptr),
       rappor_service_(nullptr),
       platform_part_(new TestingBrowserProcessPlatformPart()),
       test_network_connection_tracker_(
@@ -146,10 +145,6 @@
   return rappor_service_;
 }
 
-IOThread* TestingBrowserProcess::io_thread() {
-  return io_thread_;
-}
-
 SystemNetworkContextManager*
 TestingBrowserProcess::system_network_context_manager() {
   return nullptr;
@@ -477,10 +472,6 @@
   local_state_ = local_state;
 }
 
-void TestingBrowserProcess::SetIOThread(IOThread* io_thread) {
-  io_thread_ = io_thread;
-}
-
 void TestingBrowserProcess::ShutdownBrowserPolicyConnector() {
   if (browser_policy_connector_)
     browser_policy_connector_->Shutdown();
diff --git a/chrome/test/base/testing_browser_process.h b/chrome/test/base/testing_browser_process.h
index 55d8e75d..11595f4f 100644
--- a/chrome/test/base/testing_browser_process.h
+++ b/chrome/test/base/testing_browser_process.h
@@ -25,7 +25,6 @@
 #include "printing/buildflags/buildflags.h"
 
 class BackgroundModeManager;
-class IOThread;
 class NotificationPlatformBridge;
 class NotificationUIManager;
 class PrefService;
@@ -75,7 +74,6 @@
       override;
   metrics::MetricsService* metrics_service() override;
   rappor::RapporServiceImpl* rappor_service() override;
-  IOThread* io_thread() override;
   SystemNetworkContextManager* system_network_context_manager() override;
   scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory()
       override;
@@ -144,7 +142,6 @@
   // afterwards (using ScopedTestingLocalState, for example).
   void SetLocalState(PrefService* local_state);
   void SetProfileManager(ProfileManager* profile_manager);
-  void SetIOThread(IOThread* io_thread);
   void SetSafeBrowsingService(safe_browsing::SafeBrowsingService* sb_service);
   void SetRulesetService(
       std::unique_ptr<subresource_filter::RulesetService> ruleset_service);
@@ -204,7 +201,6 @@
 
   // The following objects are not owned by TestingBrowserProcess:
   PrefService* local_state_;
-  IOThread* io_thread_;
   scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
   rappor::RapporServiceImpl* rappor_service_;
 
diff --git a/chrome/test/base/testing_profile.cc b/chrome/test/base/testing_profile.cc
index 35e96b4..51d72b8 100644
--- a/chrome/test/base/testing_profile.cc
+++ b/chrome/test/base/testing_profile.cc
@@ -31,6 +31,7 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.h"
 #include "chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_factory.h"
+#include "chrome/browser/chrome_content_browser_client.h"
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/favicon/favicon_service_factory.h"
@@ -103,6 +104,7 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/cookie_store_factory.h"
+#include "content/public/browser/network_service_instance.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/storage_partition.h"
@@ -117,7 +119,6 @@
 #include "net/url_request/url_request_context_getter.h"
 #include "net/url_request/url_request_test_util.h"
 #include "services/identity/public/cpp/identity_test_utils.h"
-#include "services/network/public/cpp/features.h"
 #include "services/network/test/test_network_connection_tracker.h"
 #include "services/service_manager/public/cpp/service.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -1105,12 +1106,14 @@
         network_context_.get(), mojo::MakeRequest(&network_context_ptr));
     return network_context_ptr;
   }
-  if (base::FeatureList::IsEnabled(network::features::kNetworkService)) {
-    network::mojom::NetworkContextPtr network_context;
-    network_context_request_ = mojo::MakeRequest(&network_context);
-    return network_context;
-  }
-  return nullptr;
+  network::mojom::NetworkContextPtr network_context;
+  network::mojom::NetworkContextParamsPtr context_params =
+      network::mojom::NetworkContextParams::New();
+  context_params->user_agent = GetUserAgent();
+  context_params->accept_language = "en-us,en";
+  content::GetNetworkService()->CreateNetworkContext(
+      MakeRequest(&network_context), std::move(context_params));
+  return network_context;
 }
 
 TestingProfile::Builder::Builder()
diff --git a/chrome/test/base/testing_profile.h b/chrome/test/base/testing_profile.h
index e754e915..77868e6 100644
--- a/chrome/test/base/testing_profile.h
+++ b/chrome/test/base/testing_profile.h
@@ -428,9 +428,6 @@
   std::unique_ptr<net::CookieStore, content::BrowserThread::DeleteOnIOThread>
       extensions_cookie_store_;
 
-  // Holds a dummy network context request to avoid triggering connection error
-  // handler.
-  network::mojom::NetworkContextRequest network_context_request_;
   std::unique_ptr<network::mojom::NetworkContext> network_context_;
   mojo::BindingSet<network::mojom::NetworkContext> network_context_bindings_;
 
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 07833926..8fd9782 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -3325,6 +3325,16 @@
     ]
   },
 
+  "UserFeedbackAllowed": {
+    "os": ["win", "linux", "mac", "chromeos"],
+    "can_be_recommended": false,
+    "test_policy": { "UserFeedbackAllowed": false },
+    "pref_mappings": [
+      { "pref": "feedback_allowed" }
+    ]
+  },
+
+
   "ArcEnabled": {
     "os": ["chromeos"],
     "can_be_recommended": false,
diff --git a/chrome/test/data/webui/settings/edit_dictionary_page_test.js b/chrome/test/data/webui/settings/edit_dictionary_page_test.js
index bfb6a4d..f99918e 100644
--- a/chrome/test/data/webui/settings/edit_dictionary_page_test.js
+++ b/chrome/test/data/webui/settings/edit_dictionary_page_test.js
@@ -91,6 +91,22 @@
         'none');  // Make sure add-word button actually clickable.
   });
 
+  test('add duplicate word', function() {
+    const WORD = 'unique';
+    languageSettingsPrivate.onCustomDictionaryChanged.callListeners([WORD], []);
+    editDictPage.$.newWord.value = `${WORD} ${WORD}`;
+    Polymer.dom.flush();
+    assertFalse(editDictPage.$.addWord.disabled);
+
+    editDictPage.$.newWord.value = WORD;
+    Polymer.dom.flush();
+    assertTrue(editDictPage.$.addWord.disabled);
+
+    languageSettingsPrivate.onCustomDictionaryChanged.callListeners([], [WORD]);
+    Polymer.dom.flush();
+    assertFalse(editDictPage.$.addWord.disabled);
+  });
+
   test('spellcheck edit dictionary page message when empty', function() {
     assertTrue(!!editDictPage);
     return languageSettingsPrivate.whenCalled('getSpellcheckWords')
diff --git a/chrome/test/data/webui/signin_browsertest.h b/chrome/test/data/webui/signin_browsertest.h
index 6fe2d56b..c2a25a6 100644
--- a/chrome/test/data/webui/signin_browsertest.h
+++ b/chrome/test/data/webui/signin_browsertest.h
@@ -21,9 +21,9 @@
   void EnableUnity();
 
  private:
+  std::unique_ptr<ScopedAccountConsistency> scoped_account_consistency_;
   std::unique_ptr<unified_consent::ScopedUnifiedConsent>
       scoped_unified_consent_;
-  std::unique_ptr<ScopedAccountConsistency> scoped_account_consistency_;
 
   DISALLOW_COPY_AND_ASSIGN(SigninBrowserTest);
 };
diff --git a/chrome/test/data/xr/e2e_test_files/html/webxr_test_presentation_promise_rejected_if_consent_not_granted.html b/chrome/test/data/xr/e2e_test_files/html/webxr_test_presentation_promise_rejected_if_consent_not_granted.html
new file mode 100644
index 0000000..121e4022
--- /dev/null
+++ b/chrome/test/data/xr/e2e_test_files/html/webxr_test_presentation_promise_rejected_if_consent_not_granted.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<!--
+Used to test that the promise returned by WebXR's requestSession rejects if
+the user denies permission on a consent dialog.
+-->
+<html>
+  <head>
+    <link rel="stylesheet" type="text/css" href="../resources/webxr_e2e.css">
+  </head>
+  <body>
+    <canvas id="webgl-canvas"></canvas>
+    <script src="../../../../../../third_party/blink/web_tests/resources/testharness.js"></script>
+    <script src="../resources/webxr_e2e.js"></script>
+    <script src="../resources/webxr_boilerplate.js"></script>
+    <script>
+      function onImmersiveRequestWithConsent() {
+        navigator.xr.requestSession('immersive-vr').then(() => {
+          assert_unreached("requestPresent promise shouldn't resolve when consent not granted");
+        }).catch(() => {
+          done();
+        });
+      }
+    </script>
+  </body>
+</html>
diff --git a/chromecast/external_mojo/external_service_support/BUILD.gn b/chromecast/external_mojo/external_service_support/BUILD.gn
index 46f0562e..4a34e42 100644
--- a/chromecast/external_mojo/external_service_support/BUILD.gn
+++ b/chromecast/external_mojo/external_service_support/BUILD.gn
@@ -26,6 +26,7 @@
   ]
   deps = [
     "//base",
+    "//base:base_static",
   ]
 }
 
diff --git a/chromecast/external_mojo/external_service_support/chromium_service.cc b/chromecast/external_mojo/external_service_support/chromium_service.cc
index c74d207..74773e3e 100644
--- a/chromecast/external_mojo/external_service_support/chromium_service.cc
+++ b/chromecast/external_mojo/external_service_support/chromium_service.cc
@@ -55,8 +55,13 @@
 void ChromiumServiceWrapper::OnBindInterface(
     const std::string& interface_name,
     mojo::ScopedMessagePipeHandle interface_pipe) {
-  chromium_service_->OnBindInterface(service_manager::BindSourceInfo(),
-                                     interface_name, std::move(interface_pipe));
+  chromium_service_->OnBindInterface(
+      service_manager::BindSourceInfo(
+          service_manager::Identity("unique", base::Token::CreateRandom(),
+                                    base::Token::CreateRandom(),
+                                    base::Token::CreateRandom()),
+          service_manager::CapabilitySet()),
+      interface_name, std::move(interface_pipe));
 }
 
 service_manager::mojom::ServiceRequest CreateChromiumServiceRequest(
diff --git a/chromecast/external_mojo/external_service_support/process_setup.cc b/chromecast/external_mojo/external_service_support/process_setup.cc
index a7ff784..f8359abd 100644
--- a/chromecast/external_mojo/external_service_support/process_setup.cc
+++ b/chromecast/external_mojo/external_service_support/process_setup.cc
@@ -7,7 +7,9 @@
 #include <locale.h>
 #include <signal.h>
 
+#include "base/base_switches.h"
 #include "base/command_line.h"
+#include "base/feature_list.h"
 #include "base/logging.h"
 #include "build/build_config.h"
 
@@ -23,11 +25,17 @@
 #endif
 
   base::CommandLine::Init(argc, argv);
+
   logging::LoggingSettings settings;
   settings.logging_dest =
       logging::LOG_TO_SYSTEM_DEBUG_LOG | logging::LOG_TO_STDERR;
   logging::InitLogging(settings);
 
+  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+  base::FeatureList::InitializeInstance(
+      command_line->GetSwitchValueASCII(switches::kEnableFeatures),
+      command_line->GetSwitchValueASCII(switches::kDisableFeatures));
+
   CHECK_NE(SIG_ERR, signal(SIGPIPE, SIG_IGN));
 }
 
diff --git a/components/autofill/core/browser/autofill_manager_unittest.cc b/components/autofill/core/browser/autofill_manager_unittest.cc
index c19970a..eb3d1b7 100644
--- a/components/autofill/core/browser/autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/autofill_manager_unittest.cc
@@ -597,8 +597,8 @@
   MockAutofillDownloadManager* download_manager_;
   TestPersonalDataManager personal_data_;
   std::unique_ptr<MockAutocompleteHistoryManager> autocomplete_history_manager_;
-  base::test::ScopedFeatureList scoped_feature_list_;
   variations::testing::VariationParamsManager variation_params_;
+  base::test::ScopedFeatureList scoped_feature_list_;
 
  private:
   int ToHistogramSample(AutofillMetrics::CardUploadDecisionMetric metric) {
diff --git a/components/autofill/core/browser/payments/payments_client_unittest.cc b/components/autofill/core/browser/payments/payments_client_unittest.cc
index bfd515ac..532876a 100644
--- a/components/autofill/core/browser/payments/payments_client_unittest.cc
+++ b/components/autofill/core/browser/payments/payments_client_unittest.cc
@@ -574,6 +574,9 @@
   // headers. Also, the variations header provider may have been registered to
   // observe some other field trial list, so reset it.
   variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
+  // Note: This needs a base::FieldTrialList instance because it does not use
+  // ScopedFeatureList, which provides its own, unlike other tests that do via
+  // DisableAutofillSendExperimentIdsInPaymentsRPCs().
   base::FieldTrialList field_trial_list_(nullptr);
   CreateFieldTrialWithId("AutofillTest", "Group", 369);
   StartGettingUploadDetails();
@@ -590,7 +593,6 @@
   // observe some other field trial list, so reset it.
   DisableAutofillSendExperimentIdsInPaymentsRPCs();
   variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
-  base::FieldTrialList field_trial_list_(nullptr);
   CreateFieldTrialWithId("AutofillTest", "Group", 369);
   StartGettingUploadDetails();
 
@@ -702,6 +704,9 @@
   // headers. Also, the variations header provider may have been registered to
   // observe some other field trial list, so reset it.
   variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
+  // Note: This needs a base::FieldTrialList instance because it does not use
+  // ScopedFeatureList, which provides its own, unlike other tests that do via
+  // DisableAutofillSendExperimentIdsInPaymentsRPCs().
   base::FieldTrialList field_trial_list_(nullptr);
   CreateFieldTrialWithId("AutofillTest", "Group", 369);
   StartUploading(/*include_cvc=*/true);
@@ -719,7 +724,6 @@
   // observe some other field trial list, so reset it.
   DisableAutofillSendExperimentIdsInPaymentsRPCs();
   variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
-  base::FieldTrialList field_trial_list_(nullptr);
   CreateFieldTrialWithId("AutofillTest", "Group", 369);
   StartUploading(/*include_cvc=*/true);
 
@@ -734,6 +738,9 @@
   // headers. Also, the variations header provider may have been registered to
   // observe some other field trial list, so reset it.
   variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
+  // Note: This needs a base::FieldTrialList instance because it does not use
+  // ScopedFeatureList, which provides its own, unlike other tests that do via
+  // DisableAutofillSendExperimentIdsInPaymentsRPCs().
   base::FieldTrialList field_trial_list_(nullptr);
   CreateFieldTrialWithId("AutofillTest", "Group", 369);
   StartUnmasking();
@@ -751,7 +758,6 @@
   // observe some other field trial list, so reset it.
   DisableAutofillSendExperimentIdsInPaymentsRPCs();
   variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
-  base::FieldTrialList field_trial_list_(nullptr);
   CreateFieldTrialWithId("AutofillTest", "Group", 369);
   StartUnmasking();
 
@@ -767,7 +773,6 @@
   // observe some other field trial list, so reset it.
   EnableAutofillSendExperimentIdsInPaymentsRPCs();
   variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
-  base::FieldTrialList field_trial_list_(nullptr);
   CreateFieldTrialWithId("AutofillTest", "Group", 369);
   StartMigrating(/*has_cardholder_name=*/true);
   IssueOAuthToken();
@@ -784,7 +789,6 @@
   // observe some other field trial list, so reset it.
   DisableAutofillSendExperimentIdsInPaymentsRPCs();
   variations::VariationsHttpHeaderProvider::GetInstance()->ResetForTesting();
-  base::FieldTrialList field_trial_list_(nullptr);
   CreateFieldTrialWithId("AutofillTest", "Group", 369);
   StartMigrating(/*has_cardholder_name=*/true);
 
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc
index b80e6d3..494a2bb 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc
@@ -278,7 +278,13 @@
             GetConfiguredProxiesForHttp());
 }
 
-TEST_F(DataReductionProxyConfigTest, TestOnNetworkChanged) {
+// Flaky on Linux. http://crbug.com/973385
+#if defined(OS_LINUX)
+#define MAYBE_TestOnNetworkChanged DISABLED_TestOnNetworkChanged
+#else
+#define MAYBE_TestOnNetworkChanged TestOnNetworkChanged
+#endif
+TEST_F(DataReductionProxyConfigTest, MAYBE_TestOnNetworkChanged) {
   // The test manually controls the fetch of warmup URL and the response.
   test_context_->DisableWarmupURLFetchCallback();
 
diff --git a/components/offline_pages/core/prefetch/prefetch_request_test_base.cc b/components/offline_pages/core/prefetch/prefetch_request_test_base.cc
index 76a72d3..66ac5618 100644
--- a/components/offline_pages/core/prefetch/prefetch_request_test_base.cc
+++ b/components/offline_pages/core/prefetch/prefetch_request_test_base.cc
@@ -31,9 +31,6 @@
 PrefetchRequestTestBase::~PrefetchRequestTestBase() {}
 
 void PrefetchRequestTestBase::SetUp() {
-  field_trial_list_ = std::make_unique<base::FieldTrialList>(
-      std::make_unique<base::MockEntropyProvider>());
-
   test_url_loader_factory_.SetInterceptor(
       base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
         last_resource_request_ = request;
@@ -41,23 +38,11 @@
 }
 
 void PrefetchRequestTestBase::SetUpExperimentOption() {
-  const std::string kTrialName = "trial_name";
-  const std::string kGroupName = "group_name";
-
-  scoped_refptr<base::FieldTrial> trial =
-      base::FieldTrialList::CreateFieldTrial(kTrialName, kGroupName);
-
   std::map<std::string, std::string> params;
   params[kPrefetchingOfflinePagesExperimentsOption] =
       kExperimentValueSetInFieldTrial;
-  base::AssociateFieldTrialParams(kTrialName, kGroupName, params);
-
-  std::unique_ptr<base::FeatureList> feature_list =
-      std::make_unique<base::FeatureList>();
-  feature_list->RegisterFieldTrialOverride(
-      kPrefetchingOfflinePagesFeature.name,
-      base::FeatureList::OVERRIDE_ENABLE_FEATURE, trial.get());
-  scoped_feature_list_.InitWithFeatureList(std::move(feature_list));
+  scoped_feature_list_.InitAndEnableFeatureWithParameters(
+      kPrefetchingOfflinePagesFeature, params);
 }
 
 void PrefetchRequestTestBase::RespondWithNetError(int net_error) {
diff --git a/components/offline_pages/core/prefetch/prefetch_request_test_base.h b/components/offline_pages/core/prefetch/prefetch_request_test_base.h
index a6a81eb..63b2fe5f 100644
--- a/components/offline_pages/core/prefetch/prefetch_request_test_base.h
+++ b/components/offline_pages/core/prefetch/prefetch_request_test_base.h
@@ -62,7 +62,6 @@
       test_shared_url_loader_factory_;
   network::ResourceRequest last_resource_request_;
 
-  std::unique_ptr<base::FieldTrialList> field_trial_list_;
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index c67d867d..1a7a698 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -15651,6 +15651,26 @@
       If this policy is set, the user cannot change or override it.''',
     },
     {
+      'name': 'UserFeedbackAllowed',
+      'type': 'main',
+      'schema': {
+        'type': 'boolean',
+      },
+      'supported_on': ['chrome.*:77-', 'chrome_os:77-'],
+      'features': {
+        'dynamic_refresh': True,
+        'per_profile': True,
+      },
+      'example_value': True,
+      'id': 570,
+      'caption': '''Allow user feedback''',
+      'tags': [],
+      'desc': '''Allow user feedback.
+        If the policy is set to false, users can not send feedback to Google.
+
+        If the policy is unset or set to true, users can send feedback to Google via Menu->Help->Report an Issue or key combination.'''
+    },
+    {
       'name': 'SamlPasswordExpirationAdvanceWarningDays',
       'type': 'int',
       'schema': {
@@ -16840,6 +16860,6 @@
   ],
   'placeholders': [],
   'deleted_policy_ids': [412, 546, 562],
-  'highest_id_currently_used': 569,
+  'highest_id_currently_used': 570,
   'highest_atomic_group_id_currently_used': 37
 }
diff --git a/components/signin/core/browser/gaia_cookie_manager_service.cc b/components/signin/core/browser/gaia_cookie_manager_service.cc
index 1d2074f..87a8c630a 100644
--- a/components/signin/core/browser/gaia_cookie_manager_service.cc
+++ b/components/signin/core/browser/gaia_cookie_manager_service.cc
@@ -251,9 +251,11 @@
   return base::JoinString(results, ",");
 }
 
-void GaiaCookieManagerService::ExternalCcResultFetcher::Start() {
+void GaiaCookieManagerService::ExternalCcResultFetcher::Start(
+    base::OnceClosure callback) {
   DCHECK(!helper_->external_cc_result_fetched_);
   m_external_cc_result_start_time_ = base::Time::Now();
+  callback_ = std::move(callback);
 
   CleanupTransientState();
   results_.clear();
@@ -440,11 +442,7 @@
                                              time_to_check_connections);
 
   helper_->external_cc_result_fetched_ = true;
-  // Since the ExternalCCResultFetcher is only Started in place of calling
-  // StartFetchingMergeSession, we can assume we need to call
-  // StartFetchingMergeSession. If this assumption becomes invalid, a Callback
-  // will need to be passed to Start() and Run() here.
-  helper_->StartFetchingMergeSession();
+  std::move(callback_).Run();
 }
 
 GaiaCookieManagerService::GaiaCookieManagerService(
@@ -507,11 +505,7 @@
   }
   if (requests_.size() == 1) {
     fetcher_retries_ = 0;
-    // Unretained is safe because GaiaCookieManagerService owns the helper.
-    oauth_multilogin_helper_ = std::make_unique<signin::OAuthMultiloginHelper>(
-        signin_client_, token_service_, account_ids,
-        base::BindOnce(&GaiaCookieManagerService::OnSetAccountsFinished,
-                       base::Unretained(this)));
+    StartSetAccounts();
   }
 }
 
@@ -770,7 +764,9 @@
 
   if (!external_cc_result_fetched_ &&
       !external_cc_result_fetcher_.IsRunning()) {
-    external_cc_result_fetcher_.Start();
+    external_cc_result_fetcher_.Start(
+        base::BindOnce(&GaiaCookieManagerService::StartFetchingMergeSession,
+                       weak_ptr_factory_.GetWeakPtr()));
     return;
   }
 
@@ -973,6 +969,32 @@
   gaia_auth_fetcher_->StartListAccounts();
 }
 
+void GaiaCookieManagerService::StartSetAccounts() {
+  DCHECK(!requests_.empty());
+  DCHECK_EQ(GaiaCookieRequestType::SET_ACCOUNTS,
+            requests_.front().request_type());
+  DCHECK(!requests_.front().accounts().empty());
+
+  if (!external_cc_result_fetched_ &&
+      !external_cc_result_fetcher_.IsRunning()) {
+    external_cc_result_fetcher_.Start(
+        base::BindOnce(&GaiaCookieManagerService::StartSetAccounts,
+                       weak_ptr_factory_.GetWeakPtr()));
+    return;
+  }
+
+  // TODO(triploblastic): remove this block in the second part of the fix.
+  std::vector<std::string> account_ids;
+  for (const auto& id : requests_.front().accounts())
+    account_ids.push_back(id.first.id);
+
+  oauth_multilogin_helper_ = std::make_unique<signin::OAuthMultiloginHelper>(
+      signin_client_, token_service_, account_ids,
+      external_cc_result_fetcher_.GetExternalCcResult(),
+      base::BindOnce(&GaiaCookieManagerService::OnSetAccountsFinished,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
 void GaiaCookieManagerService::OnSetAccountsFinished(
     signin::SetAccountsInCookieResult result) {
   MarkListAccountsStale();
@@ -1009,18 +1031,7 @@
                            weak_ptr_factory_.GetWeakPtr()));
         break;
       case GaiaCookieRequestType::SET_ACCOUNTS: {
-        DCHECK(!requests_.front().accounts().empty());
-
-        // TODO(triploblastic): remove this block in the second part of the fix.
-        std::vector<std::string> account_ids;
-        for (const auto& id : requests_.front().accounts())
-          account_ids.push_back(id.first.id);
-
-        oauth_multilogin_helper_ =
-            std::make_unique<signin::OAuthMultiloginHelper>(
-                signin_client_, token_service_, account_ids,
-                base::BindOnce(&GaiaCookieManagerService::OnSetAccountsFinished,
-                               weak_ptr_factory_.GetWeakPtr()));
+        StartSetAccounts();
         break;
       }
       case GaiaCookieRequestType::LOG_OUT:
diff --git a/components/signin/core/browser/gaia_cookie_manager_service.h b/components/signin/core/browser/gaia_cookie_manager_service.h
index 77055d1c..a9f6f468 100644
--- a/components/signin/core/browser/gaia_cookie_manager_service.h
+++ b/components/signin/core/browser/gaia_cookie_manager_service.h
@@ -173,7 +173,7 @@
 
     // Start fetching the external CC result.  If a fetch is already in progress
     // it is canceled.
-    void Start();
+    void Start(base::OnceClosure callback);
 
     // Are external URLs still being checked?
     bool IsRunning();
@@ -210,6 +210,7 @@
     LoaderToToken loaders_;
     ResultMap results_;
     base::Time m_external_cc_result_start_time_;
+    base::OnceClosure callback_;
 
     DISALLOW_COPY_AND_ASSIGN(ExternalCcResultFetcher);
   };
@@ -358,6 +359,9 @@
   // Virtual for testing purpose.
   virtual void StartFetchingLogOut();
 
+  // Starts setting account using multilogin endpoint.
+  void StartSetAccounts();
+
   // Start the next request, if needed.
   void HandleNextRequest();
 
diff --git a/components/signin/core/browser/gaia_cookie_manager_service_unittest.cc b/components/signin/core/browser/gaia_cookie_manager_service_unittest.cc
index 5a27c8f..60a6773 100644
--- a/components/signin/core/browser/gaia_cookie_manager_service_unittest.cc
+++ b/components/signin/core/browser/gaia_cookie_manager_service_unittest.cc
@@ -807,7 +807,9 @@
   InstrumentedGaiaCookieManagerService helper(token_service(), signin_client());
   GaiaCookieManagerService::ExternalCcResultFetcher result_fetcher(&helper);
   EXPECT_CALL(helper, StartFetchingMergeSession());
-  result_fetcher.Start();
+  result_fetcher.Start(base::BindOnce(
+      &InstrumentedGaiaCookieManagerService::StartFetchingMergeSession,
+      base::Unretained(&helper)));
 
   // Simulate a successful completion of GetCheckConnctionInfo.
   SimulateGetCheckConnectionInfoSuccess(
@@ -832,7 +834,9 @@
   InstrumentedGaiaCookieManagerService helper(token_service(), signin_client());
   GaiaCookieManagerService::ExternalCcResultFetcher result_fetcher(&helper);
   EXPECT_CALL(helper, StartFetchingMergeSession());
-  result_fetcher.Start();
+  result_fetcher.Start(base::BindOnce(
+      &InstrumentedGaiaCookieManagerService::StartFetchingMergeSession,
+      base::Unretained(&helper)));
 
   // Simulate a successful completion of GetCheckConnctionInfo.
   SimulateGetCheckConnectionInfoSuccess(
@@ -861,7 +865,9 @@
   InstrumentedGaiaCookieManagerService helper(token_service(), signin_client());
   GaiaCookieManagerService::ExternalCcResultFetcher result_fetcher(&helper);
   EXPECT_CALL(helper, StartFetchingMergeSession());
-  result_fetcher.Start();
+  result_fetcher.Start(base::BindOnce(
+      &InstrumentedGaiaCookieManagerService::StartFetchingMergeSession,
+      base::Unretained(&helper)));
 
   // Simulate a successful completion of GetCheckConnctionInfo.
   SimulateGetCheckConnectionInfoSuccess(
@@ -903,7 +909,9 @@
 TEST_F(GaiaCookieManagerServiceTest, UbertokenSuccessFetchesExternalCCOnce) {
   InstrumentedGaiaCookieManagerService helper(token_service(), signin_client());
 
-  helper.external_cc_result_fetcher_for_testing()->Start();
+  helper.external_cc_result_fetcher_for_testing()->Start(base::BindOnce(
+      &InstrumentedGaiaCookieManagerService::StartFetchingMergeSession,
+      base::Unretained(&helper)));
 
   EXPECT_CALL(helper, StartFetchingUbertoken());
   helper.AddAccountToCookie(
diff --git a/components/signin/core/browser/oauth_multilogin_helper.cc b/components/signin/core/browser/oauth_multilogin_helper.cc
index 230678c..ab38dc09 100644
--- a/components/signin/core/browser/oauth_multilogin_helper.cc
+++ b/components/signin/core/browser/oauth_multilogin_helper.cc
@@ -42,10 +42,12 @@
     SigninClient* signin_client,
     OAuth2TokenService* token_service,
     const std::vector<std::string>& account_ids,
+    const std::string& external_cc_result,
     base::OnceCallback<void(signin::SetAccountsInCookieResult)> callback)
     : signin_client_(signin_client),
       token_service_(token_service),
       account_ids_(account_ids),
+      external_cc_result_(external_cc_result),
       callback_(std::move(callback)),
       weak_ptr_factory_(this) {
   DCHECK(signin_client_);
@@ -102,7 +104,8 @@
   DCHECK_EQ(token_id_pairs_.size(), account_ids_.size());
   gaia_auth_fetcher_ =
       signin_client_->CreateGaiaAuthFetcher(this, gaia::GaiaSource::kChrome);
-  gaia_auth_fetcher_->StartOAuthMultilogin(token_id_pairs_);
+  gaia_auth_fetcher_->StartOAuthMultilogin(token_id_pairs_,
+                                           external_cc_result_);
 }
 
 void OAuthMultiloginHelper::OnOAuthMultiloginFinished(
diff --git a/components/signin/core/browser/oauth_multilogin_helper.h b/components/signin/core/browser/oauth_multilogin_helper.h
index 5fcda19..69064a9 100644
--- a/components/signin/core/browser/oauth_multilogin_helper.h
+++ b/components/signin/core/browser/oauth_multilogin_helper.h
@@ -39,6 +39,7 @@
       SigninClient* signin_client,
       OAuth2TokenService* token_service,
       const std::vector<std::string>& account_ids,
+      const std::string& external_cc_result,
       base::OnceCallback<void(signin::SetAccountsInCookieResult)> callback);
 
   ~OAuthMultiloginHelper() override;
@@ -74,6 +75,8 @@
 
   // Account ids to set in the cookie.
   const std::vector<std::string> account_ids_;
+  // See GaiaCookieManagerService::ExternalCcResultFetcher for details.
+  const std::string external_cc_result_;
   // Access tokens, in the same order as the account ids.
   std::vector<GaiaAuthFetcher::MultiloginTokenIDPair> token_id_pairs_;
 
diff --git a/components/signin/core/browser/oauth_multilogin_helper_unittest.cc b/components/signin/core/browser/oauth_multilogin_helper_unittest.cc
index ce0059202..e76e385 100644
--- a/components/signin/core/browser/oauth_multilogin_helper_unittest.cc
+++ b/components/signin/core/browser/oauth_multilogin_helper_unittest.cc
@@ -26,6 +26,8 @@
 const char kAccessToken[] = "access_token_1";
 const char kAccessToken2[] = "access_token_2";
 
+const char kExternalCcResult[] = "youtube:OK";
+
 constexpr int kMaxFetcherRetries = 3;
 
 const char kMultiloginSuccessResponse[] =
@@ -76,6 +78,35 @@
        }
       )";
 
+const char kMultiloginSuccessResponseWithSecondaryDomain[] =
+    R"()]}'
+       {
+         "status": "OK",
+         "cookies":[
+           {
+             "name":"SID",
+             "value":"SID_value",
+             "domain":".youtube.com",
+             "path":"/",
+             "isSecure":true,
+             "isHttpOnly":false,
+             "priority":"HIGH",
+             "maxAge":63070000
+           },
+           {
+             "name":"FOO",
+             "value":"FOO_value",
+             "domain":".google.com",
+             "path":"/",
+             "isSecure":true,
+             "isHttpOnly":false,
+             "priority":"HIGH",
+             "maxAge":63070000
+           }
+         ]
+       }
+      )";
+
 const char kMultiloginRetryResponse[] =
     R"()]}'
        {
@@ -140,7 +171,15 @@
   std::unique_ptr<OAuthMultiloginHelper> CreateHelper(
       const std::vector<std::string> account_ids) {
     return std::make_unique<OAuthMultiloginHelper>(
-        &test_signin_client_, token_service(), account_ids,
+        &test_signin_client_, token_service(), account_ids, std::string(),
+        base::BindOnce(&OAuthMultiloginHelperTest::OnOAuthMultiloginFinished,
+                       base::Unretained(this)));
+  }
+
+  std::unique_ptr<OAuthMultiloginHelper> CreateHelperWithExternalCcResult(
+      const std::vector<std::string> account_ids) {
+    return std::make_unique<OAuthMultiloginHelper>(
+        &test_signin_client_, token_service(), account_ids, kExternalCcResult,
         base::BindOnce(&OAuthMultiloginHelperTest::OnOAuthMultiloginFinished,
                        base::Unretained(this)));
   }
@@ -154,6 +193,11 @@
            "?source=ChromiumBrowser";
   }
 
+  std::string multilogin_url_with_external_cc_result() const {
+    return GaiaUrls::GetInstance()->oauth_multilogin_url().spec() +
+           "?source=ChromiumBrowser&externalCcResult=" + kExternalCcResult;
+  }
+
   MockCookieManager* cookie_manager() { return mock_cookie_manager_; }
   MockTokenService* token_service() { return &mock_token_service_; }
 
@@ -236,6 +280,43 @@
   EXPECT_EQ(signin::SetAccountsInCookieResult::kSuccess, result_);
 }
 
+// Multiple cookies in the multilogin response.
+TEST_F(OAuthMultiloginHelperTest, SuccessWithExternalCcResult) {
+  token_service()->AddAccount(kAccountId);
+  std::unique_ptr<OAuthMultiloginHelper> helper =
+      CreateHelperWithExternalCcResult({kAccountId});
+
+  // Configure mock cookie manager:
+  // - check that the cookie is the expected one
+  // - immediately invoke the callback
+  EXPECT_CALL(
+      *cookie_manager(),
+      SetCanonicalCookie(CookieMatcher("SID", "SID_value", ".youtube.com"),
+                         "https", testing::_, testing::_))
+      .WillOnce(::testing::Invoke(RunSetCookieCallbackWithSuccess));
+  EXPECT_CALL(
+      *cookie_manager(),
+      SetCanonicalCookie(CookieMatcher("FOO", "FOO_value", ".google.com"),
+                         "https", testing::_, testing::_))
+      .WillOnce(::testing::Invoke(RunSetCookieCallbackWithSuccess));
+
+  // Issue access token.
+  OAuth2AccessTokenConsumer::TokenResponse success_response;
+  success_response.access_token = kAccessToken;
+  token_service()->IssueAllTokensForAccount(kAccountId, success_response);
+
+  // Multilogin call.
+  EXPECT_FALSE(callback_called_);
+  EXPECT_TRUE(
+      url_loader()->IsPending(multilogin_url_with_external_cc_result()));
+  url_loader()->AddResponse(multilogin_url_with_external_cc_result(),
+                            kMultiloginSuccessResponseWithSecondaryDomain);
+  EXPECT_FALSE(
+      url_loader()->IsPending(multilogin_url_with_external_cc_result()));
+  EXPECT_TRUE(callback_called_);
+  EXPECT_EQ(signin::SetAccountsInCookieResult::kSuccess, result_);
+}
+
 // Failure to get the access token.
 TEST_F(OAuthMultiloginHelperTest, OneAccountAccessTokenFailure) {
   token_service()->AddAccount(kAccountId);
diff --git a/components/translate/core/browser/translate_prefs.cc b/components/translate/core/browser/translate_prefs.cc
index ba77b0ff..edd414b 100644
--- a/components/translate/core/browser/translate_prefs.cc
+++ b/components/translate/core/browser/translate_prefs.cc
@@ -91,9 +91,6 @@
 #endif
 };
 
-const base::Feature kCompactTranslateInfobarIOS{
-    "CompactTranslateInfobarIOS", base::FEATURE_ENABLED_BY_DEFAULT};
-
 DenialTimeUpdate::DenialTimeUpdate(PrefService* prefs,
                                    const std::string& language,
                                    size_t max_denial_count)
diff --git a/components/translate/core/browser/translate_prefs.h b/components/translate/core/browser/translate_prefs.h
index 1acf50e..2f010c6b 100644
--- a/components/translate/core/browser/translate_prefs.h
+++ b/components/translate/core/browser/translate_prefs.h
@@ -46,9 +46,6 @@
 // Enable the "Translate" item in the overflow menu on Mobile.
 extern const base::Feature kTranslateMobileManualTrigger;
 
-// Enables the new compact Translate infobar on iOS.
-extern const base::Feature kCompactTranslateInfobarIOS;
-
 // Minimum number of times the user must accept a translation before we show
 // a shortcut to the "Always Translate" functionality.
 #if defined(OS_ANDROID) || defined(OS_IOS)
diff --git a/components/url_formatter/url_fixer.cc b/components/url_formatter/url_fixer.cc
index 3afc7008..502f598 100644
--- a/components/url_formatter/url_fixer.cc
+++ b/components/url_formatter/url_fixer.cc
@@ -563,11 +563,11 @@
     return GURL();
   }
 
-  // 'about:blank' is special-cased in various places in the code so it
-  // shouldn't be transformed into 'chrome://blank' as the code below will do.
+  // 'about:blank' and 'about:srcdoc' are special-cased in various places in the
+  // code and shouldn't use the chrome: scheme.
   if (base::LowerCaseEqualsASCII(scheme, url::kAboutScheme)) {
     GURL about_url(base::ToLowerASCII(trimmed));
-    if (about_url.IsAboutBlank())
+    if (about_url.IsAboutBlank() || about_url.IsAboutSrcdoc())
       return about_url;
   }
 
diff --git a/components/url_formatter/url_fixer_unittest.cc b/components/url_formatter/url_fixer_unittest.cc
index 05a2b54..91c2115 100644
--- a/components/url_formatter/url_fixer_unittest.cc
+++ b/components/url_formatter/url_fixer_unittest.cc
@@ -311,9 +311,9 @@
   if (url.length() <= 8)
     return false;
   if (std::string("file:///") != url.substr(0, 8))
-    return false; // no file:/// prefix
+    return false;  // no file:/// prefix
   if (url.find('\\') != std::string::npos)
-    return false; // contains backslashes
+    return false;  // contains backslashes
 
   base::FilePath derived_path;
   net::FileURLToFilePath(GURL(url), &derived_path);
@@ -339,6 +339,11 @@
     {"about:version", "chrome://version/"},
     {"about:blank", "about:blank"},
     {"About:blaNk", "about:blank"},
+    {"about:blank#blah", "about:blank#blah"},
+    {"about:blank/#blah", "about:blank/#blah"},
+    {"about:srcdoc", "about:srcdoc"},
+    {"about:srcdoc#blah", "about:srcdoc#blah"},
+    {"about:srcdoc/#blah", "about:srcdoc/#blah"},
     {"about:usr:pwd@hst:20/pth?qry#ref", "chrome://hst/pth?qry#ref"},
     {"about://usr:pwd@hst/pth?qry#ref", "chrome://hst/pth?qry#ref"},
     {"chrome:usr:pwd@hst/pth?qry#ref", "chrome://hst/pth?qry#ref"},
@@ -398,7 +403,6 @@
     {"chrome-devtools://bundled/devtools/inspector.html?ws=ws://localhost:9222/"
      "guid",
      "devtools://bundled/devtools/inspector.html?ws=ws://localhost:9222/guid"},
-
 };
 
 TEST(URLFixerTest, FixupURL) {
diff --git a/components/variations/field_trial_config/field_trial_util.cc b/components/variations/field_trial_config/field_trial_util.cc
index 43f914a..2c85d68 100644
--- a/components/variations/field_trial_config/field_trial_util.cc
+++ b/components/variations/field_trial_config/field_trial_util.cc
@@ -15,12 +15,10 @@
 #include "base/command_line.h"
 #include "base/feature_list.h"
 #include "base/metrics/field_trial.h"
-#include "base/strings/string_split.h"
-#include "base/strings/stringprintf.h"
+#include "base/metrics/field_trial_params.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/system/sys_info.h"
 #include "components/variations/field_trial_config/fieldtrial_testing_config.h"
-#include "components/variations/variations_associated_data.h"
 #include "components/variations/variations_seed_processor.h"
 #include "net/base/escape.h"
 #include "ui/base/device_form_factor.h"
@@ -89,12 +87,12 @@
     const VariationsSeedProcessor::UIStringOverrideCallback& callback,
     base::FeatureList* feature_list) {
   if (experiment.params_size != 0) {
-    std::map<std::string, std::string> params;
+    base::FieldTrialParams params;
     for (size_t i = 0; i < experiment.params_size; ++i) {
       const FieldTrialTestingExperimentParams& param = experiment.params[i];
       params[param.key] = param.value;
     }
-    AssociateVariationParams(study_name, experiment.name, params);
+    base::AssociateFieldTrialParams(study_name, experiment.name, params);
   }
   base::FieldTrial* trial =
       base::FieldTrialList::CreateFieldTrial(study_name, experiment.name);
@@ -188,55 +186,8 @@
 }
 
 bool AssociateParamsFromString(const std::string& varations_string) {
-  // Format: Trial1.Group1:k1/v1/k2/v2,Trial2.Group2:k1/v1/k2/v2
-  std::set<std::pair<std::string, std::string>> trial_groups;
-  for (const base::StringPiece& experiment_group : base::SplitStringPiece(
-           varations_string, ",",
-           base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
-    std::vector<base::StringPiece> experiment = base::SplitStringPiece(
-        experiment_group, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
-    if (experiment.size() != 2) {
-      DLOG(ERROR) << "Experiment and params should be separated by ':'";
-      return false;
-    }
-
-    std::vector<std::string> group_parts = base::SplitString(
-        experiment[0], ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
-    if (group_parts.size() != 2) {
-      DLOG(ERROR) << "Trial and group name should be separated by '.'";
-      return false;
-    }
-
-    std::vector<std::string> key_values = base::SplitString(
-        experiment[1], "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
-    if (key_values.size() % 2 != 0) {
-      DLOG(ERROR) << "Param name and param value should be separated by '/'";
-      return false;
-    }
-    std::string trial = UnescapeValue(group_parts[0]);
-    std::string group = UnescapeValue(group_parts[1]);
-    auto trial_group = std::make_pair(trial, group);
-    if (trial_groups.find(trial_group) != trial_groups.end()) {
-      DLOG(ERROR) << base::StringPrintf(
-          "A (trial, group) pair listed more than once. (%s, %s)",
-          trial.c_str(), group.c_str());
-      return false;
-    }
-    trial_groups.insert(trial_group);
-    std::map<std::string, std::string> params;
-    for (size_t i = 0; i < key_values.size(); i += 2) {
-      std::string key = UnescapeValue(key_values[i]);
-      std::string value = UnescapeValue(key_values[i + 1]);
-      params[key] = value;
-    }
-    bool result = AssociateVariationParams(trial, group, params);
-    if (!result) {
-      DLOG(ERROR) << "Failed to associate variation params for group \""
-                  << group << "\" in trial \"" << trial << "\"";
-      return false;
-    }
-  }
-  return true;
+  return base::AssociateFieldTrialParamsFromString(varations_string,
+                                                   &UnescapeValue);
 }
 
 void AssociateParamsFromFieldTrialConfig(
diff --git a/components/variations/variations_params_manager.h b/components/variations/variations_params_manager.h
index 2c61c52..0d63420 100644
--- a/components/variations/variations_params_manager.h
+++ b/components/variations/variations_params_manager.h
@@ -11,7 +11,6 @@
 #include <string>
 
 #include "base/macros.h"
-#include "base/metrics/field_trial.h"
 
 namespace base {
 class CommandLine;
@@ -26,6 +25,10 @@
 namespace variations {
 namespace testing {
 
+// NOTE: THIS CLASS IS DEPRECATED. Please use ScopedFeatureList instead, which
+// provides equivalent functionality.
+// TODO(asvitkine): Migrate callers and remove this class.
+//
 // Use this class as a member in your test class to set variation params for
 // your tests. You can directly set the parameters in the constructor (if they
 // are used by other members upon construction). You can change them later
diff --git a/components/viz/service/display_embedder/output_surface_provider_impl.cc b/components/viz/service/display_embedder/output_surface_provider_impl.cc
index f617eac..c01f3bc 100644
--- a/components/viz/service/display_embedder/output_surface_provider_impl.cc
+++ b/components/viz/service/display_embedder/output_surface_provider_impl.cc
@@ -173,9 +173,10 @@
 
       if (IsFatalOrSurfaceFailure(context_result)) {
 #if defined(OS_CHROMEOS) || defined(IS_CHROMECAST)
-        // TODO(kylechar): Chrome OS can't disable GPU compositing. This needs
-        // to be handled similar to Android.
-        CHECK(false);
+        // GL compositing is expected to always work on Chrome OS so we should
+        // never encounter fatal context error. This could be an unrecoverable
+        // hardware error or a bug.
+        LOG(FATAL) << "Unexpected fatal context error";
 #elif !defined(OS_ANDROID)
         gpu_service_impl_->DisableGpuCompositing();
 #endif
diff --git a/content/app/content_main_runner_impl.cc b/content/app/content_main_runner_impl.cc
index 2d853dec..275413c 100644
--- a/content/app/content_main_runner_impl.cc
+++ b/content/app/content_main_runner_impl.cc
@@ -885,8 +885,12 @@
   bool should_start_service_manager_only = start_service_manager_only;
   if (!service_manager_environment_) {
     if (delegate_->ShouldCreateFeatureList()) {
-      DCHECK(!field_trial_list_);
-      field_trial_list_ = SetUpFieldTrialsAndFeatureList();
+      // This is intentionally leaked since it needs to live for the duration
+      // of the process and there's no benefit in cleaning it up at exit.
+      base::FieldTrialList* leaked_field_trial_list =
+          SetUpFieldTrialsAndFeatureList().release();
+      ANNOTATE_LEAKING_OBJECT_PTR(leaked_field_trial_list);
+      ignore_result(leaked_field_trial_list);
       delegate_->PostFieldTrialInitialization();
     }
 
diff --git a/content/app/content_main_runner_impl.h b/content/app/content_main_runner_impl.h
index 513ff1f..6f1803aa 100644
--- a/content/app/content_main_runner_impl.h
+++ b/content/app/content_main_runner_impl.h
@@ -62,7 +62,6 @@
   std::unique_ptr<discardable_memory::DiscardableSharedMemoryManager>
       discardable_shared_memory_manager_;
   std::unique_ptr<StartupDataImpl> startup_data_;
-  std::unique_ptr<base::FieldTrialList> field_trial_list_;
   std::unique_ptr<base::PowerMonitor> power_monitor_;
   std::unique_ptr<ServiceManagerEnvironment> service_manager_environment_;
 #endif  // !defined(CHROME_MULTIPLE_DLL_CHILD)
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index da8cd31..e19a600 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -619,6 +619,13 @@
     "cache_storage/cache_storage_scheduler_types.h",
     "cache_storage/cache_storage_trace_utils.cc",
     "cache_storage/cache_storage_trace_utils.h",
+    "cache_storage/cross_sequence/cross_sequence_cache_storage.cc",
+    "cache_storage/cross_sequence/cross_sequence_cache_storage.h",
+    "cache_storage/cross_sequence/cross_sequence_cache_storage_cache.cc",
+    "cache_storage/cross_sequence/cross_sequence_cache_storage_cache.h",
+    "cache_storage/cross_sequence/cross_sequence_cache_storage_manager.cc",
+    "cache_storage/cross_sequence/cross_sequence_cache_storage_manager.h",
+    "cache_storage/cross_sequence/cross_sequence_utils.h",
     "cache_storage/legacy/legacy_cache_storage.cc",
     "cache_storage/legacy/legacy_cache_storage.h",
     "cache_storage/legacy/legacy_cache_storage_cache.cc",
diff --git a/content/browser/accessibility/web_contents_accessibility_android.cc b/content/browser/accessibility/web_contents_accessibility_android.cc
index 25af4dc..549931e 100644
--- a/content/browser/accessibility/web_contents_accessibility_android.cc
+++ b/content/browser/accessibility/web_contents_accessibility_android.cc
@@ -338,9 +338,9 @@
     RenderWidgetHostViewAndroid* old_rwhva,
     RenderWidgetHostViewAndroid* new_rwhva) {
   if (old_rwhva)
-    old_rwhva->set_web_contents_accessibility(nullptr);
+    old_rwhva->SetWebContentsAccessibility(nullptr);
   if (new_rwhva)
-    new_rwhva->set_web_contents_accessibility(accessibility_.get());
+    new_rwhva->SetWebContentsAccessibility(accessibility_.get());
 }
 
 WebContentsAccessibilityAndroid::WebContentsAccessibilityAndroid(
diff --git a/content/browser/background_fetch/background_fetch_data_manager.cc b/content/browser/background_fetch/background_fetch_data_manager.cc
index 50b2232..33b6420 100644
--- a/content/browser/background_fetch/background_fetch_data_manager.cc
+++ b/content/browser/background_fetch/background_fetch_data_manager.cc
@@ -57,9 +57,8 @@
 
 void BackgroundFetchDataManager::InitializeOnIOThread() {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  // The CacheStorageManager can only be accessed from the IO thread.
-  cache_manager_ =
-      base::WrapRefCounted(cache_storage_context_->cache_manager());
+
+  cache_manager_ = cache_storage_context_->CacheManager();
 
   // Delete inactive registrations still in the DB.
   Cleanup();
diff --git a/content/browser/cache_storage/cache_storage_context_impl.cc b/content/browser/cache_storage/cache_storage_context_impl.cc
index 8bad367..e8eedcc 100644
--- a/content/browser/cache_storage/cache_storage_context_impl.cc
+++ b/content/browser/cache_storage/cache_storage_context_impl.cc
@@ -12,6 +12,7 @@
 #include "content/browser/blob_storage/chrome_blob_storage_context.h"
 #include "content/browser/cache_storage/cache_storage_dispatcher_host.h"
 #include "content/browser/cache_storage/cache_storage_quota_client.h"
+#include "content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_manager.h"
 #include "content/browser/cache_storage/legacy/legacy_cache_storage_manager.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -90,9 +91,15 @@
                         std::move(request), origin);
 }
 
-CacheStorageManager* CacheStorageContextImpl::cache_manager() const {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  return cache_manager_.get();
+scoped_refptr<CacheStorageManager> CacheStorageContextImpl::CacheManager() {
+  // If we're shutdown or already on the target sequence, then just return the
+  // real manager.
+  if (!cache_manager_ || task_runner_->RunsTasksInCurrentSequence())
+    return cache_manager_;
+  // Otherwise we have to create a cross-sequence wrapper to provide safe
+  // access.
+  return base::MakeRefCounted<CrossSequenceCacheStorageManager>(task_runner_,
+                                                                cache_manager_);
 }
 
 void CacheStorageContextImpl::SetBlobParametersForCache(
@@ -123,11 +130,11 @@
       base::BindOnce(
           [](scoped_refptr<CacheStorageContextImpl> context,
              GetUsageInfoCallback callback) {
-            if (!context->cache_manager()) {
+            if (!context->CacheManager()) {
               std::move(callback).Run(std::vector<StorageUsageInfo>());
               return;
             }
-            context->cache_manager()->GetAllOriginsUsage(
+            context->CacheManager()->GetAllOriginsUsage(
                 CacheStorageOwner::kCacheAPI, std::move(callback));
           },
           base::RetainedRef(this), std::move(callback)));
@@ -139,9 +146,9 @@
                          base::BindOnce(
                              [](scoped_refptr<CacheStorageContextImpl> context,
                                 const GURL& origin) {
-                               if (!context->cache_manager())
+                               if (!context->CacheManager())
                                  return;
-                               context->cache_manager()->DeleteOriginData(
+                               context->CacheManager()->DeleteOriginData(
                                    url::Origin::Create(origin),
                                    CacheStorageOwner::kCacheAPI);
                              },
@@ -232,9 +239,9 @@
   if (!quota_manager_proxy.get())
     return;
   quota_manager_proxy->RegisterClient(new CacheStorageQuotaClient(
-      cache_manager(), CacheStorageOwner::kCacheAPI));
+      CacheManager(), CacheStorageOwner::kCacheAPI));
   quota_manager_proxy->RegisterClient(new CacheStorageQuotaClient(
-      cache_manager(), CacheStorageOwner::kBackgroundFetch));
+      CacheManager(), CacheStorageOwner::kBackgroundFetch));
 }
 
 }  // namespace content
diff --git a/content/browser/cache_storage/cache_storage_context_impl.h b/content/browser/cache_storage/cache_storage_context_impl.h
index d663c51..4d0b6b7 100644
--- a/content/browser/cache_storage/cache_storage_context_impl.h
+++ b/content/browser/cache_storage/cache_storage_context_impl.h
@@ -68,7 +68,11 @@
   void AddBinding(blink::mojom::CacheStorageRequest request,
                   const url::Origin& origin);
 
-  CacheStorageManager* cache_manager() const;
+  // Callable on any sequence.  If called on the cache_storage target sequence
+  // the real manager will be returned directly.  If called on any other
+  // sequence then a cross-sequence wrapper object will be created and returned
+  // instead.
+  scoped_refptr<CacheStorageManager> CacheManager();
 
   bool is_incognito() const { return is_incognito_; }
 
diff --git a/content/browser/cache_storage/cache_storage_dispatcher_host.cc b/content/browser/cache_storage/cache_storage_dispatcher_host.cc
index 48f943a..eb3a7cd 100644
--- a/content/browser/cache_storage/cache_storage_dispatcher_host.cc
+++ b/content/browser/cache_storage/cache_storage_dispatcher_host.cc
@@ -662,11 +662,11 @@
 CacheStorageHandle CacheStorageDispatcherHost::OpenCacheStorage(
     const url::Origin& origin) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!context_ || !context_->cache_manager() ||
+  if (!context_ || !context_->CacheManager() ||
       !OriginCanAccessCacheStorage(origin))
     return CacheStorageHandle();
 
-  return context_->cache_manager()->OpenCacheStorage(
+  return context_->CacheManager()->OpenCacheStorage(
       origin, CacheStorageOwner::kCacheAPI);
 }
 
diff --git a/content/browser/cache_storage/cache_storage_manager_unittest.cc b/content/browser/cache_storage/cache_storage_manager_unittest.cc
index 1b8871a..64c2c0d 100644
--- a/content/browser/cache_storage/cache_storage_manager_unittest.cc
+++ b/content/browser/cache_storage/cache_storage_manager_unittest.cc
@@ -31,6 +31,7 @@
 #include "content/browser/cache_storage/cache_storage_context_impl.h"
 #include "content/browser/cache_storage/cache_storage_quota_client.h"
 #include "content/browser/cache_storage/cache_storage_scheduler.h"
+#include "content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_manager.h"
 #include "content/browser/cache_storage/legacy/legacy_cache_storage.h"
 #include "content/browser/cache_storage/legacy/legacy_cache_storage_manager.h"
 #include "content/common/background_fetch/background_fetch_types.h"
@@ -66,6 +67,24 @@
 namespace content {
 namespace cache_storage_manager_unittest {
 
+enum class TestManager {
+  kLegacy,
+  kCrossSequence,
+};
+
+enum class TestStorage {
+  kDisk,
+  kMemory,
+};
+
+struct Param {
+  Param(TestManager manager, TestStorage storage)
+      : manager_(manager), storage_(storage) {}
+
+  TestManager manager_;
+  TestStorage storage_;
+};
+
 using blink::mojom::StorageType;
 using ResponseHeaderMap = base::flat_map<std::string, std::string>;
 
@@ -247,6 +266,7 @@
   }
 
   virtual bool MemoryOnly() { return false; }
+  virtual TestManager ManagerType() { return TestManager::kLegacy; }
 
   void BoolCallback(base::RunLoop* run_loop, bool value) {
     callback_bool_ = value;
@@ -336,15 +356,34 @@
     quota_manager_proxy_ = new MockCacheStorageQuotaManagerProxy(
         mock_quota_manager_.get(), base::ThreadTaskRunnerHandle::Get().get());
 
-    cache_manager_ = LegacyCacheStorageManager::Create(
+    auto legacy_manager = LegacyCacheStorageManager::Create(
         temp_dir_path, base::ThreadTaskRunnerHandle::Get(),
         base::ThreadTaskRunnerHandle::Get(), quota_manager_proxy_, observers_);
 
-    cache_manager_->SetBlobParametersForCache(
+    legacy_manager->SetBlobParametersForCache(
         blob_storage_context->context()->AsWeakPtr());
+
+    switch (ManagerType()) {
+      case TestManager::kLegacy:
+        cache_manager_ = std::move(legacy_manager);
+        break;
+      case TestManager::kCrossSequence:
+        cache_manager_ = base::MakeRefCounted<CrossSequenceCacheStorageManager>(
+            base::ThreadTaskRunnerHandle::Get(), std::move(legacy_manager));
+        break;
+    }
+  }
+
+  void RecreateStorageManager() {
+    DCHECK(cache_manager_);
+    auto* legacy_manager =
+        static_cast<LegacyCacheStorageManager*>(cache_manager_.get());
+    cache_manager_ =
+        LegacyCacheStorageManager::CreateForTesting(legacy_manager);
   }
 
   bool FlushCacheStorageIndex(const url::Origin& origin) {
+    DCHECK(ManagerType() == TestManager::kLegacy);
     callback_bool_ = false;
     base::RunLoop loop;
     auto* impl = LegacyCacheStorage::From(CacheStorageForOrigin(origin));
@@ -692,6 +731,7 @@
   }
 
   int64_t GetSizeThenCloseAllCaches(const url::Origin& origin) {
+    DCHECK(ManagerType() == TestManager::kLegacy);
     base::RunLoop loop;
     CacheStorageHandle cache_storage = CacheStorageForOrigin(origin);
     LegacyCacheStorage::From(cache_storage)
@@ -703,6 +743,7 @@
   }
 
   int64_t Size(const url::Origin& origin) {
+    DCHECK(ManagerType() == TestManager::kLegacy);
     base::RunLoop loop;
     CacheStorageHandle cache_storage = CacheStorageForOrigin(origin);
     LegacyCacheStorage::From(cache_storage)
@@ -747,7 +788,7 @@
   scoped_refptr<MockQuotaManager> mock_quota_manager_;
   scoped_refptr<MockCacheStorageQuotaManagerProxy> quota_manager_proxy_;
   scoped_refptr<CacheStorageContextImpl::ObserverList> observers_;
-  scoped_refptr<LegacyCacheStorageManager> cache_manager_;
+  scoped_refptr<CacheStorageManager> cache_manager_;
 
   CacheStorageCacheHandle callback_cache_handle_;
   int callback_bool_;
@@ -772,9 +813,25 @@
 };
 
 class CacheStorageManagerTestP : public CacheStorageManagerTest,
-                                 public testing::WithParamInterface<bool> {
+                                 public testing::WithParamInterface<Param> {
  public:
-  bool MemoryOnly() override { return !GetParam(); }
+  bool MemoryOnly() override {
+    return GetParam().storage_ == TestStorage::kMemory;
+  }
+  TestManager ManagerType() override { return GetParam().manager_; }
+};
+
+// Some tests must be run on the LegacyCacheStorageManager.  This could
+// be for a number of reasons:
+//  * The test needs to use internal APIs on the legacy manager.
+//  * The test is checking behavior that is only true for "real" manager's
+//    like that Open() will return the exact same c++ pointer for the
+//    underlying cache.  This assumption is not truee for the cross-sequence
+//    wrapper.
+class CacheStorageManagerLegacyOnlyTestP
+    : public CacheStorageManagerTest,
+      public testing::WithParamInterface<TestStorage> {
+  bool MemoryOnly() override { return GetParam() == TestStorage::kMemory; }
 };
 
 TEST_F(CacheStorageManagerTest, TestsRunOnIOThread) {
@@ -811,7 +868,7 @@
   EXPECT_NE(cache_handle.value(), callback_cache_handle_.value());
 }
 
-TEST_P(CacheStorageManagerTestP, OpenExistingCache) {
+TEST_P(CacheStorageManagerLegacyOnlyTestP, OpenExistingCache) {
   EXPECT_TRUE(Open(origin1_, "foo"));
   CacheStorageCacheHandle cache_handle = std::move(callback_cache_handle_);
   EXPECT_TRUE(Open(origin1_, "foo"));
@@ -1056,7 +1113,7 @@
   EXPECT_TRUE(StorageMatchAll(origin1_, GURL("http://example.com/foo")));
 }
 
-TEST_P(CacheStorageManagerTestP, Chinese) {
+TEST_P(CacheStorageManagerLegacyOnlyTestP, Chinese) {
   EXPECT_TRUE(Open(origin1_, "你好"));
   CacheStorageCacheHandle cache_handle = std::move(callback_cache_handle_);
   EXPECT_TRUE(Open(origin1_, "你好"));
@@ -1084,8 +1141,7 @@
   EXPECT_TRUE(Open(origin2_, "raz"));
   EXPECT_TRUE(Delete(origin1_, "bar"));
   quota_manager_proxy_->SimulateQuotaManagerDestroyed();
-  cache_manager_ =
-      LegacyCacheStorageManager::CreateForTesting(cache_manager_.get());
+  RecreateStorageManager();
   EXPECT_EQ(2u, Keys(origin1_));
   std::vector<std::string> expected_keys;
   expected_keys.push_back("foo");
@@ -1097,8 +1153,7 @@
   EXPECT_TRUE(Open(origin1_, "foo"));
   EXPECT_TRUE(Open(origin2_, "baz"));
   quota_manager_proxy_->SimulateQuotaManagerDestroyed();
-  cache_manager_ =
-      LegacyCacheStorageManager::CreateForTesting(cache_manager_.get());
+  RecreateStorageManager();
   EXPECT_EQ(0u, Keys(origin1_));
 }
 
@@ -1399,8 +1454,7 @@
   // Create a new CacheStorageManager that hasn't yet loaded the origin.
   CreateStorageManager();
   quota_manager_proxy_->SimulateQuotaManagerDestroyed();
-  cache_manager_ =
-      LegacyCacheStorageManager::CreateForTesting(cache_manager_.get());
+  RecreateStorageManager();
   EXPECT_TRUE(Open(origin1_, kCacheName));
 
   base::RunLoop().RunUntilIdle();
@@ -1472,8 +1526,7 @@
   // Create a new CacheStorageManager that hasn't yet loaded the origin.
   CreateStorageManager();
   quota_manager_proxy_->SimulateQuotaManagerDestroyed();
-  cache_manager_ =
-      LegacyCacheStorageManager::CreateForTesting(cache_manager_.get());
+  RecreateStorageManager();
 
   // Reopening the origin/cache creates a new CacheStorage instance with a new
   // random key.
@@ -1515,7 +1568,7 @@
   EXPECT_TRUE(callback_cache_handle_.value());
 }
 
-TEST_P(CacheStorageManagerTestP, OpenRunsSerially) {
+TEST_P(CacheStorageManagerLegacyOnlyTestP, OpenRunsSerially) {
   EXPECT_FALSE(Delete(origin1_, "tmp"));  // Init storage.
   CacheStorageHandle cache_storage = CacheStorageForOrigin(origin1_);
   auto* impl = LegacyCacheStorage::From(cache_storage);
@@ -1667,8 +1720,7 @@
   // Create a new CacheStorageManager that hasn't yet loaded the origin.
   CreateStorageManager();
   quota_manager_proxy_->SimulateQuotaManagerDestroyed();
-  cache_manager_ =
-      LegacyCacheStorageManager::CreateForTesting(cache_manager_.get());
+  RecreateStorageManager();
 
   // Create a second value (V2) in the cache.
   EXPECT_TRUE(Open(origin1_, kCacheName));
@@ -1736,8 +1788,7 @@
   // Create a new CacheStorageManager that hasn't yet loaded the origin.
   CreateStorageManager();
   quota_manager_proxy_->SimulateQuotaManagerDestroyed();
-  cache_manager_ =
-      LegacyCacheStorageManager::CreateForTesting(cache_manager_.get());
+  RecreateStorageManager();
 
   // Reopen the cache and write a second value (V2).
   EXPECT_TRUE(Open(origin1_, kCacheName));
@@ -1766,7 +1817,7 @@
   EXPECT_EQ(cache_size_v2, Size(origin1_));
 }
 
-TEST_P(CacheStorageManagerTestP, GetSizeThenCloseAllCaches) {
+TEST_P(CacheStorageManagerLegacyOnlyTestP, GetSizeThenCloseAllCaches) {
   EXPECT_TRUE(Open(origin1_, "foo"));
   EXPECT_TRUE(
       CachePut(callback_cache_handle_.value(), GURL("http://example.com/foo")));
@@ -1784,7 +1835,7 @@
       CachePut(callback_cache_handle_.value(), GURL("http://example.com/baz")));
 }
 
-TEST_P(CacheStorageManagerTestP, GetSizeThenCloseAllCachesTwoOwners) {
+TEST_P(CacheStorageManagerLegacyOnlyTestP, GetSizeThenCloseAllCachesTwoOwners) {
   EXPECT_TRUE(Open(origin1_, "foo", CacheStorageOwner::kCacheAPI));
   CacheStorageCacheHandle public_handle = std::move(callback_cache_handle_);
   EXPECT_TRUE(Open(origin1_, "foo", CacheStorageOwner::kBackgroundFetch));
@@ -1801,7 +1852,8 @@
   EXPECT_FALSE(CachePut(public_handle.value(), GURL("http://example.com/baz")));
 }
 
-TEST_P(CacheStorageManagerTestP, GetSizeThenCloseAllCachesAfterDelete) {
+TEST_P(CacheStorageManagerLegacyOnlyTestP,
+       GetSizeThenCloseAllCachesAfterDelete) {
   // Tests that doomed caches are also deleted by GetSizeThenCloseAllCaches.
   EXPECT_TRUE(Open(origin1_, "foo"));
   EXPECT_TRUE(
@@ -1830,8 +1882,10 @@
       CachePut(callback_cache_handle_.value(), GURL("http://example.com/foo")));
 
   // Create an unreferenced directory next to the referenced one.
+  auto* legacy_manager =
+      static_cast<LegacyCacheStorageManager*>(cache_manager_.get());
   base::FilePath origin_path = LegacyCacheStorageManager::ConstructOriginPath(
-      cache_manager_->root_path(), origin1_, CacheStorageOwner::kCacheAPI);
+      legacy_manager->root_path(), origin1_, CacheStorageOwner::kCacheAPI);
   base::FilePath unreferenced_path = origin_path.AppendASCII("bar");
   EXPECT_TRUE(CreateDirectory(unreferenced_path));
   EXPECT_TRUE(base::DirectoryExists(unreferenced_path));
@@ -1839,8 +1893,7 @@
   // Create a new StorageManager so that the next time the cache is opened
   // the unreferenced directory can be deleted.
   quota_manager_proxy_->SimulateQuotaManagerDestroyed();
-  cache_manager_ =
-      LegacyCacheStorageManager::CreateForTesting(cache_manager_.get());
+  RecreateStorageManager();
 
   // Verify that the referenced cache still works.
   EXPECT_TRUE(Open(origin1_, "foo"));
@@ -1887,14 +1940,14 @@
   EXPECT_EQ(1, quota_manager_proxy_->notify_storage_accessed_count());
 }
 
-TEST_P(CacheStorageManagerTestP, SizeStorageAccessed) {
+TEST_P(CacheStorageManagerLegacyOnlyTestP, SizeStorageAccessed) {
   EXPECT_EQ(0, Size(origin1_));
   // Size is not part of the web API and should not notify the quota manager of
   // an access.
   EXPECT_EQ(0, quota_manager_proxy_->notify_storage_accessed_count());
 }
 
-TEST_P(CacheStorageManagerTestP, SizeThenCloseStorageAccessed) {
+TEST_P(CacheStorageManagerLegacyOnlyTestP, SizeThenCloseStorageAccessed) {
   EXPECT_EQ(0, GetSizeThenCloseAllCaches(origin1_));
   // GetSizeThenCloseAllCaches is not part of the web API and should not notify
   // the quota manager of an access.
@@ -2341,8 +2394,11 @@
 };
 
 class CacheStorageQuotaClientTestP : public CacheStorageQuotaClientTest,
-                                     public testing::WithParamInterface<bool> {
-  bool MemoryOnly() override { return !GetParam(); }
+                                     public testing::WithParamInterface<Param> {
+  bool MemoryOnly() override {
+    return GetParam().storage_ == TestStorage::kMemory;
+  }
+  TestManager ManagerType() override { return GetParam().manager_; }
 };
 
 TEST_P(CacheStorageQuotaClientTestP, QuotaID) {
@@ -2430,8 +2486,7 @@
 
   // Create a new CacheStorageManager that hasn't yet loaded the origin.
   quota_manager_proxy_->SimulateQuotaManagerDestroyed();
-  cache_manager_ =
-      LegacyCacheStorageManager::CreateForTesting(cache_manager_.get());
+  RecreateStorageManager();
   quota_client_ = std::make_unique<CacheStorageQuotaClient>(
       cache_manager_, CacheStorageOwner::kCacheAPI);
 
@@ -2447,13 +2502,26 @@
   EXPECT_FALSE(QuotaDoesSupport(StorageType::kUnknown));
 }
 
-INSTANTIATE_TEST_SUITE_P(CacheStorageManagerTests,
-                         CacheStorageManagerTestP,
-                         ::testing::Values(false, true));
+INSTANTIATE_TEST_SUITE_P(
+    CacheStorageManagerTests,
+    CacheStorageManagerTestP,
+    ::testing::Values(Param(TestManager::kLegacy, TestStorage::kMemory),
+                      Param(TestManager::kLegacy, TestStorage::kDisk),
+                      Param(TestManager::kCrossSequence, TestStorage::kMemory),
+                      Param(TestManager::kCrossSequence, TestStorage::kDisk)));
 
-INSTANTIATE_TEST_SUITE_P(CacheStorageQuotaClientTests,
-                         CacheStorageQuotaClientTestP,
-                         ::testing::Values(false, true));
+INSTANTIATE_TEST_SUITE_P(CacheStorageManagerTests,
+                         CacheStorageManagerLegacyOnlyTestP,
+                         ::testing::Values(TestStorage::kMemory,
+                                           TestStorage::kDisk));
+
+INSTANTIATE_TEST_SUITE_P(
+    CacheStorageQuotaClientTests,
+    CacheStorageQuotaClientTestP,
+    ::testing::Values(Param(TestManager::kLegacy, TestStorage::kMemory),
+                      Param(TestManager::kLegacy, TestStorage::kDisk),
+                      Param(TestManager::kCrossSequence, TestStorage::kMemory),
+                      Param(TestManager::kCrossSequence, TestStorage::kDisk)));
 
 }  // namespace cache_storage_manager_unittest
 }  // namespace content
diff --git a/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage.cc b/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage.cc
new file mode 100644
index 0000000..3fc7dff
--- /dev/null
+++ b/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage.cc
@@ -0,0 +1,288 @@
+// 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 "content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage.h"
+
+#include "content/browser/cache_storage/cache_storage_histogram_utils.h"
+#include "content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_cache.h"
+#include "content/browser/cache_storage/cross_sequence/cross_sequence_utils.h"
+
+namespace content {
+
+// The Inner class is SequenceBound<> to the real target CacheStorage sequence
+// by the outer CrossSequenceCacheStorage.  All CacheStorage operations are
+// proxied to the Inner on the correct sequence via the Post() method.  The
+// outer storage is responsible for wrapping any callbacks in order to post on
+// the outer's original sequence.
+class CrossSequenceCacheStorage::Inner {
+ public:
+  using OpenCacheAdapterCallback =
+      base::OnceCallback<void(scoped_refptr<CrossSequenceCacheStorageCache>,
+                              blink::mojom::CacheStorageError)>;
+
+  Inner(const url::Origin& origin,
+        CacheStorageOwner owner,
+        scoped_refptr<CacheStorageManager> target_manager)
+      : handle_(target_manager->OpenCacheStorage(origin, owner)) {}
+
+  void OpenCache(scoped_refptr<CrossSequenceCacheStorageCache> cache_wrapper,
+                 const std::string& cache_name,
+                 int64_t trace_id,
+                 OpenCacheAdapterCallback adapter_callback) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    if (!handle_.value()) {
+      std::move(adapter_callback)
+          .Run(std::move(cache_wrapper),
+               MakeErrorStorage(ErrorStorageType::kStorageHandleNull));
+      return;
+    }
+
+    // Open the cache and set the handle on the wrapper object provided.  The
+    // wrapper will then be sent back to the source sequence to be exposed via
+    // its own handle.
+    handle_.value()->OpenCache(
+        cache_name, trace_id,
+        base::BindOnce(
+            [](scoped_refptr<CrossSequenceCacheStorageCache> cache_wrapper,
+               OpenCacheAdapterCallback adapter_callback,
+               CacheStorageCacheHandle handle,
+               blink::mojom::CacheStorageError error) {
+              // Called on target TaskRunner.
+              if (handle.value())
+                cache_wrapper->SetHandleOnTaskRunner(std::move(handle));
+              // Passing |cache_wrapper| back across the sequence boundary is
+              // safe because we are guaranteed this is the only reference to
+              // the object.
+              std::move(adapter_callback).Run(std::move(cache_wrapper), error);
+            },
+            std::move(cache_wrapper), std::move(adapter_callback)));
+  }
+
+  void HasCache(const std::string& cache_name,
+                int64_t trace_id,
+                BoolAndErrorCallback callback) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    if (!handle_.value()) {
+      std::move(callback).Run(
+          false, MakeErrorStorage(ErrorStorageType::kStorageHandleNull));
+      return;
+    }
+    handle_.value()->HasCache(cache_name, trace_id, std::move(callback));
+  }
+
+  void DoomCache(const std::string& cache_name,
+                 int64_t trace_id,
+                 ErrorCallback callback) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    if (!handle_.value()) {
+      std::move(callback).Run(
+          MakeErrorStorage(ErrorStorageType::kStorageHandleNull));
+      return;
+    }
+    handle_.value()->DoomCache(cache_name, trace_id, std::move(callback));
+  }
+
+  void EnumerateCaches(int64_t trace_id, EnumerateCachesCallback callback) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    if (!handle_.value()) {
+      std::move(callback).Run(std::vector<std::string>());
+      return;
+    }
+    handle_.value()->EnumerateCaches(trace_id, std::move(callback));
+  }
+
+  void MatchCache(const std::string& cache_name,
+                  blink::mojom::FetchAPIRequestPtr request,
+                  blink::mojom::CacheQueryOptionsPtr match_options,
+                  int64_t trace_id,
+                  CacheStorageCache::ResponseCallback callback) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    if (!handle_.value()) {
+      std::move(callback).Run(
+          MakeErrorStorage(ErrorStorageType::kStorageHandleNull), nullptr);
+      return;
+    }
+    handle_.value()->MatchCache(cache_name, std::move(request),
+                                std::move(match_options), trace_id,
+                                std::move(callback));
+  }
+
+  void MatchAllCaches(blink::mojom::FetchAPIRequestPtr request,
+                      blink::mojom::CacheQueryOptionsPtr match_options,
+                      int64_t trace_id,
+                      CacheStorageCache::ResponseCallback callback) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    if (!handle_.value()) {
+      std::move(callback).Run(
+          MakeErrorStorage(ErrorStorageType::kStorageHandleNull), nullptr);
+      return;
+    }
+    handle_.value()->MatchAllCaches(std::move(request),
+                                    std::move(match_options), trace_id,
+                                    std::move(callback));
+  }
+
+  void WriteToCache(const std::string& cache_name,
+                    blink::mojom::FetchAPIRequestPtr request,
+                    blink::mojom::FetchAPIResponsePtr response,
+                    int64_t trace_id,
+                    ErrorCallback callback) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    if (!handle_.value()) {
+      std::move(callback).Run(
+          MakeErrorStorage(ErrorStorageType::kStorageHandleNull));
+      return;
+    }
+    handle_.value()->WriteToCache(cache_name, std::move(request),
+                                  std::move(response), trace_id,
+                                  std::move(callback));
+  }
+
+ private:
+  const CacheStorageHandle handle_;
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+CrossSequenceCacheStorage::CrossSequenceCacheStorage(
+    const url::Origin& origin,
+    CacheStorageOwner owner,
+    scoped_refptr<base::SequencedTaskRunner> target_task_runner,
+    scoped_refptr<CacheStorageManager> target_manager)
+    : CacheStorage(origin),
+      target_task_runner_(std::move(target_task_runner)),
+      inner_(target_task_runner_,
+             origin,
+             std::move(owner),
+             std::move(target_manager)),
+      weak_factory_(this) {}
+
+CacheStorageHandle CrossSequenceCacheStorage::CreateHandle() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return CacheStorageHandle(weak_factory_.GetWeakPtr());
+}
+
+void CrossSequenceCacheStorage::AddHandleRef() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  handle_ref_count_ += 1;
+  if (handle_ref_count_ == 1)
+    self_ref_ = base::WrapRefCounted(this);
+}
+
+void CrossSequenceCacheStorage::DropHandleRef() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK_GT(handle_ref_count_, 0);
+  handle_ref_count_ -= 1;
+  if (handle_ref_count_ == 0)
+    self_ref_.reset();
+}
+
+void CrossSequenceCacheStorage::OpenCache(const std::string& cache_name,
+                                          int64_t trace_id,
+                                          CacheAndErrorCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Create our cross-sequence cache wrapper object first.  This will be sent
+  // down to the target TaskRunner with our open request.  It must already
+  // exist in order for the open request to set the real handle on it on the
+  // target TaskRunner.  If an error occurs the wrapper object is thrown
+  // away.
+  auto cache_wrapper =
+      base::MakeRefCounted<CrossSequenceCacheStorageCache>(target_task_runner_);
+
+  // After the open request sets the real handle on the target TaskRunner our
+  // cache wrapper will be passed back to this sequence.  We then create a
+  // handle to the wrapper and pass that to the external callback.
+  auto adapter_callback = base::BindOnce(
+      [](CacheAndErrorCallback inner_callback,
+         scoped_refptr<CrossSequenceCacheStorageCache> cache_wrapper,
+         blink::mojom::CacheStorageError error) {
+        if (error != blink::mojom::CacheStorageError::kSuccess) {
+          // Don't create a handle to the wrapper if there was an error.
+          // The |cache_wrapper| will be destroyed when it goes out of scope.
+          std::move(inner_callback).Run(CacheStorageCacheHandle(), error);
+          return;
+        }
+        // Called on source TaskRunner (thanks to callback wrapping below).
+        // Note, CreateHandle() will cause the cache to remain strongly
+        // referenced and survive even though |cache_wrapper| goes out of
+        // scope.
+        std::move(inner_callback).Run(cache_wrapper->CreateHandle(), error);
+      },
+      std::move(callback));
+
+  // We use our standard wrapping to ensure that we execute our adapter
+  // callback on the correct current sequence.
+  adapter_callback =
+      WrapCallbackForCurrentSequence(std::move(adapter_callback));
+
+  // Passing |cache_wrapper| across sequence boundaries is safe because
+  // we are guaranteed this is the only reference to the object.
+  inner_.Post(FROM_HERE, &Inner::OpenCache, std::move(cache_wrapper),
+              cache_name, trace_id, std::move(adapter_callback));
+}
+
+void CrossSequenceCacheStorage::HasCache(const std::string& cache_name,
+                                         int64_t trace_id,
+                                         BoolAndErrorCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  inner_.Post(FROM_HERE, &Inner::HasCache, cache_name, trace_id,
+              WrapCallbackForCurrentSequence(std::move(callback)));
+}
+
+void CrossSequenceCacheStorage::DoomCache(const std::string& cache_name,
+                                          int64_t trace_id,
+                                          ErrorCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  inner_.Post(FROM_HERE, &Inner::DoomCache, cache_name, trace_id,
+              WrapCallbackForCurrentSequence(std::move(callback)));
+}
+
+void CrossSequenceCacheStorage::EnumerateCaches(
+    int64_t trace_id,
+    EnumerateCachesCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  inner_.Post(FROM_HERE, &Inner::EnumerateCaches, trace_id,
+              WrapCallbackForCurrentSequence(std::move(callback)));
+}
+
+void CrossSequenceCacheStorage::MatchCache(
+    const std::string& cache_name,
+    blink::mojom::FetchAPIRequestPtr request,
+    blink::mojom::CacheQueryOptionsPtr match_options,
+    int64_t trace_id,
+    CacheStorageCache::ResponseCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  inner_.Post(FROM_HERE, &Inner::MatchCache, cache_name, std::move(request),
+              std::move(match_options), trace_id,
+              WrapCallbackForCurrentSequence(std::move(callback)));
+}
+
+void CrossSequenceCacheStorage::MatchAllCaches(
+    blink::mojom::FetchAPIRequestPtr request,
+    blink::mojom::CacheQueryOptionsPtr match_options,
+    int64_t trace_id,
+    CacheStorageCache::ResponseCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  inner_.Post(FROM_HERE, &Inner::MatchAllCaches, std::move(request),
+              std::move(match_options), trace_id,
+              WrapCallbackForCurrentSequence(std::move(callback)));
+}
+
+void CrossSequenceCacheStorage::WriteToCache(
+    const std::string& cache_name,
+    blink::mojom::FetchAPIRequestPtr request,
+    blink::mojom::FetchAPIResponsePtr response,
+    int64_t trace_id,
+    ErrorCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  inner_.Post(FROM_HERE, &Inner::WriteToCache, cache_name, std::move(request),
+              std::move(response), trace_id,
+              WrapCallbackForCurrentSequence(std::move(callback)));
+}
+
+CrossSequenceCacheStorage::~CrossSequenceCacheStorage() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+}  // namespace content
diff --git a/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage.h b/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage.h
new file mode 100644
index 0000000..a75301e
--- /dev/null
+++ b/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage.h
@@ -0,0 +1,88 @@
+// 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 CONTENT_BROWSER_CACHE_STORAGE_CROSS_SEQUENCE_CROSS_SEQUENCE_CACHE_STORAGE_H_
+#define CONTENT_BROWSER_CACHE_STORAGE_CROSS_SEQUENCE_CROSS_SEQUENCE_CACHE_STORAGE_H_
+
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/threading/sequence_bound.h"
+#include "content/browser/cache_storage/cache_storage.h"
+#include "content/browser/cache_storage/cache_storage_manager.h"
+
+namespace content {
+
+// A CacheStorage implementation that can be used from one sequence to access
+// a real CacheStorage executing on a different sequence.  The
+// CrossSequenceCacheStorageManager constructs instances of this class in
+// OpenCacheStorage().  Each CrossSequenceCacheStorage is ref-counted and
+// the existence of a CacheStorageHandle will cause the instance to hold a
+// scoped_refptr to itself.  Once all the handles have been dropped the
+// self-reference is also dropped allowing the CrossSequenceCacheStorage to
+// be destroyed.
+class CrossSequenceCacheStorage
+    : public CacheStorage,
+      public base::RefCounted<CrossSequenceCacheStorage> {
+ public:
+  CrossSequenceCacheStorage(
+      const url::Origin& origin,
+      CacheStorageOwner owner,
+      scoped_refptr<base::SequencedTaskRunner> target_task_runner,
+      scoped_refptr<CacheStorageManager> target_manager);
+
+  // CacheStorage
+  CacheStorageHandle CreateHandle() override;
+  void AddHandleRef() override;
+  void DropHandleRef() override;
+  void OpenCache(const std::string& cache_name,
+                 int64_t trace_id,
+                 CacheAndErrorCallback callback) override;
+  void HasCache(const std::string& cache_name,
+                int64_t trace_id,
+                BoolAndErrorCallback callback) override;
+  void DoomCache(const std::string& cache_name,
+                 int64_t trace_id,
+                 ErrorCallback callback) override;
+  void EnumerateCaches(int64_t trace_id,
+                       EnumerateCachesCallback callback) override;
+  void MatchCache(const std::string& cache_name,
+                  blink::mojom::FetchAPIRequestPtr request,
+                  blink::mojom::CacheQueryOptionsPtr match_options,
+                  int64_t trace_id,
+                  CacheStorageCache::ResponseCallback callback) override;
+  void MatchAllCaches(blink::mojom::FetchAPIRequestPtr request,
+                      blink::mojom::CacheQueryOptionsPtr match_options,
+                      int64_t trace_id,
+                      CacheStorageCache::ResponseCallback callback) override;
+  void WriteToCache(const std::string& cache_name,
+                    blink::mojom::FetchAPIRequestPtr request,
+                    blink::mojom::FetchAPIResponsePtr response,
+                    int64_t trace_id,
+                    ErrorCallback callback) override;
+
+ private:
+  friend class base::RefCounted<CrossSequenceCacheStorage>;
+  ~CrossSequenceCacheStorage() override;
+
+  const scoped_refptr<base::SequencedTaskRunner> target_task_runner_;
+
+  // The |inner_| object is SequenceBound<> to the target sequence used by the
+  // real CacheStorage.
+  class Inner;
+  base::SequenceBound<Inner> inner_;
+
+  // |self_ref_| holds a reference to the current |this| as long as
+  // |handle_ref_count_| is greater than zero.  The |handle_ref_count_| is
+  // incremented for every outstanding CacheStorageCacheHandle.
+  scoped_refptr<CrossSequenceCacheStorage> self_ref_;
+  int handle_ref_count_ = 0;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+  base::WeakPtrFactory<CrossSequenceCacheStorage> weak_factory_;
+  DISALLOW_COPY_AND_ASSIGN(CrossSequenceCacheStorage);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_CACHE_STORAGE_CROSS_SEQUENCE_CROSS_SEQUENCE_CACHE_STORAGE_H_
diff --git a/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_cache.cc b/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_cache.cc
new file mode 100644
index 0000000..75d3e9a
--- /dev/null
+++ b/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_cache.cc
@@ -0,0 +1,249 @@
+// 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 "content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_cache.h"
+
+#include "content/browser/cache_storage/cache_storage_histogram_utils.h"
+#include "content/browser/cache_storage/cross_sequence/cross_sequence_utils.h"
+
+namespace content {
+
+// The Inner class is SequenceBound<> to the real target CacheStorageCache
+// sequence by the outer CrossSequenceCacheStorageCache.  All CacheStorageCache
+// operations are proxied to the Inner on the correct sequence via the Post()
+// method.  The outer storage is responsible for wrapping any callbacks in
+// order to post on the outer's original sequence.
+class CrossSequenceCacheStorageCache::Inner {
+ public:
+  void SetHandle(CacheStorageCacheHandle handle) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    DCHECK(!handle_.value());
+    handle_ = std::move(handle);
+  }
+
+  void Match(blink::mojom::FetchAPIRequestPtr request,
+             blink::mojom::CacheQueryOptionsPtr match_options,
+             int64_t trace_id,
+             ResponseCallback callback) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    if (!handle_.value()) {
+      std::move(callback).Run(
+          MakeErrorStorage(ErrorStorageType::kStorageHandleNull), nullptr);
+      return;
+    }
+    handle_.value()->Match(std::move(request), std::move(match_options),
+                           trace_id, std::move(callback));
+  }
+
+  void MatchAll(blink::mojom::FetchAPIRequestPtr request,
+                blink::mojom::CacheQueryOptionsPtr match_options,
+                int64_t trace_id,
+                ResponsesCallback callback) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    if (!handle_.value()) {
+      std::move(callback).Run(
+          MakeErrorStorage(ErrorStorageType::kStorageHandleNull),
+          std::vector<blink::mojom::FetchAPIResponsePtr>());
+      return;
+    }
+    handle_.value()->MatchAll(std::move(request), std::move(match_options),
+                              trace_id, std::move(callback));
+  }
+
+  void WriteSideData(ErrorCallback callback,
+                     const GURL& url,
+                     base::Time expected_response_time,
+                     int64_t trace_id,
+                     scoped_refptr<net::IOBuffer> buffer,
+                     int buf_len) {
+    if (!handle_.value()) {
+      std::move(callback).Run(
+          MakeErrorStorage(ErrorStorageType::kStorageHandleNull));
+      return;
+    }
+    handle_.value()->WriteSideData(std::move(callback), url,
+                                   expected_response_time, trace_id,
+                                   std::move(buffer), buf_len);
+  }
+
+  void BatchOperation(std::vector<blink::mojom::BatchOperationPtr> operations,
+                      int64_t trace_id,
+                      VerboseErrorCallback callback,
+                      BadMessageCallback bad_message_callback) {
+    if (!handle_.value()) {
+      std::move(callback).Run(blink::mojom::CacheStorageVerboseError::New(
+          MakeErrorStorage(ErrorStorageType::kStorageHandleNull), nullptr));
+      return;
+    }
+    handle_.value()->BatchOperation(std::move(operations), trace_id,
+                                    std::move(callback),
+                                    std::move(bad_message_callback));
+  }
+
+  void Keys(blink::mojom::FetchAPIRequestPtr request,
+            blink::mojom::CacheQueryOptionsPtr options,
+            int64_t trace_id,
+            RequestsCallback callback) {
+    if (!handle_.value()) {
+      std::move(callback).Run(
+          MakeErrorStorage(ErrorStorageType::kStorageHandleNull), nullptr);
+      return;
+    }
+    handle_.value()->Keys(std::move(request), std::move(options), trace_id,
+                          std::move(callback));
+  }
+
+  void Put(blink::mojom::FetchAPIRequestPtr request,
+           blink::mojom::FetchAPIResponsePtr response,
+           int64_t trace_id,
+           ErrorCallback callback) {
+    if (!handle_.value()) {
+      std::move(callback).Run(
+          MakeErrorStorage(ErrorStorageType::kStorageHandleNull));
+      return;
+    }
+    handle_.value()->Put(std::move(request), std::move(response), trace_id,
+                         std::move(callback));
+  }
+
+  void GetAllMatchedEntries(blink::mojom::FetchAPIRequestPtr request,
+                            blink::mojom::CacheQueryOptionsPtr match_options,
+                            int64_t trace_id,
+                            CacheEntriesCallback callback) {
+    if (!handle_.value()) {
+      std::move(callback).Run(
+          MakeErrorStorage(ErrorStorageType::kStorageHandleNull),
+          std::vector<CacheEntry>());
+      return;
+    }
+    handle_.value()->GetAllMatchedEntries(std::move(request),
+                                          std::move(match_options), trace_id,
+                                          std::move(callback));
+  }
+
+ private:
+  CacheStorageCacheHandle handle_;
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+CrossSequenceCacheStorageCache::CrossSequenceCacheStorageCache(
+    scoped_refptr<base::SequencedTaskRunner> target_task_runner)
+    : inner_(std::move(target_task_runner)), weak_factory_(this) {}
+
+void CrossSequenceCacheStorageCache::SetHandleOnTaskRunner(
+    CacheStorageCacheHandle handle) {
+  // Called on target sequence.
+  DCHECK(handle.value());
+  // Even though we are already on the correct sequence we still have to Post()
+  // to the inner object.
+  inner_.Post(FROM_HERE, &Inner::SetHandle, std::move(handle));
+}
+
+CacheStorageCacheHandle CrossSequenceCacheStorageCache::CreateHandle() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return CacheStorageCacheHandle(weak_factory_.GetWeakPtr());
+}
+
+void CrossSequenceCacheStorageCache::AddHandleRef() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  handle_ref_count_ += 1;
+  if (handle_ref_count_ == 1)
+    self_ref_ = base::WrapRefCounted(this);
+}
+
+void CrossSequenceCacheStorageCache::DropHandleRef() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK_GT(handle_ref_count_, 0);
+  handle_ref_count_ -= 1;
+  if (handle_ref_count_ == 0)
+    self_ref_.reset();
+}
+
+bool CrossSequenceCacheStorageCache::IsUnreferenced() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return handle_ref_count_ == 0;
+}
+
+void CrossSequenceCacheStorageCache::Match(
+    blink::mojom::FetchAPIRequestPtr request,
+    blink::mojom::CacheQueryOptionsPtr match_options,
+    int64_t trace_id,
+    ResponseCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  inner_.Post(FROM_HERE, &Inner::Match, std::move(request),
+              std::move(match_options), trace_id,
+              WrapCallbackForCurrentSequence(std::move(callback)));
+}
+
+void CrossSequenceCacheStorageCache::MatchAll(
+    blink::mojom::FetchAPIRequestPtr request,
+    blink::mojom::CacheQueryOptionsPtr match_options,
+    int64_t trace_id,
+    ResponsesCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  inner_.Post(FROM_HERE, &Inner::MatchAll, std::move(request),
+              std::move(match_options), trace_id,
+              WrapCallbackForCurrentSequence(std::move(callback)));
+}
+
+void CrossSequenceCacheStorageCache::WriteSideData(
+    ErrorCallback callback,
+    const GURL& url,
+    base::Time expected_response_time,
+    int64_t trace_id,
+    scoped_refptr<net::IOBuffer> buffer,
+    int buf_len) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  inner_.Post(FROM_HERE, &Inner::WriteSideData,
+              WrapCallbackForCurrentSequence(std::move(callback)), url,
+              expected_response_time, trace_id, std::move(buffer), buf_len);
+}
+
+void CrossSequenceCacheStorageCache::BatchOperation(
+    std::vector<blink::mojom::BatchOperationPtr> operations,
+    int64_t trace_id,
+    VerboseErrorCallback callback,
+    BadMessageCallback bad_message_callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  inner_.Post(FROM_HERE, &Inner::BatchOperation, std::move(operations),
+              trace_id, WrapCallbackForCurrentSequence(std::move(callback)),
+              WrapCallbackForCurrentSequence(std::move(bad_message_callback)));
+}
+
+void CrossSequenceCacheStorageCache::Keys(
+    blink::mojom::FetchAPIRequestPtr request,
+    blink::mojom::CacheQueryOptionsPtr options,
+    int64_t trace_id,
+    RequestsCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  inner_.Post(FROM_HERE, &Inner::Keys, std::move(request), std::move(options),
+              trace_id, WrapCallbackForCurrentSequence(std::move(callback)));
+}
+
+void CrossSequenceCacheStorageCache::Put(
+    blink::mojom::FetchAPIRequestPtr request,
+    blink::mojom::FetchAPIResponsePtr response,
+    int64_t trace_id,
+    ErrorCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  inner_.Post(FROM_HERE, &Inner::Put, std::move(request), std::move(response),
+              trace_id, WrapCallbackForCurrentSequence(std::move(callback)));
+}
+
+void CrossSequenceCacheStorageCache::GetAllMatchedEntries(
+    blink::mojom::FetchAPIRequestPtr request,
+    blink::mojom::CacheQueryOptionsPtr match_options,
+    int64_t trace_id,
+    CacheEntriesCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  inner_.Post(FROM_HERE, &Inner::GetAllMatchedEntries, std::move(request),
+              std::move(match_options), trace_id,
+              WrapCallbackForCurrentSequence(std::move(callback)));
+}
+
+CrossSequenceCacheStorageCache::~CrossSequenceCacheStorageCache() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+}  // namespace content
diff --git a/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_cache.h b/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_cache.h
new file mode 100644
index 0000000..e7817cf
--- /dev/null
+++ b/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_cache.h
@@ -0,0 +1,101 @@
+// 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 CONTENT_BROWSER_CACHE_STORAGE_CROSS_SEQUENCE_CROSS_SEQUENCE_CACHE_STORAGE_CACHE_H_
+#define CONTENT_BROWSER_CACHE_STORAGE_CROSS_SEQUENCE_CROSS_SEQUENCE_CACHE_STORAGE_CACHE_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/threading/sequence_bound.h"
+#include "content/browser/cache_storage/cache_storage_cache.h"
+
+namespace content {
+
+// A CacheStorageCache implementation that can be used from one sequence to
+// access a real CacheStorageCache executing on a different sequence.
+// CrossSequenceCacheStorage objects construct instances of this class in
+// OpenCache().  Each CrossSequenceCacheStorageCache is ref-counted and
+// the existence of a CacheStorageCacheHandle will cause the instance to hold a
+// scoped_refptr to itself.  Once all the handles have been dropped the
+// self-reference is also dropped allowing the CrossSequenceCacheStorage to
+// be destroyed.
+class CrossSequenceCacheStorageCache
+    : public CacheStorageCache,
+      public base::RefCounted<CrossSequenceCacheStorageCache> {
+ public:
+  // Created on the outer source sequence without any reference to a real
+  // cache yet.  SetHandleOnTaskRunner() must be called before any cache
+  // methods are invoked.
+  explicit CrossSequenceCacheStorageCache(
+      scoped_refptr<base::SequencedTaskRunner> target_task_runner);
+
+  // Called on the inner cache's sequence to set the real cache's handle.
+  void SetHandleOnTaskRunner(CacheStorageCacheHandle handle);
+
+  // CacheStorageCache
+  CacheStorageCacheHandle CreateHandle() override;
+  void AddHandleRef() override;
+  void DropHandleRef() override;
+  bool IsUnreferenced() const override;
+  void Match(blink::mojom::FetchAPIRequestPtr request,
+             blink::mojom::CacheQueryOptionsPtr match_options,
+             int64_t trace_id,
+             ResponseCallback callback) override;
+
+  void MatchAll(blink::mojom::FetchAPIRequestPtr request,
+                blink::mojom::CacheQueryOptionsPtr match_options,
+                int64_t trace_id,
+                ResponsesCallback callback) override;
+
+  void WriteSideData(ErrorCallback callback,
+                     const GURL& url,
+                     base::Time expected_response_time,
+                     int64_t trace_id,
+                     scoped_refptr<net::IOBuffer> buffer,
+                     int buf_len) override;
+
+  void BatchOperation(std::vector<blink::mojom::BatchOperationPtr> operations,
+                      int64_t trace_id,
+                      VerboseErrorCallback callback,
+                      BadMessageCallback bad_message_callback) override;
+
+  void Keys(blink::mojom::FetchAPIRequestPtr request,
+            blink::mojom::CacheQueryOptionsPtr options,
+            int64_t trace_id,
+            RequestsCallback callback) override;
+
+  void Put(blink::mojom::FetchAPIRequestPtr request,
+           blink::mojom::FetchAPIResponsePtr response,
+           int64_t trace_id,
+           ErrorCallback callback) override;
+
+  void GetAllMatchedEntries(blink::mojom::FetchAPIRequestPtr request,
+                            blink::mojom::CacheQueryOptionsPtr match_options,
+                            int64_t trace_id,
+                            CacheEntriesCallback callback) override;
+
+ private:
+  friend class base::RefCounted<CrossSequenceCacheStorageCache>;
+  ~CrossSequenceCacheStorageCache() override;
+
+  // The |inner_| object is SequenceBound<> to the target sequence used by the
+  // real CacheStorageCache.
+  class Inner;
+  base::SequenceBound<Inner> inner_;
+
+  // |self_ref_| holds a reference to the current |this| as long as
+  // |handle_ref_count_| is greater than zero.  The |handle_ref_count_| is
+  // incremented for every outstanding CacheStorageCacheHandle.
+  scoped_refptr<CrossSequenceCacheStorageCache> self_ref_;
+  int handle_ref_count_ = 0;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+  base::WeakPtrFactory<CrossSequenceCacheStorageCache> weak_factory_;
+  DISALLOW_COPY_AND_ASSIGN(CrossSequenceCacheStorageCache);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_CACHE_STORAGE_CROSS_SEQUENCE_CROSS_SEQUENCE_CACHE_STORAGE_CACHE_H_
diff --git a/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_manager.cc b/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_manager.cc
new file mode 100644
index 0000000..6eb46ac
--- /dev/null
+++ b/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_manager.cc
@@ -0,0 +1,143 @@
+// 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 "content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_manager.h"
+
+#include "content/browser/cache_storage/cache_storage.h"
+#include "content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage.h"
+#include "content/browser/cache_storage/cross_sequence/cross_sequence_utils.h"
+
+namespace content {
+
+// The Inner class is SequenceBound<> to the real target manager sequence by
+// the outer CrossSequenceCacheStorageManager.  All CacheStorageManager
+// operations are proxied to the Inner on the correct sequence via the Post()
+// method.  The outer manager is responsible for wrapping any callbacks in
+// order to post on the outer's original sequence.
+class CrossSequenceCacheStorageManager::Inner {
+ public:
+  explicit Inner(scoped_refptr<CacheStorageManager> target_manager)
+      : target_manager_(std::move(target_manager)) {}
+
+  void GetAllOriginsUsage(CacheStorageOwner owner,
+                          CacheStorageContext::GetUsageInfoCallback callback) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    target_manager_->GetAllOriginsUsage(owner, std::move(callback));
+  }
+
+  void GetOriginUsage(const url::Origin& origin_url,
+                      CacheStorageOwner owner,
+                      storage::QuotaClient::GetUsageCallback callback) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    target_manager_->GetOriginUsage(origin_url, owner, std::move(callback));
+  }
+
+  void GetOrigins(CacheStorageOwner owner,
+                  storage::QuotaClient::GetOriginsCallback callback) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    target_manager_->GetOrigins(owner, std::move(callback));
+  }
+
+  void GetOriginsForHost(const std::string& host,
+                         CacheStorageOwner owner,
+                         storage::QuotaClient::GetOriginsCallback callback) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    target_manager_->GetOriginsForHost(host, owner, std::move(callback));
+  }
+
+  void DeleteOriginData(const url::Origin& origin,
+                        CacheStorageOwner owner,
+                        storage::QuotaClient::DeletionCallback callback) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    target_manager_->DeleteOriginData(origin, owner, std::move(callback));
+  }
+
+ private:
+  const scoped_refptr<CacheStorageManager> target_manager_;
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+CrossSequenceCacheStorageManager::CrossSequenceCacheStorageManager(
+    scoped_refptr<base::SequencedTaskRunner> target_task_runner,
+    scoped_refptr<CacheStorageManager> target_manager)
+    : target_task_runner_(std::move(target_task_runner)),
+      target_manager_(target_manager),
+      inner_(target_task_runner_, std::move(target_manager_)) {}
+
+CacheStorageHandle CrossSequenceCacheStorageManager::OpenCacheStorage(
+    const url::Origin& origin,
+    CacheStorageOwner owner) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Construct the CrossSequenceCacheStorage object immediately on our current
+  // sequence.  This is necessary in order to return a Handle synchronously.
+  // The CrossSequenceCacheStorage object will asynchronously open the real
+  // CacheStorage on the correct sequence.
+  auto storage = base::MakeRefCounted<CrossSequenceCacheStorage>(
+      origin, owner, target_task_runner_, target_manager_);
+  return storage->CreateHandle();
+}
+
+void CrossSequenceCacheStorageManager::GetAllOriginsUsage(
+    CacheStorageOwner owner,
+    CacheStorageContext::GetUsageInfoCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  inner_.Post(FROM_HERE, &Inner::GetAllOriginsUsage, owner,
+              WrapCallbackForCurrentSequence(std::move(callback)));
+}
+
+void CrossSequenceCacheStorageManager::GetOriginUsage(
+    const url::Origin& origin_url,
+    CacheStorageOwner owner,
+    storage::QuotaClient::GetUsageCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  inner_.Post(FROM_HERE, &Inner::GetOriginUsage, origin_url, owner,
+              WrapCallbackForCurrentSequence(std::move(callback)));
+}
+
+void CrossSequenceCacheStorageManager::GetOrigins(
+    CacheStorageOwner owner,
+    storage::QuotaClient::GetOriginsCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  inner_.Post(FROM_HERE, &Inner::GetOrigins, owner,
+              WrapCallbackForCurrentSequence(std::move(callback)));
+}
+
+void CrossSequenceCacheStorageManager::GetOriginsForHost(
+    const std::string& host,
+    CacheStorageOwner owner,
+    storage::QuotaClient::GetOriginsCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  inner_.Post(FROM_HERE, &Inner::GetOriginsForHost, host, owner,
+              WrapCallbackForCurrentSequence(std::move(callback)));
+}
+
+void CrossSequenceCacheStorageManager::DeleteOriginData(
+    const url::Origin& origin,
+    CacheStorageOwner owner,
+    storage::QuotaClient::DeletionCallback callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  inner_.Post(FROM_HERE, &Inner::DeleteOriginData, origin, owner,
+              WrapCallbackForCurrentSequence(std::move(callback)));
+}
+
+void CrossSequenceCacheStorageManager::DeleteOriginData(
+    const url::Origin& origin,
+    CacheStorageOwner owner) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DeleteOriginData(origin, owner, base::DoNothing());
+}
+
+void CrossSequenceCacheStorageManager::SetBlobParametersForCache(
+    base::WeakPtr<storage::BlobStorageContext> blob_storage_context) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // This method is used for initialization of a real manager and should not
+  // be invoked for the cross-sequence wrapper.
+  NOTREACHED();
+}
+
+CrossSequenceCacheStorageManager::~CrossSequenceCacheStorageManager() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+}  // namespace content
diff --git a/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_manager.h b/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_manager.h
new file mode 100644
index 0000000..2852fda4
--- /dev/null
+++ b/content/browser/cache_storage/cross_sequence/cross_sequence_cache_storage_manager.h
@@ -0,0 +1,69 @@
+// 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 CONTENT_BROWSER_CACHE_STORAGE_CROSS_SEQUENCE_CROSS_SEQUENCE_CACHE_STORAGE_MANAGER_H_
+#define CONTENT_BROWSER_CACHE_STORAGE_CROSS_SEQUENCE_CROSS_SEQUENCE_CACHE_STORAGE_MANAGER_H_
+
+#include "base/sequence_checker.h"
+#include "base/threading/sequence_bound.h"
+#include "content/browser/cache_storage/cache_storage_manager.h"
+
+namespace content {
+
+// A CacheStorageManager implementation that can be used from one sequence to
+// access the real CacheStorageManager executing on a different sequence.  The
+// CacheStorageContextImpl will create one of these whenever code calls the
+// CacheManager() accessor on the wrong sequence.  The cross-sequence wrapper
+// manager is ref-counted and will only live as long as that client code keeps
+// its reference alive.  Each cross-sequence wrapper is locked to the sequence
+// on which it was created.
+class CONTENT_EXPORT CrossSequenceCacheStorageManager
+    : public CacheStorageManager {
+ public:
+  CrossSequenceCacheStorageManager(
+      scoped_refptr<base::SequencedTaskRunner> target_task_runner,
+      scoped_refptr<CacheStorageManager> target_manager);
+
+  // CacheStorageManager
+  CacheStorageHandle OpenCacheStorage(const url::Origin& origin,
+                                      CacheStorageOwner owner) override;
+  void GetAllOriginsUsage(
+      CacheStorageOwner owner,
+      CacheStorageContext::GetUsageInfoCallback callback) override;
+  void GetOriginUsage(const url::Origin& origin_url,
+                      CacheStorageOwner owner,
+                      storage::QuotaClient::GetUsageCallback callback) override;
+  void GetOrigins(CacheStorageOwner owner,
+                  storage::QuotaClient::GetOriginsCallback callback) override;
+  void GetOriginsForHost(
+      const std::string& host,
+      CacheStorageOwner owner,
+      storage::QuotaClient::GetOriginsCallback callback) override;
+  void DeleteOriginData(
+      const url::Origin& origin,
+      CacheStorageOwner owner,
+      storage::QuotaClient::DeletionCallback callback) override;
+  void DeleteOriginData(const url::Origin& origin,
+                        CacheStorageOwner owner) override;
+  void SetBlobParametersForCache(
+      base::WeakPtr<storage::BlobStorageContext> blob_storage_context) override;
+
+ private:
+  ~CrossSequenceCacheStorageManager() override;
+
+  const scoped_refptr<base::SequencedTaskRunner> target_task_runner_;
+  const scoped_refptr<CacheStorageManager> target_manager_;
+
+  // The |inner_| object is SequenceBound<> to the target sequence used by the
+  // real manager.
+  class Inner;
+  base::SequenceBound<Inner> inner_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+  DISALLOW_COPY_AND_ASSIGN(CrossSequenceCacheStorageManager);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_CACHE_STORAGE_CROSS_SEQUENCE_CROSS_SEQUENCE_CACHE_STORAGE_MANAGER_H_
diff --git a/content/browser/cache_storage/cross_sequence/cross_sequence_utils.h b/content/browser/cache_storage/cross_sequence/cross_sequence_utils.h
new file mode 100644
index 0000000..5efc734
--- /dev/null
+++ b/content/browser/cache_storage/cross_sequence/cross_sequence_utils.h
@@ -0,0 +1,42 @@
+// 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 CONTENT_BROWSER_CACHE_STORAGE_CROSS_SEQUENCE_CROSS_SEQUENCE_UTILS_H_
+#define CONTENT_BROWSER_CACHE_STORAGE_CROSS_SEQUENCE_CROSS_SEQUENCE_UTILS_H_
+
+namespace content {
+
+// Helper function for WrapCallbackForCurrentSequence().
+template <typename... Args>
+void RunWrappedCallbackOnTargetSequence(
+    base::OnceCallback<void(Args...)> callback,
+    Args... args) {
+  std::move(callback).Run(std::forward<Args>(args)...);
+}
+
+// Helper function for WrapCallbackForCurrentSequence().
+template <typename... Args>
+void RunWrappedCallbackOnOtherSequence(
+    scoped_refptr<base::SequencedTaskRunner> task_runner,
+    base::OnceCallback<void(Args...)> callback,
+    Args... args) {
+  task_runner->PostTask(
+      FROM_HERE,
+      base::BindOnce(&RunWrappedCallbackOnTargetSequence<Args...>,
+                     std::move(callback), std::forward<Args>(args)...));
+}
+
+// This function wraps a given OnceCallback<> such that it will perform a
+// PostTask() to the current sequence when the callback is invoked.
+template <typename... Args>
+base::OnceCallback<void(Args...)> WrapCallbackForCurrentSequence(
+    base::OnceCallback<void(Args...)> callback) {
+  return base::BindOnce(&RunWrappedCallbackOnOtherSequence<Args...>,
+                        base::SequencedTaskRunnerHandle::Get(),
+                        std::move(callback));
+}
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_CACHE_STORAGE_CROSS_SEQUENCE_CROSS_SEQUENCE_UTILS_H_
diff --git a/content/browser/frame_host/navigation_controller_impl_browsertest.cc b/content/browser/frame_host/navigation_controller_impl_browsertest.cc
index 7c130bec0..4cfcc32 100644
--- a/content/browser/frame_host/navigation_controller_impl_browsertest.cc
+++ b/content/browser/frame_host/navigation_controller_impl_browsertest.cc
@@ -9842,8 +9842,14 @@
 // When running OpenURL to an invalid URL on a frame proxy it should not spoof
 // the url by canceling a main frame navigation.
 // See https://crbug.com/966914.
+// Failing on Linux CFI. http://crbug.com/974319
+#if defined(OS_LINUX)
+#define MAYBE_CrossProcessIframeToInvalidURLCancelsRedirectSpoof DISABLED_CrossProcessIframeToInvalidURLCancelsRedirectSpoof
+#else
+#define MAYBE_CrossProcessIframeToInvalidURLCancelsRedirectSpoof CrossProcessIframeToInvalidURLCancelsRedirectSpoof
+#endif
 IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
-                       CrossProcessIframeToInvalidURLCancelsRedirectSpoof) {
+                       MAYBE_CrossProcessIframeToInvalidURLCancelsRedirectSpoof) {
   const GURL main_frame_url(embedded_test_server()->GetURL(
       "a.com",
       "/cross_site_iframe_factory.html?a(b{sandbox-allow-scripts,sandbox-allow-"
diff --git a/content/browser/frame_host/render_frame_message_filter.cc b/content/browser/frame_host/render_frame_message_filter.cc
index 8702473..a76f403 100644
--- a/content/browser/frame_host/render_frame_message_filter.cc
+++ b/content/browser/frame_host/render_frame_message_filter.cc
@@ -215,6 +215,12 @@
   // Return early if the frame has already been navigated away from.
   content::WebContents* web_contents =
       content::WebContents::FromRenderFrameHost(render_frame_host);
+
+  // |web_contents| will be null on interstitial pages, which means
+  // the frame has been navigated away from and it's safe to return early
+  if (!web_contents)
+    return;
+
   RenderFrameHostImpl* root_frame_host = render_frame_host;
   while (root_frame_host->GetParent() != nullptr)
     root_frame_host = root_frame_host->GetParent();
diff --git a/content/browser/media/hardware_key_media_controller.cc b/content/browser/media/hardware_key_media_controller.cc
index 402e55e..cbacde782 100644
--- a/content/browser/media/hardware_key_media_controller.cc
+++ b/content/browser/media/hardware_key_media_controller.cc
@@ -9,6 +9,8 @@
 #include <vector>
 
 #include "base/metrics/histogram_macros.h"
+#include "content/browser/browser_main_loop.h"
+#include "content/browser/media/media_keys_listener_manager_impl.h"
 #include "content/public/browser/media_keys_listener_manager.h"
 #include "services/media_session/public/mojom/constants.mojom.h"
 #include "services/media_session/public/mojom/media_session.mojom.h"
@@ -46,7 +48,14 @@
 
 void HardwareKeyMediaController::MediaSessionInfoChanged(
     media_session::mojom::MediaSessionInfoPtr session_info) {
+  MediaKeysListenerManagerImpl* media_keys_listener_manager_impl =
+      BrowserMainLoop::GetInstance()->media_keys_listener_manager();
+  DCHECK(media_keys_listener_manager_impl);
+
   session_info_ = std::move(session_info);
+  media_keys_listener_manager_impl->SetIsMediaPlaying(
+      session_info_ && session_info_->playback_state ==
+                           media_session::mojom::MediaPlaybackState::kPlaying);
 }
 
 void HardwareKeyMediaController::MediaSessionActionsChanged(
diff --git a/content/browser/media/media_keys_listener_manager_impl.cc b/content/browser/media/media_keys_listener_manager_impl.cc
index 2c6ae1b..a2e879f9 100644
--- a/content/browser/media/media_keys_listener_manager_impl.cc
+++ b/content/browser/media/media_keys_listener_manager_impl.cc
@@ -155,6 +155,16 @@
     delegate.OnMediaKeysAccelerator(accelerator);
 }
 
+void MediaKeysListenerManagerImpl::SetIsMediaPlaying(bool is_playing) {
+  if (is_media_playing_ == is_playing)
+    return;
+
+  is_media_playing_ = is_playing;
+
+  if (media_keys_listener_)
+    media_keys_listener_->SetIsMediaPlaying(is_media_playing_);
+}
+
 void MediaKeysListenerManagerImpl::EnsureAuxiliaryServices() {
   if (auxiliary_services_started_)
     return;
@@ -200,6 +210,8 @@
   media_keys_listener_ = ui::MediaKeysListener::Create(
       this, ui::MediaKeysListener::Scope::kGlobal);
   DCHECK(media_keys_listener_);
+
+  media_keys_listener_->SetIsMediaPlaying(is_media_playing_);
 }
 
 MediaKeysListenerManagerImpl::ListeningData*
diff --git a/content/browser/media/media_keys_listener_manager_impl.h b/content/browser/media/media_keys_listener_manager_impl.h
index 77e6026..361d957d 100644
--- a/content/browser/media/media_keys_listener_manager_impl.h
+++ b/content/browser/media/media_keys_listener_manager_impl.h
@@ -60,6 +60,12 @@
   // ui::MediaKeysListener::Delegate:
   void OnMediaKeysAccelerator(const ui::Accelerator& accelerator) override;
 
+  // Informs the MediaKeysListener whether or not media is playing.
+  // TODO(https://crbug.com/974035): Once the MediaKeysListenerManager has been
+  // refactored to work with system media controls this should no longer be
+  // needed and should be deleted.
+  void SetIsMediaPlaying(bool is_playing);
+
   HardwareKeyMediaController* hardware_key_media_controller_for_testing() {
     return hardware_key_media_controller_.get();
   }
@@ -118,6 +124,8 @@
   // True if auxiliary services have already been started.
   bool auxiliary_services_started_;
 
+  bool is_media_playing_ = false;
+
 #if defined(OS_MACOSX)
   std::unique_ptr<NowPlayingInfoCenterNotifier>
       now_playing_info_center_notifier_;
diff --git a/content/browser/media/media_keys_listener_manager_impl_browsertest.cc b/content/browser/media/media_keys_listener_manager_impl_browsertest.cc
index 080921c..2469df3 100644
--- a/content/browser/media/media_keys_listener_manager_impl_browsertest.cc
+++ b/content/browser/media/media_keys_listener_manager_impl_browsertest.cc
@@ -40,19 +40,25 @@
   void StopWatchingMediaKey(ui::KeyboardCode key_code) override {
     key_codes_.erase(key_code);
   }
+  void SetIsMediaPlaying(bool is_playing) override {
+    is_media_playing_ = is_playing;
+  }
 
   void SimulateAccelerator(ui::Accelerator accelerator) {
     if (IsWatching(accelerator.key_code()))
       delegate_->OnMediaKeysAccelerator(accelerator);
   }
 
-  bool IsWatching(ui::KeyboardCode key_code) {
+  bool IsWatching(ui::KeyboardCode key_code) const {
     return key_codes_.contains(key_code);
   }
 
+  bool is_media_playing() const { return is_media_playing_; }
+
  private:
   ui::MediaKeysListener::Delegate* delegate_;
   base::flat_set<ui::KeyboardCode> key_codes_;
+  bool is_media_playing_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(MockMediaKeysListener);
 };
@@ -158,6 +164,9 @@
   EXPECT_EQ(0, media_controller()->suspend_count());
   EXPECT_EQ(0, media_controller()->resume_count());
 
+  // The MediaKeysListener should know that media is playing.
+  EXPECT_TRUE(media_keys_listener()->is_media_playing());
+
   // Press the play/pause media key.
   media_keys_listener()->SimulateAccelerator(
       ui::Accelerator(ui::VKEY_MEDIA_PLAY_PAUSE, 0));
@@ -176,6 +185,9 @@
     SetSupportedMediaSessionActions({MediaSessionAction::kPlay});
   }
 
+  // The MediaKeysListener should know that media is paused.
+  EXPECT_FALSE(media_keys_listener()->is_media_playing());
+
   // Press play/pause.
   media_keys_listener()->SimulateAccelerator(
       ui::Accelerator(ui::VKEY_MEDIA_PLAY_PAUSE, 0));
diff --git a/content/browser/media/session/media_session_browsertest.cc b/content/browser/media/session/media_session_browsertest.cc
index c251955..9315f9d 100644
--- a/content/browser/media/session/media_session_browsertest.cc
+++ b/content/browser/media/session/media_session_browsertest.cc
@@ -214,8 +214,8 @@
   base::Lock visited_urls_lock_;
   std::set<GURL> visited_urls_;
 
-  base::test::ScopedFeatureList disabled_feature_list_;
   base::test::ScopedFeatureList scoped_feature_list_;
+  base::test::ScopedFeatureList disabled_feature_list_;
 
   DISALLOW_COPY_AND_ASSIGN(MediaSessionBrowserTest);
 };
diff --git a/content/browser/network_service_client.cc b/content/browser/network_service_client.cc
index 82b6868..90c68dd 100644
--- a/content/browser/network_service_client.cc
+++ b/content/browser/network_service_client.cc
@@ -451,6 +451,11 @@
   // returning early should this be the case.
   WebContents* web_contents = WebContents::FromRenderFrameHost(frame);
 
+  // |web_contents| will be null on interstitial pages, which means the frame
+  // has been navigated away from and the function should return early.
+  if (!web_contents)
+    return;
+
   RenderFrameHostImpl* root_frame_host = frame;
   while (root_frame_host->GetParent() != nullptr)
     root_frame_host = root_frame_host->GetParent();
@@ -531,6 +536,12 @@
                               base::Unretained(this))))
 #endif
 {
+
+#if defined(OS_MACOSX)
+  if (base::MessageLoopCurrentForUI::IsSet())  // Not set in some unit tests.
+    net::CertDatabase::GetInstance()->StartListeningForKeychainEvents();
+#endif
+
   if (IsOutOfProcessNetworkService()) {
     net::CertDatabase::GetInstance()->AddObserver(this);
     memory_pressure_listener_ =
diff --git a/content/browser/renderer_host/code_cache_host_impl.cc b/content/browser/renderer_host/code_cache_host_impl.cc
index 51056a83..e32f490 100644
--- a/content/browser/renderer_host/code_cache_host_impl.cc
+++ b/content/browser/renderer_host/code_cache_host_impl.cc
@@ -195,7 +195,7 @@
       "CodeCacheHostImpl::DidGenerateCacheableMetadataInCacheStorage",
       TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT, "url", url.spec());
 
-  if (!cache_storage_context_->cache_manager())
+  if (!cache_storage_context_->CacheManager())
     return;
 
   scoped_refptr<net::IOBuffer> buf =
@@ -204,7 +204,7 @@
     memcpy(buf->data(), data.data(), data.size());
 
   CacheStorageHandle cache_storage =
-      cache_storage_context_->cache_manager()->OpenCacheStorage(
+      cache_storage_context_->CacheManager()->OpenCacheStorage(
           cache_storage_origin, CacheStorageOwner::kCacheAPI);
   cache_storage.value()->OpenCache(
       cache_storage_cache_name, trace_id,
diff --git a/content/browser/renderer_host/render_frame_metadata_provider_impl.cc b/content/browser/renderer_host/render_frame_metadata_provider_impl.cc
index 5c3dbee..03a157b 100644
--- a/content/browser/renderer_host/render_frame_metadata_provider_impl.cc
+++ b/content/browser/renderer_host/render_frame_metadata_provider_impl.cc
@@ -35,16 +35,37 @@
   render_frame_metadata_observer_client_binding_.Bind(std::move(client_request),
                                                       task_runner_);
 
-  if (pending_report_all_frame_submission_.has_value()) {
-    ReportAllFrameSubmissionsForTesting(*pending_report_all_frame_submission_);
-    pending_report_all_frame_submission_.reset();
+#if defined(OS_ANDROID)
+  if (pending_report_all_root_scrolls_for_accessibility_.has_value()) {
+    ReportAllRootScrollsForAccessibility(
+        *pending_report_all_root_scrolls_for_accessibility_);
+    pending_report_all_root_scrolls_for_accessibility_.reset();
+  }
+#endif
+  if (pending_report_all_frame_submission_for_testing_.has_value()) {
+    ReportAllFrameSubmissionsForTesting(
+        *pending_report_all_frame_submission_for_testing_);
+    pending_report_all_frame_submission_for_testing_.reset();
   }
 }
 
+#if defined(OS_ANDROID)
+void RenderFrameMetadataProviderImpl::ReportAllRootScrollsForAccessibility(
+    bool enabled) {
+  if (!render_frame_metadata_observer_ptr_) {
+    pending_report_all_root_scrolls_for_accessibility_ = enabled;
+    return;
+  }
+
+  render_frame_metadata_observer_ptr_->ReportAllRootScrollsForAccessibility(
+      enabled);
+}
+#endif
+
 void RenderFrameMetadataProviderImpl::ReportAllFrameSubmissionsForTesting(
     bool enabled) {
   if (!render_frame_metadata_observer_ptr_) {
-    pending_report_all_frame_submission_ = enabled;
+    pending_report_all_frame_submission_for_testing_ = enabled;
     return;
   }
 
diff --git a/content/browser/renderer_host/render_frame_metadata_provider_impl.h b/content/browser/renderer_host/render_frame_metadata_provider_impl.h
index 4516793..51650fd75 100644
--- a/content/browser/renderer_host/render_frame_metadata_provider_impl.h
+++ b/content/browser/renderer_host/render_frame_metadata_provider_impl.h
@@ -8,6 +8,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
+#include "build/build_config.h"
 #include "content/common/render_frame_metadata.mojom.h"
 #include "content/public/browser/render_frame_metadata_provider.h"
 #include "mojo/public/cpp/bindings/binding.h"
@@ -40,6 +41,12 @@
 
   const cc::RenderFrameMetadata& LastRenderFrameMetadata() override;
 
+#if defined(OS_ANDROID)
+  // Notifies the renderer to begin sending a notification on all root scroll
+  // changes, which is needed for accessibility on Android.
+  void ReportAllRootScrollsForAccessibility(bool enabled);
+#endif
+
   // Notifies the renderer to begin sending a notification on all frame
   // submissions.
   void ReportAllFrameSubmissionsForTesting(bool enabled);
@@ -81,7 +88,10 @@
       render_frame_metadata_observer_client_binding_;
   mojom::RenderFrameMetadataObserverPtr render_frame_metadata_observer_ptr_;
 
-  base::Optional<bool> pending_report_all_frame_submission_;
+#if defined(OS_ANDROID)
+  base::Optional<bool> pending_report_all_root_scrolls_for_accessibility_;
+#endif
+  base::Optional<bool> pending_report_all_frame_submission_for_testing_;
 
   base::WeakPtrFactory<RenderFrameMetadataProviderImpl> weak_factory_;
 
diff --git a/content/browser/renderer_host/render_widget_host_unittest.cc b/content/browser/renderer_host/render_widget_host_unittest.cc
index f235629..6ac81d6 100644
--- a/content/browser/renderer_host/render_widget_host_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_unittest.cc
@@ -274,6 +274,9 @@
       mojom::RenderFrameMetadataObserverClientPtrInfo client_info);
   ~FakeRenderFrameMetadataObserver() override {}
 
+#if defined(OS_ANDROID)
+  void ReportAllRootScrollsForAccessibility(bool enabled) override {}
+#endif
   void ReportAllFrameSubmissionsForTesting(bool enabled) override {}
 
  private:
diff --git a/content/browser/renderer_host/render_widget_host_view_android.cc b/content/browser/renderer_host/render_widget_host_view_android.cc
index d1af81a4..8c00e392 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android.cc
@@ -2519,4 +2519,15 @@
   }
 }
 
+void RenderWidgetHostViewAndroid::SetWebContentsAccessibility(
+    WebContentsAccessibilityAndroid* web_contents_accessibility) {
+  web_contents_accessibility_ = web_contents_accessibility;
+
+  if (host()) {
+    host()
+        ->render_frame_metadata_provider()
+        ->ReportAllRootScrollsForAccessibility(!!web_contents_accessibility_);
+  }
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/render_widget_host_view_android.h b/content/browser/renderer_host/render_widget_host_view_android.h
index 3053d08..00f0346 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.h
+++ b/content/browser/renderer_host/render_widget_host_view_android.h
@@ -350,6 +350,9 @@
 
   void WasEvicted();
 
+  void SetWebContentsAccessibility(
+      WebContentsAccessibilityAndroid* web_contents_accessibility);
+
   ui::DelegatedFrameHostAndroid* delegated_frame_host_for_testing() {
     return delegated_frame_host_.get();
   }
@@ -548,6 +551,8 @@
   // either RenderFrameMetadata or CompositorFrameMetadata.
   base::Optional<DevToolsFrameMetadata> last_devtools_frame_metadata_;
 
+  WebContentsAccessibilityAndroid* web_contents_accessibility_ = nullptr;
+
   base::WeakPtrFactory<RenderWidgetHostViewAndroid> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewAndroid);
diff --git a/content/browser/renderer_host/render_widget_host_view_base.h b/content/browser/renderer_host/render_widget_host_view_base.h
index 903131f..81bdc91 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.h
+++ b/content/browser/renderer_host/render_widget_host_view_base.h
@@ -78,7 +78,6 @@
 class SyntheticGestureTarget;
 class TextInputManager;
 class TouchSelectionControllerClientManager;
-class WebContentsAccessibility;
 class WebCursor;
 class DelegatedFrameHost;
 struct TextInputState;
@@ -565,10 +564,6 @@
 
   bool is_fullscreen() { return is_fullscreen_; }
 
-  void set_web_contents_accessibility(WebContentsAccessibility* wcax) {
-    web_contents_accessibility_ = wcax;
-  }
-
   void set_is_currently_scrolling_viewport(
       bool is_currently_scrolling_viewport) {
     is_currently_scrolling_viewport_ = is_currently_scrolling_viewport;
@@ -660,8 +655,6 @@
   // |content_background_color|.
   base::Optional<SkColor> default_background_color_;
 
-  WebContentsAccessibility* web_contents_accessibility_ = nullptr;
-
   bool is_currently_scrolling_viewport_ = false;
 
  private:
diff --git a/content/browser/service_worker/service_worker_browsertest.cc b/content/browser/service_worker/service_worker_browsertest.cc
index eb3fb92..325ac18f 100644
--- a/content/browser/service_worker/service_worker_browsertest.cc
+++ b/content/browser/service_worker/service_worker_browsertest.cc
@@ -3256,7 +3256,7 @@
 
   void OpenCacheOnIOThread(int* result, base::OnceClosure continuation) {
     CacheStorageHandle cache_storage =
-        cache_storage_context_->cache_manager()->OpenCacheStorage(
+        cache_storage_context_->CacheManager()->OpenCacheStorage(
             url::Origin::Create(origin_), CacheStorageOwner::kCacheAPI);
     cache_storage.value()->OpenCache(
         cache_name_, /* trace_id = */ 0,
diff --git a/content/browser/startup_helper.cc b/content/browser/startup_helper.cc
index eff43f8..aec43ef 100644
--- a/content/browser/startup_helper.cc
+++ b/content/browser/startup_helper.cc
@@ -4,6 +4,11 @@
 
 #include "content/browser/startup_helper.h"
 
+#include <algorithm>
+#include <memory>
+#include <set>
+#include <string>
+
 #include "base/base_switches.h"
 #include "base/command_line.h"
 #include "base/system/sys_info.h"
@@ -16,7 +21,9 @@
 namespace content {
 
 std::unique_ptr<base::FieldTrialList> SetUpFieldTrialsAndFeatureList() {
-  auto field_trial_list = std::make_unique<base::FieldTrialList>(nullptr);
+  std::unique_ptr<base::FieldTrialList> field_trial_list;
+  if (!base::FieldTrialList::GetInstance())
+    field_trial_list = std::make_unique<base::FieldTrialList>(nullptr);
   const base::CommandLine* command_line =
       base::CommandLine::ForCurrentProcess();
 
diff --git a/content/browser/tracing/startup_tracing_browsertest.cc b/content/browser/tracing/startup_tracing_browsertest.cc
index e067ff1..5ce4792 100644
--- a/content/browser/tracing/startup_tracing_browsertest.cc
+++ b/content/browser/tracing/startup_tracing_browsertest.cc
@@ -122,8 +122,7 @@
 // StartupTraceWriter, which Perfetto will then have to sync copy into
 // the SMB once the full tracing service starts up. This is to catch common
 // deadlocks.
-IN_PROC_BROWSER_TEST_F(StartupTracingInProcessTest,
-                       DISABLED_TestFilledStartupBuffer) {
+IN_PROC_BROWSER_TEST_F(StartupTracingInProcessTest, TestFilledStartupBuffer) {
   tracing::TraceEventDataSource::GetInstance()->SetupStartupTracing(
       /*privacy_filtering_enabled=*/false);
 
diff --git a/content/browser/web_package/signed_exchange_request_handler_browsertest.cc b/content/browser/web_package/signed_exchange_request_handler_browsertest.cc
index 93331d0..9343d54 100644
--- a/content/browser/web_package/signed_exchange_request_handler_browsertest.cc
+++ b/content/browser/web_package/signed_exchange_request_handler_browsertest.cc
@@ -124,11 +124,11 @@
     // created. (Needed for the tests that use real certificate, i.e.
     // RealCertVerifier)
     net::EmbeddedTestServer::RegisterTestCerts();
+    feature_list_.InitWithFeatures({features::kSignedHTTPExchange}, {});
   }
 
   void SetUp() override {
     sxg_test_helper_.SetUp();
-    feature_list_.InitWithFeatures({features::kSignedHTTPExchange}, {});
     CertVerifierBrowserTest::SetUp();
   }
 
@@ -209,10 +209,7 @@
                      bool /* sxg_subresource_prefetch_enabled */>>,
       public SignedExchangeRequestHandlerBrowserTestBase {
  public:
-  SignedExchangeRequestHandlerBrowserTest() = default;
-  ~SignedExchangeRequestHandlerBrowserTest() = default;
-
-  void SetUp() override {
+  SignedExchangeRequestHandlerBrowserTest() {
     bool network_service_enabled;
     bool sxg_subresource_prefetch_enabled;
     std::tie(prefetch_enabled_, network_service_enabled,
@@ -230,14 +227,14 @@
       disabled_features.push_back(features::kSignedExchangeSubresourcePrefetch);
     }
     feature_list_.InitWithFeatures(enable_features, disabled_features);
-    SignedExchangeRequestHandlerBrowserTestBase::SetUp();
   }
+  ~SignedExchangeRequestHandlerBrowserTest() = default;
 
  protected:
   bool PrefetchIsEnabled() const { return prefetch_enabled_; }
 
  private:
-  bool prefetch_enabled_;
+  bool prefetch_enabled_ = false;
   base::test::ScopedFeatureList feature_list_;
 
   DISALLOW_COPY_AND_ASSIGN(SignedExchangeRequestHandlerBrowserTest);
diff --git a/content/browser/webauth/authenticator_impl_unittest.cc b/content/browser/webauth/authenticator_impl_unittest.cc
index d2e0f60..bdf7225 100644
--- a/content/browser/webauth/authenticator_impl_unittest.cc
+++ b/content/browser/webauth/authenticator_impl_unittest.cc
@@ -20,6 +20,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/system/sys_info.h"
+#include "base/test/bind_test_util.h"
 #include "base/test/gtest_util.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_mock_time_task_runner.h"
@@ -112,6 +113,7 @@
 } OriginClaimedAuthorityPair;
 
 constexpr char kTestOrigin1[] = "https://a.google.com";
+constexpr char kTestOrigin2[] = "https://acme.org";
 constexpr char kTestRelyingPartyId[] = "google.com";
 constexpr char kCryptotokenOrigin[] =
     "chrome-extension://kmendfapggjehodndflmmgagdbamhnfd";
@@ -1327,19 +1329,14 @@
   TestGetAssertionCallback callback_receiver;
   authenticator->GetAssertion(std::move(options), callback_receiver.callback());
 
-  // Delete the |AuthenticatorImpl| during the registration operation to
-  // simulate a navigation while waiting for the user to press the token.
+  // Simulate a navigation while waiting for the user to press the token.
   virtual_device_factory_->mutable_state()->simulate_press_callback =
-      base::BindRepeating(
-          [](std::unique_ptr<AuthenticatorImpl>* ptr) {
-            base::ThreadTaskRunnerHandle::Get()->PostTask(
-                FROM_HERE, base::BindOnce(
-                               [](std::unique_ptr<AuthenticatorImpl>* ptr) {
-                                 ptr->reset();
-                               },
-                               ptr));
-          },
-          &authenticator_impl_);
+      base::BindLambdaForTesting([&](device::VirtualFidoDevice* device) {
+        base::ThreadTaskRunnerHandle::Get()->PostTask(
+            FROM_HERE, base::BindLambdaForTesting(
+                           [&]() { SimulateNavigation(GURL(kTestOrigin2)); }));
+        return false;
+      });
 
   run_loop.Run();
 }
@@ -1399,7 +1396,12 @@
 
     bool pressed = false;
     virtual_device_factory_->mutable_state()->simulate_press_callback =
-        base::BindRepeating([](bool* flag) { *flag = true; }, &pressed);
+        base::BindRepeating(
+            [](bool* flag, device::VirtualFidoDevice* device) {
+              *flag = true;
+              return true;
+            },
+            &pressed);
 
     TestGetAssertionCallback callback_receiver;
     AuthenticatorPtr authenticator = ConnectToAuthenticator();
diff --git a/content/browser/webauth/webauth_browsertest.cc b/content/browser/webauth/webauth_browsertest.cc
index d68f328..f1a36fe 100644
--- a/content/browser/webauth/webauth_browsertest.cc
+++ b/content/browser/webauth/webauth_browsertest.cc
@@ -1112,10 +1112,11 @@
   auto* virtual_device_factory = InjectVirtualFidoDeviceFactory();
   bool prompt_callback_was_invoked = false;
   virtual_device_factory->mutable_state()->simulate_press_callback =
-      base::BindLambdaForTesting([&]() {
+      base::BindLambdaForTesting([&](device::VirtualFidoDevice* device) {
         prompt_callback_was_invoked = true;
         NavigateIframeToURL(shell()->web_contents(), "test_iframe",
                             GURL("/title2.html"));
+        return true;
       });
 
   NavigateToURL(shell(), GetHttpsURL("www.acme.com", "/page_with_iframe.html"));
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index 9c2457e3..3fdeea8 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -245,6 +245,11 @@
           enable_experimental_web_platform_features);
 
   WebRuntimeFeatures::EnableFeatureFromString(
+      "CSSBackdropFilter",
+      base::FeatureList::IsEnabled(blink::features::kCSSBackdropFilter) ||
+          enable_experimental_web_platform_features);
+
+  WebRuntimeFeatures::EnableFeatureFromString(
       "FastBorderRadius",
       base::FeatureList::IsEnabled(blink::features::kFastBorderRadius) ||
           enable_experimental_web_platform_features);
diff --git a/content/common/render_frame_metadata.mojom b/content/common/render_frame_metadata.mojom
index f1302d7..6b841dd 100644
--- a/content/common/render_frame_metadata.mojom
+++ b/content/common/render_frame_metadata.mojom
@@ -85,17 +85,26 @@
   bool has_transparent_background;
 };
 
-// This interface is provided by the renderer. It can optionally enable
-// notifications for all frame submissions.
+// This interface is provided by the renderer. It impacts the frequency with
+// which a fully populated RenderFrameMetadata object (above) is delivered to
+// the RenderFrameMetadataObserverClient.
 interface RenderFrameMetadataObserver {
-  // When |enabled| is set to true, this will notify the associated client of
-  // all frame submissions.
+  // When |enabled| is set to true, this will send RenderFrameMetadata to
+  // the RenderFrameMetadataObserverClient for any frame in which the root
+  // scroll changes. Used only on Android for accessibility cases.
+  [EnableIf=is_android]
+  ReportAllRootScrollsForAccessibility(bool enabled);
+  // When |enabled| is set to true, this will send RenderFrameMetadata to
+  // the RenderFrameMetadataObserverClient for all frames. Only used for
+  // tests.
   ReportAllFrameSubmissionsForTesting(bool enabled);
 };
 
-// This interface is provided by the browser. It is notified of all changes to
+// This interface is provided by the browser. It is notified of changes to
 // RenderFrameMetadata. It can be notified of all frame submissions, via
-// RenderFrameMetadataObserver::ReportAllFrameSubmissionsForTesting.
+// RenderFrameMetadataObserver::ReportAllFrameSubmissionsForTesting, or of
+// additional frames with root scroll offset changes via
+// RenderFrameMetadataObserver::ReportAllRootScrollsForAccessibility.
 interface RenderFrameMetadataObserverClient {
   // Notified when RenderFrameMetadata has changed.
   OnRenderFrameMetadataChanged(uint32 frame_token, RenderFrameMetadata metadata);
diff --git a/content/public/test/browser_test_base.cc b/content/public/test/browser_test_base.cc
index 68a2523..5105923 100644
--- a/content/public/test/browser_test_base.cc
+++ b/content/public/test/browser_test_base.cc
@@ -155,8 +155,7 @@
 extern int BrowserMain(const MainFunctionParams&);
 
 BrowserTestBase::BrowserTestBase()
-    : field_trial_list_(std::make_unique<base::FieldTrialList>(nullptr)),
-      expected_exit_code_(0),
+    : expected_exit_code_(0),
       enable_pixel_output_(false),
       use_software_compositing_(false),
       set_up_called_(false) {
@@ -334,7 +333,6 @@
     command_line->AppendSwitchASCII(switches::kForceFieldTrials,
                                     field_trial_states);
   }
-  field_trial_list_.reset();
 
   // Need to wipe feature list clean, since BrowserMain calls
   // FeatureList::SetInstance, which expects no instance to exist.
diff --git a/content/public/test/render_view_test.cc b/content/public/test/render_view_test.cc
index 28605e85..3915614 100644
--- a/content/public/test/render_view_test.cc
+++ b/content/public/test/render_view_test.cc
@@ -10,7 +10,6 @@
 
 #include "base/bind.h"
 #include "base/location.h"
-#include "base/metrics/field_trial.h"
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "build/build_config.h"
@@ -308,10 +307,6 @@
 #endif
   command_line_ =
       std::make_unique<base::CommandLine>(base::CommandLine::NO_PROGRAM);
-  field_trial_list_ = std::make_unique<base::FieldTrialList>(nullptr);
-  // We don't use the descriptor here anyways so it's ok to pass -1.
-  base::FieldTrialList::CreateTrialsFromCommandLine(
-      *command_line_, switches::kFieldTrialHandle, -1);
   params_ = std::make_unique<MainFunctionParams>(*command_line_);
   platform_ = std::make_unique<RendererMainPlatformDelegate>(*params_);
   platform_->PlatformInitialize();
diff --git a/content/public/test/unittest_test_suite.cc b/content/public/test/unittest_test_suite.cc
index d158baf..178d3403 100644
--- a/content/public/test/unittest_test_suite.cc
+++ b/content/public/test/unittest_test_suite.cc
@@ -56,8 +56,7 @@
 
 }  // namespace
 
-UnitTestTestSuite::UnitTestTestSuite(base::TestSuite* test_suite,
-                                     const std::string& disabled_features)
+UnitTestTestSuite::UnitTestTestSuite(base::TestSuite* test_suite)
     : test_suite_(test_suite) {
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
   std::string enabled =
@@ -71,18 +70,6 @@
       testing::UnitTest::GetInstance()->listeners();
   listeners.Append(new ResetNetworkServiceBetweenTests);
 
-  // base::TestSuite will reset the FeatureList, so modify the underlying
-  // CommandLine object to disable the network service when it's parsed again.
-  if (!disabled_features.empty())
-    disabled += "," + disabled_features;
-  base::CommandLine new_command_line(command_line->GetProgram());
-  base::CommandLine::SwitchMap switches = command_line->GetSwitches();
-  switches.erase(switches::kDisableFeatures);
-  new_command_line.AppendSwitchASCII(switches::kDisableFeatures, disabled);
-  for (const auto& iter : switches)
-    new_command_line.AppendSwitchNative(iter.first, iter.second);
-  *base::CommandLine::ForCurrentProcess() = new_command_line;
-
   // The ThreadPool created by the test launcher is never destroyed.
   // Similarly, the FeatureList created here is never destroyed so it
   // can safely be accessed by the ThreadPool.
@@ -94,7 +81,6 @@
 #if defined(OS_FUCHSIA)
   // Use headless ozone platform on Fuchsia by default.
   // TODO(crbug.com/865172): Remove this flag.
-  command_line = base::CommandLine::ForCurrentProcess();
   if (!command_line->HasSwitch(switches::kOzonePlatform))
     command_line->AppendSwitchASCII(switches::kOzonePlatform, "headless");
 #endif
diff --git a/content/public/test/unittest_test_suite.h b/content/public/test/unittest_test_suite.h
index b329f3b2..7b28beb 100644
--- a/content/public/test/unittest_test_suite.h
+++ b/content/public/test/unittest_test_suite.h
@@ -27,10 +27,7 @@
 class UnitTestTestSuite {
  public:
   // Takes ownership of |test_suite|.
-  // |disabled_features| is an optional comma-separated list of features to
-  // disable.
-  UnitTestTestSuite(base::TestSuite* test_suite,
-                    const std::string& disabled_features = std::string());
+  explicit UnitTestTestSuite(base::TestSuite* test_suite);
   ~UnitTestTestSuite();
 
   int Run();
diff --git a/content/renderer/render_frame_metadata_observer_impl.cc b/content/renderer/render_frame_metadata_observer_impl.cc
index 31fe9a0..6d72311 100644
--- a/content/renderer/render_frame_metadata_observer_impl.cc
+++ b/content/renderer/render_frame_metadata_observer_impl.cc
@@ -59,7 +59,8 @@
   }
 
   // Allways cache the full metadata, so that it can correctly be sent upon
-  // ReportAllFrameSubmissionsForTesting. This must only be done after we've
+  // ReportAllFrameSubmissionsForTesting or
+  // ReportAllRootScrollsForAccessibility. This must only be done after we've
   // compared the two for changes.
   last_render_frame_metadata_ = render_frame_metadata;
 
@@ -67,11 +68,11 @@
   // generated for first time and same as the default value, update the default
   // value to all the observers.
   if (send_metadata && render_frame_metadata_observer_client_) {
-    // Sending |root_scroll_offset| outside of tests would leave the browser
-    // process with out of date information. It is an optional parameter
-    // which we clear here.
     auto metadata_copy = render_frame_metadata;
 #if !defined(OS_ANDROID)
+    // On non-Android, sending |root_scroll_offset| outside of tests would
+    // leave the browser process with out of date information. It is an
+    // optional parameter which we clear here.
     if (!report_all_frame_submissions_for_testing_enabled_)
       metadata_copy.root_scroll_offset = base::nullopt;
 #endif
@@ -107,11 +108,26 @@
   }
 }
 
+#if defined(OS_ANDROID)
+void RenderFrameMetadataObserverImpl::ReportAllRootScrollsForAccessibility(
+    bool enabled) {
+  report_all_root_scrolls_for_accessibility_enabled_ = enabled;
+
+  if (enabled)
+    SendLastRenderFrameMetadata();
+}
+#endif
+
 void RenderFrameMetadataObserverImpl::ReportAllFrameSubmissionsForTesting(
     bool enabled) {
   report_all_frame_submissions_for_testing_enabled_ = enabled;
 
-  if (!enabled || !last_frame_token_)
+  if (enabled)
+    SendLastRenderFrameMetadata();
+}
+
+void RenderFrameMetadataObserverImpl::SendLastRenderFrameMetadata() {
+  if (!last_frame_token_)
     return;
 
   // When enabled for testing send the cached metadata.
@@ -121,11 +137,10 @@
       last_frame_token_, *last_render_frame_metadata_);
 }
 
-// static
 bool RenderFrameMetadataObserverImpl::ShouldSendRenderFrameMetadata(
     const cc::RenderFrameMetadata& rfm1,
     const cc::RenderFrameMetadata& rfm2,
-    bool* needs_activation_notification) {
+    bool* needs_activation_notification) const {
   if (rfm1.root_background_color != rfm2.root_background_color ||
       rfm1.is_scroll_offset_at_top != rfm2.is_scroll_offset_at_top ||
       rfm1.selection != rfm2.selection ||
@@ -142,6 +157,9 @@
   }
 
 #if defined(OS_ANDROID)
+  bool need_send_root_scroll =
+      report_all_root_scrolls_for_accessibility_enabled_ &&
+      rfm1.root_scroll_offset != rfm2.root_scroll_offset;
   if (rfm1.bottom_controls_height != rfm2.bottom_controls_height ||
       rfm1.bottom_controls_shown_ratio != rfm2.bottom_controls_shown_ratio ||
       rfm1.min_page_scale_factor != rfm2.min_page_scale_factor ||
@@ -149,7 +167,8 @@
       rfm1.root_overflow_y_hidden != rfm2.root_overflow_y_hidden ||
       rfm1.scrollable_viewport_size != rfm2.scrollable_viewport_size ||
       rfm1.root_layer_size != rfm2.root_layer_size ||
-      rfm1.has_transparent_background != rfm2.has_transparent_background) {
+      rfm1.has_transparent_background != rfm2.has_transparent_background ||
+      need_send_root_scroll) {
     *needs_activation_notification = true;
     return true;
   }
diff --git a/content/renderer/render_frame_metadata_observer_impl.h b/content/renderer/render_frame_metadata_observer_impl.h
index fc830dc9..c0335ae9 100644
--- a/content/renderer/render_frame_metadata_observer_impl.h
+++ b/content/renderer/render_frame_metadata_observer_impl.h
@@ -5,6 +5,7 @@
 #ifndef CONTENT_RENDERER_RENDER_FRAME_METADATA_OBSERVER_IMPL_H_
 #define CONTENT_RENDERER_RENDER_FRAME_METADATA_OBSERVER_IMPL_H_
 
+#include "build/build_config.h"
 #include "cc/trees/render_frame_metadata.h"
 #include "cc/trees/render_frame_metadata_observer.h"
 #include "content/common/content_export.h"
@@ -39,6 +40,9 @@
       bool force_send) override;
 
   // mojom::RenderFrameMetadataObserver:
+#if defined(OS_ANDROID)
+  void ReportAllRootScrollsForAccessibility(bool enabled) override;
+#endif
   void ReportAllFrameSubmissionsForTesting(bool enabled) override;
 
  private:
@@ -50,12 +54,19 @@
   // |needs_activation_notification| indicates whether the browser process
   // expects notification of activation of the assoicated CompositorFrame from
   // Viz.
-  static bool ShouldSendRenderFrameMetadata(
-      const cc::RenderFrameMetadata& rfm1,
-      const cc::RenderFrameMetadata& rfm2,
-      bool* needs_activation_notification);
+  bool ShouldSendRenderFrameMetadata(const cc::RenderFrameMetadata& rfm1,
+                                     const cc::RenderFrameMetadata& rfm2,
+                                     bool* needs_activation_notification) const;
 
-  // When true this will notifiy |render_frame_metadata_observer_client_| of all
+  void SendLastRenderFrameMetadata();
+
+#if defined(OS_ANDROID)
+  // When true this will notify |render_frame_metadata_observer_client_| of all
+  // frame submissions that involve a root scroll offset change.
+  bool report_all_root_scrolls_for_accessibility_enabled_ = false;
+#endif
+
+  // When true this will notify |render_frame_metadata_observer_client_| of all
   // frame submissions.
   bool report_all_frame_submissions_for_testing_enabled_ = false;
 
diff --git a/content/renderer/render_frame_metadata_observer_impl_unittest.cc b/content/renderer/render_frame_metadata_observer_impl_unittest.cc
index 1f5803a..c569d77 100644
--- a/content/renderer/render_frame_metadata_observer_impl_unittest.cc
+++ b/content/renderer/render_frame_metadata_observer_impl_unittest.cc
@@ -156,6 +156,91 @@
     run_loop.Run();
   }
 }
+
+// This test verifies that a request to send root scroll changes for
+// accessibility is respected.
+TEST_F(RenderFrameMetadataObserverImplTest, SendRootScrollsForAccessibility) {
+  const uint32_t expected_frame_token = 1337;
+  viz::CompositorFrameMetadata compositor_frame_metadata;
+  compositor_frame_metadata.send_frame_token_to_embedder = false;
+  compositor_frame_metadata.frame_token = expected_frame_token;
+  cc::RenderFrameMetadata render_frame_metadata;
+
+  observer_impl().OnRenderFrameSubmission(render_frame_metadata,
+                                          &compositor_frame_metadata,
+                                          false /* force_send */);
+  // The first RenderFrameMetadata will always get a corresponding frame token
+  // from Viz because this is the first frame.
+  EXPECT_TRUE(compositor_frame_metadata.send_frame_token_to_embedder);
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(client(), OnRenderFrameMetadataChanged(expected_frame_token,
+                                                       render_frame_metadata))
+        .WillOnce(InvokeClosure(run_loop.QuitClosure()));
+    run_loop.Run();
+  }
+
+  // Submit with a root scroll change and then a scroll offset at top change, we
+  // should only get one notification, as the root scroll change will not
+  // trigger one,
+  render_frame_metadata.root_scroll_offset = gfx::Vector2dF(0.0f, 100.0f);
+  observer_impl().OnRenderFrameSubmission(render_frame_metadata,
+                                          &compositor_frame_metadata,
+                                          false /* force_send */);
+  render_frame_metadata.is_scroll_offset_at_top =
+      !render_frame_metadata.is_scroll_offset_at_top;
+  observer_impl().OnRenderFrameSubmission(render_frame_metadata,
+                                          &compositor_frame_metadata,
+                                          false /* force_send */);
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(client(), OnRenderFrameMetadataChanged(expected_frame_token,
+                                                       render_frame_metadata))
+        .WillOnce(InvokeClosure(run_loop.QuitClosure()));
+    run_loop.Run();
+  }
+
+  // Enable reporting for root scroll changes. This will generate one
+  // notification.
+  observer_impl().ReportAllRootScrollsForAccessibility(true);
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(client(), OnRenderFrameMetadataChanged(expected_frame_token,
+                                                       render_frame_metadata))
+        .WillOnce(InvokeClosure(run_loop.QuitClosure()));
+    run_loop.Run();
+  }
+
+  // Now send a single root scroll change, we should get the notification.
+  render_frame_metadata.root_scroll_offset = gfx::Vector2dF(0.0f, 200.0f);
+  observer_impl().OnRenderFrameSubmission(render_frame_metadata,
+                                          &compositor_frame_metadata,
+                                          false /* force_send */);
+  {
+    base::RunLoop run_loop;
+    // The 0u frame token indicates that the client should not expect
+    // a corresponding frame token from Viz.
+    EXPECT_CALL(client(), OnRenderFrameMetadataChanged(expected_frame_token,
+                                                       render_frame_metadata))
+        .WillOnce(InvokeClosure(run_loop.QuitClosure()));
+    run_loop.Run();
+  }
+
+  // Send one more message to ensure that no spurious
+  // OnRenderFrameMetadataChanged messages were generated.
+  render_frame_metadata.is_scroll_offset_at_top =
+      !render_frame_metadata.is_scroll_offset_at_top;
+  observer_impl().OnRenderFrameSubmission(render_frame_metadata,
+                                          &compositor_frame_metadata,
+                                          false /* force_send */);
+  {
+    base::RunLoop run_loop;
+    EXPECT_CALL(client(), OnRenderFrameMetadataChanged(expected_frame_token,
+                                                       render_frame_metadata))
+        .WillOnce(InvokeClosure(run_loop.QuitClosure()));
+    run_loop.Run();
+  }
+}
 #endif
 
 // This test verifies that a request to force send metadata is respected.
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index 73feffe..04575de 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -180,6 +180,18 @@
 static const char* kOOPIF = "OOPIF";
 static const char* kRenderer = "Renderer";
 
+#if defined(OS_ANDROID)
+// With 32 bit pixels, this would mean less than 400kb per buffer. Much less
+// than required for, say, nHD.
+static const int kSmallScreenPixelThreshold = 1e5;
+bool IsSmallScreen(const gfx::Size& size) {
+  int area = 0;
+  if (!size.GetCheckedArea().AssignIfValid(&area))
+    return false;
+  return area < kSmallScreenPixelThreshold;
+}
+#endif
+
 class WebWidgetLockTarget : public content::MouseLockDispatcher::LockTarget {
  public:
   explicit WebWidgetLockTarget(blink::WebWidget* webwidget)
@@ -3019,7 +3031,8 @@
 #if defined(OS_ANDROID)
   bool using_synchronous_compositor =
       compositor_deps->UsingSynchronousCompositing();
-  bool using_low_memory_policy = base::SysInfo::IsLowEndDevice();
+  bool using_low_memory_policy =
+      base::SysInfo::IsLowEndDevice() && !IsSmallScreen(screen_size);
 
   settings.use_stream_video_draw_quad = true;
   settings.using_synchronous_renderer_compositor = using_synchronous_compositor;
diff --git a/content/renderer/renderer_main_platform_delegate_win.cc b/content/renderer/renderer_main_platform_delegate_win.cc
index f59a53d..a93dc0ea 100644
--- a/content/renderer/renderer_main_platform_delegate_win.cc
+++ b/content/renderer/renderer_main_platform_delegate_win.cc
@@ -4,13 +4,16 @@
 
 #include "content/renderer/renderer_main_platform_delegate.h"
 
+#include <delayimp.h>
 #include <dwrite.h>
 
 #include <memory>
 
 #include "base/command_line.h"
+#include "base/debug/alias.h"
 #include "base/logging.h"
 #include "base/strings/string16.h"
+#include "base/strings/string_util.h"
 #include "base/win/win_util.h"
 #include "base/win/windows_version.h"
 #include "content/child/dwrite_font_proxy/dwrite_font_proxy_init_impl_win.h"
@@ -29,6 +32,31 @@
 
 namespace content {
 
+namespace {
+
+// Delay load failure hook that generates a crash report. By default a failure
+// to delay load will trigger an exception handled by the delay load runtime and
+// this won't generate a crash report.
+extern "C" FARPROC WINAPI DelayLoadFailureHook(unsigned reason,
+                                               DelayLoadInfo* dll_info) {
+  char dll_name[256];
+  base::strlcpy(dll_name, dll_info->szDll, base::size(dll_name));
+  base::debug::Alias(&dll_name);
+
+  CHECK(false);
+  return 0;
+}
+
+// Set the delay load failure hook to the function above.
+//
+// The |__pfnDliFailureHook2| failure notification hook gets called
+// automatically by the delay load runtime in case of failure, see
+// https://docs.microsoft.com/en-us/cpp/build/reference/failure-hooks?view=vs-2019
+// for more information about this.
+extern "C" const PfnDliHook __pfnDliFailureHook2 = DelayLoadFailureHook;
+
+}  // namespace
+
 RendererMainPlatformDelegate::RendererMainPlatformDelegate(
     const MainFunctionParams& parameters)
     : parameters_(parameters) {}
diff --git a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
index 6408d1c..90aa125a 100644
--- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -174,6 +174,7 @@
 
 # Failing on Nexus 5
 crbug.com/957714 [ android qualcomm-adreno-(tm)-330 ] Pixel_Canvas2DRedBox [ Failure ]
+crbug.com/900706 [ android qualcomm-adreno-(tm)-330 ] Pixel_CanvasLowLatency2D [ Failure ]
 
 # Failing on Pixel 2 FYI.
 crbug.com/966069 [ android qualcomm-adreno-(tm)-540 ] Pixel_CanvasLowLatency2D [ Failure ]
diff --git a/device/bluetooth/bluetooth_device.h b/device/bluetooth/bluetooth_device.h
index 13b5b3b..74028fc 100644
--- a/device/bluetooth/bluetooth_device.h
+++ b/device/bluetooth/bluetooth_device.h
@@ -252,7 +252,7 @@
   // of, by decoding the bluetooth class information for Classic devices or
   // by decoding the device's appearance for LE devices. For example,
   // Microsoft Universal Foldable Keyboard only advertises the appearance.
-  BluetoothDeviceType GetDeviceType() const;
+  virtual BluetoothDeviceType GetDeviceType() const;
 
   // Indicates whether the device is known to support pairing based on its
   // device class and address.
diff --git a/device/bluetooth/chromeos/bluetooth_utils.cc b/device/bluetooth/chromeos/bluetooth_utils.cc
index 69194dd4e..7b5a658 100644
--- a/device/bluetooth/chromeos/bluetooth_utils.cc
+++ b/device/bluetooth/chromeos/bluetooth_utils.cc
@@ -8,10 +8,12 @@
 
 #include "base/feature_list.h"
 #include "base/metrics/field_trial_params.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/optional.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
+#include "base/time/time.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "device/base/features.h"
 
@@ -27,6 +29,9 @@
 
 const size_t kLongTermKeyHexStringLength = 32;
 
+constexpr base::TimeDelta kMaxDeviceSelectionDuration =
+    base::TimeDelta::FromSeconds(30);
+
 // Get limited number of devices from |devices| and
 // prioritize paired/connecting devices over other devices.
 BluetoothAdapter::DeviceList GetLimitedNumDevices(
@@ -97,6 +102,13 @@
   return result;
 }
 
+void RecordDeviceSelectionDuration(const std::string& histogram_name,
+                                   base::TimeDelta duration) {
+  base::UmaHistogramCustomTimes(
+      histogram_name, duration, base::TimeDelta::FromMilliseconds(1) /* min */,
+      kMaxDeviceSelectionDuration /* max */, 50 /* buckets */);
+}
+
 }  // namespace
 
 device::BluetoothAdapter::DeviceList FilterBluetoothDeviceList(
@@ -134,4 +146,51 @@
   return long_term_keys;
 }
 
+void RecordDeviceSelectionDuration(base::TimeDelta duration,
+                                   BluetoothUiSurface surface,
+                                   bool was_paired,
+                                   BluetoothTransport transport) {
+  // Throw out longtail results of the user taking longer than
+  // |kMaxDeviceSelectionDuration|. Assume that these thrown out results reflect
+  // the user not being actively engaged with device connection: leaving the
+  // page open for a long time, walking away from computer, etc.
+  if (duration > kMaxDeviceSelectionDuration)
+    return;
+
+  std::string base_histogram_name =
+      "Bluetooth.ChromeOS.DeviceSelectionDuration";
+  RecordDeviceSelectionDuration(base_histogram_name, duration);
+
+  std::string surface_name =
+      (surface == BluetoothUiSurface::kSettings ? "Settings" : "SystemTray");
+  std::string surface_histogram_name = base_histogram_name + "." + surface_name;
+  RecordDeviceSelectionDuration(surface_histogram_name, duration);
+
+  std::string paired_name = (was_paired ? "Paired" : "NotPaired");
+  std::string paired_histogram_name =
+      surface_histogram_name + "." + paired_name;
+  RecordDeviceSelectionDuration(paired_histogram_name, duration);
+
+  if (!was_paired) {
+    std::string transport_name;
+    switch (transport) {
+      case BLUETOOTH_TRANSPORT_CLASSIC:
+        transport_name = "Classic";
+        break;
+      case BLUETOOTH_TRANSPORT_LE:
+        transport_name = "BLE";
+        break;
+      case BLUETOOTH_TRANSPORT_DUAL:
+        transport_name = "Dual";
+        break;
+      default:
+        return;
+    }
+
+    std::string transport_histogram_name =
+        paired_histogram_name + "." + transport_name;
+    RecordDeviceSelectionDuration(transport_histogram_name, duration);
+  }
+}
+
 }  // namespace device
diff --git a/device/bluetooth/chromeos/bluetooth_utils.h b/device/bluetooth/chromeos/bluetooth_utils.h
index 32ab5c02..12aa626f 100644
--- a/device/bluetooth/chromeos/bluetooth_utils.h
+++ b/device/bluetooth/chromeos/bluetooth_utils.h
@@ -10,6 +10,10 @@
 #include "device/bluetooth/bluetooth_adapter.h"
 #include "device/bluetooth/bluetooth_export.h"
 
+namespace base {
+class TimeDelta;
+}  // namespace base
+
 // This file contains common utilities, including filtering bluetooth devices
 // based on the filter criteria.
 namespace device {
@@ -22,6 +26,11 @@
   KNOWN,
 };
 
+enum class BluetoothUiSurface {
+  kSettings,
+  kSystemTray,
+};
+
 // Return filtered devices based on the filter type and max number of devices.
 device::BluetoothAdapter::DeviceList DEVICE_BLUETOOTH_EXPORT
 FilterBluetoothDeviceList(const BluetoothAdapter::DeviceList& devices,
@@ -31,6 +40,14 @@
 std::vector<std::vector<uint8_t>> DEVICE_BLUETOOTH_EXPORT
 GetBlockedLongTermKeys();
 
+// Record how long it took for a user to find and select the device they wished
+// to connect to.
+void DEVICE_BLUETOOTH_EXPORT
+RecordDeviceSelectionDuration(base::TimeDelta duration,
+                              BluetoothUiSurface surface,
+                              bool was_paired,
+                              BluetoothTransport transport);
+
 }  // namespace device
 
 #endif  // DEVICE_BLUETOOTH_CHROMEOS_BLUETOOTH_UTILS_H_
diff --git a/device/fido/virtual_ctap2_device.cc b/device/fido/virtual_ctap2_device.cc
index 77eef9e..af2c007 100644
--- a/device/fido/virtual_ctap2_device.cc
+++ b/device/fido/virtual_ctap2_device.cc
@@ -84,7 +84,7 @@
 
 // CheckUserVerification implements the first, common steps of
 // makeCredential and getAssertion from the CTAP2 spec.
-CtapDeviceResponseCode CheckUserVerification(
+base::Optional<CtapDeviceResponseCode> CheckUserVerification(
     bool is_make_credential,
     const AuthenticatorSupportedOptions& options,
     const base::Optional<std::vector<uint8_t>>& pin_auth,
@@ -92,7 +92,7 @@
     base::span<const uint8_t> pin_token,
     base::span<const uint8_t> client_data_hash,
     UserVerificationRequirement user_verification,
-    base::RepeatingCallback<void(void)> simulate_press_callback,
+    base::RepeatingCallback<bool(void)> simulate_press_callback,
     bool* out_user_verified) {
   // The following quotes are from the CTAP2 spec:
 
@@ -103,9 +103,9 @@
       options.client_pin_availability !=
       AuthenticatorSupportedOptions::ClientPinAvailability::kNotSupported;
   if (supports_pin && pin_auth && pin_auth->empty()) {
-    if (simulate_press_callback) {
-      simulate_press_callback.Run();
-    }
+    if (simulate_press_callback && !simulate_press_callback.Run())
+      return base::nullopt;
+
     switch (options.client_pin_availability) {
       case AuthenticatorSupportedOptions::ClientPinAvailability::
           kSupportedAndPinSet:
@@ -149,9 +149,9 @@
           AuthenticatorSupportedOptions::UserVerificationAvailability::
               kSupportedAndConfigured) {
         // Internal UV is assumed to always succeed.
-        if (simulate_press_callback) {
-          simulate_press_callback.Run();
-        }
+        if (simulate_press_callback && !simulate_press_callback.Run())
+          return base::nullopt;
+
         uv = true;
       } else {
         // UV was requested, but either not supported or not configured.
@@ -558,12 +558,24 @@
 
       response_code = OnAuthenticatorGetInfo(&response_data);
       break;
-    case CtapRequestCommand::kAuthenticatorMakeCredential:
-      response_code = OnMakeCredential(request_bytes, &response_data);
+    case CtapRequestCommand::kAuthenticatorMakeCredential: {
+      auto opt_response_code = OnMakeCredential(request_bytes, &response_data);
+      if (!opt_response_code) {
+        // Simulate timeout due to unresponded User Presence check.
+        return 0;
+      }
+      response_code = *opt_response_code;
       break;
-    case CtapRequestCommand::kAuthenticatorGetAssertion:
-      response_code = OnGetAssertion(request_bytes, &response_data);
+    }
+    case CtapRequestCommand::kAuthenticatorGetAssertion: {
+      auto opt_response_code = OnGetAssertion(request_bytes, &response_data);
+      if (!opt_response_code) {
+        // Simulate timeout due to unresponded User Presence check.
+        return 0;
+      }
+      response_code = *opt_response_code;
       break;
+    }
     case CtapRequestCommand::kAuthenticatorGetNextAssertion:
       response_code = OnGetNextAssertion(request_bytes, &response_data);
       break;
@@ -595,7 +607,7 @@
   device_info_->options = options;
 }
 
-CtapDeviceResponseCode VirtualCtap2Device::OnMakeCredential(
+base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential(
     base::span<const uint8_t> request_bytes,
     std::vector<uint8_t>* response) {
   const auto& cbor_request = cbor::Reader::Read(request_bytes);
@@ -616,10 +628,14 @@
   const AuthenticatorSupportedOptions& options = device_info_->options;
 
   bool user_verified;
-  const CtapDeviceResponseCode uv_error = CheckUserVerification(
+  const base::Optional<CtapDeviceResponseCode> uv_error = CheckUserVerification(
       true /* is makeCredential */, options, request.pin_auth,
       request.pin_protocol, mutable_state()->pin_token, client_data_hash,
-      request.user_verification, mutable_state()->simulate_press_callback,
+      request.user_verification,
+      mutable_state()->simulate_press_callback
+          ? base::BindRepeating(mutable_state()->simulate_press_callback,
+                                base::Unretained(this))
+          : base::RepeatingCallback<bool(void)>(),
       &user_verified);
   if (uv_error != CtapDeviceResponseCode::kSuccess) {
     return uv_error;
@@ -643,8 +659,9 @@
           // a credentials ends up being created it'll overwrite this one.
           continue;
         }
-        if (mutable_state()->simulate_press_callback) {
-          mutable_state()->simulate_press_callback.Run();
+        if (mutable_state()->simulate_press_callback &&
+            !mutable_state()->simulate_press_callback.Run(this)) {
+          return base::nullopt;
         }
         return CtapDeviceResponseCode::kCtap2ErrCredentialExcluded;
       }
@@ -665,8 +682,9 @@
   }
 
   // Step 10.
-  if (!user_verified && mutable_state()->simulate_press_callback) {
-    mutable_state()->simulate_press_callback.Run();
+  if (!user_verified && mutable_state()->simulate_press_callback &&
+      !mutable_state()->simulate_press_callback.Run(this)) {
+    return base::nullopt;
   }
 
   // Create key to register.
@@ -764,7 +782,7 @@
   return CtapDeviceResponseCode::kSuccess;
 }
 
-CtapDeviceResponseCode VirtualCtap2Device::OnGetAssertion(
+base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnGetAssertion(
     base::span<const uint8_t> request_bytes,
     std::vector<uint8_t>* response) {
   // Step numbers in this function refer to
@@ -787,10 +805,14 @@
   const AuthenticatorSupportedOptions& options = device_info_->options;
 
   bool user_verified;
-  const CtapDeviceResponseCode uv_error = CheckUserVerification(
+  const base::Optional<CtapDeviceResponseCode> uv_error = CheckUserVerification(
       false /* not makeCredential */, options, request.pin_auth,
       request.pin_protocol, mutable_state()->pin_token, client_data_hash,
-      request.user_verification, mutable_state()->simulate_press_callback,
+      request.user_verification,
+      mutable_state()->simulate_press_callback
+          ? base::BindRepeating(mutable_state()->simulate_press_callback,
+                                base::Unretained(this))
+          : base::RepeatingCallback<bool(void)>(),
       &user_verified);
   if (uv_error != CtapDeviceResponseCode::kSuccess) {
     return uv_error;
@@ -871,8 +893,9 @@
 
   // Step 7.
   if (request.user_presence_required && !user_verified &&
-      mutable_state()->simulate_press_callback) {
-    mutable_state()->simulate_press_callback.Run();
+      mutable_state()->simulate_press_callback &&
+      !mutable_state()->simulate_press_callback.Run(this)) {
+    return base::nullopt;
   }
 
   // Step 8.
diff --git a/device/fido/virtual_ctap2_device.h b/device/fido/virtual_ctap2_device.h
index 4bb014b..b7853a718 100644
--- a/device/fido/virtual_ctap2_device.h
+++ b/device/fido/virtual_ctap2_device.h
@@ -82,10 +82,12 @@
       const AuthenticatorSupportedOptions& options);
 
  private:
-  CtapDeviceResponseCode OnMakeCredential(base::span<const uint8_t> request,
-                                          std::vector<uint8_t>* response);
-  CtapDeviceResponseCode OnGetAssertion(base::span<const uint8_t> request,
-                                        std::vector<uint8_t>* response);
+  base::Optional<CtapDeviceResponseCode> OnMakeCredential(
+      base::span<const uint8_t> request,
+      std::vector<uint8_t>* response);
+  base::Optional<CtapDeviceResponseCode> OnGetAssertion(
+      base::span<const uint8_t> request,
+      std::vector<uint8_t>* response);
   CtapDeviceResponseCode OnGetNextAssertion(base::span<const uint8_t> request,
                                             std::vector<uint8_t>* response);
   CtapDeviceResponseCode OnPINCommand(base::span<const uint8_t> request,
diff --git a/device/fido/virtual_fido_device.h b/device/fido/virtual_fido_device.h
index debfd32..397f0b2c 100644
--- a/device/fido/virtual_fido_device.h
+++ b/device/fido/virtual_fido_device.h
@@ -73,6 +73,8 @@
     using RegistrationsMap = std::map<std::vector<uint8_t>,
                                       RegistrationData,
                                       fido_parsing_utils::RangeLess>;
+    using SimulatePressCallback =
+        base::RepeatingCallback<bool(VirtualFidoDevice*)>;
 
     State();
 
@@ -86,9 +88,10 @@
     // Registered keys. Keyed on key handle (a.k.a. "credential ID").
     RegistrationsMap registrations;
 
-    // If set, this callback is called whenever a "press" is required. It allows
-    // tests to change the state of the world during processing.
-    base::RepeatingCallback<void(void)> simulate_press_callback;
+    // If set, this callback is called whenever a "press" is required. Returning
+    // `true` will simulate a press and continue the request, returning `false`
+    // simulates the user not pressing the device and leaves the request idle.
+    SimulatePressCallback simulate_press_callback;
 
     // If true, causes the response from the device to be invalid.
     bool simulate_invalid_response = false;
diff --git a/device/fido/virtual_u2f_device.cc b/device/fido/virtual_u2f_device.cc
index 31322392..6260205 100644
--- a/device/fido/virtual_u2f_device.cc
+++ b/device/fido/virtual_u2f_device.cc
@@ -98,10 +98,12 @@
       response = ErrorStatus(apdu::ApduResponse::Status::SW_INS_NOT_SUPPORTED);
   }
 
-  // Call |callback| via the |MessageLoop| because |AuthenticatorImpl| doesn't
-  // support callback hairpinning.
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(std::move(cb), std::move(response)));
+  if (response) {
+    // Call |callback| via the |MessageLoop| because |AuthenticatorImpl| doesn't
+    // support callback hairpinning.
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(cb), std::move(response)));
+  }
   return 0;
 }
 
@@ -119,7 +121,8 @@
   }
 
   if (mutable_state()->simulate_press_callback) {
-    mutable_state()->simulate_press_callback.Run();
+    if (!mutable_state()->simulate_press_callback.Run(this))
+      return base::nullopt;
   }
 
   auto challenge_param = data.first<32>();
@@ -199,7 +202,8 @@
   }
 
   if (mutable_state()->simulate_press_callback) {
-    mutable_state()->simulate_press_callback.Run();
+    if (!mutable_state()->simulate_press_callback.Run(this))
+      return base::nullopt;
   }
 
   if (data.size() < 32 + 32 + 1)
diff --git a/docs/ui/android/mvc_simple_list_tutorial.md b/docs/ui/android/mvc_simple_list_tutorial.md
index 98b79d6..cba200e 100644
--- a/docs/ui/android/mvc_simple_list_tutorial.md
+++ b/docs/ui/android/mvc_simple_list_tutorial.md
@@ -31,7 +31,7 @@
 public class SimpleMenuCoordinator {
 
     public SimpleMenuCoordinator(Context context, ListView listView) {
-        ModelListAdapter adapter = new ModelListAdapter(context);
+        ModelListAdapter adapter = new ModelListAdapter();
 
         final LayoutInflater layoutInflater = context.getSystemService(LAYOUT_INFLATER_SERVICE);
 
diff --git a/extensions/browser/api/bluetooth/bluetooth_private_api.cc b/extensions/browser/api/bluetooth/bluetooth_private_api.cc
index 0b9acda..d88298e 100644
--- a/extensions/browser/api/bluetooth/bluetooth_private_api.cc
+++ b/extensions/browser/api/bluetooth/bluetooth_private_api.cc
@@ -26,6 +26,10 @@
 #include "extensions/common/api/bluetooth.h"
 #include "extensions/common/api/bluetooth_private.h"
 
+#if defined(OS_CHROMEOS)
+#include "device/bluetooth/chromeos/bluetooth_utils.h"
+#endif
+
 namespace bt = extensions::api::bluetooth;
 namespace bt_private = extensions::api::bluetooth_private;
 namespace SetDiscoveryFilter = bt_private::SetDiscoveryFilter;
@@ -675,6 +679,8 @@
   Respond(Error(kPairingFailed));
 }
 
+////////////////////////////////////////////////////////////////////////////////
+
 BluetoothPrivateRecordPairingFunction::BluetoothPrivateRecordPairingFunction() =
     default;
 
@@ -693,6 +699,8 @@
   RecordPairingTransport(params_->transport);
 }
 
+////////////////////////////////////////////////////////////////////////////////
+
 BluetoothPrivateRecordReconnectionFunction::
     BluetoothPrivateRecordReconnectionFunction() = default;
 
@@ -716,6 +724,44 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
+BluetoothPrivateRecordDeviceSelectionFunction::
+    BluetoothPrivateRecordDeviceSelectionFunction() = default;
+
+BluetoothPrivateRecordDeviceSelectionFunction::
+    ~BluetoothPrivateRecordDeviceSelectionFunction() = default;
+
+bool BluetoothPrivateRecordDeviceSelectionFunction::CreateParams() {
+  params_ = bt_private::RecordDeviceSelection::Params::Create(*args_);
+  return params_ != nullptr;
+}
+
+void BluetoothPrivateRecordDeviceSelectionFunction::DoWork(
+    scoped_refptr<device::BluetoothAdapter> adapter) {
+#if defined(OS_CHROMEOS)
+  device::BluetoothTransport transport;
+  switch (params_->transport) {
+    case bt::Transport::TRANSPORT_CLASSIC:
+      transport = device::BLUETOOTH_TRANSPORT_CLASSIC;
+      break;
+    case bt::Transport::TRANSPORT_LE:
+      transport = device::BLUETOOTH_TRANSPORT_LE;
+      break;
+    case bt::Transport::TRANSPORT_DUAL:
+      transport = device::BLUETOOTH_TRANSPORT_DUAL;
+      break;
+    default:
+      transport = device::BLUETOOTH_TRANSPORT_INVALID;
+      break;
+  }
+
+  device::RecordDeviceSelectionDuration(
+      base::TimeDelta::FromMilliseconds(params_->selection_duration_ms),
+      device::BluetoothUiSurface::kSettings, params_->was_paired, transport);
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
 }  // namespace api
 
 }  // namespace extensions
diff --git a/extensions/browser/api/bluetooth/bluetooth_private_api.h b/extensions/browser/api/bluetooth/bluetooth_private_api.h
index 57cfaa1d..ec8b0e5 100644
--- a/extensions/browser/api/bluetooth/bluetooth_private_api.h
+++ b/extensions/browser/api/bluetooth/bluetooth_private_api.h
@@ -76,6 +76,9 @@
 namespace RecordReconnection {
 struct Params;
 }  // namespace RecordReconnection
+namespace RecordDeviceSelection {
+struct Params;
+}  // namespace RecordDeviceSelection
 }  // namespace bluetooth_private
 
 class BluetoothPrivateSetAdapterStateFunction
@@ -282,6 +285,27 @@
   DISALLOW_COPY_AND_ASSIGN(BluetoothPrivateRecordReconnectionFunction);
 };
 
+class BluetoothPrivateRecordDeviceSelectionFunction
+    : public BluetoothExtensionFunction {
+ public:
+  DECLARE_EXTENSION_FUNCTION("bluetoothPrivate.recordDeviceSelection",
+                             BLUETOOTHPRIVATE_RECORDDEVICESELECTION)
+
+  BluetoothPrivateRecordDeviceSelectionFunction();
+
+ protected:
+  ~BluetoothPrivateRecordDeviceSelectionFunction() override;
+
+  // BluetoothExtensionFunction:
+  bool CreateParams() override;
+  void DoWork(scoped_refptr<device::BluetoothAdapter> adapter) override;
+
+ private:
+  std::unique_ptr<bluetooth_private::RecordDeviceSelection::Params> params_;
+
+  DISALLOW_COPY_AND_ASSIGN(BluetoothPrivateRecordDeviceSelectionFunction);
+};
+
 }  // namespace api
 
 }  // namespace extensions
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 03f6fd1..bd3b910 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1405,6 +1405,7 @@
   LOGINSCREENUI_CLOSE = 1342,
   DECLARATIVENETREQUEST_GETMATCHEDRULES = 1343,
   DECLARATIVENETREQUEST_SETACTIONCOUNTASBADGETEXT = 1344,
+  BLUETOOTHPRIVATE_RECORDDEVICESELECTION = 1345,
   // Last entry: Add new entries above, then run:
   // python tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/extensions/common/api/bluetooth_private.idl b/extensions/common/api/bluetooth_private.idl
index 55840a3..62c39d6 100644
--- a/extensions/common/api/bluetooth_private.idl
+++ b/extensions/common/api/bluetooth_private.idl
@@ -159,6 +159,11 @@
     // Record that a user-initiated reconnection attempt to an already paired
     // device finished. Do not record cancellations.
     static void recordReconnection(boolean success);
+
+    // Record that a user selected a device to connect to.
+    static void recordDeviceSelection(long selectionDurationMs,
+                                      boolean wasPaired,
+                                      bluetooth.Transport transport);
   };
 
   interface Events {
diff --git a/google_apis/gaia/gaia_auth_fetcher.cc b/google_apis/gaia/gaia_auth_fetcher.cc
index 2c84cbd..f580fdf 100644
--- a/google_apis/gaia/gaia_auth_fetcher.cc
+++ b/google_apis/gaia/gaia_auth_fetcher.cc
@@ -732,7 +732,8 @@
 }
 
 void GaiaAuthFetcher::StartOAuthMultilogin(
-    const std::vector<MultiloginTokenIDPair>& accounts) {
+    const std::vector<MultiloginTokenIDPair>& accounts,
+    const std::string& external_cc_result) {
   DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
 
   UMA_HISTOGRAM_COUNTS_100("Signin.Multilogin.NumberOfAccounts",
@@ -748,8 +749,13 @@
       kOAuthMultiBearerHeaderFormat,
       base::JoinString(authorization_header_parts, ",").c_str());
 
-  std::string parameters = base::StringPrintf(
-      "?source=%s", net::EscapeUrlEncodedData(source_, true).c_str());
+  std::string source_string = net::EscapeUrlEncodedData(source_, true);
+  std::string parameters =
+      external_cc_result.empty()
+          ? base::StringPrintf("?source=%s", source_string.c_str())
+          : base::StringPrintf(
+                "?source=%s&externalCcResult=%s", source_string.c_str(),
+                net::EscapeUrlEncodedData(external_cc_result, true).c_str());
 
   net::NetworkTrafficAnnotationTag traffic_annotation =
       net::DefineNetworkTrafficAnnotation("gaia_auth_multilogin", R"(
diff --git a/google_apis/gaia/gaia_auth_fetcher.h b/google_apis/gaia/gaia_auth_fetcher.h
index e77e6a7..8709676 100644
--- a/google_apis/gaia/gaia_auth_fetcher.h
+++ b/google_apis/gaia/gaia_auth_fetcher.h
@@ -151,7 +151,8 @@
                        const std::string& service);
 
   // Starts a request to get the cookie for list of accounts.
-  void StartOAuthMultilogin(const std::vector<MultiloginTokenIDPair>& accounts);
+  void StartOAuthMultilogin(const std::vector<MultiloginTokenIDPair>& accounts,
+                            const std::string& external_cc_result);
 
   // Starts a request to list the accounts in the GAIA cookie.
   void StartListAccounts();
diff --git a/google_apis/gaia/gaia_auth_fetcher_unittest.cc b/google_apis/gaia/gaia_auth_fetcher_unittest.cc
index 875e499..8e4afa9 100644
--- a/google_apis/gaia/gaia_auth_fetcher_unittest.cc
+++ b/google_apis/gaia/gaia_auth_fetcher_unittest.cc
@@ -346,7 +346,7 @@
 
   TestGaiaAuthFetcher auth(&consumer, GetURLLoaderFactory());
   auth.StartOAuthMultilogin(
-      std::vector<GaiaAuthFetcher::MultiloginTokenIDPair>());
+      std::vector<GaiaAuthFetcher::MultiloginTokenIDPair>(), std::string());
 
   EXPECT_TRUE(auth.HasPendingFetch());
   auth.TestOnURLLoadCompleteInternal(net::OK, net::HTTP_OK,
@@ -381,7 +381,7 @@
 
   TestGaiaAuthFetcher auth(&consumer, GetURLLoaderFactory());
   auth.StartOAuthMultilogin(
-      std::vector<GaiaAuthFetcher::MultiloginTokenIDPair>());
+      std::vector<GaiaAuthFetcher::MultiloginTokenIDPair>(), std::string());
 
   EXPECT_TRUE(auth.HasPendingFetch());
   auth.TestOnURLLoadCompleteInternal(net::ERR_ABORTED, net::HTTP_OK,
@@ -416,7 +416,7 @@
 
   TestGaiaAuthFetcher auth(&consumer, GetURLLoaderFactory());
   auth.StartOAuthMultilogin(
-      std::vector<GaiaAuthFetcher::MultiloginTokenIDPair>());
+      std::vector<GaiaAuthFetcher::MultiloginTokenIDPair>(), std::string());
 
   EXPECT_TRUE(auth.HasPendingFetch());
   auth.TestOnURLLoadCompleteInternal(net::OK, net::HTTP_OK,
diff --git a/gpu/command_buffer/service/external_vk_image_backing.cc b/gpu/command_buffer/service/external_vk_image_backing.cc
index 9c6523e0..62a55ca 100644
--- a/gpu/command_buffer/service/external_vk_image_backing.cc
+++ b/gpu/command_buffer/service/external_vk_image_backing.cc
@@ -43,11 +43,12 @@
                        1 /* levelCount */);
 }
 
-VkResult CreateExternalVkImage(SharedContextState* context_state,
-                               VkFormat format,
-                               const gfx::Size& size,
-                               bool is_transfer_dst,
-                               VkImage* image) {
+VkResult CreateVkImage(SharedContextState* context_state,
+                       VkFormat format,
+                       const gfx::Size& size,
+                       bool is_transfer_dst,
+                       bool is_external,
+                       VkImage* image) {
   VkExternalMemoryImageCreateInfoKHR external_info = {
       .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO_KHR,
       .handleTypes = context_state->vk_context_provider()
@@ -61,7 +62,7 @@
 
   VkImageCreateInfo create_info = {
       .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
-      .pNext = &external_info,
+      .pNext = is_external ? &external_info : nullptr,
       .flags = 0,
       .imageType = VK_IMAGE_TYPE_2D,
       .format = format,
@@ -102,6 +103,28 @@
   return kInvalidTypeIndex;
 }
 
+class ScopedPixelStore {
+ public:
+  ScopedPixelStore(gl::GLApi* api, GLenum name, GLint value)
+      : api_(api), name_(name), value_(value) {
+    api_->glGetIntegervFn(name_, &old_value_);
+    if (value_ != old_value_)
+      api->glPixelStoreiFn(name_, value_);
+  }
+  ~ScopedPixelStore() {
+    if (value_ != old_value_)
+      api_->glPixelStoreiFn(name_, old_value_);
+  }
+
+ private:
+  gl::GLApi* const api_;
+  const GLenum name_;
+  const GLint value_;
+  GLint old_value_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedPixelStore);
+};
+
 }  // namespace
 
 // static
@@ -120,8 +143,9 @@
   VkFormat vk_format = ToVkFormat(format);
   VkImage image;
   bool is_transfer_dst = using_gmb || !pixel_data.empty();
-  VkResult result = CreateExternalVkImage(context_state, vk_format, size,
-                                          is_transfer_dst, &image);
+  bool is_external = context_state->support_vulkan_external_object();
+  VkResult result = CreateVkImage(context_state, vk_format, size,
+                                  is_transfer_dst, is_external, &image);
   if (result != VK_SUCCESS) {
     DLOG(ERROR) << "Failed to create external VkImage: " << result;
     return nullptr;
@@ -146,7 +170,7 @@
 
   VkMemoryAllocateInfo mem_alloc_info = {
       .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
-      .pNext = &external_info,
+      .pNext = is_external ? &external_info : nullptr,
       .allocationSize = requirements.size,
       .memoryTypeIndex = FindMemoryTypeIndex(
           context_state, requirements, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT),
@@ -175,8 +199,13 @@
       mailbox, format, size, color_space, usage, context_state, image, memory,
       requirements.size, vk_format, command_pool));
 
-  if (!pixel_data.empty())
-    backing->WritePixels(pixel_data, 0);
+  if (!pixel_data.empty()) {
+    backing->WritePixels(
+        pixel_data.size(), 0,
+        base::BindOnce([](const void* data, size_t size,
+                          void* buffer) { memcpy(buffer, data, size); },
+                       pixel_data.data(), pixel_data.size()));
+  }
 
   return backing;
 }
@@ -346,26 +375,24 @@
 
 bool ExternalVkImageBacking::BeginAccess(
     bool readonly,
-    std::vector<SemaphoreHandle>* semaphore_handles) {
-  if (readonly) {
-    if (reads_in_progress_ == 0 && shared_memory_mapping_.IsValid() &&
-        shared_memory_is_updated_) {
-      if (!WritePixels(
-              shared_memory_mapping_.GetMemoryAsSpan<const uint8_t>().subspan(
-                  memory_offset_),
-              stride_))
-        return false;
-      shared_memory_is_updated_ = false;
-    }
-  }
+    std::vector<SemaphoreHandle>* semaphore_handles,
+    bool is_gl) {
+  if (readonly && !reads_in_progress_)
+    UpdateContent(is_gl ? kInGLTexture : kInVkImage);
   return BeginAccessInternal(readonly, semaphore_handles);
 }
 
 void ExternalVkImageBacking::EndAccess(bool readonly,
-                                       SemaphoreHandle semaphore_handle) {
+                                       SemaphoreHandle semaphore_handle,
+                                       bool is_gl) {
   EndAccessInternal(readonly, std::move(semaphore_handle));
-  // TODO(penghuang): read pixels back from VkImage to shared memory GMB, if
-  // this feature is needed.
+  if (!readonly) {
+    if (use_separate_gl_texture()) {
+      latest_content_ = is_gl ? kInGLTexture : kInVkImage;
+    } else {
+      latest_content_ = kInVkImage | kInGLTexture;
+    }
+  }
 }
 
 bool ExternalVkImageBacking::IsCleared() const {
@@ -378,7 +405,7 @@
 
 void ExternalVkImageBacking::Update(std::unique_ptr<gfx::GpuFence> in_fence) {
   DCHECK(!in_fence);
-  shared_memory_is_updated_ = true;
+  latest_content_ = kInSharedMemory;
 }
 
 void ExternalVkImageBacking::Destroy() {
@@ -397,8 +424,8 @@
   if (texture_) {
     // Ensure that a context is current before removing the ref and calling
     // glDeleteTextures.
-    if (!context_state()->context()->IsCurrent(nullptr))
-      context_state()->context()->MakeCurrent(context_state()->surface());
+    if (!gl::g_current_gl_context)
+      context_state()->MakeCurrent(nullptr, true /* need_gl */);
     texture_->RemoveLightweightRef(have_context());
   }
 }
@@ -427,47 +454,50 @@
   bool result = backend_texture_.getVkImageInfo(&image_info);
   DCHECK(result);
   if (!texture_) {
-    VkMemoryGetFdInfoKHR get_fd_info;
-    get_fd_info.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR;
-    get_fd_info.pNext = nullptr;
-    get_fd_info.memory = image_info.fAlloc.fMemory;
-    get_fd_info.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;
+    gl::GLApi* api = gl::g_current_gl_context;
+    GLuint memory_object = 0;
+    if (!use_separate_gl_texture()) {
+      VkMemoryGetFdInfoKHR get_fd_info;
+      get_fd_info.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR;
+      get_fd_info.pNext = nullptr;
+      get_fd_info.memory = image_info.fAlloc.fMemory;
+      get_fd_info.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR;
 
-    int memory_fd = -1;
-    vkGetMemoryFdKHR(device(), &get_fd_info, &memory_fd);
-    if (memory_fd < 0) {
-      DLOG(ERROR)
-          << "Unable to extract file descriptor out of external VkImage";
-      return nullptr;
+      int memory_fd = -1;
+      vkGetMemoryFdKHR(device(), &get_fd_info, &memory_fd);
+      if (memory_fd < 0) {
+        DLOG(ERROR)
+            << "Unable to extract file descriptor out of external VkImage";
+        return nullptr;
+      }
+
+      api->glCreateMemoryObjectsEXTFn(1, &memory_object);
+      api->glImportMemoryFdEXTFn(memory_object, image_info.fAlloc.fSize,
+                                 GL_HANDLE_TYPE_OPAQUE_FD_EXT, memory_fd);
     }
 
-    gl::GLApi* api = gl::g_current_gl_context;
-
-    constexpr GLenum target = GL_TEXTURE_2D;
-    constexpr GLenum get_target = GL_TEXTURE_BINDING_2D;
     GLuint internal_format = viz::TextureStorageFormat(format());
-
-    GLuint memory_object;
-    api->glCreateMemoryObjectsEXTFn(1, &memory_object);
-    api->glImportMemoryFdEXTFn(memory_object, image_info.fAlloc.fSize,
-                               GL_HANDLE_TYPE_OPAQUE_FD_EXT, memory_fd);
+    GLint old_texture_binding = 0;
+    api->glGetIntegervFn(GL_TEXTURE_BINDING_2D, &old_texture_binding);
     GLuint texture_service_id;
     api->glGenTexturesFn(1, &texture_service_id);
-
-    GLint old_texture_binding = 0;
-    api->glGetIntegervFn(get_target, &old_texture_binding);
-    api->glBindTextureFn(target, texture_service_id);
-    api->glTexParameteriFn(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-    api->glTexParameteriFn(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-    api->glTexParameteriFn(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-    api->glTexParameteriFn(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-    api->glTexStorageMem2DEXTFn(GL_TEXTURE_2D, 1, internal_format,
-                                size().width(), size().height(), memory_object,
-                                0);
-
+    api->glBindTextureFn(GL_TEXTURE_2D, texture_service_id);
+    api->glTexParameteriFn(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    api->glTexParameteriFn(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    api->glTexParameteriFn(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    api->glTexParameteriFn(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    if (use_separate_gl_texture()) {
+      api->glTexStorage2DEXTFn(GL_TEXTURE_2D, 1, internal_format,
+                               size().width(), size().height());
+    } else {
+      DCHECK(memory_object);
+      api->glTexStorageMem2DEXTFn(GL_TEXTURE_2D, 1, internal_format,
+                                  size().width(), size().height(),
+                                  memory_object, 0);
+    }
     texture_ = new gles2::Texture(texture_service_id);
     texture_->SetLightweightRef();
-    texture_->SetTarget(target, 1);
+    texture_->SetTarget(GL_TEXTURE_2D, 1);
     texture_->sampler_state_.min_filter = GL_LINEAR;
     texture_->sampler_state_.mag_filter = GL_LINEAR;
     texture_->sampler_state_.wrap_t = GL_CLAMP_TO_EDGE;
@@ -479,12 +509,12 @@
 
     GLenum gl_format = viz::GLDataFormat(format());
     GLenum gl_type = viz::GLDataType(format());
-    texture_->SetLevelInfo(target, 0, internal_format, size().width(),
+    texture_->SetLevelInfo(GL_TEXTURE_2D, 0, internal_format, size().width(),
                            size().height(), 1, 0, gl_format, gl_type,
                            cleared_rect);
     texture_->SetImmutable(true);
 
-    api->glBindTextureFn(target, old_texture_binding);
+    api->glBindTextureFn(GL_TEXTURE_2D, old_texture_binding);
   }
   return std::make_unique<ExternalVkImageGlRepresentation>(
       manager, this, tracker, texture_, texture_->service_id());
@@ -526,13 +556,57 @@
   Update(nullptr);
 }
 
-bool ExternalVkImageBacking::WritePixels(
-    const base::span<const uint8_t>& pixel_data,
-    size_t stride) {
-  DCHECK(stride == 0 || size().height() * stride <= pixel_data.size());
+void ExternalVkImageBacking::UpdateContent(uint32_t content_flags) {
+  // Only support one backing for now.
+  DCHECK(content_flags == kInVkImage || content_flags == kInGLTexture ||
+         content_flags == kInSharedMemory);
+
+  if ((latest_content_ & content_flags) == content_flags)
+    return;
+
+  if (content_flags == kInGLTexture && !use_separate_gl_texture())
+    content_flags = kInVkImage;
+
+  if (content_flags == kInVkImage) {
+    if (latest_content_ & kInSharedMemory) {
+      if (!shared_memory_mapping_.IsValid())
+        return;
+      auto pixel_data =
+          shared_memory_mapping_.GetMemoryAsSpan<const uint8_t>().subspan(
+              memory_offset_);
+      if (!WritePixels(
+              pixel_data.size(), stride_,
+              base::BindOnce([](const void* data, size_t size,
+                                void* buffer) { memcpy(buffer, data, size); },
+                             pixel_data.data(), pixel_data.size()))) {
+        return;
+      }
+      latest_content_ |=
+          use_separate_gl_texture() ? kInVkImage : kInVkImage | kInGLTexture;
+      return;
+    }
+    if ((latest_content_ & kInGLTexture) && use_separate_gl_texture()) {
+      CopyPixelsFromGLTexture();
+      latest_content_ |= kInVkImage;
+      return;
+    }
+  } else if (content_flags == kInGLTexture) {
+    // TODO(penghuang): support updating content in gl texture.
+    NOTIMPLEMENTED_LOG_ONCE();
+  } else if (content_flags == kInSharedMemory) {
+    // TODO(penghuang): read pixels back from VkImage to shared memory GMB, if
+    // this feature is needed.
+    NOTIMPLEMENTED_LOG_ONCE();
+  }
+}
+
+bool ExternalVkImageBacking::WritePixels(size_t data_size,
+                                         size_t stride,
+                                         FillBufferCallback callback) {
+  DCHECK(stride == 0 || size().height() * stride <= data_size);
   VkBufferCreateInfo buffer_create_info = {
       .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
-      .size = pixel_data.size(),
+      .size = data_size,
       .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
       .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
   };
@@ -576,16 +650,17 @@
     return false;
   }
 
-  void* data = nullptr;
-  result = vkMapMemory(device(), stage_memory, 0 /* memoryOffset */,
-                       pixel_data.size(), 0, &data);
+  void* buffer = nullptr;
+  result = vkMapMemory(device(), stage_memory, 0 /* memoryOffset */, data_size,
+                       0, &buffer);
   if (result != VK_SUCCESS) {
     DLOG(ERROR) << "vkMapMemory() failed. " << result;
     vkDestroyBuffer(device(), stage_buffer, nullptr /* pAllocator */);
     vkFreeMemory(device(), stage_memory, nullptr /* pAllocator */);
     return false;
   }
-  memcpy(data, pixel_data.data(), pixel_data.size());
+
+  std::move(callback).Run(buffer);
   vkUnmapMemory(device(), stage_memory);
 
   std::vector<gpu::SemaphoreHandle> handles;
@@ -664,6 +739,97 @@
   return true;
 }
 
+void ExternalVkImageBacking::CopyPixelsFromGLTexture() {
+  DCHECK(use_separate_gl_texture());
+  DCHECK(texture_);
+
+  GLenum gl_format = GL_NONE;
+  GLenum gl_type = GL_NONE;
+  size_t bytes_per_pixel = 0;
+  switch (ToVkFormat(format())) {
+    case VK_FORMAT_R8G8B8A8_UNORM:
+      gl_format = GL_RGBA;
+      gl_type = GL_UNSIGNED_BYTE;
+      bytes_per_pixel = 4;
+      break;
+    case VK_FORMAT_B8G8R8A8_UNORM:
+      gl_format = GL_BGRA;
+      gl_type = GL_UNSIGNED_BYTE;
+      bytes_per_pixel = 4;
+      break;
+    case VK_FORMAT_R8_UNORM:
+      gl_format = GL_RED;
+      gl_type = GL_UNSIGNED_BYTE;
+      bytes_per_pixel = 1;
+      break;
+    case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
+      gl_format = GL_RGBA;
+      gl_type = GL_UNSIGNED_SHORT_4_4_4_4;
+      bytes_per_pixel = 2;
+      break;
+    case VK_FORMAT_R5G6B5_UNORM_PACK16:
+      gl_format = GL_RGB;
+      gl_type = GL_UNSIGNED_SHORT_5_6_5;
+      bytes_per_pixel = 2;
+      break;
+    case VK_FORMAT_R16_UNORM:
+      gl_format = GL_RED;
+      gl_type = GL_UNSIGNED_SHORT;
+      bytes_per_pixel = 2;
+      break;
+    case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
+      gl_format = GL_RGBA;
+      gl_type = GL_UNSIGNED_INT_2_10_10_10_REV;
+      bytes_per_pixel = 4;
+      break;
+    default:
+      NOTREACHED() << "Not supported resource format=" << format();
+      return;
+  }
+
+  // Make sure GrContext is not using GL. So we don't need reset GrContext
+  DCHECK(!context_state_->GrContextIsGL());
+
+  // Make sure a gl context is current, since textures are shared between all gl
+  // contexts, we don't care which gl context is current.
+  if (!gl::g_current_gl_context &&
+      !context_state_->MakeCurrent(nullptr, true /* needs_gl */))
+    return;
+
+  gl::GLApi* api = gl::g_current_gl_context;
+  GLuint framebuffer;
+  GLint old_framebuffer;
+  api->glGetIntegervFn(GL_READ_FRAMEBUFFER_BINDING, &old_framebuffer);
+  api->glGenFramebuffersEXTFn(1, &framebuffer);
+  api->glBindFramebufferEXTFn(GL_READ_FRAMEBUFFER, framebuffer);
+  api->glFramebufferTexture2DEXTFn(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+                                   GL_TEXTURE_2D, texture_->service_id(), 0);
+  GLenum status = api->glCheckFramebufferStatusEXTFn(GL_READ_FRAMEBUFFER);
+  DCHECK_EQ(status, static_cast<GLenum>(GL_FRAMEBUFFER_COMPLETE))
+      << "CheckFramebufferStatusEXT() failed.";
+
+  base::CheckedNumeric<size_t> checked_size = bytes_per_pixel;
+  checked_size *= size().width();
+  checked_size *= size().height();
+  DCHECK(checked_size.IsValid());
+
+  ScopedPixelStore pack_row_length(api, GL_PACK_ROW_LENGTH, 0);
+  ScopedPixelStore pack_skip_pixels(api, GL_PACK_SKIP_PIXELS, 0);
+  ScopedPixelStore pack_skip_rows(api, GL_PACK_SKIP_ROWS, 0);
+  ScopedPixelStore pack_aligment(api, GL_PACK_ALIGNMENT, 1);
+
+  WritePixels(checked_size.ValueOrDie(), 0,
+              base::BindOnce(
+                  [](gl::GLApi* api, const gfx::Size& size, GLenum format,
+                     GLenum type, void* buffer) {
+                    api->glReadPixelsFn(0, 0, size.width(), size.height(),
+                                        format, type, buffer);
+                  },
+                  api, size(), gl_format, gl_type));
+  api->glBindFramebufferEXTFn(GL_READ_FRAMEBUFFER, old_framebuffer);
+  api->glDeleteFramebuffersEXTFn(1, &framebuffer);
+}
+
 bool ExternalVkImageBacking::BeginAccessInternal(
     bool readonly,
     std::vector<SemaphoreHandle>* semaphore_handles) {
diff --git a/gpu/command_buffer/service/external_vk_image_backing.h b/gpu/command_buffer/service/external_vk_image_backing.h
index d02f8b9..54d73b4e 100644
--- a/gpu/command_buffer/service/external_vk_image_backing.h
+++ b/gpu/command_buffer/service/external_vk_image_backing.h
@@ -60,19 +60,25 @@
         ->GetVulkanDevice();
   }
   bool need_sychronization() const {
+    if (use_separate_gl_texture())
+      return false;
     return usage() & SHARED_IMAGE_USAGE_GLES2;
   }
+  bool use_separate_gl_texture() const {
+    return !context_state()->support_vulkan_external_object();
+  }
 
   // Notifies the backing that an access will start. Return false if there is
   // currently any other conflict access in progress. Otherwise, returns true
   // and semaphore handles which will be waited on before accessing.
   bool BeginAccess(bool readonly,
-                   std::vector<SemaphoreHandle>* semaphore_handles);
+                   std::vector<SemaphoreHandle>* semaphore_handles,
+                   bool is_gl);
 
   // Notifies the backing that an access has ended. The representation must
   // provide a semaphore handle that has been signaled at the end of the write
   // access.
-  void EndAccess(bool readonly, SemaphoreHandle semaphore_handle);
+  void EndAccess(bool readonly, SemaphoreHandle semaphore_handle, bool is_gl);
 
   // SharedImageBacking implementation.
   bool IsCleared() const override;
@@ -82,6 +88,7 @@
   bool ProduceLegacyMailbox(MailboxManager* mailbox_manager) override;
 
  protected:
+  void UpdateContent(uint32_t content_flags);
   bool BeginAccessInternal(bool readonly,
                            std::vector<SemaphoreHandle>* semaphore_handles);
   void EndAccessInternal(bool readonly, SemaphoreHandle semaphore_handle);
@@ -117,7 +124,11 @@
       size_t stride,
       size_t memory_offset);
 
-  bool WritePixels(const base::span<const uint8_t>& pixel_data, size_t stride);
+  using FillBufferCallback = base::OnceCallback<void(void* buffer)>;
+  bool WritePixels(size_t data_size,
+                   size_t stride,
+                   FillBufferCallback callback);
+  void CopyPixelsFromGLTexture();
 
   SharedContextState* const context_state_;
   GrBackendTexture backend_texture_;
@@ -132,11 +143,17 @@
   gles2::Texture* texture_ = nullptr;
 
   // GMB related stuff.
-  bool shared_memory_is_updated_ = false;
   base::WritableSharedMemoryMapping shared_memory_mapping_;
   size_t stride_ = 0;
   size_t memory_offset_ = 0;
 
+  enum LatestContent {
+    kInVkImage = 1 << 0,
+    kInSharedMemory = 1 << 1,
+    kInGLTexture = 1 << 2,
+  };
+  uint32_t latest_content_ = 0;
+
   DISALLOW_COPY_AND_ASSIGN(ExternalVkImageBacking);
 };
 
diff --git a/gpu/command_buffer/service/external_vk_image_gl_representation.cc b/gpu/command_buffer/service/external_vk_image_gl_representation.cc
index 1c1155b..cb889f3 100644
--- a/gpu/command_buffer/service/external_vk_image_gl_representation.cc
+++ b/gpu/command_buffer/service/external_vk_image_gl_representation.cc
@@ -89,7 +89,7 @@
 
   std::vector<SemaphoreHandle> handles;
 
-  if (!backing_impl()->BeginAccess(readonly, &handles))
+  if (!backing_impl()->BeginAccess(readonly, &handles, true /* is_gl */))
     return false;
 
   for (auto& handle : handles) {
@@ -124,48 +124,56 @@
       (current_access_mode_ == GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM);
   current_access_mode_ = 0;
 
-  VkSemaphore semaphore =
-      vk_implementation()->CreateExternalSemaphore(backing_impl()->device());
-  if (semaphore == VK_NULL_HANDLE) {
-    // TODO(crbug.com/933452): We should be able to handle this failure more
-    // gracefully rather than shutting down the whole process.
-    LOG(FATAL) << "Unable to create a VkSemaphore in "
-               << "ExternalVkImageGlRepresentation for synchronization with "
-               << "Vulkan";
-    return;
-  }
+  VkSemaphore semaphore = VK_NULL_HANDLE;
+  SemaphoreHandle semaphore_handle;
+  GLuint gl_semaphore = 0;
+  if (backing_impl()->need_sychronization()) {
+    semaphore =
+        vk_implementation()->CreateExternalSemaphore(backing_impl()->device());
+    if (semaphore == VK_NULL_HANDLE) {
+      // TODO(crbug.com/933452): We should be able to handle this failure more
+      // gracefully rather than shutting down the whole process.
+      LOG(FATAL) << "Unable to create a VkSemaphore in "
+                 << "ExternalVkImageGlRepresentation for synchronization with "
+                 << "Vulkan";
+      return;
+    }
 
-  SemaphoreHandle semaphore_handle =
-      vk_implementation()->GetSemaphoreHandle(vk_device(), semaphore);
-  vkDestroySemaphore(backing_impl()->device(), semaphore, nullptr);
-  if (!semaphore_handle.is_valid()) {
-    LOG(FATAL) << "Unable to export VkSemaphore into GL in "
-               << "ExternalVkImageGlRepresentation for synchronization with "
-               << "Vulkan";
-    return;
-  }
+    semaphore_handle =
+        vk_implementation()->GetSemaphoreHandle(vk_device(), semaphore);
+    vkDestroySemaphore(backing_impl()->device(), semaphore, nullptr);
+    if (!semaphore_handle.is_valid()) {
+      LOG(FATAL) << "Unable to export VkSemaphore into GL in "
+                 << "ExternalVkImageGlRepresentation for synchronization with "
+                 << "Vulkan";
+      return;
+    }
 
-  SemaphoreHandle dup_semaphore_handle = semaphore_handle.Duplicate();
-  GLuint gl_semaphore =
-      ImportVkSemaphoreIntoGL(std::move(dup_semaphore_handle));
+    SemaphoreHandle dup_semaphore_handle = semaphore_handle.Duplicate();
+    gl_semaphore = ImportVkSemaphoreIntoGL(std::move(dup_semaphore_handle));
 
-  if (!gl_semaphore) {
-    // TODO(crbug.com/933452): We should be able to semaphore_handle this
-    // failure more gracefully rather than shutting down the whole process.
-    LOG(FATAL) << "Unable to export VkSemaphore into GL in "
-               << "ExternalVkImageGlRepresentation for synchronization with "
-               << "Vulkan";
-    return;
+    if (!gl_semaphore) {
+      // TODO(crbug.com/933452): We should be able to semaphore_handle this
+      // failure more gracefully rather than shutting down the whole process.
+      LOG(FATAL) << "Unable to export VkSemaphore into GL in "
+                 << "ExternalVkImageGlRepresentation for synchronization with "
+                 << "Vulkan";
+      return;
+    }
   }
 
   GrVkImageInfo info;
   auto result = backing_impl()->backend_texture().getVkImageInfo(&info);
   DCHECK(result);
   GLenum dst_layout = ToGLImageLayout(info.fImageLayout);
-  api()->glSignalSemaphoreEXTFn(gl_semaphore, 0, nullptr, 1,
-                                &texture_service_id_, &dst_layout);
-  api()->glDeleteSemaphoresEXTFn(1, &gl_semaphore);
-  backing_impl()->EndAccess(readonly, std::move(semaphore_handle));
+  if (backing_impl()->need_sychronization()) {
+    api()->glSignalSemaphoreEXTFn(gl_semaphore, 0, nullptr, 1,
+                                  &texture_service_id_, &dst_layout);
+    api()->glDeleteSemaphoresEXTFn(1, &gl_semaphore);
+  }
+
+  backing_impl()->EndAccess(readonly, std::move(semaphore_handle),
+                            true /* is_gl */);
 }
 
 GLuint ExternalVkImageGlRepresentation::ImportVkSemaphoreIntoGL(
diff --git a/gpu/command_buffer/service/external_vk_image_skia_representation.cc b/gpu/command_buffer/service/external_vk_image_skia_representation.cc
index 572e4d1..d4faa91 100644
--- a/gpu/command_buffer/service/external_vk_image_skia_representation.cc
+++ b/gpu/command_buffer/service/external_vk_image_skia_representation.cc
@@ -93,7 +93,7 @@
   DCHECK(end_access_semaphore_ == VK_NULL_HANDLE);
 
   std::vector<SemaphoreHandle> handles;
-  if (!backing_impl()->BeginAccess(readonly, &handles))
+  if (!backing_impl()->BeginAccess(readonly, &handles, false /* is_gl */))
     return nullptr;
 
   for (auto& handle : handles) {
@@ -137,7 +137,7 @@
     DCHECK(end_access_semaphore_ == VK_NULL_HANDLE);
   }
 
-  backing_impl()->EndAccess(readonly, std::move(handle));
+  backing_impl()->EndAccess(readonly, std::move(handle), false /* is_gl */);
 }
 
 }  // namespace gpu
\ No newline at end of file
diff --git a/gpu/command_buffer/service/shared_context_state.cc b/gpu/command_buffer/service/shared_context_state.cc
index 2010eb46..061c1ccb 100644
--- a/gpu/command_buffer/service/shared_context_state.cc
+++ b/gpu/command_buffer/service/shared_context_state.cc
@@ -223,11 +223,16 @@
     context_ = std::move(virtual_context);
     MakeCurrent(nullptr);
   }
+
+  // TODO(penghuang): query extension from VulkanInstance.
+  support_vulkan_external_object_ =
+      gpu_preferences.use_vulkan == gpu::VulkanImplementationName::kNative;
+
   return true;
 }
 
-bool SharedContextState::MakeCurrent(gl::GLSurface* surface) {
-  if (!GrContextIsGL())
+bool SharedContextState::MakeCurrent(gl::GLSurface* surface, bool needs_gl) {
+  if (!GrContextIsGL() && !needs_gl)
     return true;
 
   if (context_lost_)
diff --git a/gpu/command_buffer/service/shared_context_state.h b/gpu/command_buffer/service/shared_context_state.h
index 5c57a62..56b335e 100644
--- a/gpu/command_buffer/service/shared_context_state.h
+++ b/gpu/command_buffer/service/shared_context_state.h
@@ -72,7 +72,7 @@
                     scoped_refptr<gles2::FeatureInfo> feature_info);
   bool IsGLInitialized() const { return !!feature_info_; }
 
-  bool MakeCurrent(gl::GLSurface* surface);
+  bool MakeCurrent(gl::GLSurface* surface, bool needs_gl = false);
   void MarkContextLost();
   bool IsCurrent(gl::GLSurface* surface);
 
@@ -110,6 +110,9 @@
   bool use_virtualized_gl_contexts() const {
     return use_virtualized_gl_contexts_;
   }
+  bool support_vulkan_external_object() const {
+    return support_vulkan_external_object_;
+  }
 
   // base::trace_event::MemoryDumpProvider implementation.
   bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
@@ -152,6 +155,7 @@
   QueryManager* GetQueryManager() override;
 
   bool use_virtualized_gl_contexts_ = false;
+  bool support_vulkan_external_object_ = false;
   base::OnceClosure context_lost_callback_;
   viz::VulkanContextProvider* const vk_context_provider_;
   viz::MetalContextProvider* const metal_context_provider_;
diff --git a/ios/build/bots/chromium.mac/ios-slimnav.json b/ios/build/bots/chromium.mac/ios-slimnav.json
index cae98882..cb53220 100644
--- a/ios/build/bots/chromium.mac/ios-slimnav.json
+++ b/ios/build/bots/chromium.mac/ios-slimnav.json
@@ -250,7 +250,7 @@
     {
       "app": "ios_chrome_translate_egtests",
       "test args": [
-        "--enable-features=CompactTranslateInfobarIOS,SlimNavigationManager,OfflineVersionWithoutNativeContent"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPad Air 2",
       "os": "11.4",
@@ -372,7 +372,7 @@
       {
       "app": "ios_chrome_translate_egtests",
       "test args": [
-        "--enable-features=CompactTranslateInfobarIOS,SlimNavigationManager,OfflineVersionWithoutNativeContent"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPad Air 2",
       "os": "12.1",
@@ -494,7 +494,7 @@
     {
       "app": "ios_chrome_translate_egtests",
       "test args": [
-        "--enable-features=CompactTranslateInfobarIOS,SlimNavigationManager,OfflineVersionWithoutNativeContent"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone X",
       "os": "11.4",
@@ -616,7 +616,7 @@
     {
       "app": "ios_chrome_translate_egtests",
       "test args": [
-        "--enable-features=CompactTranslateInfobarIOS,SlimNavigationManager,OfflineVersionWithoutNativeContent"
+        "--enable-features=SlimNavigationManager,OfflineVersionWithoutNativeContent"
       ],
       "device type": "iPhone X",
       "os": "12.1",
diff --git a/ios/build/bots/tests/eg_tests.json b/ios/build/bots/tests/eg_tests.json
index 565bc49..6052600c8f 100644
--- a/ios/build/bots/tests/eg_tests.json
+++ b/ios/build/bots/tests/eg_tests.json
@@ -20,9 +20,6 @@
     },
     {
       "app": "ios_chrome_translate_egtests",
-      "test args": [
-        "--enable-features=CompactTranslateInfobarIOS"
-      ],
       "xctest": true,
       "xcode parallelization": true
     },
diff --git a/ios/chrome/browser/about_flags.mm b/ios/chrome/browser/about_flags.mm
index a589c02..4577443 100644
--- a/ios/chrome/browser/about_flags.mm
+++ b/ios/chrome/browser/about_flags.mm
@@ -413,10 +413,6 @@
      flag_descriptions::kBreakpadNoDelayInitialUploadDescription,
      flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(crash_report::kBreakpadNoDelayInitialUpload)},
-    {"enable-compact-translate-infobar",
-     flag_descriptions::kCompactTranslateInfobarName,
-     flag_descriptions::kCompactTranslateInfobarDescription, flags_ui::kOsIos,
-     FEATURE_VALUE_TYPE(translate::kCompactTranslateInfobarIOS)},
     {"non-modal-dialogs", flag_descriptions::kNonModalDialogsName,
      flag_descriptions::kNonModalDialogsDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(dialogs::kNonModalDialogs)},
diff --git a/ios/chrome/browser/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/ios_chrome_flag_descriptions.cc
index f27883c..a351350 100644
--- a/ios/chrome/browser/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/ios_chrome_flag_descriptions.cc
@@ -126,12 +126,6 @@
     "Automatically switches to the regular tabs panel in the tab grid after "
     "closing the last incognito tab";
 
-const char kCompactTranslateInfobarName[] =
-    "Enable the compact translate infobar";
-const char kCompactTranslateInfobarDescription[] =
-    "When enabled, replaces the exisitng translate infobars with a new compact "
-    "one.";
-
 const char kContextualSearch[] = "Contextual Search";
 const char kContextualSearchDescription[] =
     "Whether or not Contextual Search is enabled.";
diff --git a/ios/chrome/browser/ios_chrome_flag_descriptions.h b/ios/chrome/browser/ios_chrome_flag_descriptions.h
index 35b566ae..46e41f1 100644
--- a/ios/chrome/browser/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/ios_chrome_flag_descriptions.h
@@ -97,10 +97,6 @@
 extern const char kClosingLastIncognitoTabName[];
 extern const char kClosingLastIncognitoTabDescription[];
 
-// Title and description for the flag to enable the compact translate infobar.
-extern const char kCompactTranslateInfobarName[];
-extern const char kCompactTranslateInfobarDescription[];
-
 // Title and description for the flag to enable Contextual Search.
 extern const char kContextualSearch[];
 extern const char kContextualSearchDescription[];
diff --git a/ios/chrome/browser/translate/BUILD.gn b/ios/chrome/browser/translate/BUILD.gn
index 857141fd..8439b23f 100644
--- a/ios/chrome/browser/translate/BUILD.gn
+++ b/ios/chrome/browser/translate/BUILD.gn
@@ -5,18 +5,12 @@
 source_set("translate") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
-    "after_translate_infobar_controller.h",
-    "after_translate_infobar_controller.mm",
-    "before_translate_infobar_controller.h",
-    "before_translate_infobar_controller.mm",
     "chrome_ios_translate_client.h",
     "chrome_ios_translate_client.mm",
     "language_selection_context.h",
     "language_selection_context.mm",
     "language_selection_delegate.h",
     "language_selection_handler.h",
-    "never_translate_infobar_controller.h",
-    "never_translate_infobar_controller.mm",
     "translate_accept_languages_factory.cc",
     "translate_accept_languages_factory.h",
     "translate_infobar_controller.h",
@@ -24,8 +18,6 @@
     "translate_infobar_delegate_observer_bridge.h",
     "translate_infobar_delegate_observer_bridge.mm",
     "translate_infobar_tags.h",
-    "translate_message_infobar_controller.h",
-    "translate_message_infobar_controller.mm",
     "translate_option_selection_delegate.h",
     "translate_option_selection_handler.h",
     "translate_ranker_factory.cc",
@@ -131,32 +123,3 @@
   ]
   libs = [ "XCTest.framework" ]
 }
-
-source_set("external_url_eg_tests") {
-  configs += [ "//build/config/compiler:enable_arc" ]
-  testonly = true
-  sources = [
-    "legacy_translate_infobar_egtest.mm",
-  ]
-  deps = [
-    ":translate",
-    "//base",
-    "//base/test:test_support",
-    "//components/language/ios/browser",
-    "//components/strings",
-    "//components/translate/core/browser",
-    "//components/translate/core/common",
-    "//components/translate/ios/browser",
-    "//ios/chrome/browser/browser_state",
-    "//ios/chrome/browser/ui/translate:translate_ui",
-    "//ios/chrome/test/app:test_support",
-    "//ios/chrome/test/earl_grey:test_support",
-    "//ios/third_party/earl_grey:earl_grey+link",
-    "//ios/web:earl_grey_test_support",
-    "//ios/web/public/test",
-    "//ios/web/public/test/http_server",
-    "//net",
-    "//ui/base",
-  ]
-  libs = [ "XCTest.framework" ]
-}
diff --git a/ios/chrome/browser/translate/after_translate_infobar_controller.h b/ios/chrome/browser/translate/after_translate_infobar_controller.h
deleted file mode 100644
index 57ee824..0000000
--- a/ios/chrome/browser/translate/after_translate_infobar_controller.h
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_TRANSLATE_AFTER_TRANSLATE_INFOBAR_CONTROLLER_H_
-#define IOS_CHROME_BROWSER_TRANSLATE_AFTER_TRANSLATE_INFOBAR_CONTROLLER_H_
-
-#include "ios/chrome/browser/infobars/infobar_controller.h"
-
-namespace translate {
-class TranslateInfoBarDelegate;
-}
-
-@interface AfterTranslateInfoBarController : InfoBarController
-
-- (instancetype)initWithInfoBarDelegate:
-    (translate::TranslateInfoBarDelegate*)infoBarDelegate
-    NS_DESIGNATED_INITIALIZER;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_TRANSLATE_AFTER_TRANSLATE_INFOBAR_CONTROLLER_H_
diff --git a/ios/chrome/browser/translate/after_translate_infobar_controller.mm b/ios/chrome/browser/translate/after_translate_infobar_controller.mm
deleted file mode 100644
index 645879e..0000000
--- a/ios/chrome/browser/translate/after_translate_infobar_controller.mm
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright 2013 The Chromium 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/chrome/browser/translate/after_translate_infobar_controller.h"
-
-#include "base/mac/foundation_util.h"
-#include "base/strings/sys_string_conversions.h"
-#include "base/strings/utf_string_conversions.h"
-#include "components/strings/grit/components_strings.h"
-#include "components/translate/core/browser/translate_infobar_delegate.h"
-#import "ios/chrome/browser/infobars/infobar_controller+protected.h"
-#include "ios/chrome/browser/infobars/infobar_controller_delegate.h"
-#include "ios/chrome/browser/translate/translate_infobar_tags.h"
-#import "ios/chrome/browser/ui/infobars/confirm_infobar_view.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/gfx/image/image.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-namespace {
-enum AlwaysTranslateSwitchState {
-  ALWAYS_TRANSLATE_SWITCH_NOT_CHANGED,
-  ALWAYS_TRANSLATE_SWITCH_SET_TO_ENABLED,
-  ALWAYS_TRANSLATE_SWITCH_SET_TO_DISABLED,
-};
-}  // namespace
-
-@interface AfterTranslateInfoBarController () {
-  AlwaysTranslateSwitchState _alwaysTranslateSwitchState;
-}
-
-// Overrides superclass property.
-@property(nonatomic, readonly)
-    translate::TranslateInfoBarDelegate* infoBarDelegate;
-
-@end
-
-@implementation AfterTranslateInfoBarController
-
-@dynamic infoBarDelegate;
-
-#pragma mark -
-#pragma mark InfoBarControllerProtocol
-
-- (instancetype)initWithInfoBarDelegate:
-    (translate::TranslateInfoBarDelegate*)infoBarDelegate {
-  return [super initWithInfoBarDelegate:infoBarDelegate];
-}
-
-- (UIView*)infobarView {
-  ConfirmInfoBarView* infoBarView =
-      [[ConfirmInfoBarView alloc] initWithFrame:CGRectZero];
-  // Icon
-  gfx::Image icon = self.infoBarDelegate->GetIcon();
-  if (!icon.IsEmpty())
-    [infoBarView addLeftIcon:icon.ToUIImage()];
-  // Main text.
-  const bool autodeterminedSourceLanguage =
-      self.infoBarDelegate->original_language_code() ==
-      translate::kUnknownLanguageCode;
-  bool swappedLanguageButtons;
-  std::vector<base::string16> strings;
-  translate::TranslateInfoBarDelegate::GetAfterTranslateStrings(
-      &strings, &swappedLanguageButtons, autodeterminedSourceLanguage);
-  DCHECK_EQ(autodeterminedSourceLanguage ? 2U : 3U, strings.size());
-  NSString* label1 = base::SysUTF16ToNSString(strings[0]);
-  NSString* label2 = base::SysUTF16ToNSString(strings[1]);
-  NSString* label3 = autodeterminedSourceLanguage
-                         ? @""
-                         : base::SysUTF16ToNSString(strings[2]);
-  base::string16 stdOriginal = self.infoBarDelegate->original_language_name();
-  NSString* original = base::SysUTF16ToNSString(stdOriginal);
-  NSString* target =
-      base::SysUTF16ToNSString(self.infoBarDelegate->target_language_name());
-  NSString* label =
-      [[NSString alloc] initWithFormat:@"%@ %@ %@%@ %@.", label1, original,
-                                       label2, label3, target];
-  [infoBarView addLabel:label];
-  // Close button.
-  [infoBarView addCloseButtonWithTag:TranslateInfoBarIOSTag::CLOSE
-                              target:self
-                              action:@selector(infoBarButtonDidPress:)];
-  // Other buttons.
-  NSString* buttonRevert = l10n_util::GetNSString(IDS_TRANSLATE_INFOBAR_REVERT);
-  NSString* buttonOptions = l10n_util::GetNSString(IDS_DONE);
-  [infoBarView addButton1:buttonOptions
-                     tag1:TranslateInfoBarIOSTag::AFTER_DONE
-                  button2:buttonRevert
-                     tag2:TranslateInfoBarIOSTag::AFTER_REVERT
-                   target:self
-                   action:@selector(infoBarButtonDidPress:)];
-  // Always translate switch.
-  _alwaysTranslateSwitchState = ALWAYS_TRANSLATE_SWITCH_NOT_CHANGED;
-  if (self.infoBarDelegate->ShouldShowAlwaysTranslateShortcut()) {
-    base::string16 alwaysTranslate = l10n_util::GetStringFUTF16(
-        IDS_TRANSLATE_INFOBAR_ALWAYS_TRANSLATE, stdOriginal);
-    const BOOL switchValue = self.infoBarDelegate->ShouldAlwaysTranslate();
-    [infoBarView
-        addSwitchWithLabel:base::SysUTF16ToNSString(alwaysTranslate)
-                      isOn:switchValue
-                       tag:TranslateInfoBarIOSTag::ALWAYS_TRANSLATE_SWITCH
-                    target:self
-                    action:@selector(infoBarSwitchDidPress:)];
-  }
-  return infoBarView;
-}
-
-#pragma mark - Handling of User Events
-
-- (void)infoBarButtonDidPress:(id)sender {
-  if ([self shouldIgnoreUserInteraction])
-    return;
-
-  NSUInteger buttonId = base::mac::ObjCCastStrict<UIButton>(sender).tag;
-  switch (buttonId) {
-    case TranslateInfoBarIOSTag::CLOSE:
-      self.infoBarDelegate->InfoBarDismissed();
-      self.delegate->RemoveInfoBar();
-      break;
-    case TranslateInfoBarIOSTag::AFTER_DONE:
-      [self saveAlwaysTranslateState];
-      self.infoBarDelegate->InfoBarDismissed();
-      self.delegate->RemoveInfoBar();
-      break;
-    case TranslateInfoBarIOSTag::AFTER_REVERT:
-      self.infoBarDelegate->RevertTranslation();
-      break;
-    default:
-      NOTREACHED() << "Unexpected Translate button label";
-      break;
-  }
-}
-
-- (void)infoBarSwitchDidPress:(id)sender {
-  DCHECK_EQ(TranslateInfoBarIOSTag::ALWAYS_TRANSLATE_SWITCH, [sender tag]);
-  DCHECK([sender respondsToSelector:@selector(isOn)]);
-  _alwaysTranslateSwitchState = [sender isOn]
-                                    ? ALWAYS_TRANSLATE_SWITCH_SET_TO_ENABLED
-                                    : ALWAYS_TRANSLATE_SWITCH_SET_TO_DISABLED;
-}
-
-#pragma mark - Private methods
-
-- (void)saveAlwaysTranslateState {
-  if (_alwaysTranslateSwitchState == ALWAYS_TRANSLATE_SWITCH_NOT_CHANGED)
-    return;
-
-  const bool alwaysTranslate =
-      _alwaysTranslateSwitchState == ALWAYS_TRANSLATE_SWITCH_SET_TO_ENABLED;
-  if (alwaysTranslate == self.infoBarDelegate->ShouldAlwaysTranslate())
-    return;
-
-  self.infoBarDelegate->ToggleAlwaysTranslate();
-}
-
-@end
diff --git a/ios/chrome/browser/translate/before_translate_infobar_controller.h b/ios/chrome/browser/translate/before_translate_infobar_controller.h
deleted file mode 100644
index 73ac4a1..0000000
--- a/ios/chrome/browser/translate/before_translate_infobar_controller.h
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_TRANSLATE_BEFORE_TRANSLATE_INFOBAR_CONTROLLER_H_
-#define IOS_CHROME_BROWSER_TRANSLATE_BEFORE_TRANSLATE_INFOBAR_CONTROLLER_H_
-
-#include "ios/chrome/browser/infobars/infobar_controller.h"
-
-@protocol LanguageSelectionHandler;
-namespace translate {
-class TranslateInfoBarDelegate;
-}
-
-@interface BeforeTranslateInfoBarController : InfoBarController
-
-- (instancetype)initWithInfoBarDelegate:
-    (translate::TranslateInfoBarDelegate*)infoBarDelegate
-    NS_DESIGNATED_INITIALIZER;
-
-@property(nonatomic, weak) id<LanguageSelectionHandler>
-    languageSelectionHandler;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_TRANSLATE_BEFORE_TRANSLATE_INFOBAR_CONTROLLER_H_
diff --git a/ios/chrome/browser/translate/before_translate_infobar_controller.mm b/ios/chrome/browser/translate/before_translate_infobar_controller.mm
deleted file mode 100644
index 8ace3664..0000000
--- a/ios/chrome/browser/translate/before_translate_infobar_controller.mm
+++ /dev/null
@@ -1,201 +0,0 @@
-// Copyright 2013 The Chromium 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/chrome/browser/translate/before_translate_infobar_controller.h"
-
-#include <stddef.h>
-#import <UIKit/UIKit.h>
-
-#include "base/logging.h"
-#include "base/mac/foundation_util.h"
-#include "base/strings/sys_string_conversions.h"
-#include "base/strings/utf_string_conversions.h"
-#include "components/strings/grit/components_strings.h"
-#include "components/translate/core/browser/translate_infobar_delegate.h"
-#import "ios/chrome/browser/infobars/infobar_controller+protected.h"
-#include "ios/chrome/browser/infobars/infobar_controller_delegate.h"
-#include "ios/chrome/browser/translate/language_selection_context.h"
-#include "ios/chrome/browser/translate/language_selection_delegate.h"
-#include "ios/chrome/browser/translate/language_selection_handler.h"
-#include "ios/chrome/browser/translate/translate_infobar_tags.h"
-#import "ios/chrome/browser/ui/infobars/confirm_infobar_view.h"
-#import "ios/chrome/browser/ui/util/top_view_controller.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/gfx/image/image.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@interface BeforeTranslateInfoBarController ()<LanguageSelectionDelegate>
-
-// Overrides superclass property.
-@property(nonatomic, readonly)
-    translate::TranslateInfoBarDelegate* infoBarDelegate;
-
-@end
-
-@implementation BeforeTranslateInfoBarController {
-  // Stores whether the user is currently choosing in the UIPickerView the
-  // original language, or the target language.
-  TranslateInfoBarIOSTag::Tag _languageSelectionType;
-  __weak ConfirmInfoBarView* _infoBarView;
-}
-
-@synthesize languageSelectionHandler = _languageSelectionHandler;
-@dynamic infoBarDelegate;
-
-#pragma mark -
-#pragma mark InfoBarControllerProtocol
-
-- (instancetype)initWithInfoBarDelegate:
-    (translate::TranslateInfoBarDelegate*)infoBarDelegate {
-  return [super initWithInfoBarDelegate:infoBarDelegate];
-}
-
-- (UIView*)infobarView {
-  ConfirmInfoBarView* infoBarView =
-      [[ConfirmInfoBarView alloc] initWithFrame:CGRectZero];
-  _infoBarView = infoBarView;
-  // Icon
-  gfx::Image icon = self.infoBarDelegate->GetIcon();
-  if (!icon.IsEmpty())
-    [infoBarView addLeftIcon:icon.ToUIImage()];
-
-  // Main text.
-  [self updateInfobarLabelOnView:infoBarView];
-
-  // Close button.
-  [infoBarView addCloseButtonWithTag:TranslateInfoBarIOSTag::BEFORE_DENY
-                              target:self
-                              action:@selector(infoBarButtonDidPress:)];
-  // Other buttons.
-  NSString* buttonAccept = l10n_util::GetNSString(IDS_TRANSLATE_INFOBAR_ACCEPT);
-  NSString* buttonDeny = l10n_util::GetNSString(IDS_TRANSLATE_INFOBAR_DENY);
-  [infoBarView addButton1:buttonAccept
-                     tag1:TranslateInfoBarIOSTag::BEFORE_ACCEPT
-                  button2:buttonDeny
-                     tag2:TranslateInfoBarIOSTag::BEFORE_DENY
-                   target:self
-                   action:@selector(infoBarButtonDidPress:)];
-  return infoBarView;
-}
-
-- (void)updateInfobarLabelOnView:(ConfirmInfoBarView*)view {
-  NSString* originalLanguage =
-      base::SysUTF16ToNSString(self.infoBarDelegate->original_language_name());
-  NSString* targetLanguage =
-      base::SysUTF16ToNSString(self.infoBarDelegate->target_language_name());
-  base::string16 originalLanguageWithLink =
-      base::SysNSStringToUTF16([[view class]
-          stringAsLink:originalLanguage
-                   tag:TranslateInfoBarIOSTag::BEFORE_SOURCE_LANGUAGE]);
-  base::string16 targetLanguageWithLink = base::SysNSStringToUTF16([[view class]
-      stringAsLink:targetLanguage
-               tag:TranslateInfoBarIOSTag::BEFORE_TARGET_LANGUAGE]);
-  NSString* label =
-      l10n_util::GetNSStringF(IDS_TRANSLATE_INFOBAR_BEFORE_MESSAGE_IOS,
-                              originalLanguageWithLink, targetLanguageWithLink);
-
-  __weak BeforeTranslateInfoBarController* weakSelf = self;
-  [view addLabel:label
-          action:^(NSUInteger tag) {
-            [weakSelf infobarLinkDidPress:tag];
-          }];
-}
-
-#pragma mark - Handling of User Events
-
-- (void)infoBarButtonDidPress:(id)sender {
-  if ([self shouldIgnoreUserInteraction])
-    return;
-
-  NSUInteger buttonId = base::mac::ObjCCastStrict<UIButton>(sender).tag;
-  switch (buttonId) {
-    case TranslateInfoBarIOSTag::BEFORE_ACCEPT:
-      self.infoBarDelegate->Translate();
-      break;
-    case TranslateInfoBarIOSTag::BEFORE_DENY:
-      self.infoBarDelegate->TranslationDeclined();
-      if (self.infoBarDelegate->ShouldShowNeverTranslateShortcut())
-        self.infoBarDelegate->ShowNeverTranslateInfobar();
-      else
-        self.delegate->RemoveInfoBar();
-      break;
-    default:
-      NOTREACHED() << "Unexpected Translate button label";
-      break;
-  }
-}
-
-- (void)infobarLinkDidPress:(NSUInteger)tag {
-  if ([self shouldIgnoreUserInteraction])
-    return;
-
-  _languageSelectionType = static_cast<TranslateInfoBarIOSTag::Tag>(tag);
-  DCHECK(_languageSelectionType ==
-             TranslateInfoBarIOSTag::BEFORE_SOURCE_LANGUAGE ||
-         _languageSelectionType ==
-             TranslateInfoBarIOSTag::BEFORE_TARGET_LANGUAGE);
-
-  size_t selectedRow;
-  size_t disabledRow;
-  int originalLanguageIndex = -1;
-  int targetLanguageIndex = -1;
-
-  for (size_t i = 0; i < self.infoBarDelegate->num_languages(); ++i) {
-    if (self.infoBarDelegate->language_code_at(i) ==
-        self.infoBarDelegate->original_language_code()) {
-      originalLanguageIndex = i;
-    }
-    if (self.infoBarDelegate->language_code_at(i) ==
-        self.infoBarDelegate->target_language_code()) {
-      targetLanguageIndex = i;
-    }
-  }
-  DCHECK_GT(originalLanguageIndex, -1);
-  DCHECK_GT(targetLanguageIndex, -1);
-
-  if (_languageSelectionType ==
-      TranslateInfoBarIOSTag::BEFORE_SOURCE_LANGUAGE) {
-    selectedRow = originalLanguageIndex;
-    disabledRow = targetLanguageIndex;
-  } else {
-    selectedRow = targetLanguageIndex;
-    disabledRow = originalLanguageIndex;
-  }
-  LanguageSelectionContext* context =
-      [LanguageSelectionContext contextWithLanguageData:self.infoBarDelegate
-                                           initialIndex:selectedRow
-                                       unavailableIndex:disabledRow];
-  DCHECK(self.languageSelectionHandler);
-  [self.languageSelectionHandler showLanguageSelectorWithContext:context
-                                                        delegate:self];
-}
-
-#pragma mark - LanguageSelectionDelegate
-
-- (void)languageSelectorSelectedLanguage:(std::string)languageCode {
-  if ([self shouldIgnoreUserInteraction])
-    return;
-
-  if (_languageSelectionType ==
-          TranslateInfoBarIOSTag::BEFORE_SOURCE_LANGUAGE &&
-      languageCode != self.infoBarDelegate->target_language_code()) {
-    self.infoBarDelegate->UpdateOriginalLanguage(languageCode);
-  }
-  if (_languageSelectionType ==
-          TranslateInfoBarIOSTag::BEFORE_TARGET_LANGUAGE &&
-      languageCode != self.infoBarDelegate->original_language_code()) {
-    self.infoBarDelegate->UpdateTargetLanguage(languageCode);
-  }
-  [self updateInfobarLabelOnView:_infoBarView];
-}
-
-- (void)languageSelectorClosedWithoutSelection {
-  // No-op in this implementation, but (for example) metrics for this state
-  // might be added.
-}
-
-@end
diff --git a/ios/chrome/browser/translate/chrome_ios_translate_client.mm b/ios/chrome/browser/translate/chrome_ios_translate_client.mm
index 12e5f257..adc1c35 100644
--- a/ios/chrome/browser/translate/chrome_ios_translate_client.mm
+++ b/ios/chrome/browser/translate/chrome_ios_translate_client.mm
@@ -18,7 +18,6 @@
 #include "components/translate/core/browser/translate_accept_languages.h"
 #include "components/translate/core/browser/translate_infobar_delegate.h"
 #include "components/translate/core/browser/translate_manager.h"
-#include "components/translate/core/browser/translate_prefs.h"
 #include "components/translate/core/browser/translate_step.h"
 #include "components/translate/core/common/language_detection_details.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
@@ -26,13 +25,9 @@
 #include "ios/chrome/browser/infobars/infobar_controller.h"
 #include "ios/chrome/browser/infobars/infobar_manager_impl.h"
 #include "ios/chrome/browser/language/language_model_manager_factory.h"
-#import "ios/chrome/browser/translate/after_translate_infobar_controller.h"
-#import "ios/chrome/browser/translate/before_translate_infobar_controller.h"
 #import "ios/chrome/browser/translate/language_selection_handler.h"
-#import "ios/chrome/browser/translate/never_translate_infobar_controller.h"
 #include "ios/chrome/browser/translate/translate_accept_languages_factory.h"
 #import "ios/chrome/browser/translate/translate_infobar_controller.h"
-#import "ios/chrome/browser/translate/translate_message_infobar_controller.h"
 #import "ios/chrome/browser/translate/translate_option_selection_handler.h"
 #include "ios/chrome/browser/translate/translate_ranker_factory.h"
 #include "ios/chrome/browser/translate/translate_service_ios.h"
@@ -96,44 +91,12 @@
 
 std::unique_ptr<infobars::InfoBar> ChromeIOSTranslateClient::CreateInfoBar(
     std::unique_ptr<translate::TranslateInfoBarDelegate> delegate) const {
-  if (base::FeatureList::IsEnabled(translate::kCompactTranslateInfobarIOS)) {
-    TranslateInfoBarController* controller = [[TranslateInfoBarController alloc]
-        initWithInfoBarDelegate:delegate.get()];
-    controller.languageSelectionHandler = language_selection_handler_;
-    controller.translateOptionSelectionHandler =
-        translate_option_selection_handler_;
-    controller.translateNotificationHandler = translate_notification_handler_;
-    return std::make_unique<InfoBarIOS>(controller, std::move(delegate));
-  }
-
-  translate::TranslateStep step = delegate->translate_step();
-
-  InfoBarController* controller;
-  switch (step) {
-    case translate::TRANSLATE_STEP_AFTER_TRANSLATE:
-      controller = [[AfterTranslateInfoBarController alloc]
-          initWithInfoBarDelegate:delegate.get()];
-      break;
-    case translate::TRANSLATE_STEP_BEFORE_TRANSLATE: {
-      BeforeTranslateInfoBarController* beforeController =
-          [[BeforeTranslateInfoBarController alloc]
-              initWithInfoBarDelegate:delegate.get()];
-      beforeController.languageSelectionHandler = language_selection_handler_;
-      controller = beforeController;
-      break;
-    }
-    case translate::TRANSLATE_STEP_NEVER_TRANSLATE:
-      controller = [[NeverTranslateInfoBarController alloc]
-          initWithInfoBarDelegate:delegate.get()];
-      break;
-    case translate::TRANSLATE_STEP_TRANSLATING:
-    case translate::TRANSLATE_STEP_TRANSLATE_ERROR:
-      controller = [[TranslateMessageInfoBarController alloc]
-          initWithInfoBarDelegate:delegate.get()];
-      break;
-    default:
-      NOTREACHED();
-  }
+  TranslateInfoBarController* controller = [[TranslateInfoBarController alloc]
+      initWithInfoBarDelegate:delegate.get()];
+  controller.languageSelectionHandler = language_selection_handler_;
+  controller.translateOptionSelectionHandler =
+      translate_option_selection_handler_;
+  controller.translateNotificationHandler = translate_notification_handler_;
   return std::make_unique<InfoBarIOS>(controller, std::move(delegate));
 }
 
diff --git a/ios/chrome/browser/translate/never_translate_infobar_controller.h b/ios/chrome/browser/translate/never_translate_infobar_controller.h
deleted file mode 100644
index 0bf8367..0000000
--- a/ios/chrome/browser/translate/never_translate_infobar_controller.h
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_TRANSLATE_NEVER_TRANSLATE_INFOBAR_CONTROLLER_H_
-#define IOS_CHROME_BROWSER_TRANSLATE_NEVER_TRANSLATE_INFOBAR_CONTROLLER_H_
-
-#include "ios/chrome/browser/infobars/infobar_controller.h"
-
-namespace translate {
-class TranslateInfoBarDelegate;
-}
-
-@interface NeverTranslateInfoBarController : InfoBarController
-
-- (instancetype)initWithInfoBarDelegate:
-    (translate::TranslateInfoBarDelegate*)infoBarDelegate
-    NS_DESIGNATED_INITIALIZER;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_TRANSLATE_NEVER_TRANSLATE_INFOBAR_CONTROLLER_H_
diff --git a/ios/chrome/browser/translate/never_translate_infobar_controller.mm b/ios/chrome/browser/translate/never_translate_infobar_controller.mm
deleted file mode 100644
index 9d344a3..0000000
--- a/ios/chrome/browser/translate/never_translate_infobar_controller.mm
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2013 The Chromium 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/chrome/browser/translate/never_translate_infobar_controller.h"
-
-#include "base/mac/foundation_util.h"
-#include "base/strings/sys_string_conversions.h"
-#include "base/strings/utf_string_conversions.h"
-#include "components/strings/grit/components_strings.h"
-#include "components/translate/core/browser/translate_infobar_delegate.h"
-#import "ios/chrome/browser/infobars/infobar_controller+protected.h"
-#include "ios/chrome/browser/infobars/infobar_controller_delegate.h"
-#include "ios/chrome/browser/translate/translate_infobar_tags.h"
-#import "ios/chrome/browser/ui/infobars/confirm_infobar_view.h"
-#include "ios/chrome/grit/ios_chromium_strings.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/gfx/image/image.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@interface NeverTranslateInfoBarController ()
-
-// Overrides superclass property.
-@property(nonatomic, readonly)
-    translate::TranslateInfoBarDelegate* infoBarDelegate;
-
-// Action for any of the user defined buttons.
-- (void)infoBarButtonDidPress:(id)sender;
-
-@end
-
-@implementation NeverTranslateInfoBarController
-
-@dynamic infoBarDelegate;
-
-#pragma mark -
-#pragma mark InfoBarControllerProtocol
-
-- (instancetype)initWithInfoBarDelegate:
-    (translate::TranslateInfoBarDelegate*)infoBarDelegate {
-  return [super initWithInfoBarDelegate:infoBarDelegate];
-}
-
-- (UIView*)infobarView {
-  ConfirmInfoBarView* infoBarView =
-      [[ConfirmInfoBarView alloc] initWithFrame:CGRectZero];
-  // Icon
-  gfx::Image icon = self.infoBarDelegate->GetIcon();
-  if (!icon.IsEmpty())
-    [infoBarView addLeftIcon:icon.ToUIImage()];
-  // Main text.
-  base::string16 originalLanguage =
-      self.infoBarDelegate->original_language_name();
-  [infoBarView
-      addLabel:l10n_util::GetNSStringF(IDS_IOS_TRANSLATE_INFOBAR_NEVER_MESSAGE,
-                                       originalLanguage)];
-  // Close button.
-  [infoBarView addCloseButtonWithTag:TranslateInfoBarIOSTag::CLOSE
-                              target:self
-                              action:@selector(infoBarButtonDidPress:)];
-  // Other buttons.
-  NSString* buttonLanguage = l10n_util::GetNSStringF(
-      IDS_TRANSLATE_INFOBAR_NEVER_TRANSLATE, originalLanguage);
-  NSString* buttonSite = l10n_util::GetNSString(
-      IDS_TRANSLATE_INFOBAR_OPTIONS_NEVER_TRANSLATE_SITE);
-  [infoBarView addButton1:buttonLanguage
-                     tag1:TranslateInfoBarIOSTag::DENY_LANGUAGE
-                  button2:buttonSite
-                     tag2:TranslateInfoBarIOSTag::DENY_WEBSITE
-                   target:self
-                   action:@selector(infoBarButtonDidPress:)];
-  return infoBarView;
-}
-
-#pragma mark - Handling of User Events
-
-- (void)infoBarButtonDidPress:(id)sender {
-  if ([self shouldIgnoreUserInteraction])
-    return;
-
-  NSUInteger buttonId = base::mac::ObjCCastStrict<UIButton>(sender).tag;
-  switch (buttonId) {
-    case TranslateInfoBarIOSTag::CLOSE:
-      self.infoBarDelegate->InfoBarDismissed();
-      self.delegate->RemoveInfoBar();
-      break;
-    case TranslateInfoBarIOSTag::DENY_LANGUAGE:
-      self.infoBarDelegate->NeverTranslatePageLanguage();
-      self.delegate->RemoveInfoBar();
-      break;
-    case TranslateInfoBarIOSTag::DENY_WEBSITE:
-      if (!self.infoBarDelegate->IsSiteBlacklisted())
-        self.infoBarDelegate->ToggleSiteBlacklist();
-      self.delegate->RemoveInfoBar();
-      break;
-    default:
-      NOTREACHED() << "Unexpected Translate button label";
-      break;
-  }
-}
-
-@end
diff --git a/ios/chrome/browser/translate/translate_egtest.mm b/ios/chrome/browser/translate/translate_egtest.mm
index 950eec5a..e5ac48a 100644
--- a/ios/chrome/browser/translate/translate_egtest.mm
+++ b/ios/chrome/browser/translate/translate_egtest.mm
@@ -21,7 +21,6 @@
 #include "components/translate/core/browser/translate_infobar_delegate.h"
 #include "components/translate/core/browser/translate_manager.h"
 #include "components/translate/core/browser/translate_pref_names.h"
-#include "components/translate/core/browser/translate_prefs.h"
 #include "components/translate/core/common/language_detection_details.h"
 #include "components/translate/core/common/translate_constants.h"
 #include "components/translate/core/common/translate_switches.h"
@@ -31,7 +30,6 @@
 #import "ios/chrome/browser/chrome_url_util.h"
 #include "ios/chrome/browser/translate/chrome_ios_translate_client.h"
 #import "ios/chrome/browser/ui/popup_menu/popup_menu_constants.h"
-#include "ios/chrome/browser/ui/translate/language_selection_view_controller.h"
 #import "ios/chrome/browser/ui/translate/translate_infobar_coordinator.h"
 #import "ios/chrome/browser/ui/translate/translate_infobar_view.h"
 #import "ios/chrome/browser/ui/util/ui_util.h"
@@ -437,17 +435,6 @@
 
 @implementation TranslateTestCase
 
-+ (void)setUp {
-  [super setUp];
-
-  if (!base::FeatureList::IsEnabled(translate::kCompactTranslateInfobarIOS)) {
-    // translate::kCompactTranslateInfobarIOS feature is not enabled. You need
-    // to pass --enable-features=CompactTranslateInfobarIOS command line
-    // argument in order to run this test.
-    DCHECK(false);
-  }
-}
-
 - (void)setUp {
   [super setUp];
 
diff --git a/ios/chrome/browser/translate/translate_message_infobar_controller.h b/ios/chrome/browser/translate/translate_message_infobar_controller.h
deleted file mode 100644
index efdf830f..0000000
--- a/ios/chrome/browser/translate/translate_message_infobar_controller.h
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_TRANSLATE_TRANSLATE_MESSAGE_INFOBAR_CONTROLLER_H_
-#define IOS_CHROME_BROWSER_TRANSLATE_TRANSLATE_MESSAGE_INFOBAR_CONTROLLER_H_
-
-#include "ios/chrome/browser/infobars/infobar_controller.h"
-
-namespace translate {
-class TranslateInfoBarDelegate;
-}
-
-@interface TranslateMessageInfoBarController : InfoBarController
-
-- (instancetype)initWithInfoBarDelegate:
-    (translate::TranslateInfoBarDelegate*)infoBarDelegate
-    NS_DESIGNATED_INITIALIZER;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_TRANSLATE_TRANSLATE_MESSAGE_INFOBAR_CONTROLLER_H_
diff --git a/ios/chrome/browser/translate/translate_message_infobar_controller.mm b/ios/chrome/browser/translate/translate_message_infobar_controller.mm
deleted file mode 100644
index a0846c1..0000000
--- a/ios/chrome/browser/translate/translate_message_infobar_controller.mm
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2013 The Chromium 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/chrome/browser/translate/translate_message_infobar_controller.h"
-
-#include "base/mac/foundation_util.h"
-#include "base/strings/sys_string_conversions.h"
-#include "components/translate/core/browser/translate_infobar_delegate.h"
-#import "ios/chrome/browser/infobars/infobar_controller+protected.h"
-#include "ios/chrome/browser/infobars/infobar_controller_delegate.h"
-#include "ios/chrome/browser/translate/translate_infobar_tags.h"
-#import "ios/chrome/browser/ui/infobars/confirm_infobar_view.h"
-#include "ui/gfx/image/image.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@interface TranslateMessageInfoBarController ()
-
-// Overrides superclass property.
-@property(nonatomic, readonly)
-    translate::TranslateInfoBarDelegate* infoBarDelegate;
-
-@end
-
-@implementation TranslateMessageInfoBarController
-
-@dynamic infoBarDelegate;
-
-- (instancetype)initWithInfoBarDelegate:
-    (translate::TranslateInfoBarDelegate*)infoBarDelegate {
-  return [super initWithInfoBarDelegate:infoBarDelegate];
-}
-
-- (UIView*)infobarView {
-  ConfirmInfoBarView* infoBarView =
-      [[ConfirmInfoBarView alloc] initWithFrame:CGRectZero];
-  // Icon
-  gfx::Image icon = self.infoBarDelegate->GetIcon();
-  if (!icon.IsEmpty())
-    [infoBarView addLeftIcon:icon.ToUIImage()];
-  // Text.
-  [infoBarView addLabel:base::SysUTF16ToNSString(
-                            self.infoBarDelegate->GetMessageInfoBarText())];
-  // Close button.
-  [infoBarView addCloseButtonWithTag:TranslateInfoBarIOSTag::CLOSE
-                              target:self
-                              action:@selector(infoBarButtonDidPress:)];
-  // Other button.
-  base::string16 buttonText(
-      self.infoBarDelegate->GetMessageInfoBarButtonText());
-  if (!buttonText.empty()) {
-    [infoBarView addButton:base::SysUTF16ToNSString(buttonText)
-                       tag:TranslateInfoBarIOSTag::MESSAGE
-                    target:self
-                    action:@selector(infoBarButtonDidPress:)];
-  }
-  return infoBarView;
-}
-
-#pragma mark - Handling of User Events
-
-- (void)infoBarButtonDidPress:(id)sender {
-  if ([self shouldIgnoreUserInteraction])
-    return;
-
-  NSUInteger buttonId = base::mac::ObjCCastStrict<UIButton>(sender).tag;
-  switch (buttonId) {
-    case TranslateInfoBarIOSTag::CLOSE:
-      self.infoBarDelegate->InfoBarDismissed();
-      self.delegate->RemoveInfoBar();
-      break;
-    case TranslateInfoBarIOSTag::MESSAGE:
-      self.infoBarDelegate->MessageInfoBarButtonPressed();
-      break;
-    default:
-      NOTREACHED() << "Unexpected Translate button label";
-      break;
-  }
-}
-
-@end
diff --git a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
index 145d489..66c6e9e7 100644
--- a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
@@ -7,7 +7,6 @@
 #include <memory>
 
 #include "base/scoped_observer.h"
-#include "components/translate/core/browser/translate_prefs.h"
 #import "ios/chrome/browser/app_launcher/app_launcher_abuse_detector.h"
 #import "ios/chrome/browser/app_launcher/app_launcher_tab_helper.h"
 #import "ios/chrome/browser/autofill/autofill_tab_helper.h"
@@ -38,7 +37,6 @@
 #import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
 #import "ios/chrome/browser/ui/recent_tabs/recent_tabs_coordinator.h"
 #import "ios/chrome/browser/ui/snackbar/snackbar_coordinator.h"
-#import "ios/chrome/browser/ui/translate/language_selection_coordinator.h"
 #import "ios/chrome/browser/ui/translate/translate_infobar_coordinator.h"
 #import "ios/chrome/browser/url_loading/url_loading_params.h"
 #import "ios/chrome/browser/url_loading/url_loading_service.h"
@@ -84,10 +82,6 @@
 @property(nonatomic, strong)
     FormInputAccessoryCoordinator* formInputAccessoryCoordinator;
 
-// Coordinator for a language selection UI.
-@property(nonatomic, strong)
-    LanguageSelectionCoordinator* languageSelectionCoordinator;
-
 // Coordinator for Page Info UI.
 @property(nonatomic, strong) PageInfoLegacyCoordinator* pageInfoCoordinator;
 
@@ -256,19 +250,11 @@
   self.formInputAccessoryCoordinator.delegate = self;
   [self.formInputAccessoryCoordinator start];
 
-  if (base::FeatureList::IsEnabled(translate::kCompactTranslateInfobarIOS)) {
-    self.translateInfobarCoordinator = [[TranslateInfobarCoordinator alloc]
-        initWithBaseViewController:self.viewController
-                      browserState:self.browserState
-                      webStateList:self.tabModel.webStateList];
-    [self.translateInfobarCoordinator start];
-  } else {
-    self.languageSelectionCoordinator = [[LanguageSelectionCoordinator alloc]
-        initWithBaseViewController:self.viewController
-                      browserState:self.browserState
-                      webStateList:self.tabModel.webStateList];
-    [self.languageSelectionCoordinator start];
-  }
+  self.translateInfobarCoordinator = [[TranslateInfobarCoordinator alloc]
+      initWithBaseViewController:self.viewController
+                    browserState:self.browserState
+                    webStateList:self.tabModel.webStateList];
+  [self.translateInfobarCoordinator start];
 
   self.pageInfoCoordinator = [[PageInfoLegacyCoordinator alloc]
       initWithBaseViewController:self.viewController
@@ -314,9 +300,6 @@
   [self.formInputAccessoryCoordinator stop];
   self.formInputAccessoryCoordinator = nil;
 
-  [self.languageSelectionCoordinator stop];
-  self.languageSelectionCoordinator = nil;
-
   [self.pageInfoCoordinator stop];
   self.pageInfoCoordinator = nil;
 
diff --git a/ios/chrome/browser/ui/translate/BUILD.gn b/ios/chrome/browser/ui/translate/BUILD.gn
index 5bcf310..11a653e 100644
--- a/ios/chrome/browser/ui/translate/BUILD.gn
+++ b/ios/chrome/browser/ui/translate/BUILD.gn
@@ -5,10 +5,6 @@
 source_set("translate") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
-    "language_selection_coordinator.h",
-    "language_selection_coordinator.mm",
-    "language_selection_mediator.h",
-    "language_selection_mediator.mm",
     "translate_infobar_coordinator.h",
     "translate_infobar_coordinator.mm",
     "translate_infobar_mediator.h",
@@ -37,10 +33,6 @@
 source_set("translate_ui") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
-    "language_selection_consumer.h",
-    "language_selection_provider.h",
-    "language_selection_view_controller.h",
-    "language_selection_view_controller.mm",
     "translate_infobar_language_tab_strip_view.h",
     "translate_infobar_language_tab_strip_view.mm",
     "translate_infobar_language_tab_strip_view_delegate.h",
diff --git a/ios/chrome/browser/ui/translate/language_selection_consumer.h b/ios/chrome/browser/ui/translate/language_selection_consumer.h
deleted file mode 100644
index 9c884335..0000000
--- a/ios/chrome/browser/ui/translate/language_selection_consumer.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_CONSUMER_H_
-#define IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_CONSUMER_H_
-
-#import <Foundation/Foundation.h>
-
-@protocol LanguageSelectionProvider;
-
-// Consumer protocol for a view controller providing a language selection
-// interface.
-@protocol LanguageSelectionConsumer
-// The language provider that the consumer should use to fetch language
-// information for display.
-@property(nonatomic, weak) id<LanguageSelectionProvider> provider;
-// The number of languages available for display in the interface.
-@property(nonatomic) int languageCount;
-// The index of the initially selected language.
-@property(nonatomic) int initialLanguageIndex;
-// The index of a language unavailable for selection (because it has already
-// been selected, for example).
-@property(nonatomic) int disabledLanguageIndex;
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_CONSUMER_H_
diff --git a/ios/chrome/browser/ui/translate/language_selection_coordinator.h b/ios/chrome/browser/ui/translate/language_selection_coordinator.h
deleted file mode 100644
index a83c76db8..0000000
--- a/ios/chrome/browser/ui/translate/language_selection_coordinator.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_COORDINATOR_H_
-#define IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_COORDINATOR_H_
-
-#import <UIKit/UIKit.h>
-
-#import "ios/chrome/browser/ui/coordinators/chrome_coordinator.h"
-
-@protocol ContainedPresenter;
-class WebStateList;
-
-// A coordinator for a language selection UI. This is intended for display as
-// a contained, not presented, view controller.
-// The methods defined in the LanguageSelectionHandler protocol will be called
-// to start the coordinator.
-@interface LanguageSelectionCoordinator : ChromeCoordinator
-
-// Creates a coordinator that uses a |viewController| a |browserState| and
-// a |webStateList|.
-- (instancetype)initWithBaseViewController:(UIViewController*)viewController
-                              browserState:
-                                  (ios::ChromeBrowserState*)browserState
-                              webStateList:(WebStateList*)webStateList;
-
-// Unavailable, use -initWithBaseViewController:browserState:webStateList:.
-- (instancetype)initWithBaseViewController:(UIViewController*)viewController
-                              browserState:
-                                  (ios::ChromeBrowserState*)browserState
-    NS_UNAVAILABLE;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_COORDINATOR_H_
diff --git a/ios/chrome/browser/ui/translate/language_selection_coordinator.mm b/ios/chrome/browser/ui/translate/language_selection_coordinator.mm
deleted file mode 100644
index 320c9b3a..0000000
--- a/ios/chrome/browser/ui/translate/language_selection_coordinator.mm
+++ /dev/null
@@ -1,119 +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.
-
-#import "ios/chrome/browser/ui/translate/language_selection_coordinator.h"
-
-#include "base/logging.h"
-#import "ios/chrome/browser/translate/language_selection_delegate.h"
-#import "ios/chrome/browser/translate/language_selection_handler.h"
-#import "ios/chrome/browser/ui/presenters/contained_presenter.h"
-#import "ios/chrome/browser/ui/presenters/vertical_animation_container.h"
-#import "ios/chrome/browser/ui/translate/language_selection_mediator.h"
-#import "ios/chrome/browser/ui/translate/language_selection_view_controller.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@interface LanguageSelectionCoordinator () <
-    LanguageSelectionHandler,
-    LanguageSelectionViewControllerDelegate>
-
-// The WebStateList being observed.
-@property(nonatomic) WebStateList* webStateList;
-// Presenter to use to for presenting the view controller.
-@property(nonatomic) id<ContainedPresenter> presenter;
-// The view controller this coordinator manages.
-@property(nonatomic) LanguageSelectionViewController* selectionViewController;
-// A mediator to interoperate with the translation model.
-@property(nonatomic) LanguageSelectionMediator* selectionMediator;
-// A delegate, provided by showLanguageSelectorWithContext:delegate:.
-@property(nonatomic, weak) id<LanguageSelectionDelegate> selectionDelegate;
-// YES if the coordinator has been started.
-@property(nonatomic) BOOL started;
-
-@end
-
-@implementation LanguageSelectionCoordinator
-
-- (instancetype)initWithBaseViewController:(UIViewController*)viewController
-                              browserState:
-                                  (ios::ChromeBrowserState*)browserState
-                              webStateList:(WebStateList*)webStateList {
-  DCHECK(webStateList);
-  self = [super initWithBaseViewController:viewController
-                              browserState:browserState];
-  if (self) {
-    _webStateList = webStateList;
-  }
-  return self;
-}
-
-#pragma mark - private methods
-
-- (void)start {
-  if (self.started)
-    return;
-
-  self.selectionMediator =
-      [[LanguageSelectionMediator alloc] initWithLanguageSelectionHandler:self];
-  self.selectionMediator.webStateList = self.webStateList;
-
-  self.started = YES;
-}
-
-- (void)stop {
-  if (!self.started)
-    return;
-
-  [self dismissLanguageSelector];
-  [self.selectionMediator disconnect];
-  self.selectionMediator = nil;
-  self.presenter = nil;
-  self.selectionViewController = nil;
-  self.selectionDelegate = nil;
-
-  self.started = NO;
-}
-
-#pragma mark - LanguageSelectionHandler
-
-- (void)showLanguageSelectorWithContext:(LanguageSelectionContext*)context
-                               delegate:
-                                   (id<LanguageSelectionDelegate>)delegate {
-  self.selectionDelegate = delegate;
-
-  self.selectionMediator.context = context;
-
-  self.selectionViewController = [[LanguageSelectionViewController alloc] init];
-  self.selectionViewController.delegate = self;
-  self.selectionMediator.consumer = self.selectionViewController;
-
-  self.presenter = [[VerticalAnimationContainer alloc] init];
-  self.presenter.baseViewController = self.baseViewController;
-  self.presenter.presentedViewController = self.selectionViewController;
-  [self.presenter prepareForPresentation];
-  [self.presenter presentAnimated:YES];
-}
-
-- (void)dismissLanguageSelector {
-  [self.selectionDelegate languageSelectorClosedWithoutSelection];
-  [self.presenter dismissAnimated:NO];
-}
-
-#pragma mark - LanguageSelectionViewControllerDelegate
-
-- (void)languageSelectedAtIndex:(int)index {
-  [self.selectionDelegate
-      languageSelectorSelectedLanguage:
-          [self.selectionMediator languageCodeForLanguageAtIndex:index]];
-  [self.presenter dismissAnimated:NO];
-}
-
-- (void)languageSelectionCanceled {
-  [self.selectionDelegate languageSelectorClosedWithoutSelection];
-  [self.presenter dismissAnimated:NO];
-}
-
-@end
diff --git a/ios/chrome/browser/ui/translate/language_selection_mediator.h b/ios/chrome/browser/ui/translate/language_selection_mediator.h
deleted file mode 100644
index 46154619..0000000
--- a/ios/chrome/browser/ui/translate/language_selection_mediator.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_MEDIATOR_H_
-#define IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_MEDIATOR_H_
-
-#import <Foundation/Foundation.h>
-#include <string>
-
-@protocol LanguageSelectionConsumer;
-@class LanguageSelectionContext;
-@protocol LanguageSelectionHandler;
-class WebStateList;
-
-// Mediator object to configure and provide data for language selection.
-@interface LanguageSelectionMediator : NSObject
-
-// |handler| presents and dismisses the language selection UI.
-- (instancetype)initWithLanguageSelectionHandler:
-    (id<LanguageSelectionHandler>)handler NS_DESIGNATED_INITIALIZER;
-- (instancetype)init NS_UNAVAILABLE;
-
-// Disconnects the mediator.
-- (void)disconnect;
-
-// The WebStateList being observed.
-@property(nonatomic) WebStateList* webStateList;
-
-// The context object provided for language selection.
-@property(nonatomic) LanguageSelectionContext* context;
-
-// Consumer for this mediator.
-@property(nonatomic, weak) id<LanguageSelectionConsumer> consumer;
-
-// Utility method for the coordinator to map a selected language index to a
-// language name.
-- (std::string)languageCodeForLanguageAtIndex:(int)index;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_MEDIATOR_H_
diff --git a/ios/chrome/browser/ui/translate/language_selection_mediator.mm b/ios/chrome/browser/ui/translate/language_selection_mediator.mm
deleted file mode 100644
index 4ac89b5..0000000
--- a/ios/chrome/browser/ui/translate/language_selection_mediator.mm
+++ /dev/null
@@ -1,170 +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.
-
-#import "ios/chrome/browser/ui/translate/language_selection_mediator.h"
-
-#include <memory>
-
-#include "base/logging.h"
-#include "base/scoped_observer.h"
-#include "base/strings/sys_string_conversions.h"
-#include "base/strings/utf_string_conversions.h"
-#include "components/translate/core/browser/translate_infobar_delegate.h"
-#include "ios/chrome/browser/translate/chrome_ios_translate_client.h"
-#import "ios/chrome/browser/translate/language_selection_context.h"
-#import "ios/chrome/browser/translate/language_selection_handler.h"
-#import "ios/chrome/browser/ui/translate/language_selection_consumer.h"
-#import "ios/chrome/browser/ui/translate/language_selection_provider.h"
-#import "ios/chrome/browser/web_state_list/web_state_list.h"
-#import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@interface LanguageSelectionMediator () <WebStateListObserving,
-                                         LanguageSelectionProvider> {
-  // WebStateList observers.
-  std::unique_ptr<WebStateListObserverBridge> _webStateListObserverBridge;
-  std::unique_ptr<ScopedObserver<WebStateList, WebStateListObserver>>
-      _scopedWebStateListObserver;
-}
-
-// The language selection handler.
-@property(nonatomic, weak) id<LanguageSelectionHandler>
-    languageSelectionHandler;
-
-@end
-
-@implementation LanguageSelectionMediator
-
-- (instancetype)initWithLanguageSelectionHandler:
-    (id<LanguageSelectionHandler>)handler {
-  DCHECK(handler);
-  if ((self = [super init])) {
-    _languageSelectionHandler = handler;
-  }
-  return self;
-}
-
-#pragma mark - Public methods
-
-- (void)disconnect {
-  self.webStateList = nil;
-}
-
-- (std::string)languageCodeForLanguageAtIndex:(int)index {
-  DCHECK(self.context);
-  return self.context.languageData->language_code_at(index);
-}
-
-#pragma mark - Properties
-
-- (void)setConsumer:(id<LanguageSelectionConsumer>)consumer {
-  _consumer = consumer;
-  DCHECK(self.context);
-  self.consumer.languageCount = self.context.languageData->num_languages();
-  self.consumer.initialLanguageIndex = self.context.initialLanguageIndex;
-  self.consumer.disabledLanguageIndex = self.context.unavailableLanguageIndex;
-  self.consumer.provider = self;
-}
-
-- (void)setWebStateList:(WebStateList*)webStateList {
-  if (_webStateList == webStateList)
-    return;
-
-  if (_webStateList) {
-    [self removeWebStateListObserver];
-
-    // Uninstall delegates for each WebState in WebStateList.
-    for (int i = 0; i < self.webStateList->count(); i++) {
-      [self uninstallDelegatesForWebState:self.webStateList->GetWebStateAt(i)];
-    }
-  }
-
-  _webStateList = webStateList;
-
-  if (_webStateList) {
-    // Install delegates for each WebState in WebStateList.
-    for (int i = 0; i < _webStateList->count(); i++) {
-      [self installDelegatesForWebState:_webStateList->GetWebStateAt(i)];
-    }
-
-    [self addWebStateListObserver];
-  }
-}
-
-#pragma mark - Private
-
-// Adds observer for WebStateList.
-- (void)addWebStateListObserver {
-  _webStateListObserverBridge =
-      std::make_unique<WebStateListObserverBridge>(self);
-  _scopedWebStateListObserver =
-      std::make_unique<ScopedObserver<WebStateList, WebStateListObserver>>(
-          _webStateListObserverBridge.get());
-  _scopedWebStateListObserver->Add(self.webStateList);
-}
-
-// Removes observer for WebStateList.
-- (void)removeWebStateListObserver {
-  _scopedWebStateListObserver.reset();
-  _webStateListObserverBridge.reset();
-}
-
-// Installs delegates for |webState|.
-- (void)installDelegatesForWebState:(web::WebState*)webState {
-  if (ChromeIOSTranslateClient::FromWebState(webState)) {
-    ChromeIOSTranslateClient::FromWebState(webState)
-        ->set_language_selection_handler(self.languageSelectionHandler);
-  }
-}
-
-// Uninstalls delegates for |webState|.
-- (void)uninstallDelegatesForWebState:(web::WebState*)webState {
-  if (ChromeIOSTranslateClient::FromWebState(webState)) {
-    ChromeIOSTranslateClient::FromWebState(webState)
-        ->set_language_selection_handler(nil);
-  }
-}
-
-#pragma mark - WebStateListObserving
-
-- (void)webStateList:(WebStateList*)webStateList
-    didInsertWebState:(web::WebState*)webState
-              atIndex:(int)index
-           activating:(BOOL)activating {
-  [self installDelegatesForWebState:webState];
-}
-
-- (void)webStateList:(WebStateList*)webStateList
-    didReplaceWebState:(web::WebState*)oldWebState
-          withWebState:(web::WebState*)newWebState
-               atIndex:(int)index {
-  [self uninstallDelegatesForWebState:oldWebState];
-  [self installDelegatesForWebState:newWebState];
-}
-
-- (void)webStateList:(WebStateList*)webStateList
-    didDetachWebState:(web::WebState*)webState
-              atIndex:(int)index {
-  [self uninstallDelegatesForWebState:webState];
-}
-
-#pragma mark - LanguageSelectionProvider
-
-- (NSString*)languageNameAtIndex:(int)languageIndex {
-  DCHECK(self.context);
-  if (languageIndex < 0 ||
-      languageIndex >=
-          static_cast<int>(self.context.languageData->num_languages())) {
-    NOTREACHED() << "Language index " << languageIndex
-                 << " out of expected range.";
-    return nil;
-  }
-  return base::SysUTF16ToNSString(
-      self.context.languageData->language_name_at(languageIndex));
-}
-
-@end
diff --git a/ios/chrome/browser/ui/translate/language_selection_provider.h b/ios/chrome/browser/ui/translate/language_selection_provider.h
deleted file mode 100644
index dc94236..0000000
--- a/ios/chrome/browser/ui/translate/language_selection_provider.h
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_PROVIDER_H_
-#define IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_PROVIDER_H_
-
-#import <Foundation/Foundation.h>
-
-// Protocol for a provider that can map indexes to language names. An
-// implementer of this protocol should be consistent over its lifetime, always
-// returning the same language information for a given index.
-@protocol LanguageSelectionProvider
-
-// The name of the language (in the application's locale) of the language at
-// index |languageIndex|, or nil if |languageIndex| is outside of the range
-// of indices handled by the implementer.
-- (NSString*)languageNameAtIndex:(int)languageIndex;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_PROVIDER_H_
diff --git a/ios/chrome/browser/ui/translate/language_selection_view_controller.h b/ios/chrome/browser/ui/translate/language_selection_view_controller.h
deleted file mode 100644
index 7ab5d9d..0000000
--- a/ios/chrome/browser/ui/translate/language_selection_view_controller.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_VIEW_CONTROLLER_H_
-#define IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_VIEW_CONTROLLER_H_
-
-#import <UIKit/UIKit.h>
-
-#import "ios/chrome/browser/ui/translate/language_selection_consumer.h"
-
-// The accessibility identifier of the cancel button on language picker view.
-// NOTE: this should not be used on iOS 9 for testing.
-extern NSString* const kLanguagePickerCancelButtonId;
-
-// The accessibility identifier of the done button on language picker view.
-// NOTE: this should not be used on iOS 9 for testing.
-extern NSString* const kLanguagePickerDoneButtonId;
-
-// A delegate for a LanguageSelectionViewController instance, which the view
-// controller tells about selection events.
-@protocol LanguageSelectionViewControllerDelegate
-
-// Tells the delegate that a language was selected. |index| will be an index
-// in the range provided to the view controller over via the consumer protocol.
-- (void)languageSelectedAtIndex:(int)index;
-
-// Tells the delegate that language selection was cancelled.
-- (void)languageSelectionCanceled;
-
-@end
-
-// A view controller that displays a picker view for selecting a language from
-// a list of provided languages.
-@interface LanguageSelectionViewController
-    : UIViewController<LanguageSelectionConsumer>
-
-// The delegate for this view controller.
-@property(nonatomic, weak) id<LanguageSelectionViewControllerDelegate> delegate;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/translate/language_selection_view_controller.mm b/ios/chrome/browser/ui/translate/language_selection_view_controller.mm
deleted file mode 100644
index c8d8459..0000000
--- a/ios/chrome/browser/ui/translate/language_selection_view_controller.mm
+++ /dev/null
@@ -1,152 +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.
-
-#import "ios/chrome/browser/ui/translate/language_selection_view_controller.h"
-
-#include "base/logging.h"
-#import "base/mac/foundation_util.h"
-#import "ios/chrome/browser/ui/translate/language_selection_provider.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-NSString* const kLanguagePickerCancelButtonId = @"LanguagePickerCancelButton";
-NSString* const kLanguagePickerDoneButtonId = @"LanguagePickerDoneButton";
-
-namespace {
-CGFloat kUIPickerFontSize = 26;
-}
-
-@interface LanguageSelectionViewController ()<UIPickerViewDataSource,
-                                              UIPickerViewDelegate> {
-  // YES if NSLayoutConstraits were added.
-  BOOL _addedConstraints;
-}
-
-@property(nonatomic, weak) UIPickerView* picker;
-
-// Action methods for navigation bar buttons.
-- (void)languageSelectionDone;
-- (void)languageSelectionCancelled;
-
-@end
-
-@implementation LanguageSelectionViewController
-
-// Synthesize properties defined by LanguageSelectionConsumer
-@synthesize languageCount = _languageCount;
-@synthesize initialLanguageIndex = _initialLanguageIndex;
-@synthesize disabledLanguageIndex = _disabledLanguageIndex;
-@synthesize provider = _provider;
-// Synthesize public properties
-@synthesize delegate = _delegate;
-// Synthesize private properties
-@synthesize picker = _picker;
-
-#pragma mark - UIViewController
-
-- (void)viewDidLoad {
-  DCHECK(_languageCount && _provider);
-
-  UIPickerView* picker = [[UIPickerView alloc] initWithFrame:CGRectZero];
-  picker.backgroundColor = UIColor.whiteColor;
-  picker.translatesAutoresizingMaskIntoConstraints = NO;
-  picker.showsSelectionIndicator = YES;
-  picker.dataSource = self;
-  picker.delegate = self;
-  [picker selectRow:self.initialLanguageIndex inComponent:0 animated:NO];
-
-  [self.view addSubview:picker];
-  self.picker = picker;
-
-  UINavigationBar* bar = [[UINavigationBar alloc] initWithFrame:CGRectZero];
-  bar.translatesAutoresizingMaskIntoConstraints = NO;
-
-  UIBarButtonItem* doneButton = [[UIBarButtonItem alloc]
-      initWithBarButtonSystemItem:UIBarButtonSystemItemDone
-                           target:self
-                           action:@selector(languageSelectionDone)];
-  [doneButton setAccessibilityIdentifier:kLanguagePickerDoneButtonId];
-
-  UIBarButtonItem* cancelButton = [[UIBarButtonItem alloc]
-      initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
-                           target:self
-                           action:@selector(languageSelectionCancelled)];
-  [cancelButton setAccessibilityIdentifier:kLanguagePickerCancelButtonId];
-
-  UINavigationItem* item = [[UINavigationItem alloc] initWithTitle:@""];
-  [item setRightBarButtonItem:doneButton];
-  [item setLeftBarButtonItem:cancelButton];
-  [item setHidesBackButton:YES];
-  [bar pushNavigationItem:item animated:NO];
-
-  [self.view addSubview:bar];
-
-  // Bar sits on top of the picker, both are full width. Constraints don't
-  // change. Height is entirely determined by the preferred content sizes of
-  // the bar and picker.
-  [NSLayoutConstraint activateConstraints:@[
-    [bar.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
-    [picker.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
-    [bar.widthAnchor constraintEqualToAnchor:self.view.widthAnchor],
-    [picker.widthAnchor constraintEqualToAnchor:self.view.widthAnchor],
-    [bar.topAnchor constraintEqualToAnchor:self.view.topAnchor],
-    [picker.topAnchor constraintEqualToAnchor:bar.bottomAnchor],
-    [picker.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
-  ]];
-}
-
-- (void)updateViewConstraints {
-  if (!_addedConstraints) {
-    [[self.view.superview.widthAnchor
-        constraintEqualToAnchor:self.view.widthAnchor] setActive:YES];
-    _addedConstraints = YES;
-  }
-  [super updateViewConstraints];
-}
-
-#pragma mark - UIPickerViewDataSource
-
-- (NSInteger)pickerView:(UIPickerView*)pickerView
-    numberOfRowsInComponent:(NSInteger)component {
-  return _languageCount;
-}
-
-- (NSInteger)numberOfComponentsInPickerView:(UIPickerView*)pickerView {
-  return 1;
-}
-
-#pragma mark - UIPickerViewDelegate
-
-- (UIView*)pickerView:(UIPickerView*)pickerView
-           viewForRow:(NSInteger)row
-         forComponent:(NSInteger)component
-          reusingView:(UIView*)view {
-  DCHECK_EQ(0, component);
-  UILabel* label = base::mac::ObjCCast<UILabel>(view) ?: [[UILabel alloc] init];
-  label.text = [_provider languageNameAtIndex:row];
-  label.textAlignment = NSTextAlignmentCenter;
-  UIFont* font = [UIFont systemFontOfSize:kUIPickerFontSize];
-  if (row == _initialLanguageIndex) {
-    font = [UIFont boldSystemFontOfSize:kUIPickerFontSize];
-  } else if (row == _disabledLanguageIndex) {
-    label.enabled = NO;
-  }
-  label.font = font;
-  return label;
-}
-
-#pragma mark - Navigation buttons
-
-- (void)languageSelectionDone {
-  [self.delegate
-      languageSelectedAtIndex:[self.picker selectedRowInComponent:0]];
-}
-
-- (void)languageSelectionCancelled {
-  [self.delegate languageSelectionCanceled];
-}
-
-@end
diff --git a/ios/chrome/test/earl_grey/BUILD.gn b/ios/chrome/test/earl_grey/BUILD.gn
index 515ca912..eaf4b44c 100644
--- a/ios/chrome/test/earl_grey/BUILD.gn
+++ b/ios/chrome/test/earl_grey/BUILD.gn
@@ -10,7 +10,6 @@
     ":ios_chrome_autofill_automation_egtests",
     ":ios_chrome_bookmarks_egtests",
     ":ios_chrome_device_check_egtests",
-    ":ios_chrome_external_url_egtests",
     ":ios_chrome_flaky_egtests",
     ":ios_chrome_integration_egtests",
     ":ios_chrome_multitasking_egtests",
@@ -195,12 +194,6 @@
   ]
 }
 
-chrome_ios_eg_test("ios_chrome_external_url_egtests") {
-  deps = [
-    "//ios/chrome/browser/translate:external_url_eg_tests",
-  ]
-}
-
 source_set("test_support") {
   defines = [ "CHROME_EARL_GREY_1" ]
   configs += [ "//build/config/compiler:enable_arc" ]
diff --git a/mojo/public/cpp/bindings/interface_ptr_set.h b/mojo/public/cpp/bindings/interface_ptr_set.h
index 8130785..faf91ad 100644
--- a/mojo/public/cpp/bindings/interface_ptr_set.h
+++ b/mojo/public/cpp/bindings/interface_ptr_set.h
@@ -21,8 +21,8 @@
 
 namespace internal {
 
-// TODO(blundell): This class should be rewritten to be structured
-// similarly to BindingSet if possible, with PtrSet owning its
+// TODO(https://crbug.com/965668): This class should be rewritten to be
+// structured similarly to BindingSet if possible, with PtrSet owning its
 // Elements and those Elements calling back into PtrSet on connection
 // error.
 template <typename Interface, template <typename> class Ptr>
diff --git a/printing/backend/cups_jobs.cc b/printing/backend/cups_jobs.cc
index 0ed0653..76df73c 100644
--- a/printing/backend/cups_jobs.cc
+++ b/printing/backend/cups_jobs.cc
@@ -429,18 +429,18 @@
   }
 }
 
-bool GetPrinterInfo(const std::string& address,
-                    const int port,
-                    const std::string& resource,
-                    bool encrypted,
-                    PrinterInfo* printer_info) {
+PrinterQueryResult GetPrinterInfo(const std::string& address,
+                                  const int port,
+                                  const std::string& resource,
+                                  bool encrypted,
+                                  PrinterInfo* printer_info) {
   ScopedHttpPtr http = ScopedHttpPtr(httpConnect2(
       address.c_str(), port, nullptr, AF_INET,
       encrypted ? HTTP_ENCRYPTION_ALWAYS : HTTP_ENCRYPTION_IF_REQUESTED, 0,
       kHttpConnectTimeoutMs, nullptr));
   if (!http) {
     LOG(WARNING) << "Could not connect to host";
-    return false;
+    return PrinterQueryResult::UNREACHABLE;
   }
 
   // TODO(crbug.com/821497): Use a library to canonicalize the URL.
@@ -459,10 +459,13 @@
                            kPrinterInfo.size(), kPrinterInfo.data(), &status);
   if (status != IPP_STATUS_OK || response.get() == nullptr) {
     LOG(WARNING) << "Get attributes failure: " << status;
-    return false;
+    return PrinterQueryResult::UNKNOWN_FAILURE;
   }
 
-  return ParsePrinterInfo(response.get(), printer_info);
+  if (ParsePrinterInfo(response.get(), printer_info)) {
+    return PrinterQueryResult::SUCCESS;
+  }
+  return PrinterQueryResult::UNKNOWN_FAILURE;
 }
 
 bool GetPrinterStatus(http_t* http,
diff --git a/printing/backend/cups_jobs.h b/printing/backend/cups_jobs.h
index c2606eb..f67359d 100644
--- a/printing/backend/cups_jobs.h
+++ b/printing/backend/cups_jobs.h
@@ -143,6 +143,13 @@
   PROCESSING  // only jobs that are being processed
 };
 
+// Specifies query status codes.
+enum PRINTING_EXPORT PrinterQueryResult {
+  UNKNOWN_FAILURE,  // catchall error
+  SUCCESS,          // successful
+  UNREACHABLE,      // failed to reach the host
+};
+
 // Extracts structured job information from the |response| for |printer_id|.
 // Extracted jobs are added to |jobs|.
 void ParseJobsResponse(ipp_t* response,
@@ -155,11 +162,11 @@
 // Queries the printer at |address| on |port| with a Get-Printer-Attributes
 // request to populate |printer_info|. If |encrypted| is true, request is made
 // using ipps, otherwise, ipp is used. Returns false if the request failed.
-bool PRINTING_EXPORT GetPrinterInfo(const std::string& address,
-                                    const int port,
-                                    const std::string& resource,
-                                    bool encrypted,
-                                    PrinterInfo* printer_info);
+PrinterQueryResult PRINTING_EXPORT GetPrinterInfo(const std::string& address,
+                                                  const int port,
+                                                  const std::string& resource,
+                                                  bool encrypted,
+                                                  PrinterInfo* printer_info);
 
 // Attempts to retrieve printer status using connection |http| for |printer_id|.
 // Returns true if succcssful and updates the fields in |printer_status| as
diff --git a/sandbox/win/src/process_mitigations_dyncode_unittest.cc b/sandbox/win/src/process_mitigations_dyncode_unittest.cc
index df1c24d..59db0b0 100644
--- a/sandbox/win/src/process_mitigations_dyncode_unittest.cc
+++ b/sandbox/win/src/process_mitigations_dyncode_unittest.cc
@@ -447,17 +447,12 @@
   if (base::win::GetVersion() < base::win::Version::WIN8_1)
     return;
 
-  HANDLE mutex =
-      ::CreateMutexW(nullptr, false, hooking_dll::g_hooking_dll_mutex);
-  EXPECT_TRUE(mutex);
-  EXPECT_EQ(WAIT_OBJECT_0,
-            ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
+  ScopedTestMutex mutex(hooking_dll::g_hooking_dll_mutex);
 
   // Expect success, no mitigation.
-  DynamicCodeTestHarness(sandbox::MITIGATION_DYNAMIC_CODE_DISABLE, true, false);
-
-  EXPECT_TRUE(::ReleaseMutex(mutex));
-  EXPECT_TRUE(::CloseHandle(mutex));
+  DynamicCodeTestHarness(sandbox::MITIGATION_DYNAMIC_CODE_DISABLE,
+                         true /* expect_success */,
+                         false /* enable_mitigation */);
 }
 
 // This test validates that setting the MITIGATION_DYNAMIC_CODE_DISABLE
@@ -466,17 +461,12 @@
   if (base::win::GetVersion() < base::win::Version::WIN8_1)
     return;
 
-  HANDLE mutex =
-      ::CreateMutexW(nullptr, false, hooking_dll::g_hooking_dll_mutex);
-  EXPECT_TRUE(mutex);
-  EXPECT_EQ(WAIT_OBJECT_0,
-            ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
+  ScopedTestMutex mutex(hooking_dll::g_hooking_dll_mutex);
 
   // Expect failure, with mitigation.
-  DynamicCodeTestHarness(sandbox::MITIGATION_DYNAMIC_CODE_DISABLE, false, true);
-
-  EXPECT_TRUE(::ReleaseMutex(mutex));
-  EXPECT_TRUE(::CloseHandle(mutex));
+  DynamicCodeTestHarness(sandbox::MITIGATION_DYNAMIC_CODE_DISABLE,
+                         false /* expect_success */,
+                         true /* enable_mitigation */);
 }
 
 //------------------------------------------------------------------------------
@@ -534,18 +524,13 @@
   if (base::win::GetVersion() < base::win::Version::WIN10_RS1)
     return;
 
-  HANDLE mutex =
-      ::CreateMutexW(nullptr, false, hooking_dll::g_hooking_dll_mutex);
-  EXPECT_TRUE(mutex);
-  EXPECT_EQ(WAIT_OBJECT_0,
-            ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
+  ScopedTestMutex mutex(hooking_dll::g_hooking_dll_mutex);
 
   // Expect success, no mitigation (and therefore no thread opt-out).
   DynamicCodeTestHarness(sandbox::MITIGATION_DYNAMIC_CODE_DISABLE_WITH_OPT_OUT,
-                         true, false, false);
-
-  EXPECT_TRUE(::ReleaseMutex(mutex));
-  EXPECT_TRUE(::CloseHandle(mutex));
+                         true /* expect_success */,
+                         false /* enable_mitigation */,
+                         false /* with_thread_opt_out */);
 }
 
 // This test validates that setting the
@@ -555,18 +540,13 @@
   if (base::win::GetVersion() < base::win::Version::WIN10_RS1)
     return;
 
-  HANDLE mutex =
-      ::CreateMutexW(nullptr, false, hooking_dll::g_hooking_dll_mutex);
-  EXPECT_TRUE(mutex);
-  EXPECT_EQ(WAIT_OBJECT_0,
-            ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
+  ScopedTestMutex mutex(hooking_dll::g_hooking_dll_mutex);
 
   // Expect failure, with mitigation, no thread opt-out.
   DynamicCodeTestHarness(sandbox::MITIGATION_DYNAMIC_CODE_DISABLE_WITH_OPT_OUT,
-                         false, true, false);
-
-  EXPECT_TRUE(::ReleaseMutex(mutex));
-  EXPECT_TRUE(::CloseHandle(mutex));
+                         false /* expect_success */,
+                         true /* enable_mitigation */,
+                         false /* with_thread_opt_out */);
 }
 
 // This test validates that setting the
@@ -577,18 +557,13 @@
   if (base::win::GetVersion() < base::win::Version::WIN10_RS1)
     return;
 
-  HANDLE mutex =
-      ::CreateMutexW(nullptr, false, hooking_dll::g_hooking_dll_mutex);
-  EXPECT_TRUE(mutex);
-  EXPECT_EQ(WAIT_OBJECT_0,
-            ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
+  ScopedTestMutex mutex(hooking_dll::g_hooking_dll_mutex);
 
   // Expect success, with mitigation, with thread opt-out.
   DynamicCodeTestHarness(sandbox::MITIGATION_DYNAMIC_CODE_DISABLE_WITH_OPT_OUT,
-                         true, true, true);
-
-  EXPECT_TRUE(::ReleaseMutex(mutex));
-  EXPECT_TRUE(::CloseHandle(mutex));
+                         true /* expect_success */,
+                         true /* enable_mitigation */,
+                         true /* with_thread_opt_out */);
 }
 
 }  // namespace sandbox
diff --git a/sandbox/win/src/process_mitigations_extensionpoints_unittest.cc b/sandbox/win/src/process_mitigations_extensionpoints_unittest.cc
index 3c4234f..a9da035 100644
--- a/sandbox/win/src/process_mitigations_extensionpoints_unittest.cc
+++ b/sandbox/win/src/process_mitigations_extensionpoints_unittest.cc
@@ -402,16 +402,10 @@
   if (base::win::GetVersion() < base::win::Version::WIN8)
     return;
 
-  HANDLE mutex = ::CreateMutexW(nullptr, false, g_extension_point_test_mutex);
-  EXPECT_TRUE(mutex);
-  EXPECT_EQ(WAIT_OBJECT_0,
-            ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
+  ScopedTestMutex mutex(g_extension_point_test_mutex);
 
-  // (is_success_test, global_hook)
-  TestWin8ExtensionPointHookWrapper(true, true);
-
-  EXPECT_TRUE(::ReleaseMutex(mutex));
-  EXPECT_TRUE(::CloseHandle(mutex));
+  TestWin8ExtensionPointHookWrapper(true /* is_success_test */,
+                                    true /* global hook */);
 }
 
 // This test validates that setting the MITIGATION_EXTENSION_POINT_DISABLE
@@ -423,16 +417,10 @@
   if (base::win::GetVersion() < base::win::Version::WIN8)
     return;
 
-  HANDLE mutex = ::CreateMutexW(nullptr, false, g_extension_point_test_mutex);
-  EXPECT_TRUE(mutex);
-  EXPECT_EQ(WAIT_OBJECT_0,
-            ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
+  ScopedTestMutex mutex(g_extension_point_test_mutex);
 
-  // (is_success_test, global_hook)
-  TestWin8ExtensionPointHookWrapper(false, true);
-
-  EXPECT_TRUE(::ReleaseMutex(mutex));
-  EXPECT_TRUE(::CloseHandle(mutex));
+  TestWin8ExtensionPointHookWrapper(false /* is_success_test */,
+                                    true /* global hook */);
 }
 
 // This test validates that a "legitimate" hook CAN be set on the sandboxed
@@ -443,16 +431,10 @@
   if (base::win::GetVersion() < base::win::Version::WIN8)
     return;
 
-  HANDLE mutex = ::CreateMutexW(nullptr, false, g_extension_point_test_mutex);
-  EXPECT_TRUE(mutex);
-  EXPECT_EQ(WAIT_OBJECT_0,
-            ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
+  ScopedTestMutex mutex(g_extension_point_test_mutex);
 
-  // (is_success_test, global_hook)
-  TestWin8ExtensionPointHookWrapper(true, false);
-
-  EXPECT_TRUE(::ReleaseMutex(mutex));
-  EXPECT_TRUE(::CloseHandle(mutex));
+  TestWin8ExtensionPointHookWrapper(true /* is_success_test */,
+                                    false /* global hook */);
 }
 
 // *** Important: MITIGATION_EXTENSION_POINT_DISABLE does NOT prevent
@@ -466,16 +448,10 @@
   if (base::win::GetVersion() < base::win::Version::WIN8)
     return;
 
-  HANDLE mutex = ::CreateMutexW(nullptr, false, g_extension_point_test_mutex);
-  EXPECT_TRUE(mutex);
-  EXPECT_EQ(WAIT_OBJECT_0,
-            ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
+  ScopedTestMutex mutex(g_extension_point_test_mutex);
 
-  // (is_success_test, global_hook)
-  TestWin8ExtensionPointHookWrapper(false, false);
-
-  EXPECT_TRUE(::ReleaseMutex(mutex));
-  EXPECT_TRUE(::CloseHandle(mutex));
+  TestWin8ExtensionPointHookWrapper(false /* is_success_test */,
+                                    false /* global hook */);
 }
 
 // This test validates that an AppInit Dll CAN be added to a target
@@ -487,15 +463,9 @@
   if (base::win::GetVersion() < base::win::Version::WIN8)
     return;
 
-  HANDLE mutex = ::CreateMutexW(nullptr, false, g_extension_point_test_mutex);
-  EXPECT_TRUE(mutex);
-  EXPECT_EQ(WAIT_OBJECT_0,
-            ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
+  ScopedTestMutex mutex(g_extension_point_test_mutex);
 
-  TestWin8ExtensionPointAppInitWrapper(true);
-
-  EXPECT_TRUE(::ReleaseMutex(mutex));
-  EXPECT_TRUE(::CloseHandle(mutex));
+  TestWin8ExtensionPointAppInitWrapper(true /* is_success_test */);
 }
 
 // This test validates that setting the MITIGATION_EXTENSION_POINT_DISABLE
@@ -507,15 +477,9 @@
   if (base::win::GetVersion() < base::win::Version::WIN8)
     return;
 
-  HANDLE mutex = ::CreateMutexW(nullptr, false, g_extension_point_test_mutex);
-  EXPECT_TRUE(mutex);
-  EXPECT_EQ(WAIT_OBJECT_0,
-            ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
+  ScopedTestMutex mutex(g_extension_point_test_mutex);
 
-  TestWin8ExtensionPointAppInitWrapper(false);
-
-  EXPECT_TRUE(::ReleaseMutex(mutex));
-  EXPECT_TRUE(::CloseHandle(mutex));
+  TestWin8ExtensionPointAppInitWrapper(false /* is_success_test */);
 }
 
 }  // namespace sandbox
diff --git a/sandbox/win/src/process_mitigations_imageload_unittest.cc b/sandbox/win/src/process_mitigations_imageload_unittest.cc
index f6af3d8..cece245 100644
--- a/sandbox/win/src/process_mitigations_imageload_unittest.cc
+++ b/sandbox/win/src/process_mitigations_imageload_unittest.cc
@@ -302,7 +302,7 @@
   if (base::win::GetVersion() < base::win::Version::WIN10_TH2)
     return;
 
-  TestWin10ImageLoadRemote(true);
+  TestWin10ImageLoadRemote(true /* is_success_test */);
 }
 
 // This test validates that setting the MITIGATION_IMAGE_LOAD_NO_REMOTE
@@ -314,7 +314,7 @@
   if (base::win::GetVersion() < base::win::Version::WIN10_TH2)
     return;
 
-  TestWin10ImageLoadRemote(false);
+  TestWin10ImageLoadRemote(false /* is_success_test */);
 }
 
 //------------------------------------------------------------------------------
@@ -361,7 +361,7 @@
   if (base::win::GetVersion() < base::win::Version::WIN10_TH2)
     return;
 
-  TestWin10ImageLoadLowLabel(true);
+  TestWin10ImageLoadLowLabel(true /* is_success_test */);
 }
 
 // This test validates that setting the MITIGATION_IMAGE_LOAD_NO_LOW_LABEL
@@ -370,7 +370,7 @@
   if (base::win::GetVersion() < base::win::Version::WIN10_TH2)
     return;
 
-  TestWin10ImageLoadLowLabel(false);
+  TestWin10ImageLoadLowLabel(false /* is_success_test */);
 }
 
 //------------------------------------------------------------------------------
@@ -421,16 +421,11 @@
   if (base::win::GetVersion() < base::win::Version::WIN10_RS1)
     return;
 
-  HANDLE mutex = ::CreateMutexW(nullptr, false, g_hijack_dlls_mutex);
-  EXPECT_TRUE(mutex);
-  EXPECT_EQ(WAIT_OBJECT_0,
-            ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
+  ScopedTestMutex mutex(g_hijack_dlls_mutex);
 
   // Baseline test, and expect the DLL to NOT be in system32.
-  TestWin10ImageLoadPreferSys32(true, false);
-
-  EXPECT_TRUE(::ReleaseMutex(mutex));
-  EXPECT_TRUE(::CloseHandle(mutex));
+  TestWin10ImageLoadPreferSys32(true /* baseline_test */,
+                                false /* expect_sys32_path */);
 }
 
 // This test validates that import hijacking succeeds, if the
@@ -442,16 +437,11 @@
   if (base::win::GetVersion() < base::win::Version::WIN10_RS1)
     return;
 
-  HANDLE mutex = ::CreateMutexW(nullptr, false, g_hijack_dlls_mutex);
-  EXPECT_TRUE(mutex);
-  EXPECT_EQ(WAIT_OBJECT_0,
-            ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
+  ScopedTestMutex mutex(g_hijack_dlls_mutex);
 
   // Not a baseline test, and expect the DLL to be in system32.
-  TestWin10ImageLoadPreferSys32(false, true);
-
-  EXPECT_TRUE(::ReleaseMutex(mutex));
-  EXPECT_TRUE(::CloseHandle(mutex));
+  TestWin10ImageLoadPreferSys32(false /* baseline_test */,
+                                true /* expect_sys32_path */);
 }
 
 // This test validates that setting the MITIGATION_IMAGE_LOAD_PREFER_SYS32
@@ -462,16 +452,11 @@
   if (base::win::GetVersion() < base::win::Version::WIN10_RS1)
     return;
 
-  HANDLE mutex = ::CreateMutexW(nullptr, false, g_hijack_dlls_mutex);
-  EXPECT_TRUE(mutex);
-  EXPECT_EQ(WAIT_OBJECT_0,
-            ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
+  ScopedTestMutex mutex(g_hijack_dlls_mutex);
 
   // Not a baseline test, and expect the DLL to NOT be in system32.
-  TestWin10ImageLoadPreferSys32(false, false);
-
-  EXPECT_TRUE(::ReleaseMutex(mutex));
-  EXPECT_TRUE(::CloseHandle(mutex));
+  TestWin10ImageLoadPreferSys32(false /* baseline_test */,
+                                false /* expect_sys32_path */);
 }
 
 }  // namespace sandbox
diff --git a/sandbox/win/src/process_mitigations_unittest.cc b/sandbox/win/src/process_mitigations_unittest.cc
index a0cd2790..270cfa1d 100644
--- a/sandbox/win/src/process_mitigations_unittest.cc
+++ b/sandbox/win/src/process_mitigations_unittest.cc
@@ -689,7 +689,7 @@
   if (base::win::GetVersion() < base::win::Version::WIN10)
     return;
 
-  TestWin10NonSystemFont(true);
+  TestWin10NonSystemFont(true /* is_success_test */);
 }
 
 // This test validates that setting the MITIGATION_NON_SYSTEM_FONTS_DISABLE
@@ -698,7 +698,7 @@
   if (base::win::GetVersion() < base::win::Version::WIN10)
     return;
 
-  TestWin10NonSystemFont(false);
+  TestWin10NonSystemFont(false /* is_success_test */);
 }
 
 //------------------------------------------------------------------------------
@@ -742,17 +742,11 @@
   if (base::win::GetVersion() < base::win::Version::WIN10_TH2)
     return;
 
-  HANDLE mutex =
-      ::CreateMutexW(nullptr, false, hooking_dll::g_hooking_dll_mutex);
-  EXPECT_TRUE(mutex);
-  EXPECT_EQ(WAIT_OBJECT_0,
-            ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
+  ScopedTestMutex mutex(hooking_dll::g_hooking_dll_mutex);
 
-  // Expect success; Do not enable mitigation; Use non MS-signed binary.
-  TestWin10MsSigned(true, false, false);
-
-  EXPECT_TRUE(::ReleaseMutex(mutex));
-  EXPECT_TRUE(::CloseHandle(mutex));
+  TestWin10MsSigned(true /* expect_success */,
+                    false /* enable_mitigation */,
+                    false /* use_ms_signed_binary */);
 }
 
 // This test validates that setting the MITIGATION_FORCE_MS_SIGNED_BINS
@@ -761,17 +755,11 @@
   if (base::win::GetVersion() < base::win::Version::WIN10_TH2)
     return;
 
-  HANDLE mutex =
-      ::CreateMutexW(nullptr, false, hooking_dll::g_hooking_dll_mutex);
-  EXPECT_TRUE(mutex);
-  EXPECT_EQ(WAIT_OBJECT_0,
-            ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
+  ScopedTestMutex mutex(hooking_dll::g_hooking_dll_mutex);
 
-  // Expect failure; Enable mitigation; Use non MS-signed binary.
-  TestWin10MsSigned(false, true, false);
-
-  EXPECT_TRUE(::ReleaseMutex(mutex));
-  EXPECT_TRUE(::CloseHandle(mutex));
+  TestWin10MsSigned(false /* expect_success */,
+                    true /* enable_mitigation */,
+                    false /* use_ms_signed_binary */);
 }
 
 // This test validates that we can load a signed Microsoft DLL if the
@@ -781,17 +769,11 @@
   if (base::win::GetVersion() < base::win::Version::WIN10_TH2)
     return;
 
-  HANDLE mutex =
-      ::CreateMutexW(nullptr, false, hooking_dll::g_hooking_dll_mutex);
-  EXPECT_TRUE(mutex);
-  EXPECT_EQ(WAIT_OBJECT_0,
-            ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
+  ScopedTestMutex mutex(hooking_dll::g_hooking_dll_mutex);
 
-  // Expect success; Do not enable mitigation; Use MS-signed binary.
-  TestWin10MsSigned(true, false, true);
-
-  EXPECT_TRUE(::ReleaseMutex(mutex));
-  EXPECT_TRUE(::CloseHandle(mutex));
+  TestWin10MsSigned(true /* expect_success */,
+                    false /* enable_mitigation */,
+                    true /* use_ms_signed_binary */);
 }
 
 // This test validates that setting the MITIGATION_FORCE_MS_SIGNED_BINS
@@ -800,17 +782,11 @@
   if (base::win::GetVersion() < base::win::Version::WIN10_TH2)
     return;
 
-  HANDLE mutex =
-      ::CreateMutexW(nullptr, false, hooking_dll::g_hooking_dll_mutex);
-  EXPECT_TRUE(mutex);
-  EXPECT_EQ(WAIT_OBJECT_0,
-            ::WaitForSingleObject(mutex, SboxTestEventTimeout()));
+  ScopedTestMutex mutex(hooking_dll::g_hooking_dll_mutex);
 
-  // Expect success; Enable mitigation; Use MS-signed binary.
-  TestWin10MsSigned(true, true, true);
-
-  EXPECT_TRUE(::ReleaseMutex(mutex));
-  EXPECT_TRUE(::CloseHandle(mutex));
+  TestWin10MsSigned(true /* expect_success */,
+                    true /* enable_mitigation */,
+                    true /* use_ms_signed_binary */);
 }
 
 //------------------------------------------------------------------------------
diff --git a/sandbox/win/src/win_utils_unittest.cc b/sandbox/win/src/win_utils_unittest.cc
index c814dbb3..507ceaf 100644
--- a/sandbox/win/src/win_utils_unittest.cc
+++ b/sandbox/win/src/win_utils_unittest.cc
@@ -20,6 +20,8 @@
 #include "sandbox/win/tests/common/test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+namespace sandbox {
+
 namespace {
 
 class ScopedTerminateProcess {
@@ -252,4 +254,6 @@
   EXPECT_TRUE(base::DeleteFileW(temp_path, false));
 
   return;
-}
\ No newline at end of file
+}
+
+}  // namespace sandbox
diff --git a/sandbox/win/tests/common/test_utils.cc b/sandbox/win/tests/common/test_utils.cc
index 9c03ac2..53fcc10 100644
--- a/sandbox/win/tests/common/test_utils.cc
+++ b/sandbox/win/tests/common/test_utils.cc
@@ -9,6 +9,8 @@
 
 #include "base/numerics/safe_conversions.h"
 
+namespace sandbox {
+
 typedef struct _REPARSE_DATA_BUFFER {
   ULONG  ReparseTag;
   USHORT  ReparseDataLength;
@@ -145,3 +147,5 @@
   return !!::GetTokenInformation(token, information_class, information->data(),
                                  return_length, &return_length);
 }
+
+}  // namespace sandbox
diff --git a/sandbox/win/tests/common/test_utils.h b/sandbox/win/tests/common/test_utils.h
index 049321d..2d63047d 100644
--- a/sandbox/win/tests/common/test_utils.h
+++ b/sandbox/win/tests/common/test_utils.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef SANDBOX_TESTS_COMMON_TEST_UTILS_H_
-#define SANDBOX_TESTS_COMMON_TEST_UTILS_H_
+#ifndef SANDBOX_WIN_TESTS_COMMON_TEST_UTILS_H_
+#define SANDBOX_WIN_TESTS_COMMON_TEST_UTILS_H_
 
 #include <windows.h>
 
@@ -12,6 +12,8 @@
 
 #include "sandbox/win/src/sid.h"
 
+namespace sandbox {
+
 // Sets a reparse point. |source| will now point to |target|. Returns true if
 // the call succeeds, false otherwise.
 bool SetReparsePoint(HANDLE source, const wchar_t* target);
@@ -48,5 +50,6 @@
                                  TOKEN_INFORMATION_CLASS information_class,
                                  std::vector<char>* information);
 
-#endif  // SANDBOX_TESTS_COMMON_TEST_UTILS_H_
+}  // namespace sandbox
 
+#endif  // SANDBOX_WIN_TESTS_COMMON_TEST_UTILS_H_
diff --git a/sandbox/win/tests/integration_tests/integration_tests_common.h b/sandbox/win/tests/integration_tests/integration_tests_common.h
index b18bad1a..4093bfa 100644
--- a/sandbox/win/tests/integration_tests/integration_tests_common.h
+++ b/sandbox/win/tests/integration_tests/integration_tests_common.h
@@ -7,6 +7,8 @@
 
 #include <windows.h>
 
+#include "testing/gtest/include/gtest/gtest.h"
+
 namespace sandbox {
 
 //------------------------------------------------------------------------------
@@ -32,6 +34,28 @@
 // Timeout for ::WaitForSingleObject synchronization.
 DWORD SboxTestEventTimeout();
 
+// Ensures that a given set of tests specified by |name| never run at the same
+// time, as they deal with machine-global data.
+class ScopedTestMutex {
+public:
+  explicit ScopedTestMutex(const wchar_t* name)
+    : mutex_(::CreateMutexW(nullptr, false, name)) {
+    EXPECT_TRUE(mutex_);
+    EXPECT_EQ(WAIT_OBJECT_0,
+      ::WaitForSingleObject(mutex_, SboxTestEventTimeout()));
+  }
+
+  ~ScopedTestMutex() {
+    EXPECT_TRUE(::ReleaseMutex(mutex_));
+    EXPECT_TRUE(::CloseHandle(mutex_));
+  }
+
+private:
+  HANDLE mutex_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedTestMutex);
+};
+
 }  // namespace sandbox
 
 #endif  // SANDBOX_TESTS_INTEGRATION_TESTS_COMMON_H_
diff --git a/services/network/network_change_manager.cc b/services/network/network_change_manager.cc
index 14ce87b..4b9ab32a 100644
--- a/services/network/network_change_manager.cc
+++ b/services/network/network_change_manager.cc
@@ -53,7 +53,10 @@
     mojom::ConnectionType new_connection_type,
     bool connection_subtype_changed,
     mojom::ConnectionSubtype new_connection_subtype) {
-  DCHECK(network_change_notifier_);
+  // network_change_notifier_ can be null in unit tests.
+  if (!network_change_notifier_)
+    return;
+
   net::NetworkChangeNotifierPosix* notifier =
       static_cast<net::NetworkChangeNotifierPosix*>(
           network_change_notifier_.get());
diff --git a/services/network/public/cpp/features.cc b/services/network/public/cpp/features.cc
index fc98e3f..d3c7ff5 100644
--- a/services/network/public/cpp/features.cc
+++ b/services/network/public/cpp/features.cc
@@ -9,15 +9,6 @@
 namespace network {
 namespace features {
 
-const char kNetworkServiceFeatureName[] =
-#if defined(OS_ANDROID)
-    "NetworkService";
-#else
-    // Rename the flag so that shortcuts with the old name don't work.
-    // This codepath doesn't work anymore on desktop.
-    "NetworkServiceNotSupported";
-#endif
-
 // Enables Expect CT reporting, which sends reports for opted-in sites
 // that don't serve sufficient Certificate Transparency information.
 const base::Feature kExpectCTReporting{"ExpectCTReporting",
@@ -26,8 +17,14 @@
 const base::Feature kNetworkErrorLogging{"NetworkErrorLogging",
                                          base::FEATURE_ENABLED_BY_DEFAULT};
 // Enables the network service.
-const base::Feature kNetworkService{kNetworkServiceFeatureName,
-                                    base::FEATURE_ENABLED_BY_DEFAULT};
+const base::Feature kNetworkService {
+#if defined(OS_ANDROID)
+  "NetworkService",
+#else
+  "NetworkServiceNotSupported",
+#endif
+      base::FEATURE_ENABLED_BY_DEFAULT
+};
 
 // Out of Blink CORS
 const base::Feature kOutOfBlinkCors{"OutOfBlinkCors",
diff --git a/services/network/public/cpp/features.h b/services/network/public/cpp/features.h
index 361a088..1fe733d 100644
--- a/services/network/public/cpp/features.h
+++ b/services/network/public/cpp/features.h
@@ -12,9 +12,6 @@
 namespace features {
 
 COMPONENT_EXPORT(NETWORK_CPP)
-extern const char kNetworkServiceFeatureName[];
-
-COMPONENT_EXPORT(NETWORK_CPP)
 extern const base::Feature kExpectCTReporting;
 COMPONENT_EXPORT(NETWORK_CPP)
 extern const base::Feature kNetworkErrorLogging;
diff --git a/services/tracing/perfetto/json_trace_exporter.cc b/services/tracing/perfetto/json_trace_exporter.cc
index f3504332..a647f64 100644
--- a/services/tracing/perfetto/json_trace_exporter.cc
+++ b/services/tracing/perfetto/json_trace_exporter.cc
@@ -584,6 +584,11 @@
   out_->AppendF(",\"tts\":%" PRId64, thread_timestamp);
 }
 
+void JSONTraceExporter::ScopedJSONTraceEventAppender::AddThreadInstructionDelta(
+    int64_t thread_instruction_delta) {
+  out_->AppendF(",\"tidelta\":%" PRId64, thread_instruction_delta);
+}
+
 void JSONTraceExporter::ScopedJSONTraceEventAppender::AddBindId(
     uint64_t bind_id) {
   out_->AppendF(",\"bind_id\":\"0x%" PRIx64 "\"",
diff --git a/services/tracing/perfetto/json_trace_exporter.h b/services/tracing/perfetto/json_trace_exporter.h
index f14ca24..a1bbb9ed 100644
--- a/services/tracing/perfetto/json_trace_exporter.h
+++ b/services/tracing/perfetto/json_trace_exporter.h
@@ -166,6 +166,7 @@
     void AddDuration(int64_t duration);
     void AddThreadDuration(int64_t thread_duration);
     void AddThreadTimestamp(int64_t thread_timestamp);
+    void AddThreadInstructionDelta(int64_t thread_instruction_delta);
     void AddBindId(uint64_t bind_id);
     // A set of bit flags for this trace event, along with a |scope|. |scope| is
     // ignored if empty.
diff --git a/services/tracing/perfetto/track_event_json_exporter.cc b/services/tracing/perfetto/track_event_json_exporter.cc
index 5c6be7d..33e6c902 100644
--- a/services/tracing/perfetto/track_event_json_exporter.cc
+++ b/services/tracing/perfetto/track_event_json_exporter.cc
@@ -522,6 +522,9 @@
   if (event.has_thread_duration_us()) {
     builder.AddThreadDuration(event.thread_duration_us());
   }
+  if (event.has_thread_instruction_delta()) {
+    builder.AddThreadInstructionDelta(event.thread_instruction_delta());
+  }
 
   // For flags and ID we need to determine all possible flag bits and set them
   // correctly.
diff --git a/services/tracing/public/cpp/perfetto/thread_local_event_sink.h b/services/tracing/public/cpp/perfetto/thread_local_event_sink.h
index db85b12c..34c719e 100644
--- a/services/tracing/public/cpp/perfetto/thread_local_event_sink.h
+++ b/services/tracing/public/cpp/perfetto/thread_local_event_sink.h
@@ -38,9 +38,11 @@
   virtual void AddTraceEvent(base::trace_event::TraceEvent* trace_event,
                              base::trace_event::TraceEventHandle* handle) = 0;
 
-  virtual void UpdateDuration(base::trace_event::TraceEventHandle handle,
-                              const base::TimeTicks& now,
-                              const base::ThreadTicks& thread_now) = 0;
+  virtual void UpdateDuration(
+      base::trace_event::TraceEventHandle handle,
+      const base::TimeTicks& now,
+      const base::ThreadTicks& thread_now,
+      base::trace_event::ThreadInstructionCount thread_instruction_now) = 0;
 
   virtual void Flush() = 0;
 
diff --git a/services/tracing/public/cpp/perfetto/trace_event_data_source.cc b/services/tracing/public/cpp/perfetto/trace_event_data_source.cc
index 56c4fd4..1d34808 100644
--- a/services/tracing/public/cpp/perfetto/trace_event_data_source.cc
+++ b/services/tracing/public/cpp/perfetto/trace_event_data_source.cc
@@ -493,7 +493,8 @@
 void TraceEventDataSource::OnUpdateDuration(
     base::trace_event::TraceEventHandle handle,
     const base::TimeTicks& now,
-    const base::ThreadTicks& thread_now) {
+    const base::ThreadTicks& thread_now,
+    base::trace_event::ThreadInstructionCount thread_instruction_now) {
   if (GetThreadIsInTraceEventTLS()->Get()) {
     return;
   }
@@ -503,7 +504,8 @@
   auto* thread_local_event_sink =
       static_cast<ThreadLocalEventSink*>(ThreadLocalEventSinkSlot()->Get());
   if (thread_local_event_sink) {
-    thread_local_event_sink->UpdateDuration(handle, now, thread_now);
+    thread_local_event_sink->UpdateDuration(handle, now, thread_now,
+                                            thread_instruction_now);
   }
 }
 
diff --git a/services/tracing/public/cpp/perfetto/trace_event_data_source.h b/services/tracing/public/cpp/perfetto/trace_event_data_source.h
index de921efc..9d6ab70 100644
--- a/services/tracing/public/cpp/perfetto/trace_event_data_source.h
+++ b/services/tracing/public/cpp/perfetto/trace_event_data_source.h
@@ -147,9 +147,11 @@
   static void OnAddTraceEvent(base::trace_event::TraceEvent* trace_event,
                               bool thread_will_flush,
                               base::trace_event::TraceEventHandle* handle);
-  static void OnUpdateDuration(base::trace_event::TraceEventHandle handle,
-                               const base::TimeTicks& now,
-                               const base::ThreadTicks& thread_now);
+  static void OnUpdateDuration(
+      base::trace_event::TraceEventHandle handle,
+      const base::TimeTicks& now,
+      const base::ThreadTicks& thread_now,
+      base::trace_event::ThreadInstructionCount thread_instruction_now);
 
   // Extracts UMA histogram names that should be logged in traces and logs their
   // starting values.
diff --git a/services/tracing/public/cpp/perfetto/trace_event_data_source_unittest.cc b/services/tracing/public/cpp/perfetto/trace_event_data_source_unittest.cc
index 2efd501..4aab130a 100644
--- a/services/tracing/public/cpp/perfetto/trace_event_data_source_unittest.cc
+++ b/services/tracing/public/cpp/perfetto/trace_event_data_source_unittest.cc
@@ -17,6 +17,7 @@
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/threading/thread_id_name_manager.h"
+#include "base/trace_event/thread_instruction_count.h"
 #include "base/trace_event/trace_event.h"
 #include "components/tracing/common/tracing_switches.h"
 #include "services/tracing/public/mojom/perfetto_service.mojom.h"
@@ -854,7 +855,8 @@
   base::trace_event::TraceLog::GetInstance()->UpdateTraceEventDurationExplicit(
       category_group_enabled, kEventName, handle,
       base::TimeTicks() + base::TimeDelta::FromMicroseconds(30),
-      base::ThreadTicks() + base::TimeDelta::FromMicroseconds(50));
+      base::ThreadTicks() + base::TimeDelta::FromMicroseconds(50),
+      base::trace_event::ThreadInstructionCount());
 
   // The call to UpdateTraceEventDurationExplicit should have successfully
   // updated the duration of the event which was added in the
@@ -875,7 +877,8 @@
   base::trace_event::TraceLog::GetInstance()->UpdateTraceEventDurationExplicit(
       category_group_enabled, kEventName, handle,
       base::TimeTicks() + base::TimeDelta::FromMicroseconds(30),
-      base::ThreadTicks() + base::TimeDelta::FromMicroseconds(50));
+      base::ThreadTicks() + base::TimeDelta::FromMicroseconds(50),
+      base::trace_event::ThreadInstructionCount());
 
   // No further packets should have been emitted.
   EXPECT_EQ(producer_client()->GetFinalizedPacketCount(), 2u);
diff --git a/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.cc b/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.cc
index 7f8ac8e..0516209 100644
--- a/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.cc
+++ b/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.cc
@@ -344,6 +344,13 @@
         legacy_event->set_thread_duration_us(thread_duration);
       }
     }
+
+    // TODO(acomminos): Add thread instruction count for BEGIN/END events
+    if (!trace_event->thread_instruction_count().is_null()) {
+      int64_t instruction_delta =
+          trace_event->thread_instruction_delta().ToInternalValue();
+      legacy_event->set_thread_instruction_delta(instruction_delta);
+    }
   } else if (phase == TRACE_EVENT_PHASE_INSTANT) {
     switch (flags & TRACE_EVENT_FLAG_SCOPE_MASK) {
       case TRACE_EVENT_SCOPE_GLOBAL:
@@ -473,7 +480,8 @@
 void TrackEventThreadLocalEventSink::UpdateDuration(
     base::trace_event::TraceEventHandle handle,
     const base::TimeTicks& now,
-    const base::ThreadTicks& thread_now) {
+    const base::ThreadTicks& thread_now,
+    base::trace_event::ThreadInstructionCount thread_instruction_now) {
   if (!handle.event_index || handle.chunk_index != kMagicChunkIndex ||
       handle.chunk_seq != session_id_) {
     return;
@@ -494,7 +502,8 @@
   }
 
   current_stack_depth_--;
-  complete_event_stack_[current_stack_depth_].UpdateDuration(now, thread_now);
+  complete_event_stack_[current_stack_depth_].UpdateDuration(
+      now, thread_now, thread_instruction_now);
   AddTraceEvent(&complete_event_stack_[current_stack_depth_], nullptr);
 
 #if defined(OS_ANDROID)
diff --git a/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.h b/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.h
index 5519857..10ff6f8 100644
--- a/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.h
+++ b/services/tracing/public/cpp/perfetto/track_event_thread_local_event_sink.h
@@ -46,7 +46,9 @@
                      base::trace_event::TraceEventHandle* handle) override;
   void UpdateDuration(base::trace_event::TraceEventHandle handle,
                       const base::TimeTicks& now,
-                      const base::ThreadTicks& thread_now) override;
+                      const base::ThreadTicks& thread_now,
+                      base::trace_event::ThreadInstructionCount
+                          thread_instruction_now) override;
   void Flush() override;
 
   // ThreadIdNameManager::Observer implementation:
diff --git a/services/tracing/public/cpp/tracing_features.cc b/services/tracing/public/cpp/tracing_features.cc
index 9a20fc7..50562f9 100644
--- a/services/tracing/public/cpp/tracing_features.cc
+++ b/services/tracing/public/cpp/tracing_features.cc
@@ -26,7 +26,7 @@
 
 // Causes Perfetto to run in-process mode for in-process tracing producers.
 const base::Feature kPerfettoForceOutOfProcessProducer{
-    "PerfettoForceOutOfProcessProducer", base::FEATURE_ENABLED_BY_DEFAULT};
+    "PerfettoForceOutOfProcessProducer", base::FEATURE_DISABLED_BY_DEFAULT};
 
 // Runs the tracing service as an in-process browser service.
 const base::Feature kTracingServiceInProcess {
diff --git a/styleguide/c++/c++-dos-and-donts.md b/styleguide/c++/c++-dos-and-donts.md
index 2e342677..d88800a 100644
--- a/styleguide/c++/c++-dos-and-donts.md
+++ b/styleguide/c++/c++-dos-and-donts.md
@@ -81,7 +81,7 @@
    };
    class Vexing {
     public:
-     explicit Vexing(const std::string&amp; s) { ... };
+     explicit Vexing(const std::string& s) { ... };
      ...
    };
    void func() {
@@ -97,6 +97,34 @@
    auto x{1};  // Until C++17, decltype(x) is std::initializer_list<int>, not int!
    ```
 
+## Initialize members in the declaration where possible
+
+If possible, initialize class members in their declarations, except where a
+member's value is explicitly set by every constructor.
+
+This reduces the chance of uninitialized variables, documents default values in
+the declaration, and increases the number of constructors that can use
+`=default` (see below).
+
+```cpp
+class C {
+ public:
+  C() : a_(2) {}
+  C(int b) : a_(1), b_(b) {}
+
+ private:
+  int a_;          // Not necessary to init this since all constructors set it.
+  int b_ = 0;      // Not all constructors set this.
+  std::string c_;  // No initializer needed due to string's default constructor.
+  base::WeakPtrFactory<C> factory_{this};
+                   // {} allows calling of explicit constructors.
+};
+```
+
+Note that it's possible to call functions or pass `this` and other expressions
+in initializers, so even some complex initializations can be done in the
+declaration.
+
 ## Prefer structs over pairs/tuples when used repeatedly
 
 The Google style guide
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index daf4aa3..b8da67667 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -16064,7 +16064,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
+              "os": "Mac-10.13.6"
             },
             {
               "gpu": "1002:6821",
@@ -16092,7 +16092,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
+              "os": "Mac-10.13.6"
             },
             {
               "gpu": "1002:6821",
@@ -16121,7 +16121,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
+              "os": "Mac-10.13.6"
             },
             {
               "gpu": "1002:6821",
@@ -16147,7 +16147,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
+              "os": "Mac-10.13.6"
             },
             {
               "gpu": "1002:6821",
@@ -16172,7 +16172,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
+              "os": "Mac-10.13.6"
             },
             {
               "gpu": "1002:6821",
@@ -16197,7 +16197,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
+              "os": "Mac-10.13.6"
             },
             {
               "gpu": "1002:6821",
@@ -16219,7 +16219,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
+              "os": "Mac-10.13.6"
             },
             {
               "gpu": "1002:6821",
@@ -16245,7 +16245,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
+              "os": "Mac-10.13.6"
             },
             {
               "gpu": "1002:6821",
@@ -16267,7 +16267,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
+              "os": "Mac-10.13.6"
             },
             {
               "gpu": "1002:6821",
@@ -16303,7 +16303,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
+              "os": "Mac-10.13.6"
             },
             {
               "gpu": "1002:6821",
@@ -16336,7 +16336,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
+              "os": "Mac-10.13.6"
             },
             {
               "gpu": "1002:6821",
@@ -16369,7 +16369,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
+              "os": "Mac-10.13.6"
             },
             {
               "gpu": "1002:6821",
@@ -16402,7 +16402,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
+              "os": "Mac-10.13.6"
             },
             {
               "gpu": "1002:6821",
@@ -16442,7 +16442,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
+              "os": "Mac-10.13.6"
             },
             {
               "gpu": "1002:6821",
@@ -16490,7 +16490,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
+              "os": "Mac-10.13.6"
             },
             {
               "gpu": "1002:6821",
@@ -16524,7 +16524,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
+              "os": "Mac-10.13.6"
             },
             {
               "gpu": "1002:6821",
@@ -16557,7 +16557,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
+              "os": "Mac-10.13.6"
             },
             {
               "gpu": "1002:6821",
@@ -16593,7 +16593,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
+              "os": "Mac-10.13.6"
             },
             {
               "gpu": "1002:6821",
@@ -16628,7 +16628,7 @@
           "dimension_sets": [
             {
               "gpu": "8086:0a2e",
-              "os": "Mac-10.12.6"
+              "os": "Mac-10.13.6"
             },
             {
               "gpu": "1002:6821",
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index df04fda..2e154ede 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -1278,9 +1278,6 @@
     "type": "raw",
   },
   "ios_chrome_translate_egtests": {
-    "args": [
-      "--enable-features=CompactTranslateInfobarIOS",
-    ],
     "label": "//ios/chrome/test/earl_grey:ios_chrome_translate_egtests",
     "type": "raw",
   },
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 076112d3..92fbc1e7 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -2847,7 +2847,7 @@
             # TODO(kbr): figure out how to use mixins for these.
             {
               'gpu': '8086:0a2e',
-              'os': 'Mac-10.12.6',
+              'os': 'Mac-10.13.6',
             },
             {
               'gpu': '1002:6821',
diff --git a/testing/libfuzzer/libprotobuf-mutator.md b/testing/libfuzzer/libprotobuf-mutator.md
index 982bf37..51d0a033 100644
--- a/testing/libfuzzer/libprotobuf-mutator.md
+++ b/testing/libfuzzer/libprotobuf-mutator.md
@@ -60,7 +60,7 @@
 // Assuming the .proto file is path/to/your/proto_file/my_proto.proto.
 #include "path/to/your/proto_file/my_proto.pb.h"
 
-DEFINE_BINARY_PROTO_FUZZER(
+DEFINE_PROTO_FUZZER(
   const my_proto::MyProtoMessage& my_proto_message) {
   targeted_function(my_proto_message);
 }
@@ -79,7 +79,7 @@
     sources = [ "my_fuzzer.cc" ]
     deps = [
       // The proto library defining the message accepted by
-      // DEFINE_BINARY_PROTO_FUZZER().
+      // DEFINE_PROTO_FUZZER().
       ":my_proto",
 
       "//third_party/libprotobuf-mutator",
@@ -140,7 +140,7 @@
 the definition of the Url message.
 
 ### Write the Fuzz Target and Conversion Code
-Create a new .cc and write a `DEFINE_BINARY_PROTO_FUZZER` function:
+Create a new .cc and write a `DEFINE_PROTO_FUZZER` function:
 
 ```c++
 // Needed since we use getenv().
@@ -158,7 +158,7 @@
 // your fuzzing code (or just pass "my_format", if your target accepts
 // protobufs).
 
-DEFINE_BINARY_PROTO_FUZZER(const my_fuzzer::MyFormat& my_proto_format) {
+DEFINE_PROTO_FUZZER(const my_fuzzer::MyFormat& my_proto_format) {
     // Convert your protobuf to whatever format your targeted code accepts
     // if it doesn't accept protobufs.
     std::string native_input = convert_to_native_input(my_proto_format);
@@ -299,7 +299,7 @@
 // Assuming the .proto file is path/to/your/proto_file/my_fuzzer_input.proto.
 #include "path/to/your/proto_file/my_proto.pb.h"
 
-DEFINE_BINARY_PROTO_FUZZER(
+DEFINE_PROTO_FUZZER(
   const my_proto::FuzzerInput& fuzzer_input) {
   if (fuzzer_input.has_arg3())
     targeted_function_1(fuzzer_input.arg1(), fuzzer_input.arg2(), fuzzer_input.arg3());
@@ -348,17 +348,21 @@
 partially defined by an existing proto definition (if you are writing a grammar
 fuzzer).
 
-* `DEFINE_TEXT_PROTO_FUZZER` can be used instead of `DEFINE_BINARY_PROTO_FUZZER`
-to have a corpus that is human readable and modifiable (ie: not in protobuf's
-binary format). However, `DEFINE_TEXT_PROTO_FUZZER` does come with a
-performance penalty, so it may be better to only use it during development.
-
+* `DEFINE_BINARY_PROTO_FUZZER` can be used instead of `DEFINE_PROTO_FUZZER` (or
+  `DEFINE_TEXT_PROTO_FUZZER`) to use protobuf's binary format for the corpus.
+  This will make it hard/impossible to modify the corpus manually (i.e. when not
+  fuzzing). However, protobuf's text format (and by extension
+  `DEFINE_PROTO_FUZZER`) is believed by some to come with a performance penalty
+  compared to the binary format. We've never seen a case where this penalty
+  was important, but if profiling reveals that protobuf deserialization is the
+  bottleneck in your fuzzer, you may want to consider using the binary format.
+  This will probably not be the case.
 
 [libfuzzer in Chromium]: getting_started.md
 [Protocol Buffers]: https://developers.google.com/protocol-buffers/docs/cpptutorial
 [fuzzing@chromium.org]: mailto:fuzzing@chromium.org
 [this]: https://github.com/google/libprotobuf-mutator/tree/master/examples/libfuzzer/libfuzzer_example.cc
-[existing proto fuzzers]: https://cs.chromium.org/search/?q=DEFINE_(BINARY_%7CTEXT_)?PROTO_FUZZER+-file:src/third_party/libprotobuf-mutator/src/src/libfuzzer/libfuzzer_macro.h&sq=package:chromium&type=cs
+[existing proto fuzzers]: https://cs.chromium.org/search/?q=DEFINE_(BINARY_%7CTEXT_)?PROTO_FUZZER+-file:src/third_party/libprotobuf-mutator/src/src/libfuzzer/libfuzzer_macro.h+lang:cpp&sq=package:chromium&type=cs
 [here]: https://github.com/google/libprotobuf-mutator/blob/master/README.md#utf-8-strings
 [override_lite_runtime_plugin_test_fuzzer]: https://cs.chromium.org/#search&q=override_lite_runtime_plugin_test_fuzzer+file:%5Esrc/third_party/libprotobuf-mutator/BUILD.gn
 [mojo_parse_messages_proto_fuzzer]: https://cs.chromium.org/chromium/src/mojo/public/tools/fuzzers/mojo_parse_message_proto_fuzzer.cc?l=25
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 7b45c1d..09c91f8 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -66,6 +66,10 @@
 const base::Feature kBlinkGenPropertyTrees{"BlinkGenPropertyTrees",
                                            base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Enable a new CSS property called backdrop-filter.
+const base::Feature kCSSBackdropFilter{"CSSBackdropFilter",
+                                       base::FEATURE_ENABLED_BY_DEFAULT};
+
 // Enable Display Locking JavaScript APIs.
 const base::Feature kDisplayLocking{"DisplayLocking",
                                     base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index dd21498..0f64ee7 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -27,6 +27,7 @@
 BLINK_COMMON_EXPORT extern const base::Feature kImplicitRootScroller;
 BLINK_COMMON_EXPORT extern const base::Feature kJankTrackingSweepLine;
 BLINK_COMMON_EXPORT extern const base::Feature kBlinkGenPropertyTrees;
+BLINK_COMMON_EXPORT extern const base::Feature kCSSBackdropFilter;
 BLINK_COMMON_EXPORT extern const base::Feature kDisplayLocking;
 BLINK_COMMON_EXPORT extern const base::Feature kFastBorderRadius;
 BLINK_COMMON_EXPORT extern const base::Feature kLayoutNG;
diff --git a/third_party/blink/renderer/core/dom/events/event_dispatcher.cc b/third_party/blink/renderer/core/dom/events/event_dispatcher.cc
index 4b6c4e9..2274c0d 100644
--- a/third_party/blink/renderer/core/dom/events/event_dispatcher.cc
+++ b/third_party/blink/renderer/core/dom/events/event_dispatcher.cc
@@ -36,6 +36,7 @@
 #include "third_party/blink/renderer/core/dom/events/event_path.h"
 #include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h"
 #include "third_party/blink/renderer/core/dom/events/window_event_context.h"
+#include "third_party/blink/renderer/core/events/keyboard_event.h"
 #include "third_party/blink/renderer/core/events/mouse_event.h"
 #include "third_party/blink/renderer/core/frame/ad_tracker.h"
 #include "third_party/blink/renderer/core/frame/deprecation.h"
@@ -44,6 +45,8 @@
 #include "third_party/blink/renderer/core/frame/settings.h"
 #include "third_party/blink/renderer/core/html/html_element.h"
 #include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
+#include "third_party/blink/renderer/core/page/page.h"
+#include "third_party/blink/renderer/core/page/spatial_navigation_controller.h"
 #include "third_party/blink/renderer/core/timing/event_timing.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
@@ -365,6 +368,15 @@
     }
   }
 
+  if (Page* page = node_->GetDocument().GetPage()) {
+    if (page->GetSettings().GetSpatialNavigationEnabled() &&
+        is_trusted_or_click && event_->IsKeyboardEvent() &&
+        ToKeyboardEvent(*event_).key() == "Enter" &&
+        event_->type() == event_type_names::kKeyup) {
+      page->GetSpatialNavigationController().ResetEnterKeyState();
+    }
+  }
+
   // Track the usage of sending a mousedown event to a select element to force
   // it to open. This measures a possible breakage of not allowing untrusted
   // events to open select boxes.
diff --git a/third_party/blink/renderer/core/editing/ime/input_method_controller.cc b/third_party/blink/renderer/core/editing/ime/input_method_controller.cc
index 57005359..cd6943b8 100644
--- a/third_party/blink/renderer/core/editing/ime/input_method_controller.cc
+++ b/third_party/blink/renderer/core/editing/ime/input_method_controller.cc
@@ -548,6 +548,22 @@
   return InsertTextAndMoveCaret(text, relative_caret_position, ime_text_spans);
 }
 
+bool InputMethodController::ReplaceText(const String& text,
+                                        PlainTextRange range) {
+  EventQueueScope scope;
+  const PlainTextRange old_selection(GetSelectionOffsets());
+  if (!SetSelectionOffsets(range))
+    return false;
+  if (!InsertText(text))
+    return false;
+  wtf_size_t selection_delta = text.length() - range.length();
+  wtf_size_t start = old_selection.Start();
+  wtf_size_t end = old_selection.End();
+  return SetSelectionOffsets(
+      {start >= range.End() ? start + selection_delta : start,
+       end >= range.End() ? end + selection_delta : end});
+}
+
 bool InputMethodController::ReplaceComposition(const String& text) {
   // Verify that the caller is using an EventQueueScope to suppress the input
   // event from being fired until the proper time (e.g. after applying an IME
diff --git a/third_party/blink/renderer/core/editing/ime/input_method_controller.h b/third_party/blink/renderer/core/editing/ime/input_method_controller.h
index 6bb7ad03..37cde8df 100644
--- a/third_party/blink/renderer/core/editing/ime/input_method_controller.h
+++ b/third_party/blink/renderer/core/editing/ime/input_method_controller.h
@@ -78,6 +78,9 @@
                   const Vector<ImeTextSpan>& ime_text_spans,
                   int relative_caret_position);
 
+  // Replaces the text in the specified range without changing the selection.
+  bool ReplaceText(const String&, PlainTextRange);
+
   // Inserts ongoing composing text; changes the selection to the end of
   // the inserting text if DoNotKeepSelection, or holds the selection if
   // KeepSelection.
diff --git a/third_party/blink/renderer/core/editing/layout_selection.cc b/third_party/blink/renderer/core/editing/layout_selection.cc
index 1eef465..ea77044e 100644
--- a/third_party/blink/renderer/core/editing/layout_selection.cc
+++ b/third_party/blink/renderer/core/editing/layout_selection.cc
@@ -762,13 +762,6 @@
     if (!layout_object || !layout_object->CanBeSelectionLeaf())
       continue;
 
-    if (UNLIKELY(layout_object->NeedsLayout())) {
-      // TODO(crbug.com/973226): This is a workaround for missing layout on
-      // |node|. We should eliminate such missing layout eventually.
-      NOTREACHED() << node;
-      continue;
-    }
-
     if (!start_node) {
       DCHECK(!end_node);
       start_node = end_node = &node;
diff --git a/third_party/blink/renderer/core/exported/web_input_method_controller_impl.cc b/third_party/blink/renderer/core/exported/web_input_method_controller_impl.cc
index f502778..f70eb17 100644
--- a/third_party/blink/renderer/core/exported/web_input_method_controller_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_input_method_controller_impl.cc
@@ -122,17 +122,16 @@
                               relative_caret_position);
   }
 
-  // Select the range to be replaced with the composition later.
-  if (!replacement_range.IsNull()) {
-    web_frame_->SelectRange(replacement_range,
-                            WebLocalFrame::kHideSelectionHandle,
-                            blink::mojom::SelectionMenuBehavior::kHide);
-  }
-
   // TODO(editing-dev): The use of UpdateStyleAndLayout
   // needs to be audited.  See http://crbug.com/590369 for more details.
   GetFrame()->GetDocument()->UpdateStyleAndLayout();
 
+  if (!replacement_range.IsNull()) {
+    return GetInputMethodController().ReplaceText(
+        text, PlainTextRange(replacement_range.StartOffset(),
+                             replacement_range.EndOffset()));
+  }
+
   return GetInputMethodController().CommitText(
       text, ImeTextSpanVectorBuilder::Build(ime_text_spans),
       relative_caret_position);
diff --git a/third_party/blink/renderer/core/html/forms/radio_input_type.cc b/third_party/blink/renderer/core/html/forms/radio_input_type.cc
index 730db85..f4f691d 100644
--- a/third_party/blink/renderer/core/html/forms/radio_input_type.cc
+++ b/third_party/blink/renderer/core/html/forms/radio_input_type.cc
@@ -151,8 +151,6 @@
        event.key() == "Enter")) {
     DispatchSimulatedClickIfActive(event);
   }
-
-  DispatchSimulatedClickIfActive(event);
 }
 
 bool RadioInputType::IsKeyboardFocusable() const {
diff --git a/third_party/blink/renderer/core/html/html_element.cc b/third_party/blink/renderer/core/html/html_element.cc
index a3f5f49..b6b0f3e 100644
--- a/third_party/blink/renderer/core/html/html_element.cc
+++ b/third_party/blink/renderer/core/html/html_element.cc
@@ -1332,6 +1332,8 @@
 void HTMLElement::HandleKeypressEvent(KeyboardEvent& event) {
   if (!IsSpatialNavigationEnabled(GetDocument().GetFrame()) || !SupportsFocus())
     return;
+  if (RuntimeEnabledFeatures::FocuslessSpatialNavigationEnabled())
+    return;
   GetDocument().UpdateStyleAndLayoutTree();
   // if the element is a text form control (like <input type=text> or
   // <textarea>) or has contentEditable attribute on, we should enter a space or
diff --git a/third_party/blink/renderer/core/html/resources/html.css b/third_party/blink/renderer/core/html/resources/html.css
index 07956cd..d4c2d23 100644
--- a/third_party/blink/renderer/core/html/resources/html.css
+++ b/third_party/blink/renderer/core/html/resources/html.css
@@ -530,6 +530,11 @@
     overflow: hidden;
 }
 
+input::-internal-input-suggested,
+textarea::-internal-input-suggested {
+    font-family: system-ui !important;
+}
+
 input[type="password" i] {
     -webkit-text-security: disc !important;
 }
@@ -561,7 +566,6 @@
 input:-internal-autofill-previewed,
 textarea:-internal-autofill-previewed,
 select:-internal-autofill-previewed {
-  font-family: system-ui !important;
   background-color: #E8F0FE !important;
   background-image:none !important;
   color: #000000 !important;
diff --git a/third_party/blink/renderer/core/input/keyboard_event_manager.cc b/third_party/blink/renderer/core/input/keyboard_event_manager.cc
index 06c313c3..100e7a4d 100644
--- a/third_party/blink/renderer/core/input/keyboard_event_manager.cc
+++ b/third_party/blink/renderer/core/input/keyboard_event_manager.cc
@@ -352,16 +352,19 @@
     frame_->GetEditor().HandleKeyboardEvent(event);
     if (event->DefaultHandled())
       return;
-    if (event->charCode() == ' ')
-      DefaultSpaceEventHandler(event, possible_focused_node);
-  } else if (event->type() == event_type_names::kKeyup) {
     if (event->key() == "Enter") {
       DefaultEnterEventHandler(event);
-      return;
+    } else if (event->charCode() == ' ') {
+      DefaultSpaceEventHandler(event, possible_focused_node);
     }
-
-    if (event->keyCode() == kVKeySpatNavBack)
+  } else if (event->type() == event_type_names::kKeyup) {
+    if (event->DefaultHandled())
+      return;
+    if (event->key() == "Enter") {
+      DefaultEnterEventHandler(event);
+    } else if (event->keyCode() == kVKeySpatNavBack) {
       DefaultSpatNavBackEventHandler(event);
+    }
   }
 }
 
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics.cc
index c3b0e50f..d6549af 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics.cc
@@ -13,12 +13,6 @@
 TextFragmentAnchorMetrics::TextFragmentAnchorMetrics(Document* document)
     : document_(document) {}
 
-TextFragmentAnchorMetrics::~TextFragmentAnchorMetrics() {
-#ifndef NDEBUG
-  DCHECK(metrics_reported_);
-#endif
-}
-
 void TextFragmentAnchorMetrics::DidCreateAnchor(int selector_count) {
   UseCounter::Count(document_, WebFeature::kTextFragmentAnchor);
   create_time_ = WTF::CurrentTimeTicks();
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics.h b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics.h
index 5f9c049..baebecf 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics.h
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics.h
@@ -17,7 +17,6 @@
     : public GarbageCollectedFinalized<TextFragmentAnchorMetrics> {
  public:
   TextFragmentAnchorMetrics(Document* document);
-  ~TextFragmentAnchorMetrics();
 
   void DidCreateAnchor(int selector_count);
 
diff --git a/third_party/blink/renderer/core/page/spatial_navigation_controller.cc b/third_party/blink/renderer/core/page/spatial_navigation_controller.cc
index 5c4f5ee..f00e13e 100644
--- a/third_party/blink/renderer/core/page/spatial_navigation_controller.cc
+++ b/third_party/blink/renderer/core/page/spatial_navigation_controller.cc
@@ -163,10 +163,18 @@
     return false;
 
   if (event->type() == event_type_names::kKeydown) {
+    enter_key_down_seen_ = true;
     interest_element->SetActive(true);
+  } else if (event->type() == event_type_names::kKeypress) {
+    enter_key_press_seen_ = true;
   } else if (event->type() == event_type_names::kKeyup) {
     interest_element->SetActive(false);
-    if (RuntimeEnabledFeatures::FocuslessSpatialNavigationEnabled()) {
+
+    // Ensure that the enter key has not already been handled by something else,
+    // or we can end up clicking elements multiple times. Some elements already
+    // convert the Enter key into click on down and press (and up) events.
+    if (RuntimeEnabledFeatures::FocuslessSpatialNavigationEnabled() &&
+        enter_key_down_seen_ && enter_key_press_seen_) {
       interest_element->focus(FocusParams(SelectionBehaviorOnFocus::kReset,
                                           kWebFocusTypeSpatialNavigation,
                                           nullptr));
@@ -179,6 +187,11 @@
   return true;
 }
 
+void SpatialNavigationController::ResetEnterKeyState() {
+  enter_key_down_seen_ = false;
+  enter_key_press_seen_ = false;
+}
+
 bool SpatialNavigationController::HandleImeSubmitKeyboardEvent(
     KeyboardEvent* event) {
   DCHECK(page_->GetSettings().GetSpatialNavigationEnabled());
diff --git a/third_party/blink/renderer/core/page/spatial_navigation_controller.h b/third_party/blink/renderer/core/page/spatial_navigation_controller.h
index d1ac834..e748a5e5 100644
--- a/third_party/blink/renderer/core/page/spatial_navigation_controller.h
+++ b/third_party/blink/renderer/core/page/spatial_navigation_controller.h
@@ -32,6 +32,10 @@
   bool HandleEscapeKeyboardEvent(KeyboardEvent* event);
   bool HandleImeSubmitKeyboardEvent(KeyboardEvent* event);
 
+  // Called when the enter key is released to clear local state because we don't
+  // get a consistent event stream when the Enter key is partially handled.
+  void ResetEnterKeyState();
+
   // Returns the element that's currently interested. i.e. the Element that's
   // currently indicated to the user.
   Element* GetInterestedElement() const;
@@ -107,6 +111,11 @@
   WeakMember<Element> interest_element_;
   Member<Page> page_;
 
+  // We need to track whether the enter key has been handled in down or press to
+  // know whether to generate a click on the up.
+  bool enter_key_down_seen_ = false;
+  bool enter_key_press_seen_ = false;
+
   mojom::blink::SpatialNavigationStatePtr spatial_navigation_state_;
   mojom::blink::SpatialNavigationHostPtr spatial_navigation_host_;
 };
diff --git a/third_party/blink/renderer/devtools/front_end/network/BlockedURLsPane.js b/third_party/blink/renderer/devtools/front_end/network/BlockedURLsPane.js
index 21e81fb..3991d919 100644
--- a/third_party/blink/renderer/devtools/front_end/network/BlockedURLsPane.js
+++ b/third_party/blink/renderer/devtools/front_end/network/BlockedURLsPane.js
@@ -50,11 +50,9 @@
    */
   _createEmptyPlaceholder() {
     const element = this.contentElement.createChild('div', 'no-blocked-urls');
-    element.createChild('span').textContent = Common.UIString('Requests are not blocked. ');
-    const addLink = element.createChild('span', 'link');
-    addLink.textContent = Common.UIString('Add pattern.');
-    addLink.href = '';
+    const addLink = UI.XLink.create('', ls`Add pattern`);
     addLink.addEventListener('click', this._addButtonClicked.bind(this), false);
+    element.appendChild(UI.formatLocalized('Requests are not blocked. %s.', [addLink]));
     return element;
   }
 
diff --git a/third_party/blink/renderer/devtools/front_end/network/network_strings.grdp b/third_party/blink/renderer/devtools/front_end/network/network_strings.grdp
index 5b75ce6..754e400 100644
--- a/third_party/blink/renderer/devtools/front_end/network/network_strings.grdp
+++ b/third_party/blink/renderer/devtools/front_end/network/network_strings.grdp
@@ -84,6 +84,9 @@
   <message name="IDS_DEVTOOLS_2537b3e6907e17001b7cdf121cd39dc0" desc="">
     (from ServiceWorker)
   </message>
+  <message name="IDS_DEVTOOLS_25f77a709e3c31285e8411402420b72d" desc="">
+    Requests are not blocked. <ph name="ADDLINK">$1s</ph>.
+  </message>
   <message name="IDS_DEVTOOLS_26b6b5ae2c6e2575b2a451c35e94df23" desc="">
     Waiting (TTFB)
   </message>
@@ -108,9 +111,6 @@
   <message name="IDS_DEVTOOLS_30ad3ea5e350075c1c0a1171c5006ed7" desc="">
     Fetching frames...
   </message>
-  <message name="IDS_DEVTOOLS_3136ed000d43bebb4ca53a9312fbd32c" desc="">
-    Add pattern.
-  </message>
   <message name="IDS_DEVTOOLS_31977081ea2c828cc70e6151ab5d7da8" desc="">
     view decoded
   </message>
@@ -576,9 +576,6 @@
   <message name="IDS_DEVTOOLS_c2cc7082a89c1ad6631a2f66af5f00c0" desc="">
     Connection
   </message>
-  <message name="IDS_DEVTOOLS_c33fa8b7d663932b656f0f7f53a74e77" desc="">
-    Requests are not blocked. '''
-  </message>
   <message name="IDS_DEVTOOLS_c3c14eb17a6cf9c6120f381790ed06eb" desc="">
     Copy all as PowerShell
   </message>
diff --git a/third_party/blink/renderer/devtools/front_end/node_main/NodeConnectionsPanel.js b/third_party/blink/renderer/devtools/front_end/node_main/NodeConnectionsPanel.js
index c4d4274..ff9ed37 100644
--- a/third_party/blink/renderer/devtools/front_end/node_main/NodeConnectionsPanel.js
+++ b/third_party/blink/renderer/devtools/front_end/node_main/NodeConnectionsPanel.js
@@ -55,11 +55,10 @@
     this.element.classList.add('network-discovery-view');
 
     const networkDiscoveryFooter = this.element.createChild('div', 'network-discovery-footer');
-    networkDiscoveryFooter.createChild('span').textContent =
-        Common.UIString('Specify network endpoint and DevTools will connect to it automatically. ');
-    const link = networkDiscoveryFooter.createChild('span', 'link');
-    link.textContent = Common.UIString('Learn more');
-    link.addEventListener('click', () => InspectorFrontendHost.openInNewTab('https://nodejs.org/en/docs/inspector/'));
+    const documentationLink = UI.XLink.create('https://nodejs.org/en/docs/inspector/', ls`Node.js debugging guide`);
+    networkDiscoveryFooter.appendChild(UI.formatLocalized(
+        'Specify network endpoint and DevTools will connect to it automatically. Read %s to learn more.',
+        [documentationLink]));
 
     /** @type {!UI.ListWidget<!Adb.PortForwardingRule>} */
     this._list = new UI.ListWidget(this);
diff --git a/third_party/blink/renderer/devtools/front_end/node_main/node_main_strings.grdp b/third_party/blink/renderer/devtools/front_end/node_main/node_main_strings.grdp
index 44081351..1241b38 100644
--- a/third_party/blink/renderer/devtools/front_end/node_main/node_main_strings.grdp
+++ b/third_party/blink/renderer/devtools/front_end/node_main/node_main_strings.grdp
@@ -6,13 +6,16 @@
   <message name="IDS_DEVTOOLS_3fd49f986cbd5dd3148c27c9aaaaa700" desc="">
     Network address (e.g. localhost:9229)
   </message>
+  <message name="IDS_DEVTOOLS_43d755260901476a60e28982f36b2913" desc="">
+    Node.js debugging guide
+  </message>
   <message name="IDS_DEVTOOLS_75997049f01ae448c39b8a937c4e2035" desc="">
     No connections specified
   </message>
-  <message name="IDS_DEVTOOLS_81c731365b52528d7d0fdb477ebc3b1d" desc="">
-    Specify network endpoint and DevTools will connect to it automatically. '''
-  </message>
   <message name="IDS_DEVTOOLS_b1f0f05f6f7dcbf25e065bb4c31fec72" desc="">
     Node.js: <ph name="TARGETINFO_URL">$1s</ph>
   </message>
+  <message name="IDS_DEVTOOLS_ed6b1cccfa7455e9506ff7e5127e1df4" desc="">
+    Specify network endpoint and DevTools will connect to it automatically. Read <ph name="DOCUMENTATIONLINK">$1s</ph> to learn more.
+  </message>
 </grit-part>
\ No newline at end of file
diff --git a/third_party/blink/renderer/devtools/front_end/object_ui/ObjectPropertiesSection.js b/third_party/blink/renderer/devtools/front_end/object_ui/ObjectPropertiesSection.js
index bbfb68d..dcfb44c6 100644
--- a/third_party/blink/renderer/devtools/front_end/object_ui/ObjectPropertiesSection.js
+++ b/third_party/blink/renderer/devtools/front_end/object_ui/ObjectPropertiesSection.js
@@ -286,9 +286,7 @@
 
     if (wasThrown) {
       const wrapperElement = createElementWithClass('span', 'error value');
-      wrapperElement.createTextChild('[' + Common.UIString('Exception') + ': ');
-      wrapperElement.appendChild(valueElement);
-      wrapperElement.createTextChild(']');
+      wrapperElement.appendChild(UI.formatLocalized('[Exception: %s]', [valueElement]));
       return wrapperElement;
     }
     valueElement.classList.add('value');
diff --git a/third_party/blink/renderer/devtools/front_end/object_ui/object_ui_strings.grdp b/third_party/blink/renderer/devtools/front_end/object_ui/object_ui_strings.grdp
index 46d6fa38..83a8248 100644
--- a/third_party/blink/renderer/devtools/front_end/object_ui/object_ui_strings.grdp
+++ b/third_party/blink/renderer/devtools/front_end/object_ui/object_ui_strings.grdp
@@ -39,13 +39,13 @@
   <message name="IDS_DEVTOOLS_ad921d60486366258809553a3db49a4a" desc="">
     unknown
   </message>
-  <message name="IDS_DEVTOOLS_b0d4998a26f5b5742ad38c4af8817e32" desc="">
-    Exception
-  </message>
   <message name="IDS_DEVTOOLS_b46f6ce1cece499f73c9d5c36e4a3de4" desc="">
     Invoke property getter
   </message>
   <message name="IDS_DEVTOOLS_d50bbea3c85be098dcf221e622cd7718" desc="">
     Lexical scope variables
   </message>
+  <message name="IDS_DEVTOOLS_ecaa3f03d3f2556acc42a409ae0e00d4" desc="">
+    [Exception: <ph name="VALUEELEMENT">$1s</ph>]
+  </message>
 </grit-part>
\ No newline at end of file
diff --git a/third_party/blink/renderer/devtools/front_end/sources/sources_strings.grdp b/third_party/blink/renderer/devtools/front_end/sources/sources_strings.grdp
index ca62f1c0..107c2522 100644
--- a/third_party/blink/renderer/devtools/front_end/sources/sources_strings.grdp
+++ b/third_party/blink/renderer/devtools/front_end/sources/sources_strings.grdp
@@ -342,6 +342,9 @@
   <message name="IDS_DEVTOOLS_ae76c8d643589ff25d35455a80f12061" desc="">
     Do not display variable values inline while debugging
   </message>
+  <message name="IDS_DEVTOOLS_b0d4998a26f5b5742ad38c4af8817e32" desc="">
+    Exception
+  </message>
   <message name="IDS_DEVTOOLS_b16d05636bde9fe84392fd4af198e9c1" desc="">
     Quick source
   </message>
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 6f5b6c4..b330dec2 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -516,7 +516,7 @@
     },
     {
       name: "EnterKeyHintAttribute",
-      status: "experimental",
+      status: "stable",
     },
     {
       name: "EventTiming",
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
index 618e42a63..a4415607 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
@@ -209,7 +209,6 @@
 
   base::test::ScopedFeatureList& scoped_feature_list() { return feature_list_; }
 
-  std::unique_ptr<base::FieldTrialList> field_trial_list_;
   base::test::ScopedFeatureList feature_list_;
   base::test::ScopedTaskEnvironment task_environment_;
   std::unique_ptr<MainThreadSchedulerImpl> scheduler_;
@@ -1539,7 +1538,6 @@
     const char kStudyName[] = "ResourceFetchPriorityExperiment";
     const char kGroupName[] = "GroupName1";
 
-    field_trial_list_ = std::make_unique<base::FieldTrialList>(nullptr);
     base::AssociateFieldTrialParams(kStudyName, kGroupName, params);
     base::FieldTrialList::CreateFieldTrial(kStudyName, kGroupName);
   }
@@ -1574,7 +1572,6 @@
     const char kStudyName[] = "ResourceFetchPriorityExperiment";
     const char kGroupName[] = "GroupName2";
 
-    field_trial_list_ = std::make_unique<base::FieldTrialList>(nullptr);
     base::AssociateFieldTrialParams(kStudyName, kGroupName, params);
     base::FieldTrialList::CreateFieldTrial(kStudyName, kGroupName);
   }
@@ -1748,21 +1745,8 @@
  public:
   ThrottleAndFreezeTaskTypesExperimentTest(const base::FieldTrialParams& params,
                                            const char* group_name) {
-    const char kStudyName[] = "ThrottleAndFreezeTaskTypes";
-
-    field_trial_list_ = std::make_unique<base::FieldTrialList>(nullptr);
-
-    scoped_refptr<base::FieldTrial> trial =
-        base::FieldTrialList::CreateFieldTrial(kStudyName, group_name);
-
-    base::FieldTrialParamAssociator::GetInstance()->AssociateFieldTrialParams(
-        kStudyName, group_name, params);
-
-    std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList);
-    feature_list->RegisterFieldTrialOverride(
-        kThrottleAndFreezeTaskTypes.name,
-        base::FeatureList::OVERRIDE_ENABLE_FEATURE, trial.get());
-    scoped_feature_list().InitWithFeatureList(std::move(feature_list));
+    scoped_feature_list().InitAndEnableFeatureWithParameters(
+        kThrottleAndFreezeTaskTypes, params);
   }
 };
 
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/page_scheduler_impl_unittest.cc b/third_party/blink/renderer/platform/scheduler/main_thread/page_scheduler_impl_unittest.cc
index 3b4e8159..39b78fba 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/page_scheduler_impl_unittest.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/page_scheduler_impl_unittest.cc
@@ -1095,8 +1095,6 @@
   ScopedExpensiveBackgroundTimerThrottlingForTest
       budget_background_throttling_enabler(true);
 
-  std::unique_ptr<base::FieldTrialList> field_trial_list =
-      std::make_unique<base::FieldTrialList>(nullptr);
   InitializeTrialParams();
   page_scheduler_.reset(new PageSchedulerImpl(nullptr, scheduler_.get()));
   EXPECT_FALSE(page_scheduler_->IsThrottled());
@@ -1160,8 +1158,6 @@
   ScopedExpensiveBackgroundTimerThrottlingForTest
       budget_background_throttling_enabler(true);
 
-  std::unique_ptr<base::FieldTrialList> field_trial_list =
-      std::make_unique<base::FieldTrialList>(nullptr);
   InitializeTrialParams();
   std::unique_ptr<PageSchedulerImpl> page_scheduler(
       new PageSchedulerImpl(nullptr, scheduler_.get()));
diff --git a/third_party/blink/web_tests/W3CImportExpectations b/third_party/blink/web_tests/W3CImportExpectations
index 3f35cc1..629060b 100644
--- a/third_party/blink/web_tests/W3CImportExpectations
+++ b/third_party/blink/web_tests/W3CImportExpectations
@@ -466,11 +466,11 @@
 external/wpt/client-hints/accept_ch.https.html [ Skip ]
 
 # TODO(crbug.com/974254): Remove the following once we can handle spaces in test names.
-external/wpt/webdriver/tests/element_click/interactability.py
-external/wpt/webdriver/tests/find_element/find.py
-external/wpt/webdriver/tests/find_element_from_element/find.py
-external/wpt/webdriver/tests/find_elements/find.py
-external/wpt/webdriver/tests/find_elements_from_element/find.py
-external/wpt/webdriver/tests/new_session/invalid_capabilities.py
-external/wpt/webdriver/tests/new_session/response.py
-external/wpt/webdriver/tests/send_alert_text/send.py
+external/wpt/webdriver/tests/element_click/interactability.py [ Skip ]
+external/wpt/webdriver/tests/find_element/find.py [ Skip ]
+external/wpt/webdriver/tests/find_element_from_element/find.py [ Skip ]
+external/wpt/webdriver/tests/find_elements/find.py [ Skip ]
+external/wpt/webdriver/tests/find_elements_from_element/find.py [ Skip ]
+external/wpt/webdriver/tests/new_session/invalid_capabilities.py [ Skip ]
+external/wpt/webdriver/tests/new_session/response.py [ Skip ]
+external/wpt/webdriver/tests/send_alert_text/send.py [ Skip ]
diff --git a/third_party/blink/web_tests/fast/forms/text/input-appearance-autocomplete-expected.html b/third_party/blink/web_tests/fast/forms/text/input-appearance-autocomplete-expected.html
deleted file mode 100644
index 18a14ac5..0000000
--- a/third_party/blink/web_tests/fast/forms/text/input-appearance-autocomplete-expected.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<input id="input" value="hello" style="width: 99px" >
-<script>
-input.focus();
-input.setSelectionRange(0,0);
-internals.setAutofilled(input, true);
-</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/forms/text/input-appearance-autocomplete-suggested-value-over-placeholder-value-expected.html b/third_party/blink/web_tests/fast/forms/text/input-appearance-autocomplete-suggested-value-over-placeholder-value-expected.html
deleted file mode 100644
index 0fb8fc3..0000000
--- a/third_party/blink/web_tests/fast/forms/text/input-appearance-autocomplete-suggested-value-over-placeholder-value-expected.html
+++ /dev/null
@@ -1 +0,0 @@
-<input id="input" value="Springfield">
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/forms/text/input-appearance-autocomplete-suggested-value-when-underlying-placeholder-is-removed-expected.html b/third_party/blink/web_tests/fast/forms/text/input-appearance-autocomplete-suggested-value-when-underlying-placeholder-is-removed-expected.html
deleted file mode 100644
index 0fb8fc3..0000000
--- a/third_party/blink/web_tests/fast/forms/text/input-appearance-autocomplete-suggested-value-when-underlying-placeholder-is-removed-expected.html
+++ /dev/null
@@ -1 +0,0 @@
-<input id="input" value="Springfield">
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/forms/text/input-appearance-autocomplete-suggestion-maxlength-expected.html b/third_party/blink/web_tests/fast/forms/text/input-appearance-autocomplete-suggestion-maxlength-expected.html
deleted file mode 100644
index 2a9bfb0..0000000
--- a/third_party/blink/web_tests/fast/forms/text/input-appearance-autocomplete-suggestion-maxlength-expected.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<input id="input" maxlength="5" value="Miste">
-<script>
-input.focus();
-input.setSelectionRange(0,0);
-internals.setAutofilled(input, true);
-</script>
diff --git a/third_party/blink/web_tests/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.html b/third_party/blink/web_tests/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.html
deleted file mode 100644
index 6bfd2ec0..0000000
--- a/third_party/blink/web_tests/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<!DOCTYPE html>
-<script src="../../../resources/ahem.js"></script>
-<input id="input" value="Guy WithAVeryLongLastNameThatCouldWrap" style="width: 99px; font: 20px Ahem" >
-<script>
-input.focus();
-input.setSelectionRange(0,0);
-internals.setAutofilled(input, true);
-</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/forms/text/input-appearance-autocomplete-with-initial-value-expected.html b/third_party/blink/web_tests/fast/forms/text/input-appearance-autocomplete-with-initial-value-expected.html
deleted file mode 100644
index 8d30029..0000000
--- a/third_party/blink/web_tests/fast/forms/text/input-appearance-autocomplete-with-initial-value-expected.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<input id="input" value="hello" style="width: 99px">
-<script>
-internals.setAutofilled(input, true);
-</script>
diff --git a/third_party/blink/web_tests/fast/forms/text/input-appearance-offset-size.html b/third_party/blink/web_tests/fast/forms/text/input-appearance-offset-size.html
new file mode 100644
index 0000000..7158354
--- /dev/null
+++ b/third_party/blink/web_tests/fast/forms/text/input-appearance-offset-size.html
@@ -0,0 +1,35 @@
+<html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<body>
+<p>This tests that an input field does not change its size when a value is previewed.</p>
+
+<div><input id="emptyInput" style="font: 12px Arial"></div>
+<div><input id="previewInput" style="font: 12px Arial"></div>
+
+<div><textarea id="emptyTextArea" style="font: 12px Arial"></textarea></div>
+<div><textarea id="previewTextArea" style="font: 12px Arial"></textarea></div>
+<script>
+test(() => {
+    var empty = document.getElementById('emptyInput');
+    var preview = document.getElementById('previewInput');
+
+    internals.setSuggestedValue(preview, 'Foooooooooooooooooooooooooooooooooooo');
+
+    assert_equals(empty.offsetWidth, preview.offsetWidth, 'Elements should have same width');
+    assert_equals(empty.offsetHeight, preview.offsetHeight, 'Elements should have same height');
+}, 'Testing that an <input> field does not change its size when a value is previewed.');
+
+test(() => {
+    var empty = document.getElementById('emptyTextArea');
+    var preview = document.getElementById('previewTextArea');
+
+    internals.setSuggestedValue(preview, 'Foooooooooooooooooooooooooooooooooooo');
+
+    assert_equals(empty.offsetWidth, preview.offsetWidth, 'Elements should have same width');
+    assert_equals(empty.offsetHeight, preview.offsetHeight, 'Elements should have same height');
+}, 'Testing that an <textarea> field does not change its size when a value is previewed.');
+
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/fast/forms/text/input-appearance-scroll-size.html b/third_party/blink/web_tests/fast/forms/text/input-appearance-scroll-size.html
new file mode 100644
index 0000000..f927f1a
--- /dev/null
+++ b/third_party/blink/web_tests/fast/forms/text/input-appearance-scroll-size.html
@@ -0,0 +1,44 @@
+<html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<body>
+<p>An input element can have its scrollWidth/scrollHeight changed while being
+temporarily in preview state. This test ensures that these two values do not
+depend on the configured font type (the preview always happens with system-ui
+font).</p>
+<div><input id="referenceInput" style="font: 12px system-ui; width: 10px; height: 6px"></div>
+<div><input id="modifiedInput" style="font: 12px Arial; width: 10px; height: 6px"></div>
+<div><textarea id="referenceTextArea" style="font: 12px system-ui; width: 10px; height: 6px"></textarea></div>
+<div><textarea id="modifiedTextArea" style="font: 12px Arial; width: 10px; height: 6px"></textarea></div>
+<script>
+test(() => {
+    var reference = document.getElementById('referenceInput');
+    var modified = document.getElementById('modifiedInput');
+
+    var value = 'Foooooooooooooooooooooooooooooooooooo';
+    internals.setSuggestedValue(reference, value);
+    internals.setSuggestedValue(modified, value);
+
+    assert_equals(reference.scrollWidth, modified.scrollWidth,
+        'Elements should have same scrollWidth');
+    assert_equals(reference.scrollHeight, modified.scrollHeight,
+        'Elements should have same scrollHeight');
+}, 'Testing that <input> elements\' scroll size does not depend on font type.');
+
+test(() => {
+    var reference = document.getElementById('referenceTextArea');
+    var modified = document.getElementById('modifiedTextArea');
+
+    var value = 'Foooooooooooooooooooooooooooooooooooo';
+    internals.setSuggestedValue(reference, value);
+    internals.setSuggestedValue(modified, value);
+
+    assert_equals(reference.scrollWidth, modified.scrollWidth,
+        'Elements should have same scrollWidth');
+    assert_equals(reference.scrollHeight, modified.scrollHeight,
+        'Elements should have same scrollHeight');
+}, 'Testing that <textarea> elements\' scroll size does not depend on font type.');
+</script>
+</body>
+</html>
+
diff --git a/third_party/blink/web_tests/fast/forms/text/password-input-suggested-value-appearance-expected.html b/third_party/blink/web_tests/fast/forms/text/password-input-suggested-value-appearance-expected.html
deleted file mode 100644
index 4af5de1..0000000
--- a/third_party/blink/web_tests/fast/forms/text/password-input-suggested-value-appearance-expected.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<input id="input" type="password" value="MyPassword" style="width: 99px" >
-<script>
-input.focus();
-input.setSelectionRange(0,0);
-internals.setAutofilled(input, true);
-</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/fast/forms/text/textarea-appearance-autocomplete-suggestion-maxlength-expected.html b/third_party/blink/web_tests/fast/forms/text/textarea-appearance-autocomplete-suggestion-maxlength-expected.html
deleted file mode 100644
index 7aef426..0000000
--- a/third_party/blink/web_tests/fast/forms/text/textarea-appearance-autocomplete-suggestion-maxlength-expected.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<textarea id="textarea" maxlength="5">Miste</textarea>
-<script>
-textarea.focus();
-textarea.setSelectionRange(0,0);
-internals.setAutofilled(textarea, true);
-</script>
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/text/input-appearance-autocomplete-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/text/input-appearance-autocomplete-expected.png
new file mode 100644
index 0000000..27916cb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/text/input-appearance-autocomplete-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/text/input-appearance-autocomplete-suggested-value-over-placeholder-value-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/text/input-appearance-autocomplete-suggested-value-over-placeholder-value-expected.png
new file mode 100644
index 0000000..bc6012dd
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/text/input-appearance-autocomplete-suggested-value-over-placeholder-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/text/input-appearance-autocomplete-suggested-value-when-underlying-placeholder-is-removed-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/text/input-appearance-autocomplete-suggested-value-when-underlying-placeholder-is-removed-expected.png
new file mode 100644
index 0000000..bc6012dd
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/text/input-appearance-autocomplete-suggested-value-when-underlying-placeholder-is-removed-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/text/input-appearance-autocomplete-suggestion-maxlength-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/text/input-appearance-autocomplete-suggestion-maxlength-expected.png
new file mode 100644
index 0000000..54fb80c5
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/text/input-appearance-autocomplete-suggestion-maxlength-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png
new file mode 100644
index 0000000..f8552621
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/text/input-appearance-autocomplete-with-initial-value-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/text/input-appearance-autocomplete-with-initial-value-expected.png
new file mode 100644
index 0000000..639ad18
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/text/input-appearance-autocomplete-with-initial-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/text/password-input-suggested-value-appearance-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/text/password-input-suggested-value-appearance-expected.png
new file mode 100644
index 0000000..c038964f
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/text/password-input-suggested-value-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/forms/text/textarea-appearance-autocomplete-suggestion-maxlength-expected.png b/third_party/blink/web_tests/platform/linux/fast/forms/text/textarea-appearance-autocomplete-suggestion-maxlength-expected.png
new file mode 100644
index 0000000..7cdc73f
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/fast/forms/text/textarea-appearance-autocomplete-suggestion-maxlength-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/images/jpeg-yuv-image-decoding-expected.png b/third_party/blink/web_tests/platform/linux/images/jpeg-yuv-image-decoding-expected.png
index 97a98172..55cd69a8 100644
--- a/third_party/blink/web_tests/platform/linux/images/jpeg-yuv-image-decoding-expected.png
+++ b/third_party/blink/web_tests/platform/linux/images/jpeg-yuv-image-decoding-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/exotic-color-space/images/jpeg-yuv-image-decoding-expected.png b/third_party/blink/web_tests/platform/linux/virtual/exotic-color-space/images/jpeg-yuv-image-decoding-expected.png
index d2e7d00..b8cdcdf 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/exotic-color-space/images/jpeg-yuv-image-decoding-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/exotic-color-space/images/jpeg-yuv-image-decoding-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/jpeg-yuv-image-decoding-expected.png b/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/jpeg-yuv-image-decoding-expected.png
index 97a98172..55cd69a8 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/jpeg-yuv-image-decoding-expected.png
+++ b/third_party/blink/web_tests/platform/linux/virtual/gpu-rasterization/images/jpeg-yuv-image-decoding-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/input-appearance-autocomplete-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/input-appearance-autocomplete-expected.png
new file mode 100644
index 0000000..30ea5b5
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/input-appearance-autocomplete-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/input-appearance-autocomplete-suggested-value-over-placeholder-value-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/input-appearance-autocomplete-suggested-value-over-placeholder-value-expected.png
new file mode 100644
index 0000000..0e61c71
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/input-appearance-autocomplete-suggested-value-over-placeholder-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/input-appearance-autocomplete-suggested-value-when-underlying-placeholder-is-removed-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/input-appearance-autocomplete-suggested-value-when-underlying-placeholder-is-removed-expected.png
new file mode 100644
index 0000000..0e61c71
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/input-appearance-autocomplete-suggested-value-when-underlying-placeholder-is-removed-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/input-appearance-autocomplete-suggestion-maxlength-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/input-appearance-autocomplete-suggestion-maxlength-expected.png
new file mode 100644
index 0000000..f76879e
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/input-appearance-autocomplete-suggestion-maxlength-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png
new file mode 100644
index 0000000..998da681
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/input-appearance-autocomplete-with-initial-value-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/input-appearance-autocomplete-with-initial-value-expected.png
new file mode 100644
index 0000000..4dade790
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/input-appearance-autocomplete-with-initial-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/password-input-suggested-value-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/password-input-suggested-value-appearance-expected.png
new file mode 100644
index 0000000..f22a1edd
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/password-input-suggested-value-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/textarea-appearance-autocomplete-suggestion-maxlength-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/textarea-appearance-autocomplete-suggestion-maxlength-expected.png
new file mode 100644
index 0000000..d68533d
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/fast/forms/text/textarea-appearance-autocomplete-suggestion-maxlength-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/images/jpeg-yuv-image-decoding-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/images/jpeg-yuv-image-decoding-expected.png
index f9c2fe3..20f3f44 100644
--- a/third_party/blink/web_tests/platform/mac-mac10.10/images/jpeg-yuv-image-decoding-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/images/jpeg-yuv-image-decoding-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/exotic-color-space/images/jpeg-yuv-image-decoding-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/exotic-color-space/images/jpeg-yuv-image-decoding-expected.png
index 6f504d8b4..7e77b7b0 100644
--- a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/exotic-color-space/images/jpeg-yuv-image-decoding-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/exotic-color-space/images/jpeg-yuv-image-decoding-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/jpeg-yuv-image-decoding-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/jpeg-yuv-image-decoding-expected.png
index f9c2fe3..20f3f44 100644
--- a/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/jpeg-yuv-image-decoding-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac10.10/virtual/gpu-rasterization/images/jpeg-yuv-image-decoding-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/input-appearance-autocomplete-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/input-appearance-autocomplete-expected.png
new file mode 100644
index 0000000..6c2b505
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/input-appearance-autocomplete-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/input-appearance-autocomplete-suggested-value-over-placeholder-value-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/input-appearance-autocomplete-suggested-value-over-placeholder-value-expected.png
new file mode 100644
index 0000000..0fca354
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/input-appearance-autocomplete-suggested-value-over-placeholder-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/input-appearance-autocomplete-suggested-value-when-underlying-placeholder-is-removed-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/input-appearance-autocomplete-suggested-value-when-underlying-placeholder-is-removed-expected.png
new file mode 100644
index 0000000..0fca354
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/input-appearance-autocomplete-suggested-value-when-underlying-placeholder-is-removed-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/input-appearance-autocomplete-suggestion-maxlength-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/input-appearance-autocomplete-suggestion-maxlength-expected.png
new file mode 100644
index 0000000..cddd14f4
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/input-appearance-autocomplete-suggestion-maxlength-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png
new file mode 100644
index 0000000..ce00c9c
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/input-appearance-autocomplete-with-initial-value-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/input-appearance-autocomplete-with-initial-value-expected.png
new file mode 100644
index 0000000..ad30d563
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/input-appearance-autocomplete-with-initial-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/password-input-suggested-value-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/password-input-suggested-value-appearance-expected.png
new file mode 100644
index 0000000..68eb023
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/password-input-suggested-value-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/textarea-appearance-autocomplete-suggestion-maxlength-expected.png b/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/textarea-appearance-autocomplete-suggestion-maxlength-expected.png
new file mode 100644
index 0000000..7c17f831
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.11/fast/forms/text/textarea-appearance-autocomplete-suggestion-maxlength-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png
new file mode 100644
index 0000000..582e8d7
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/fast/forms/text/password-input-suggested-value-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/fast/forms/text/password-input-suggested-value-appearance-expected.png
new file mode 100644
index 0000000..c943b40
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac10.12/fast/forms/text/password-input-suggested-value-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/text/input-appearance-autocomplete-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/text/input-appearance-autocomplete-expected.png
new file mode 100644
index 0000000..5d14614
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/text/input-appearance-autocomplete-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/text/input-appearance-autocomplete-suggested-value-over-placeholder-value-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/text/input-appearance-autocomplete-suggested-value-over-placeholder-value-expected.png
new file mode 100644
index 0000000..de1589d
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/text/input-appearance-autocomplete-suggested-value-over-placeholder-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/text/input-appearance-autocomplete-suggested-value-when-underlying-placeholder-is-removed-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/text/input-appearance-autocomplete-suggested-value-when-underlying-placeholder-is-removed-expected.png
new file mode 100644
index 0000000..de1589d
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/text/input-appearance-autocomplete-suggested-value-when-underlying-placeholder-is-removed-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/text/input-appearance-autocomplete-suggestion-maxlength-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/text/input-appearance-autocomplete-suggestion-maxlength-expected.png
new file mode 100644
index 0000000..edfff76
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/text/input-appearance-autocomplete-suggestion-maxlength-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png
new file mode 100644
index 0000000..655a48e
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/text/input-appearance-autocomplete-with-initial-value-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/text/input-appearance-autocomplete-with-initial-value-expected.png
new file mode 100644
index 0000000..690a9708
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/text/input-appearance-autocomplete-with-initial-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/text/password-input-suggested-value-appearance-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/text/password-input-suggested-value-appearance-expected.png
new file mode 100644
index 0000000..d8b3992
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/text/password-input-suggested-value-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/text/textarea-appearance-autocomplete-suggestion-maxlength-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/text/textarea-appearance-autocomplete-suggestion-maxlength-expected.png
new file mode 100644
index 0000000..6d5471288
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/text/textarea-appearance-autocomplete-suggestion-maxlength-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/images/jpeg-yuv-image-decoding-expected.png b/third_party/blink/web_tests/platform/mac/images/jpeg-yuv-image-decoding-expected.png
index 6f1f4ce..99c21b6 100644
--- a/third_party/blink/web_tests/platform/mac/images/jpeg-yuv-image-decoding-expected.png
+++ b/third_party/blink/web_tests/platform/mac/images/jpeg-yuv-image-decoding-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/jpeg-yuv-image-decoding-expected.png b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/jpeg-yuv-image-decoding-expected.png
index db8f44e..80f01a3 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/jpeg-yuv-image-decoding-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/jpeg-yuv-image-decoding-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/jpeg-yuv-image-decoding-expected.png b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/jpeg-yuv-image-decoding-expected.png
index 6f1f4ce..99c21b6 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/jpeg-yuv-image-decoding-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu-rasterization/images/jpeg-yuv-image-decoding-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/text/input-appearance-autocomplete-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/text/input-appearance-autocomplete-expected.png
new file mode 100644
index 0000000..0b43e7be
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/fast/forms/text/input-appearance-autocomplete-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/text/input-appearance-autocomplete-suggested-value-over-placeholder-value-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/text/input-appearance-autocomplete-suggested-value-over-placeholder-value-expected.png
new file mode 100644
index 0000000..e6de28dc
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/fast/forms/text/input-appearance-autocomplete-suggested-value-over-placeholder-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/text/input-appearance-autocomplete-suggested-value-when-underlying-placeholder-is-removed-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/text/input-appearance-autocomplete-suggested-value-when-underlying-placeholder-is-removed-expected.png
new file mode 100644
index 0000000..e6de28dc
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/fast/forms/text/input-appearance-autocomplete-suggested-value-when-underlying-placeholder-is-removed-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/text/input-appearance-autocomplete-suggestion-maxlength-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/text/input-appearance-autocomplete-suggestion-maxlength-expected.png
new file mode 100644
index 0000000..5429c3d
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/fast/forms/text/input-appearance-autocomplete-suggestion-maxlength-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png
new file mode 100644
index 0000000..df42b059
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/text/input-appearance-autocomplete-with-initial-value-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/text/input-appearance-autocomplete-with-initial-value-expected.png
new file mode 100644
index 0000000..14a3face
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/fast/forms/text/input-appearance-autocomplete-with-initial-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/text/password-input-suggested-value-appearance-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/text/password-input-suggested-value-appearance-expected.png
new file mode 100644
index 0000000..d59d461
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/fast/forms/text/password-input-suggested-value-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/text/textarea-appearance-autocomplete-suggestion-maxlength-expected.png b/third_party/blink/web_tests/platform/win/fast/forms/text/textarea-appearance-autocomplete-suggestion-maxlength-expected.png
new file mode 100644
index 0000000..3a88be45
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win/fast/forms/text/textarea-appearance-autocomplete-suggestion-maxlength-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/images/jpeg-yuv-image-decoding-expected.png b/third_party/blink/web_tests/platform/win/images/jpeg-yuv-image-decoding-expected.png
index adb719c4..0205be9 100644
--- a/third_party/blink/web_tests/platform/win/images/jpeg-yuv-image-decoding-expected.png
+++ b/third_party/blink/web_tests/platform/win/images/jpeg-yuv-image-decoding-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/jpeg-yuv-image-decoding-expected.png b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/jpeg-yuv-image-decoding-expected.png
index f1626ca..6a6b470 100644
--- a/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/jpeg-yuv-image-decoding-expected.png
+++ b/third_party/blink/web_tests/platform/win/virtual/exotic-color-space/images/jpeg-yuv-image-decoding-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png b/third_party/blink/web_tests/platform/win7/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png
new file mode 100644
index 0000000..a1e8ee0
--- /dev/null
+++ b/third_party/blink/web_tests/platform/win7/fast/forms/text/input-appearance-autocomplete-very-long-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/focusless-spat-nav/fast/spatial-navigation/focusless/snav-focusless-click-once.html b/third_party/blink/web_tests/virtual/focusless-spat-nav/fast/spatial-navigation/focusless/snav-focusless-click-once.html
new file mode 100644
index 0000000..7e01c704
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/focusless-spat-nav/fast/spatial-navigation/focusless/snav-focusless-click-once.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<script src="../../../../../resources/testharness.js"></script>
+<script src="../../../../../resources/testharnessreport.js"></script>
+<script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
+<script src="file:///gen/third_party/blink/public/mojom/page/spatial_navigation.mojom.js"></script>
+<script src="../../../../../fast/spatial-navigation/resources/mock-snav-service.js"></script>
+<script src="../../../../../fast/spatial-navigation/resources/snav-testharness.js"></script>
+
+<button type="button" id="button" autofocus> button </button>
+
+<svg height="100" width="100" id="svg" tabindex="0">
+  <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
+</svg>
+
+<img src="" height="100" width="100" id="img" tabindex="1" />
+
+<script>
+  let button = document.getElementById("button");
+  let svg = document.getElementById("svg");
+  let img = document.getElementById("img");
+  let buttonClicked = 0;
+  let svgClicked = 0;
+  let imgClicked = 0;
+
+  button.addEventListener('click', function() {
+    console.log('wtf');
+    buttonClicked++;
+  })
+
+  svg.addEventListener('click', function() {
+    svgClicked++;
+  })
+
+  img.addEventListener('keydown', function(e) {
+    e.preventDefault();
+  })
+  img.addEventListener('click', function() {
+    imgClicked++;
+  })
+
+  promise_test(async () => {
+    await snav.rAF();
+    eventSender.keyDown('Enter');
+    await snav.rAF();
+    assert_equals(buttonClicked,
+                  1,
+                  "button should have been clicked once.");
+
+    svg.focus();
+    eventSender.keyDown('Enter');
+    await snav.rAF();
+    assert_equals(svgClicked,
+                  1,
+                  "svg should have been clicked once.");
+
+    img.focus();
+    eventSender.keyDown('Enter');
+    await snav.rAF();
+    assert_equals(imgClicked,
+                  0,
+                  "img should not have been clicked.");
+  }, 'Spat Nav doesn\'t click multiple times.');
+</script>
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/element-instance-property-listing-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/element-instance-property-listing-expected.txt
index dc22bfa4..fe2505f 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/element-instance-property-listing-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/element-instance-property-listing-expected.txt
@@ -59,6 +59,7 @@
     property dir
     property dispatchEvent
     property draggable
+    property enterKeyHint
     property firstChild
     property firstElementChild
     property focus
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 5afccc46..0b8d2e66 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
@@ -2195,6 +2195,7 @@
     getter dataset
     getter dir
     getter draggable
+    getter enterKeyHint
     getter hidden
     getter innerText
     getter inputMode
@@ -2302,6 +2303,7 @@
     setter contentEditable
     setter dir
     setter draggable
+    setter enterKeyHint
     setter hidden
     setter innerText
     setter inputMode
diff --git a/third_party/closure_compiler/externs/bluetooth_private.js b/third_party/closure_compiler/externs/bluetooth_private.js
index 0ba0571..d53aff1 100644
--- a/third_party/closure_compiler/externs/bluetooth_private.js
+++ b/third_party/closure_compiler/externs/bluetooth_private.js
@@ -173,6 +173,14 @@
 chrome.bluetoothPrivate.recordReconnection = function(success) {};
 
 /**
+ * Record that a user selected a device to connect to.
+ * @param {number} selectionDurationMs
+ * @param {boolean} wasPaired
+ * @param {!chrome.bluetooth.Transport} transport
+ */
+chrome.bluetoothPrivate.recordDeviceSelection = function(selectionDurationMs, wasPaired, transport) {};
+
+/**
  * Fired when a pairing event occurs.
  * @type {!ChromeEvent}
  */
diff --git a/third_party/webxr_test_pages/webxr-samples/index.html b/third_party/webxr_test_pages/webxr-samples/index.html
index db4d01e..57d1a8e 100644
--- a/third_party/webxr_test_pages/webxr-samples/index.html
+++ b/third_party/webxr_test_pages/webxr-samples/index.html
@@ -100,6 +100,13 @@
 
         <script>
           let pages = [
+            { title: 'Barebones', category: 'Basics',
+              path: 'xr-barebones.html',
+              description: 'Extremely simple use of WebXR with no library dependencies. Doesn\'t render anything exciting.' },
+
+            { tag: 'hr' },
+            { tag: 'br' },
+
             { title: 'XR Presentation', category: 'Basics',
               path: 'xr-presentation.html',
               description: 'Demonstrates how to present a simple WebGL scene to a XRDevice.' },
@@ -154,22 +161,17 @@
             { title: 'Spectator Mode', category: 'Advanced Techniques',
               path: 'spectator-mode.html',
               description: 'Demonstrates rendering a 3rd person view of the scene to an external monitor.' },
-
-            { tag: 'hr' },
-            { tag: 'br' },
-
-            { title: 'Barebones', category: 'WIN32_LEAN_AND_MEAN',
-              path: 'xr-barebones.html',
-              description: 'Extremely simple use of WebXR with no library dependencies. Doesn\'t render anything exciting.' },
           ];
 
           let mainElement = document.getElementById("main");
 
           // Append an element for every item in the pages list.
+          let tag_count = 0;
           for (var i = 0; i < pages.length; ++i) {
             var page = pages[i];
 
             if (page.tag) {
+              tag_count++;
               mainElement.appendChild(document.createElement(page.tag));
               continue;
             }
@@ -177,7 +179,7 @@
             let article = document.createElement('article');
 
             let title = document.createElement('h3');
-            title.setAttribute('data-index', i + 1);
+            title.setAttribute('data-index', i + 1 - tag_count);
 
             let titleLink = document.createElement('a');
             titleLink.href = page.path;
diff --git a/third_party/webxr_test_pages/webxr-samples/xr-barebones.html b/third_party/webxr_test_pages/webxr-samples/xr-barebones.html
index 0120898..94f2f94 100644
--- a/third_party/webxr_test_pages/webxr-samples/xr-barebones.html
+++ b/third_party/webxr_test_pages/webxr-samples/xr-barebones.html
@@ -30,9 +30,7 @@
     <meta http-equiv="origin-trial" data-feature="WebXR Device API (For Chrome M76+)" data-expires="2019-07-24" content="Ap6io/uhkGK7vXCD+golNnQfj8wJ4so790EzZoqb8YOljMXIBTvBEQFPTHYIz5d/BgtuwZTKOLrmHAOt30f38g8AAABxeyJvcmlnaW4iOiJodHRwczovL3N0b3JhZ2UuZ29vZ2xlYXBpcy5jb206NDQzIiwiZmVhdHVyZSI6IldlYlhSRGV2aWNlTTc2IiwiZXhwaXJ5IjoxNTY0MDA5MzU2LCJpc1N1YmRvbWFpbiI6dHJ1ZX0=">
 
     <title>Barebones WebXR</title>
-
     <link href='css/common.css' rel='stylesheet'></link>
-
   </head>
   <body>
     <header>
@@ -60,32 +58,36 @@
       let gl = null;
 
       function checkSupportedState() {
-          // If the device allows creation of exclusive sessions enable the
-          // button.  If the supportsSession request is rejected, then
-          // disable the button because it means that XR is not supported.
-          navigator.xr.supportsSession('immersive-vr').then(() => {
-            // Updates the button to start an XR session when clicked.
-            xrButton.innerHTML = 'Enter XR';
-            xrButton.disabled = false;
-          }, () => {
-            xrButton.innerHTML = 'XR not found';
-            xrButton.disabled = true;
-          });
+        // If the device allows creation of immersive VR sessions, enable the
+        // button. If the supportsSession request is rejected, then
+        // disable the button because it means that the desired session mode is
+        // not supported.
+        navigator.xr.supportsSession('immersive-vr').then(() => {
+          // Updates the button to start an XR session when clicked.
+          xrButton.innerHTML = 'Enter XR';
+          xrButton.disabled = false;
+        }, () => {
+          xrButton.innerHTML = 'XR not found';
+          xrButton.disabled = true;
+        });
       }
 
-      // Checks to see if WebXR is available and, if so, requests an XRDevice
-      // that is connected to the system and tests it to ensure it supports the
-      // desired session options.
+      // Checks to see if WebXR is available. If it is, checks if the desired
+      // session options are supported both right now and whenever a device is
+      // added or removed.
       function initXR() {
-        // Is WebXR available on this UA?
+        // If WebXR is available on this UA, check if any devices are connected
+        // that would support an immersive VR session.
         if (navigator.xr) {
-          // Register our click handler
+          // Register our click handler.
+          // In the other WebXR samples, most of the button details are handled
+          // by XRDeviceButton from js/webxr-button.js.
           xrButton.addEventListener('click', onButtonClicked);
 
-          // Register for device change events, this indicates that a device has
-          // been added or removed and that whether or not XR is supported has
-          // likely changed.
-          navigator.xr.addEventListener('devicechange',checkSupportedState);
+          // Register for device change events. This indicates that a device has
+          // been added or removed, which means that whether or not XR is
+          // supported has likely changed.
+          navigator.xr.addEventListener('devicechange', checkSupportedState);
 
           // Just in case the devicechange event has already fired, call it now
           // as well.
@@ -97,7 +99,9 @@
       // session already we'll request one, and if we do we'll end it.
       function onButtonClicked() {
         if (!xrSession) {
-          navigator.xr.requestSession('immersive-vr').then(onSessionStarted);
+          navigator.xr.requestSession('immersive-vr').then(
+            onSessionStarted,
+            onRequestSessionError);
         } else {
           xrSession.end();
         }
@@ -106,7 +110,6 @@
       // Called when we've successfully acquired a XRSession. In response we
       // will set up the necessary session state and kick off the frame loop.
       function onSessionStarted(session) {
-        session.mode = 'immersive-vr';
         xrSession = session;
         xrButton.innerHTML = 'Exit XR';
 
@@ -123,12 +126,13 @@
 
         // Use the new WebGL context to create a XRWebGLLayer and set it as the
         // sessions baseLayer. This allows any content rendered to the layer to
-        // be displayed on the XRDevice.
+        // be displayed on the XR Device.
         session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
 
-        // Get a frame of reference, which is required for querying poses. In
-        // this case an 'eye-level' frame of reference means that all poses will
-        // be relative to the location where the XRDevice was first detected.
+        // Get a reference space, which is required for querying poses. In
+        // this case a 'local' reference space means that all poses will be
+        // relative to the location of the user's head at the time this
+        // reference space is first created.
         session.requestReferenceSpace('local').then((refSpace) => {
           xrRefSpace = refSpace;
 
@@ -137,6 +141,12 @@
         });
       }
 
+      // Called when the requestSession promise was rejected with an exception.
+      function onRequestSessionError(ex) {
+        alert("Failed to start immersive VR session.");
+        console.error(ex.message);
+      }
+
       // Called when the user clicks the 'Exit XR' button. In response we end
       // the session.
       function onEndSession(session) {
@@ -160,22 +170,26 @@
       function onXRFrame(t, frame) {
         let session = frame.session;
 
-        // Inform the session that we're ready for the next frame.
+        // There are a few advantages to requesting the next frame immediately
+        // instead of at the end of this function.
+        // 1) It's less likely to forget to call requestAnimationFrame in cases
+        //    when onXRFrame has more complicated control flow.
+        // 2) If an exception is thrown later in this function, the render loop
+        //    will still continue.
         session.requestAnimationFrame(onXRFrame);
 
-        // Get the XRDevice pose relative to the Frame of Reference we created
+        // Get the viewer pose relative to the reference space we created
         // earlier.
         let pose = frame.getViewerPose(xrRefSpace);
 
         // Getting the pose may fail if, for example, tracking is lost. So we
         // have to check to make sure that we got a valid pose before attempting
-        // to render with it. If not in this case we'll just leave the
+        // to render with it. If not, in this case we'll just leave the
         // framebuffer cleared, so tracking loss means the scene will simply
-        // dissapear.
+        // disappear.
         if (pose) {
-
           // If we do have a valid pose, bind the WebGL layer's framebuffer,
-          // which is where any content to be displayed on the XRDevice must be
+          // which is where any content to be displayed on the XR Device must be
           // rendered.
           gl.bindFramebuffer(gl.FRAMEBUFFER, session.renderState.baseLayer.framebuffer);
 
@@ -195,7 +209,6 @@
             let viewport = session.renderState.baseLayer.getViewport(view);
             gl.viewport(viewport.x, viewport.y,
                         viewport.width, viewport.height);
-
             // Draw something.
           }*/
         }
diff --git a/tools/OWNERS b/tools/OWNERS
index 9796621..d771315 100644
--- a/tools/OWNERS
+++ b/tools/OWNERS
@@ -43,10 +43,6 @@
 
 per-file remove_stale_pyc_files.py=dtu@chromium.org
 
-per-file roll_angle.py=kbr@chromium.org
-per-file roll_angle.py=geofflang@chromium.org
-per-file roll_swiftshader.py=capn@chromium.org
-per-file roll_swiftshader.py=sugoi@chromium.org
 per-file roll_webgl_conformance.py=bajones@chromium.org
 per-file roll_webgl_conformance.py=kbr@chromium.org
 per-file roll_webgl_conformance.py=geofflang@chromium.org
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index b91d0e6..90ef0f71 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -12885,6 +12885,12 @@
   <description>Please enter the description of this user action.</description>
 </action>
 
+<action name="MobileToolbarIdentityDiscTap">
+  <owner>pavely@chromium.org</owner>
+  <owner>chrome-android-app@chromium.org</owner>
+  <description>User tapped on IdentityDisc button on toolbar.</description>
+</action>
+
 <action name="MobileToolbarNewTab">
   <obsolete>Deprecated as of 5/2015</obsolete>
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 0e0318a1..998be5c3 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -17222,6 +17222,7 @@
   <int value="567" label="RelaunchHeadsUpPeriod"/>
   <int value="568" label="StartupBrowserWindowLaunchSuppressed"/>
   <int value="569" label="DeviceWebUsbAllowDevicesForUrls"/>
+  <int value="570" label="UserFeedbackAllowed"/>
 </enum>
 
 <enum name="EnterprisePolicyInvalidations">
@@ -19717,6 +19718,7 @@
   <int value="1342" label="LOGINSCREENUI_CLOSE"/>
   <int value="1343" label="DECLARATIVENETREQUEST_GETMATCHEDRULES"/>
   <int value="1344" label="DECLARATIVENETREQUEST_SETACTIONCOUNTASBADGETEXT"/>
+  <int value="1345" label="BLUETOOTHPRIVATE_RECORDDEVICESELECTION"/>
 </enum>
 
 <enum name="ExtensionIconState">
@@ -33427,6 +33429,7 @@
   <int value="-1937077699" label="http-form-warning"/>
   <int value="-1934661084" label="ForceUnifiedConsentBump:disabled"/>
   <int value="-1933425042" label="OfflinePreviews:enabled"/>
+  <int value="-1932609987" label="CSSBackdropFilter:disabled"/>
   <int value="-1932379839"
       label="OmniboxUIExperimentHideSteadyStateUrlTrivialSubdomains:enabled"/>
   <int value="-1930720286" label="nacl-debug-mask"/>
@@ -34392,6 +34395,7 @@
   <int value="-557742250" label="ContentSuggestionsCategories:disabled"/>
   <int value="-548082154" label="protect-sync-credential:disabled"/>
   <int value="-547301855" label="SyncPseudoUSSSupervisedUsers:enabled"/>
+  <int value="-544629557" label="CSSBackdropFilter:enabled"/>
   <int value="-541611402" label="OfflinePagesPrefetching:enabled"/>
   <int value="-540150399" label="TapVisualizerApp:enabled"/>
   <int value="-539105193" label="SendTabToSelfBroadcast:disabled"/>
@@ -34770,6 +34774,7 @@
   <int value="5654819" label="CrostiniGpuSupport:disabled"/>
   <int value="7444737" label="NTPSuggestionsStandaloneUI:disabled"/>
   <int value="7533886" label="disable-offer-store-unmasked-wallet-cards"/>
+  <int value="8891567" label="CaptionSettings:enabled"/>
   <int value="10458238" label="disable-print-preview-simplify"/>
   <int value="11698808" label="enable-dom-distiller-button-animation"/>
   <int value="15614295" label="Portals:enabled"/>
@@ -35813,6 +35818,7 @@
   <int value="1597880096" label="FocusMode:disabled"/>
   <int value="1600850069" label="MobileIdentityConsistency:disabled"/>
   <int value="1600926040" label="TranslateCompactUI:enabled"/>
+  <int value="1603578716" label="CaptionSettings:disabled"/>
   <int value="1605398303" label="MacSystemMediaPermissionsInfoUI:enabled"/>
   <int value="1605611615" label="enable-webrtc-srtp-aes-gcm"/>
   <int value="1611522475" label="AutofillPrimaryInfoStyleExperiment:disabled"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 8e85ebc..bf3fd024 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -14331,6 +14331,20 @@
   </summary>
 </histogram>
 
+<histogram name="Bluetooth.ChromeOS.DeviceSelectionDuration" units="ms"
+    expires_after="2020-06-13">
+<!-- Name completed by histogram_suffixes name="BluetoothUISurfaces",
+     name="BluetoothPairingStates", and name="BluetoothTransportTypes" -->
+
+  <owner>hansberry@chromium.org</owner>
+  <owner>jlklein@chromium.org</owner>
+  <summary>
+    Records how long it takes for the user to select a device either after they
+    open the UI and Bluetooth is on, or after Bluetooth turns on while the UI is
+    open.
+  </summary>
+</histogram>
+
 <histogram name="Bluetooth.ChromeOS.Pairing.Duration.Failure" units="ms"
     expires_after="2020-06-06">
 <!-- Name completed by histogram_suffixes name="BluetoothTransportTypes" -->
@@ -39143,6 +39157,9 @@
 
 <histogram name="Extensions.HostedAppUnlimitedStorageUsage" units="KB"
     expires_after="2018-08-30">
+  <obsolete>
+    Expired 2018-08-30
+  </obsolete>
   <owner>rdevlin.cronin@chromium.org</owner>
   <summary>
     The usage (in kilobytes) of a hosted app with the unlimitedStorage api
@@ -149051,10 +149068,23 @@
   <affected-histogram name="Blink.VisibleLoadTime.LazyLoadImages.BelowTheFold"/>
 </histogram_suffixes>
 
+<histogram_suffixes name="BluetoothPairedStates" separator=".">
+  <suffix name="NotPaired"/>
+  <suffix name="Paired"/>
+  <affected-histogram
+      name="Bluetooth.ChromeOS.DeviceSelectionDuration.Settings"/>
+  <affected-histogram
+      name="Bluetooth.ChromeOS.DeviceSelectionDuration.SystemTray"/>
+</histogram_suffixes>
+
 <histogram_suffixes name="BluetoothTransportTypes" separator=".">
   <suffix name="BLE"/>
   <suffix name="Classic"/>
   <suffix name="Dual"/>
+  <affected-histogram
+      name="Bluetooth.ChromeOS.DeviceSelectionDuration.Settings.NotPaired"/>
+  <affected-histogram
+      name="Bluetooth.ChromeOS.DeviceSelectionDuration.SystemTray.NotPaired"/>
   <affected-histogram name="Bluetooth.ChromeOS.Pairing.Duration.Failure"/>
   <affected-histogram name="Bluetooth.ChromeOS.Pairing.Duration.Success"/>
   <affected-histogram name="Bluetooth.ChromeOS.Pairing.Result"/>
@@ -149063,6 +149093,7 @@
 <histogram_suffixes name="BluetoothUISurfaces" separator=".">
   <suffix name="Settings"/>
   <suffix name="SystemTray"/>
+  <affected-histogram name="Bluetooth.ChromeOS.DeviceSelectionDuration"/>
   <affected-histogram
       name="Bluetooth.ChromeOS.UserInitiatedReconnectionAttempt.Result"/>
 </histogram_suffixes>
diff --git a/tools/perf/benchmark.csv b/tools/perf/benchmark.csv
index e49b146..cad8206b 100644
--- a/tools/perf/benchmark.csv
+++ b/tools/perf/benchmark.csv
@@ -27,8 +27,8 @@
 load_library_perf_tests,"xhwang@chromium.org, crouleau@chromium.org",Internals>Media>Encrypted,,
 loading.desktop,"kouhei@chromium.org, ksakamoto@chromium.org",Blink>Loader,https://bit.ly/loading-benchmarks,"cache_temperature_cold,cache_temperature_warm,international,intl_ar_fa_he,intl_es_fr_pt_BR,intl_hi_ru,intl_ja_zh,intl_ko_th_vi,typical"
 loading.mobile,"kouhei@chromium.org, ksakamoto@chromium.org",Blink>Loader,https://bit.ly/loading-benchmarks,"cache_temperature_cold,cache_temperature_hot,cache_temperature_warm,easy_ttfmp,easy_tti,global,pwa,tough_ttfmp,tough_tti"
-media.desktop,dalecurtis@chromium.org,Internals>Media,,"aac,audio_only,audio_video,av1,background,beginning_to_end,busyjs,cns,h264,is_4k,is_50fps,mp3,mse,opus,pcm,seek,src,video_only,vorbis,vp8,vp9"
-media.mobile,dalecurtis@chromium.org,Internals>Media,,"aac,audio_only,audio_video,background,beginning_to_end,busyjs,cns,h264,mp3,mse,opus,pcm,seek,src,video_only,vorbis,vp9"
+media.desktop,dalecurtis@chromium.org,Internals>Media,,"aac,audio_only,audio_video,av1,background,beginning_to_end,busyjs,cns,h264,is_4k,is_50fps,mp3,mse,opus,seek,src,video_only,vorbis,vp8,vp9"
+media.mobile,dalecurtis@chromium.org,Internals>Media,,"aac,audio_only,audio_video,background,beginning_to_end,busyjs,cns,h264,mp3,mse,opus,seek,src,video_only,vorbis,vp9"
 media_perftests,"crouleau@chromium.org, dalecurtis@chromium.org",Internals>Media,,
 memory.desktop,erikchen@chromium.org,,,
 memory.top_10_mobile,perezju@chromium.org,,,
diff --git a/tools/perf/page_sets/media_cases.py b/tools/perf/page_sets/media_cases.py
index 5f213b8..6e39f55 100644
--- a/tools/perf/page_sets/media_cases.py
+++ b/tools/perf/page_sets/media_cases.py
@@ -224,83 +224,70 @@
 
 def _GetCrossPlatformMediaPages(page_set):
   return [
-      _BeginningToEndPlayPage(
-          url=_URL_BASE + 'video.html?src=crowd.ogg&type=audio',
-          page_set=page_set,
-          tags=['vorbis', 'audio_only']),
+      # 1080p 50fps crowd test cases. High non-60fps frame rate is good for
+      # finding rendering and efficiency regressions.
       _BeginningToEndPlayPage(
           url=_URL_BASE + 'video.html?src=crowd1080.webm',
           page_set=page_set,
           tags=['is_50fps', 'vp8', 'vorbis', 'audio_video']),
       _BeginningToEndPlayPage(
-          url=_URL_BASE + 'video.html?src=tulip2.ogg&type=audio',
-          page_set=page_set,
-          tags=['vorbis', 'audio_only']),
-      _BeginningToEndPlayPage(
-          url=_URL_BASE + 'video.html?src=tulip2.wav&type=audio',
-          page_set=page_set,
-          tags=['pcm', 'audio_only']),
-      _BeginningToEndPlayPage(
           url=_URL_BASE + 'video.html?src=crowd1080.mp4',
           page_set=page_set,
           tags=['is_50fps', 'h264', 'aac', 'audio_video']),
       _BeginningToEndPlayPage(
+          url=_URL_BASE + 'video.html?src=crowd1080_vp9.webm',
+          page_set=page_set,
+          tags=['is_50fps', 'vp9', 'video_only']),
+
+      # Audio only test cases. MP3 and OGG are important to test since they are
+      # unstructured containers and thus are prone to efficiency regressions.
+      _BeginningToEndPlayPage(
+          url=_URL_BASE + 'video.html?src=tulip2.ogg&type=audio',
+          page_set=page_set,
+          tags=['vorbis', 'audio_only']),
+      _BeginningToEndPlayPage(
           url=_URL_BASE + 'video.html?src=tulip2.mp3&type=audio',
           page_set=page_set,
           tags=['mp3', 'audio_only']),
       _BeginningToEndPlayPage(
+          url=_URL_BASE + 'video.html?src=tulip2.m4a&type=audio',
+          page_set=page_set,
+          tags=['aac', 'audio_only']),
+
+      # Baseline + busyjs test.
+      _BeginningToEndPlayPage(
           url=_URL_BASE + 'video.html?src=tulip2.mp4',
           page_set=page_set,
           tags=['h264', 'aac', 'audio_video']),
       _BeginningToEndPlayPage(
-          url=_URL_BASE + 'video.html?src=tulip2.m4a&type=audio',
+          url=_URL_BASE + 'video.html?src=tulip2.mp4&busyjs',
           page_set=page_set,
-          tags=['aac', 'audio_only']),
-      _BeginningToEndPlayPage(
-          url=_URL_BASE + 'video.html?src=garden2_10s.webm',
-          page_set=page_set,
-          tags=['is_4k', 'vp8', 'vorbis', 'audio_video']),
-      _BeginningToEndPlayPage(
-          url=_URL_BASE + 'video.html?src=garden2_10s.mp4',
-          page_set=page_set,
-          tags=['is_4k', 'h264', 'aac', 'audio_video']),
-      _BeginningToEndPlayPage(
-          url=_URL_BASE + 'video.html?src=tulip2.vp9.webm',
-          page_set=page_set,
-          tags=['vp9', 'opus', 'audio_video'],
-          traffic_setting=traffic_setting_module.WIFI),
+          tags=['h264', 'aac', 'audio_video', 'busyjs']),
+
+      # Baseline + WiFi test.
       _BeginningToEndPlayPage(
           url=_URL_BASE + 'video.html?src=tulip2.vp9.webm',
           page_set=page_set,
           tags=['vp9', 'opus', 'audio_video']),
       _BeginningToEndPlayPage(
-          url=_URL_BASE + 'video.html?src=crowd1080_vp9.webm',
+          url=_URL_BASE + 'video.html?src=tulip2.vp9.webm',
           page_set=page_set,
-          tags=['vp9', 'video_only']),
-      _BeginningToEndPlayPage(
-          url=_URL_BASE + 'video.html?src=crowd720_vp9.webm',
-          page_set=page_set,
-          tags=['vp9', 'video_only']),
-      _BeginningToEndPlayPage(
-          url=_URL_BASE + 'video.html?src=tulip2.mp4&busyjs',
-          page_set=page_set,
-          tags=['h264', 'aac', 'audio_video', 'busyjs']),
+          tags=['vp9', 'opus', 'audio_video'],
+          traffic_setting=traffic_setting_module.WIFI),
+
+      # Seek tests in MP3 and OGG are important since they don't have seek
+      # indices and thus show off efficiency regressions easily.
       _SeekPage(
           url=_URL_BASE + 'video.html?src=tulip2.ogg&type=audio&seek',
           page_set=page_set,
           tags=['vorbis', 'audio_only']),
       _SeekPage(
-          url=_URL_BASE + 'video.html?src=tulip2.wav&type=audio&seek',
-          page_set=page_set,
-          tags=['pcm', 'audio_only']),
-      _SeekPage(
           url=_URL_BASE + 'video.html?src=tulip2.mp3&type=audio&seek',
           page_set=page_set,
           tags=['mp3', 'audio_only']),
-      _SeekPage(
-          url=_URL_BASE + 'video.html?src=tulip2.mp4&seek',
-          page_set=page_set,
-          tags=['h264', 'aac', 'audio_video']),
+
+      # High resolution seek test cases which will exaggerate any decoding
+      # efficiency or buffering regressions.
       _SeekPage(
           url=_URL_BASE + 'video.html?src=garden2_10s.webm&seek',
           page_set=page_set,
@@ -310,32 +297,27 @@
           page_set=page_set,
           tags=['is_4k', 'h264', 'aac', 'audio_video']),
       _SeekPage(
-          url=_URL_BASE + 'video.html?src=tulip2.vp9.webm&seek',
-          page_set=page_set,
-          tags=['vp9', 'opus', 'audio_video']),
-      _SeekPage(
-          url=_URL_BASE + 'video.html?src=crowd1080_vp9.webm&seek',
-          page_set=page_set,
-          tags=['vp9', 'video_only', 'seek']),
-      _SeekPage(
           url=(_URL_BASE + 'video.html?src='
                'smpte_3840x2160_60fps_vp9.webm&seek'),
           page_set=page_set,
           tags=['is_4k', 'vp9', 'video_only'],
           action_timeout_in_seconds=120),
+
+      # Basic test that ensures background playback works properly.
       _BackgroundPlaybackPage(
           url=_URL_BASE + 'video.html?src=tulip2.vp9.webm&background',
           page_set=page_set,
           tags=['vp9', 'opus', 'audio_video']),
+
+      # Basic MSE test pages for common configurations.
       _MSEPage(
           url=_URL_BASE + 'mse.html?media=aac_audio.mp4,h264_video.mp4',
           page_set=page_set,
           tags=['h264', 'aac', 'audio_video']),
       _MSEPage(
-          url=(_URL_BASE + 'mse.html?'
-               'media=aac_audio.mp4,h264_video.mp4&waitForPageLoaded=true'),
+          url=_URL_BASE + 'mse.html?media=tulip2.vp9.webm',
           page_set=page_set,
-          tags=['h264', 'aac', 'audio_video']),
+          tags=['vp9', 'opus', 'audio_video']),
       _MSEPage(
           url=_URL_BASE + 'mse.html?media=aac_audio.mp4',
           page_set=page_set,
diff --git a/tools/perf/page_sets/media_cases/mse.js b/tools/perf/page_sets/media_cases/mse.js
index 6a097f8..794315f5 100644
--- a/tools/perf/page_sets/media_cases/mse.js
+++ b/tools/perf/page_sets/media_cases/mse.js
@@ -13,6 +13,7 @@
     "aac_audio.mp4": "audio/mp4; codecs=\"mp4a.40.2\"",
     "h264_video.mp4": "video/mp4; codecs=\"avc1.640028\"",
     "tulip0.av1.mp4": "video/mp4; codecs=\"av01.0.05M.08\"",
+    "tulip2.vp9.webm": "video/webm; codecs=\"opus,vp9\"",
   };
   const testParams = {}
 
diff --git a/tools/roll_swiftshader.py b/tools/roll_swiftshader.py
deleted file mode 100755
index 785e9c28..0000000
--- a/tools/roll_swiftshader.py
+++ /dev/null
@@ -1,405 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2015 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import argparse
-import collections
-import logging
-import os
-import re
-import subprocess
-import sys
-import time
-
-extra_cq_trybots = [
-  {
-    "mastername": "luci.chromium.try",
-    "buildernames": ["win_optional_gpu_tests_rel"]
-  },
-  {
-    "mastername": "luci.chromium.try",
-    "buildernames": ["mac_optional_gpu_tests_rel"]
-  },
-  {
-    "mastername": "luci.chromium.try",
-    "buildernames": ["linux_optional_gpu_tests_rel"]
-  },
-  {
-    "mastername": "luci.chromium.try",
-    "buildernames": ["android_optional_gpu_tests_rel"]
-  }
-]
-
-SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
-SRC_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, os.pardir))
-sys.path.insert(0, os.path.join(SRC_DIR, 'build'))
-import find_depot_tools
-find_depot_tools.add_depot_tools_to_path()
-import roll_dep_svn
-from third_party import upload
-
-# Avoid depot_tools/third_party/upload.py print verbose messages.
-upload.verbosity = 0  # Errors only.
-
-CHROMIUM_GIT_URL = 'https://chromium.googlesource.com/chromium/src.git'
-CL_ISSUE_RE = re.compile('^Issue number: ([0-9]+) \((.*)\)$')
-REVIEW_URL_RE = re.compile('^https?://(.*)/(.*)')
-ROLL_BRANCH_NAME = 'special_swiftshader_roll_branch'
-TRYJOB_STATUS_SLEEP_SECONDS = 30
-
-# Use a shell for subcommands on Windows to get a PATH search.
-IS_WIN = sys.platform.startswith('win')
-SWIFTSHADER_PATH = os.path.join('third_party', 'swiftshader')
-
-CommitInfo = collections.namedtuple('CommitInfo', ['git_commit',
-                                                   'git_repo_url'])
-CLInfo = collections.namedtuple('CLInfo', ['issue', 'url', 'review_server'])
-
-def _VarLookup(local_scope):
-  return lambda var_name: local_scope['vars'][var_name]
-
-def _PosixPath(path):
-  """Convert a possibly-Windows path to a posix-style path."""
-  (_, path) = os.path.splitdrive(path)
-  return path.replace(os.sep, '/')
-
-def _ParseGitCommitHash(description):
-  for line in description.splitlines():
-    if line.startswith('commit '):
-      return line.split()[1]
-  logging.error('Failed to parse git commit id from:\n%s\n', description)
-  sys.exit(-1)
-  return None
-
-
-def _ParseDepsFile(filename):
-  with open(filename, 'rb') as f:
-    deps_content = f.read()
-  return _ParseDepsDict(deps_content)
-
-
-def _ParseDepsDict(deps_content):
-  local_scope = {}
-  global_scope = {
-    'Var': _VarLookup(local_scope),
-    'deps_os': {},
-  }
-  exec(deps_content, global_scope, local_scope)
-  return local_scope
-
-
-def _GenerateCLDescriptionCommand(swiftshader_current, swiftshader_new, bugs,
-                                  tbr):
-  def GetChangeString(current_hash, new_hash):
-    return '%s..%s' % (current_hash[0:7], new_hash[0:7]);
-
-  def GetChangeLogURL(git_repo_url, change_string):
-    return '%s/+log/%s' % (git_repo_url, change_string)
-
-  def GetBugString(bugs):
-    bug_str = 'BUG='
-    for bug in bugs:
-      bug_str += bug + ','
-    return bug_str.rstrip(',')
-
-  if swiftshader_current.git_commit != swiftshader_new.git_commit:
-    change_str = GetChangeString(swiftshader_current.git_commit,
-                                 swiftshader_new.git_commit)
-    changelog_url = GetChangeLogURL(swiftshader_current.git_repo_url,
-                                    change_str)
-
-  def GetExtraCQTrybotString():
-    s = ''
-    for t in extra_cq_trybots:
-      if s:
-        s += ';'
-      s += t['mastername'] + ':' + ','.join(t['buildernames'])
-    return s
-
-  def GetTBRString(tbr):
-    if not tbr:
-      return ''
-    return 'TBR=' + tbr
-
-  extra_trybot_args = []
-  if extra_cq_trybots:
-    extra_trybot_string = GetExtraCQTrybotString()
-    extra_trybot_args = ['-m', 'CQ_INCLUDE_TRYBOTS=' + extra_trybot_string]
-
-  return [
-    '-m', 'Roll SwiftShader ' + change_str,
-    '-m', '%s' % changelog_url,
-    '-m', GetBugString(bugs),
-    '-m', GetTBRString(tbr),
-    '-m', 'TEST=bots',
-  ] + extra_trybot_args
-
-
-class AutoRoller(object):
-  def __init__(self, chromium_src):
-    self._chromium_src = chromium_src
-
-  def _RunCommand(self, command, working_dir=None, ignore_exit_code=False,
-                  extra_env=None):
-    """Runs a command and returns the stdout from that command.
-
-    If the command fails (exit code != 0), the function will exit the process.
-    """
-    working_dir = working_dir or self._chromium_src
-    logging.debug('cmd: %s cwd: %s', ' '.join(command), working_dir)
-    env = os.environ.copy()
-    if extra_env:
-      logging.debug('extra env: %s', extra_env)
-      env.update(extra_env)
-    p = subprocess.Popen(command, stdout=subprocess.PIPE,
-                         stderr=subprocess.PIPE, shell=IS_WIN, env=env,
-                         cwd=working_dir, universal_newlines=True)
-    output = p.stdout.read()
-    p.wait()
-    p.stdout.close()
-    p.stderr.close()
-
-    if not ignore_exit_code and p.returncode != 0:
-      logging.error('Command failed: %s\n%s', str(command), output)
-      sys.exit(p.returncode)
-    return output
-
-  def _GetCommitInfo(self, path_below_src, git_hash=None, git_repo_url=None):
-    working_dir = os.path.join(self._chromium_src, path_below_src)
-    self._RunCommand(['git', 'fetch', 'origin'], working_dir=working_dir)
-    revision_range = git_hash or 'origin'
-    ret = self._RunCommand(
-        ['git', '--no-pager', 'log', revision_range,
-         '--no-abbrev-commit', '--pretty=full', '-1'],
-        working_dir=working_dir)
-    return CommitInfo(_ParseGitCommitHash(ret), git_repo_url)
-
-  def _GetDepsCommitInfo(self, deps_dict, path_below_src):
-    entry = deps_dict['deps'][_PosixPath('src/%s' % path_below_src)]
-    at_index = entry.find('@')
-    git_repo_url = entry[:at_index]
-    git_hash = entry[at_index + 1:]
-    return self._GetCommitInfo(path_below_src, git_hash, git_repo_url)
-
-  def _GetCLInfo(self):
-    cl_output = self._RunCommand(['git', 'cl', 'issue'])
-    m = CL_ISSUE_RE.match(cl_output.strip())
-    if not m:
-      logging.error('Cannot find any CL info. Output was:\n%s', cl_output)
-      sys.exit(-1)
-    issue_number = int(m.group(1))
-    url = m.group(2)
-
-    # Parse the codereview host from the URL.
-    m = REVIEW_URL_RE.match(url)
-    if not m:
-      logging.error('Cannot parse codereview host from URL: %s', url)
-      sys.exit(-1)
-    review_server = m.group(1)
-    return CLInfo(issue_number, url, review_server)
-
-  def _GetCurrentBranchName(self):
-    return self._RunCommand(
-        ['git', 'rev-parse', '--abbrev-ref', 'HEAD']).splitlines()[0]
-
-  def _IsTreeClean(self):
-    lines = self._RunCommand(
-      ['git', 'status', '--porcelain', '-uno']).splitlines()
-    if len(lines) == 0:
-      return True
-
-    logging.debug('Dirty/unversioned files:\n%s', '\n'.join(lines))
-    return False
-
-  def _GetBugList(self, path_below_src, swiftshader_current, swiftshader_new):
-    working_dir = os.path.join(self._chromium_src, path_below_src)
-    lines = self._RunCommand(
-        ['git','log',
-            '%s..%s' % (swiftshader_current.git_commit,
-                        swiftshader_new.git_commit)],
-        working_dir=working_dir).split('\n')
-    ignored_projects = set(['swiftshader'])
-    bugs = set()
-    for line in lines:
-      line = line.strip()
-      bug_prefix = 'BUG='
-      if line.startswith(bug_prefix):
-        bugs_strings = line[len(bug_prefix):].split(',')
-        for bug_string in bugs_strings:
-          ignore_bug = False
-          for ignored_project in ignored_projects:
-            if bug_string.startswith(ignored_project + ':'):
-              ignore_bug = True
-              break
-          if not ignore_bug:
-            bugs.add(bug_string)
-    return bugs
-
-  def _UpdateReadmeFile(self, readme_path, new_revision):
-    readme = open(os.path.join(self._chromium_src, readme_path), 'r+')
-    txt = readme.read()
-    m = re.sub(re.compile('.*^Revision\: ([0-9]*).*', re.MULTILINE),
-        ('Revision: %s' % new_revision), txt)
-    readme.seek(0)
-    readme.write(m)
-    readme.truncate()
-
-  def _TriggerExtraTrybots(self, trybots):
-    for trybot in trybots:
-      for builder in trybot['buildernames']:
-        self._RunCommand([
-            'git', 'cl', 'try',
-            '-m', trybot['mastername'],
-            '-b', builder])
-
-  def PrepareRoll(self, ignore_checks, tbr, should_commit):
-    # TODO(kjellander): use os.path.normcase, os.path.join etc for all paths for
-    # cross platform compatibility.
-
-    if not ignore_checks:
-      if self._GetCurrentBranchName() != 'master':
-        logging.error('Please checkout the master branch.')
-        return -1
-      if not self._IsTreeClean():
-        logging.error('Please make sure you don\'t have any modified files.')
-        return -1
-
-    # Always clean up any previous roll.
-    self.Abort()
-
-    logging.debug('Pulling latest changes')
-    if not ignore_checks:
-      self._RunCommand(['git', 'pull'])
-
-    self._RunCommand(['git', 'checkout', '-b', ROLL_BRANCH_NAME])
-
-    # Modify Chromium's DEPS file.
-
-    # Parse current hashes.
-    deps_filename = os.path.join(self._chromium_src, 'DEPS')
-    deps = _ParseDepsFile(deps_filename)
-    swiftshader_current = self._GetDepsCommitInfo(deps, SWIFTSHADER_PATH)
-
-    # Find ToT revisions.
-    swiftshader_latest = self._GetCommitInfo(SWIFTSHADER_PATH)
-
-    if IS_WIN:
-      # Make sure the roll script doesn't use windows line endings
-      self._RunCommand(['git', 'config', 'core.autocrlf', 'true'])
-
-    self._UpdateDep(deps_filename, SWIFTSHADER_PATH, swiftshader_latest)
-
-    if self._IsTreeClean():
-      logging.debug('Tree is clean - no changes detected.')
-      self._DeleteRollBranch()
-    else:
-      bugs = self._GetBugList(SWIFTSHADER_PATH, swiftshader_current,
-                              swiftshader_latest)
-      description = _GenerateCLDescriptionCommand(
-          swiftshader_current, swiftshader_latest, bugs, tbr)
-      logging.debug('Committing changes locally.')
-      self._RunCommand(['git', 'add', '--update', '.'])
-      self._RunCommand(['git', 'commit'] + description)
-      logging.debug('Uploading changes...')
-      self._RunCommand(['git', 'cl', 'upload'],
-                       extra_env={'EDITOR': 'true'})
-
-      # Kick off tryjobs.
-      base_try_cmd = ['git', 'cl', 'try']
-      self._RunCommand(base_try_cmd)
-
-      # Mark the CL to be committed if requested
-      if should_commit:
-        self._RunCommand(['git', 'cl', 'set-commit'])
-
-      cl_info = self._GetCLInfo()
-      print 'Issue: %d URL: %s' % (cl_info.issue, cl_info.url)
-
-    # Checkout master again.
-    self._RunCommand(['git', 'checkout', 'master'])
-    print 'Roll branch left as ' + ROLL_BRANCH_NAME
-    return 0
-
-  def _UpdateDep(self, deps_filename, dep_relative_to_src, commit_info):
-    dep_name = _PosixPath(os.path.join('src', dep_relative_to_src))
-
-    # roll_dep_svn.py relies on cwd being the Chromium checkout, so let's
-    # temporarily change the working directory and then change back.
-    cwd = os.getcwd()
-    os.chdir(os.path.dirname(deps_filename))
-    roll_dep_svn.update_deps(deps_filename, dep_relative_to_src, dep_name,
-                             commit_info.git_commit, '')
-    os.chdir(cwd)
-
-  def _DeleteRollBranch(self):
-    self._RunCommand(['git', 'checkout', 'master'])
-    self._RunCommand(['git', 'branch', '-D', ROLL_BRANCH_NAME])
-    logging.debug('Deleted the local roll branch (%s)', ROLL_BRANCH_NAME)
-
-
-  def _GetBranches(self):
-    """Returns a tuple of active,branches.
-
-    The 'active' is the name of the currently active branch and 'branches' is a
-    list of all branches.
-    """
-    lines = self._RunCommand(['git', 'branch']).split('\n')
-    branches = []
-    active = ''
-    for l in lines:
-      if '*' in l:
-        # The assumption is that the first char will always be the '*'.
-        active = l[1:].strip()
-        branches.append(active)
-      else:
-        b = l.strip()
-        if b:
-          branches.append(b)
-    return (active, branches)
-
-  def Abort(self):
-    active_branch, branches = self._GetBranches()
-    if active_branch == ROLL_BRANCH_NAME:
-      active_branch = 'master'
-    if ROLL_BRANCH_NAME in branches:
-      print 'Aborting pending roll.'
-      self._RunCommand(['git', 'checkout', ROLL_BRANCH_NAME])
-      # Ignore an error here in case an issue wasn't created for some reason.
-      self._RunCommand(['git', 'cl', 'set_close'], ignore_exit_code=True)
-      self._RunCommand(['git', 'checkout', active_branch])
-      self._RunCommand(['git', 'branch', '-D', ROLL_BRANCH_NAME])
-    return 0
-
-
-def main():
-  parser = argparse.ArgumentParser(
-      description='Auto-generates a CL containing a SwiftShader roll.')
-  parser.add_argument('--abort',
-    help=('Aborts a previously prepared roll. '
-          'Closes any associated issues and deletes the roll branches'),
-    action='store_true')
-  parser.add_argument('--ignore-checks', action='store_true', default=False,
-      help=('Skips checks for being on the master branch, dirty workspaces and '
-            'the updating of the checkout. Will still delete and create local '
-            'Git branches.'))
-  parser.add_argument('--tbr', help='Add a TBR to the commit message.')
-  parser.add_argument('--commit', action='store_true', default=False,
-      help='Submit the roll to the CQ after uploading.')
-  parser.add_argument('-v', '--verbose', action='store_true', default=False,
-      help='Be extra verbose in printing of log messages.')
-  args = parser.parse_args()
-
-  if args.verbose:
-    logging.basicConfig(level=logging.DEBUG)
-  else:
-    logging.basicConfig(level=logging.ERROR)
-
-  autoroller = AutoRoller(SRC_DIR)
-  if args.abort:
-    return autoroller.Abort()
-  else:
-    return autoroller.PrepareRoll(args.ignore_checks, args.tbr, args.commit)
-
-if __name__ == '__main__':
-  sys.exit(main())
diff --git a/ui/android/BUILD.gn b/ui/android/BUILD.gn
index f51fbc2..7bedfd47 100644
--- a/ui/android/BUILD.gn
+++ b/ui/android/BUILD.gn
@@ -379,6 +379,7 @@
     "junit/src/org/chromium/ui/modaldialog/ModalDialogManagerTest.java",
     "junit/src/org/chromium/ui/modelutil/LazyConstructionPropertyMcpTest.java",
     "junit/src/org/chromium/ui/modelutil/ListModelBaseTest.java",
+    "junit/src/org/chromium/ui/modelutil/ModelListAdapterTest.java",
     "junit/src/org/chromium/ui/modelutil/PropertyListModelTest.java",
     "junit/src/org/chromium/ui/modelutil/PropertyModelTest.java",
     "junit/src/org/chromium/ui/modelutil/SimpleListObservableTest.java",
diff --git a/ui/android/java/res/values/ids.xml b/ui/android/java/res/values/ids.xml
index efb2f14..4a6b138e 100644
--- a/ui/android/java/res/values/ids.xml
+++ b/ui/android/java/res/values/ids.xml
@@ -6,4 +6,5 @@
     <!-- Model Utils -->
     <item type="id" name="view_model" />
     <item type="id" name="view_type" />
+    <item type="id" name="view_mcp" />
 </resources>
\ No newline at end of file
diff --git a/ui/android/java/src/org/chromium/ui/modelutil/ModelListAdapter.java b/ui/android/java/src/org/chromium/ui/modelutil/ModelListAdapter.java
index c45003d4..13eab2d 100644
--- a/ui/android/java/src/org/chromium/ui/modelutil/ModelListAdapter.java
+++ b/ui/android/java/src/org/chromium/ui/modelutil/ModelListAdapter.java
@@ -4,20 +4,18 @@
 
 package org.chromium.ui.modelutil;
 
-import android.content.Context;
+import android.support.annotation.Nullable;
 import android.util.Pair;
 import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
 
+import org.chromium.base.VisibleForTesting;
 import org.chromium.ui.R;
-import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey;
-import org.chromium.ui.modelutil.PropertyModel.WritableFloatPropertyKey;
-import org.chromium.ui.modelutil.PropertyModel.WritableIntPropertyKey;
-import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -35,15 +33,10 @@
         T buildView();
     }
 
-    private final Context mContext;
     private final List<Pair<Integer, PropertyModel>> mModelList = new ArrayList<>();
     private final SparseArray<Pair<ViewBuilder, PropertyModelChangeProcessor.ViewBinder>>
             mViewBuilderMap = new SparseArray<>();
 
-    public ModelListAdapter(Context context) {
-        mContext = context;
-    }
-
     /**
      * Update the visible models (list items).
      */
@@ -94,6 +87,15 @@
     @SuppressWarnings("unchecked")
     @Override
     public View getView(int position, View convertView, ViewGroup parent) {
+        //  1. Destroy the old PropertyModelChangeProcessor if it exists.
+        if (convertView != null && convertView.getTag(R.id.view_mcp) != null) {
+            PropertyModelChangeProcessor propertyModelChangeProcessor =
+                    (PropertyModelChangeProcessor) convertView.getTag(R.id.view_mcp);
+            propertyModelChangeProcessor.destroy();
+        }
+
+        // 2. Build a new view if needed. Otherwise, fetch the old model from the convertView.
+        PropertyModel oldModel = null;
         if (convertView == null || convertView.getTag(R.id.view_type) == null
                 || (int) convertView.getTag(R.id.view_type) != getItemViewType(position)) {
             int modelTypeId = mModelList.get(position).first;
@@ -104,45 +106,62 @@
             // and identify what type the view is. This should allow lists that aren't necessarily
             // recycler views to work correctly with heterogeneous lists.
             convertView.setTag(R.id.view_type, modelTypeId);
+        } else {
+            oldModel = (PropertyModel) convertView.getTag(R.id.view_model);
         }
 
         PropertyModel model = mModelList.get(position).second;
-        PropertyModel viewModel =
-                getOrCreateModelFromExisting(convertView, mModelList.get(position));
-        for (PropertyKey key : model.getAllSetProperties()) {
-            if (key instanceof WritableIntPropertyKey) {
-                WritableIntPropertyKey intKey = (WritableIntPropertyKey) key;
-                viewModel.set(intKey, model.get(intKey));
-            } else if (key instanceof WritableBooleanPropertyKey) {
-                WritableBooleanPropertyKey booleanKey = (WritableBooleanPropertyKey) key;
-                viewModel.set(booleanKey, model.get(booleanKey));
-            } else if (key instanceof WritableFloatPropertyKey) {
-                WritableFloatPropertyKey floatKey = (WritableFloatPropertyKey) key;
-                viewModel.set(floatKey, model.get(floatKey));
-            } else if (key instanceof WritableObjectPropertyKey<?>) {
-                @SuppressWarnings({"unchecked", "rawtypes"})
-                WritableObjectPropertyKey objectKey = (WritableObjectPropertyKey) key;
-                viewModel.set(objectKey, model.get(objectKey));
-            } else {
-                assert false : "Unexpected key received";
-            }
-        }
+        PropertyModelChangeProcessor.ViewBinder binder =
+                mViewBuilderMap.get(mModelList.get(position).first).second;
+
+        // 3. Attach a PropertyModelChangeProcessor and PropertyModel to the view (for #1/2 above
+        //    when re-using a view).
+        convertView.setTag(R.id.view_mcp,
+                PropertyModelChangeProcessor.create(
+                        model, convertView, binder, /* performInitialBind = */ false));
+        convertView.setTag(R.id.view_model, model);
+
+        // 4. Bind properties to the convertView.
+        bindNewModel(model, oldModel, convertView, binder);
+
         // TODO(tedchoc): Investigate whether this is still needed.
         convertView.jumpDrawablesToCurrentState();
 
         return convertView;
     }
 
-    @SuppressWarnings("unchecked")
-    private PropertyModel getOrCreateModelFromExisting(
-            View view, Pair<Integer, PropertyModel> item) {
-        PropertyModel model = (PropertyModel) view.getTag(R.id.view_model);
-        if (model == null) {
-            model = new PropertyModel(item.second.getAllProperties());
-            PropertyModelChangeProcessor.create(
-                    model, view, mViewBuilderMap.get(item.first).second);
-            view.setTag(R.id.view_model, model);
+    /**
+     * Binds all set properties to the view. If oldModel is not null, binds properties that were
+     * previously set in the oldModel but are not set in the new model.
+     *
+     * @param newModel The new model to bind to {@code view}.
+     * @param oldModel The old model previously bound to {@code view}. May be null.
+     * @param view The view to bind.
+     * @param binder The ViewBinder that will bind model properties to {@code view}.
+     */
+    @VisibleForTesting
+    static void bindNewModel(PropertyModel newModel, @Nullable PropertyModel oldModel, View view,
+            PropertyModelChangeProcessor.ViewBinder binder) {
+        Collection<PropertyKey> setProperties = newModel.getAllSetProperties();
+        for (PropertyKey key : newModel.getAllProperties()) {
+            if (oldModel != null) {
+                // Skip binding properties that haven't changed.
+                if (newModel.compareValue(oldModel, key)) {
+                    continue;
+                } else {
+                    // When rebinding a view, if the value has changed it should be writable as
+                    // views reasonably may not expect read-only keys not to change or be rebound.
+                    assert key instanceof PropertyModel.WritableObjectPropertyKey
+                            || key instanceof PropertyModel.WritableIntPropertyKey
+                            || key instanceof PropertyModel.WritableBooleanPropertyKey
+                            || key instanceof PropertyModel.WritableFloatPropertyKey;
+                }
+            } else if (!setProperties.contains(key)) {
+                // If there is no previous model, skip binding properties that haven't been set.
+                continue;
+            }
+
+            binder.bind(newModel, view, key);
         }
-        return model;
     }
 }
diff --git a/ui/android/java/src/org/chromium/ui/modelutil/PropertyModel.java b/ui/android/java/src/org/chromium/ui/modelutil/PropertyModel.java
index 093d3b17..4496aaf 100644
--- a/ui/android/java/src/org/chromium/ui/modelutil/PropertyModel.java
+++ b/ui/android/java/src/org/chromium/ui/modelutil/PropertyModel.java
@@ -368,6 +368,26 @@
     }
 
     /**
+     * Determines whether the value for the provided key is the same in this model and a different
+     * model.
+     * @param otherModel The other {@link PropertyModel} to check.
+     * @param key The {@link PropertyKey} to check.
+     * @return Whether this model and {@code otherModel} have the same value set for {@code key}.
+     */
+    public boolean compareValue(PropertyModel otherModel, PropertyKey key) {
+        validateKey(key);
+        otherModel.validateKey(key);
+        if (!mData.containsKey(key) || !otherModel.mData.containsKey(key)) return false;
+
+        if (key instanceof WritableObjectPropertyKey
+                && ((WritableObjectPropertyKey) key).mSkipEquality) {
+            return false;
+        }
+
+        return ObjectsCompat.equals(mData.get(key), otherModel.mData.get(key));
+    }
+
+    /**
      * Allows constructing a new {@link PropertyModel} with read-only properties.
      */
     public static class Builder {
@@ -481,6 +501,12 @@
         public String toString() {
             return value + " in " + super.toString();
         }
+
+        @Override
+        public boolean equals(Object other) {
+            return other != null && other instanceof FloatContainer
+                    && ((FloatContainer) other).value == value;
+        }
     }
 
     private static class IntContainer extends ValueContainer {
@@ -490,6 +516,12 @@
         public String toString() {
             return value + " in " + super.toString();
         }
+
+        @Override
+        public boolean equals(Object other) {
+            return other != null && other instanceof IntContainer
+                    && ((IntContainer) other).value == value;
+        }
     }
 
     private static class BooleanContainer extends ValueContainer {
@@ -499,6 +531,12 @@
         public String toString() {
             return value + " in " + super.toString();
         }
+
+        @Override
+        public boolean equals(Object other) {
+            return other != null && other instanceof BooleanContainer
+                    && ((BooleanContainer) other).value == value;
+        }
     }
 
     private static class ObjectContainer<T> extends ValueContainer {
@@ -508,5 +546,11 @@
         public String toString() {
             return value + " in " + super.toString();
         }
+
+        @Override
+        public boolean equals(Object other) {
+            return other != null && other instanceof ObjectContainer
+                    && ObjectsCompat.equals(((ObjectContainer) other).value, value);
+        }
     }
 }
diff --git a/ui/android/java/src/org/chromium/ui/modelutil/PropertyModelChangeProcessor.java b/ui/android/java/src/org/chromium/ui/modelutil/PropertyModelChangeProcessor.java
index 69ac149f..623bc1a 100644
--- a/ui/android/java/src/org/chromium/ui/modelutil/PropertyModelChangeProcessor.java
+++ b/ui/android/java/src/org/chromium/ui/modelutil/PropertyModelChangeProcessor.java
@@ -34,26 +34,45 @@
      * @param model The model containing the data to be bound.
      * @param view The view to which data will be bound.
      * @param viewBinder A class that binds the model to the view.
+     * @param performInitialBind Whether all set model properties should be immediately bound.
      */
-    private PropertyModelChangeProcessor(M model, V view, ViewBinder<M, V, P> viewBinder) {
+    private PropertyModelChangeProcessor(
+            M model, V view, ViewBinder<M, V, P> viewBinder, boolean performInitialBind) {
         mModel = model;
         mView = view;
         mViewBinder = viewBinder;
-        for (P property : model.getAllSetProperties()) {
-            onPropertyChanged(model, property);
+
+        if (performInitialBind) {
+            for (P property : model.getAllSetProperties()) {
+                onPropertyChanged(model, property);
+            }
         }
+
         model.addObserver(mPropertyObserver);
     }
 
     /**
+     * Creates a new PropertyModelChangeProcessor observing the given {@code model}. All set model
+     * properties will be bound.
+     * @param model The model containing the data to be bound.
+     * @param view The view to which data will be bound.
+     * @param viewBinder A class that binds the model to the view.
+     */
+    public static <M extends PropertyObservable<P>, V, P> PropertyModelChangeProcessor<M, V, P>
+    create(M model, V view, ViewBinder<M, V, P> viewBinder) {
+        return create(model, view, viewBinder, true);
+    }
+
+    /**
      * Creates a new PropertyModelChangeProcessor observing the given {@code model}.
      * @param model The model containing the data to be bound.
      * @param view The view to which data will be bound.
      * @param viewBinder A class that binds the model to the view.
+     * @param performInitialBind Whether all set model properties should be immediately bound.
      */
     public static <M extends PropertyObservable<P>, V, P> PropertyModelChangeProcessor<M, V, P>
-    create(M model, V view, ViewBinder<M, V, P> viewBinder) {
-        return new PropertyModelChangeProcessor<>(model, view, viewBinder);
+    create(M model, V view, ViewBinder<M, V, P> viewBinder, boolean performInitialBind) {
+        return new PropertyModelChangeProcessor<>(model, view, viewBinder, performInitialBind);
     }
 
     /**
diff --git a/ui/android/junit/src/org/chromium/ui/modelutil/ModelListAdapterTest.java b/ui/android/junit/src/org/chromium/ui/modelutil/ModelListAdapterTest.java
new file mode 100644
index 0000000..f57326f
--- /dev/null
+++ b/ui/android/junit/src/org/chromium/ui/modelutil/ModelListAdapterTest.java
@@ -0,0 +1,338 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.ui.modelutil;
+
+import android.text.TextUtils;
+import android.util.Pair;
+import android.view.View;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.ui.R;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Tests to ensure/validate ModelListAdapter behavior.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class ModelListAdapterTest {
+    private static final Integer VIEW_TYPE_1 = 0;
+    private static final Integer VIEW_TYPE_2 = 1;
+
+    private static final PropertyModel.WritableBooleanPropertyKey BOOLEAN_PROPERTY =
+            new PropertyModel.WritableBooleanPropertyKey();
+    private static final PropertyModel.WritableFloatPropertyKey FLOAT_PROPERTY =
+            new PropertyModel.WritableFloatPropertyKey();
+    private static final PropertyModel.WritableIntPropertyKey INT_PROPERTY =
+            new PropertyModel.WritableIntPropertyKey();
+    private static final PropertyModel.WritableObjectPropertyKey OBJECT_PROPERTY =
+            new PropertyModel.WritableObjectPropertyKey();
+    private static final PropertyModel.ReadableBooleanPropertyKey READONLY_BOOLEAN_PROPERTY =
+            new PropertyModel.ReadableBooleanPropertyKey();
+
+    private class TestViewBinder implements PropertyModelChangeProcessor.ViewBinder {
+        @Override
+        public void bind(Object model, Object view, Object propertyKey) {
+            if (propertyKey.equals(BOOLEAN_PROPERTY)) {
+                mBindBooleanCallbackHelper.notifyCalled();
+            } else if (propertyKey.equals(FLOAT_PROPERTY)) {
+                mBindFloatCallbackHelper.notifyCalled();
+            } else if (propertyKey.equals(INT_PROPERTY)) {
+                mBindIntCallbackHelper.notifyCalled();
+            } else if (propertyKey.equals(OBJECT_PROPERTY)) {
+                mBindObjectCallbackHelper.notifyCalled();
+            }
+        }
+    }
+
+    private class TestViewBuilder implements ModelListAdapter.ViewBuilder<View> {
+        @Override
+        public View buildView() {
+            return new View(RuntimeEnvironment.application);
+        }
+    }
+
+    private class TestObject {
+        private String mId;
+        public TestObject(String id) {
+            mId = id;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            return other == this
+                    || (other != null && other instanceof TestObject
+                            && TextUtils.equals(((TestObject) other).mId, mId));
+        }
+    }
+
+    private PropertyModel mModel;
+    private ModelListAdapter mModelListAdapter;
+    private final CallbackHelper mBindBooleanCallbackHelper = new CallbackHelper();
+    private final CallbackHelper mBindFloatCallbackHelper = new CallbackHelper();
+    private final CallbackHelper mBindIntCallbackHelper = new CallbackHelper();
+    private final CallbackHelper mBindObjectCallbackHelper = new CallbackHelper();
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mModelListAdapter = new ModelListAdapter();
+        mModelListAdapter.registerType(VIEW_TYPE_1, new TestViewBuilder(), new TestViewBinder());
+        mModelListAdapter.registerType(VIEW_TYPE_2, new TestViewBuilder(), new TestViewBinder());
+
+        mModel = new PropertyModel(BOOLEAN_PROPERTY, FLOAT_PROPERTY, INT_PROPERTY, OBJECT_PROPERTY);
+
+        List<Pair<Integer, PropertyModel>> testData = new ArrayList<>();
+        testData.add(new Pair(VIEW_TYPE_1, mModel));
+
+        mModelListAdapter.updateModels(testData);
+    }
+
+    @Test
+    public void testNullConvertView() throws TimeoutException, InterruptedException {
+        // Set a property to test that it gets bound.
+        mModel.set(BOOLEAN_PROPERTY, true);
+
+        View view = mModelListAdapter.getView(0, null, null);
+
+        mBindBooleanCallbackHelper.waitForCallback(0);
+
+        Assert.assertEquals("Incorrect view type", VIEW_TYPE_1, view.getTag(R.id.view_type));
+        Assert.assertEquals("Incorrect model", mModel, view.getTag(R.id.view_model));
+        Assert.assertNotNull("MCP not set", view.getTag(R.id.view_mcp));
+
+        // Check that the MCP is hooked up.
+        mModel.set(BOOLEAN_PROPERTY, false);
+        mBindBooleanCallbackHelper.waitForCallback(1);
+    }
+
+    @Test
+    public void testNullTypeConvertView() throws TimeoutException, InterruptedException {
+        // Set a property to test that it gets bound.
+        mModel.set(BOOLEAN_PROPERTY, true);
+
+        View nullTypeView = new View(RuntimeEnvironment.application);
+        View view = mModelListAdapter.getView(0, nullTypeView, null);
+
+        mBindBooleanCallbackHelper.waitForCallback(0);
+        Assert.assertEquals("Incorrect view type", VIEW_TYPE_1, view.getTag(R.id.view_type));
+        Assert.assertNotEquals("nullTypeView incorrectly reused", nullTypeView, view);
+    }
+
+    @Test
+    public void testSameTypeConvertView_SameProperties()
+            throws TimeoutException, InterruptedException {
+        // Construct a test model for the convertView.
+        PropertyModel convertViewModel =
+                new PropertyModel(BOOLEAN_PROPERTY, FLOAT_PROPERTY, INT_PROPERTY, OBJECT_PROPERTY);
+
+        // Set a property to the same value for both models.
+        convertViewModel.set(BOOLEAN_PROPERTY, true);
+        mModel.set(BOOLEAN_PROPERTY, true);
+
+        // Set up a convertView with the same type, the test model, and a test MCP.
+        View convertView = new View(RuntimeEnvironment.application);
+        PropertyModelChangeProcessor convertViewMcp =
+                Mockito.spy(PropertyModelChangeProcessor.create(
+                        convertViewModel, convertView, new TestViewBinder(), false));
+        convertView.setTag(R.id.view_type, VIEW_TYPE_1);
+        convertView.setTag(R.id.view_model, convertViewModel);
+        convertView.setTag(R.id.view_mcp, convertViewMcp);
+
+        View view = mModelListAdapter.getView(0, convertView, null);
+
+        Assert.assertEquals("Incorrect callback count for boolean property", 0,
+                mBindBooleanCallbackHelper.getCallCount());
+        Assert.assertEquals("convertView not reused", convertView, view);
+        Assert.assertEquals("Incorrect view type", VIEW_TYPE_1, view.getTag(R.id.view_type));
+        Assert.assertEquals("Incorrect model", mModel, view.getTag(R.id.view_model));
+        Assert.assertNotNull("MCP not set", view.getTag(R.id.view_mcp));
+        Assert.assertNotEquals(
+                "MCP not properly switched", convertViewMcp, view.getTag(R.id.view_mcp));
+        Mockito.verify(convertViewMcp).destroy();
+
+        // Set a property on the new view's model and assert the ViewBinder is invoked.
+        mModel.set(BOOLEAN_PROPERTY, false);
+        mBindBooleanCallbackHelper.waitForCallback(0);
+        Assert.assertEquals("Incorrect callback count for boolean property", 1,
+                mBindBooleanCallbackHelper.getCallCount());
+
+        // Set a property on the convertView's model and assert the ViewBinder is not invoked.
+        convertViewModel.set(BOOLEAN_PROPERTY, true);
+        Assert.assertEquals("Incorrect callback count for boolean property", 1,
+                mBindBooleanCallbackHelper.getCallCount());
+    }
+
+    @Test
+    public void testSameTypeConvertView_DifferentProperties()
+            throws TimeoutException, InterruptedException {
+        // Construct a test model for the convertView.
+        PropertyModel convertViewModel =
+                new PropertyModel(BOOLEAN_PROPERTY, FLOAT_PROPERTY, INT_PROPERTY, OBJECT_PROPERTY);
+
+        // Set a property to a different value for each model.
+        convertViewModel.set(BOOLEAN_PROPERTY, true);
+        mModel.set(BOOLEAN_PROPERTY, false);
+
+        View convertView = new View(RuntimeEnvironment.application);
+        convertView.setTag(R.id.view_type, VIEW_TYPE_1);
+        convertView.setTag(R.id.view_model, convertViewModel);
+
+        View view = mModelListAdapter.getView(0, convertView, null);
+
+        mBindBooleanCallbackHelper.waitForCallback(0);
+        Assert.assertEquals("convertView not reused", convertView, view);
+    }
+
+    @Test
+    public void testDifferentTypeConvertView() throws TimeoutException, InterruptedException {
+        // Construct a test model for the convertView.
+        PropertyModel convertViewModel =
+                new PropertyModel(BOOLEAN_PROPERTY, FLOAT_PROPERTY, INT_PROPERTY, OBJECT_PROPERTY);
+
+        // Set a property to the same value for each model.
+        convertViewModel.set(BOOLEAN_PROPERTY, true);
+        mModel.set(BOOLEAN_PROPERTY, true);
+
+        View convertView = new View(RuntimeEnvironment.application);
+        convertView.setTag(R.id.view_type, VIEW_TYPE_2);
+        convertView.setTag(R.id.view_model, convertViewModel);
+
+        View view = mModelListAdapter.getView(0, convertView, null);
+
+        mBindBooleanCallbackHelper.waitForCallback(0);
+        Assert.assertEquals("Incorrect view type", VIEW_TYPE_1, view.getTag(R.id.view_type));
+        Assert.assertNotEquals("convertView incorrectly reused", convertView, view);
+    }
+
+    @Test
+    public void testBindNewModel_NullOldModel_SetPropertyValues()
+            throws TimeoutException, InterruptedException {
+        mModel.set(BOOLEAN_PROPERTY, true);
+        mModel.set(FLOAT_PROPERTY, 1.2f);
+        mModel.set(INT_PROPERTY, 3);
+        mModel.set(OBJECT_PROPERTY, new TestObject("Test"));
+
+        ModelListAdapter.bindNewModel(
+                mModel, null, new View(RuntimeEnvironment.application), new TestViewBinder());
+
+        mBindBooleanCallbackHelper.waitForCallback(0);
+        mBindFloatCallbackHelper.waitForCallback(0);
+        mBindIntCallbackHelper.waitForCallback(0);
+        mBindObjectCallbackHelper.waitForCallback(0);
+    }
+
+    @Test
+    public void testBindNewModel_NullOldModel_UnsetPropertyValues() {
+        ModelListAdapter.bindNewModel(
+                mModel, null, new View(RuntimeEnvironment.application), new TestViewBinder());
+
+        Assert.assertEquals("Incorrect callback count for boolean property", 0,
+                mBindBooleanCallbackHelper.getCallCount());
+        Assert.assertEquals("Incorrect callback count for float property", 0,
+                mBindFloatCallbackHelper.getCallCount());
+        Assert.assertEquals("Incorrect callback count for int property", 0,
+                mBindIntCallbackHelper.getCallCount());
+        Assert.assertEquals("Incorrect callback count for object property", 0,
+                mBindObjectCallbackHelper.getCallCount());
+    }
+
+    @Test
+    public void testBindNewModel_NonNullOldModel_SamePropertyValues() {
+        PropertyModel oldModel =
+                new PropertyModel(BOOLEAN_PROPERTY, FLOAT_PROPERTY, INT_PROPERTY, OBJECT_PROPERTY);
+
+        oldModel.set(BOOLEAN_PROPERTY, true);
+        mModel.set(BOOLEAN_PROPERTY, true);
+        oldModel.set(FLOAT_PROPERTY, 1.2f);
+        mModel.set(FLOAT_PROPERTY, 1.2f);
+        oldModel.set(INT_PROPERTY, 3);
+        mModel.set(INT_PROPERTY, 3);
+        oldModel.set(OBJECT_PROPERTY, new TestObject("Test"));
+        mModel.set(OBJECT_PROPERTY, new TestObject("Test"));
+
+        ModelListAdapter.bindNewModel(
+                mModel, oldModel, new View(RuntimeEnvironment.application), new TestViewBinder());
+
+        Assert.assertEquals("Incorrect callback count for boolean property", 0,
+                mBindBooleanCallbackHelper.getCallCount());
+        Assert.assertEquals("Incorrect callback count for float property", 0,
+                mBindFloatCallbackHelper.getCallCount());
+        Assert.assertEquals("Incorrect callback count for int property", 0,
+                mBindIntCallbackHelper.getCallCount());
+        Assert.assertEquals("Incorrect callback count for object property", 0,
+                mBindObjectCallbackHelper.getCallCount());
+    }
+
+    @Test
+    public void testBindNewModel_NonNullOldModel_DifferentPropertyValues()
+            throws TimeoutException, InterruptedException {
+        PropertyModel oldModel =
+                new PropertyModel(BOOLEAN_PROPERTY, FLOAT_PROPERTY, INT_PROPERTY, OBJECT_PROPERTY);
+
+        oldModel.set(BOOLEAN_PROPERTY, true);
+        mModel.set(BOOLEAN_PROPERTY, false);
+        oldModel.set(FLOAT_PROPERTY, 1.2f);
+        mModel.set(FLOAT_PROPERTY, 1.21f);
+        oldModel.set(INT_PROPERTY, 3);
+        mModel.set(INT_PROPERTY, 4);
+        oldModel.set(OBJECT_PROPERTY, new TestObject("Test"));
+        mModel.set(OBJECT_PROPERTY, new TestObject("Test1"));
+
+        ModelListAdapter.bindNewModel(
+                mModel, oldModel, new View(RuntimeEnvironment.application), new TestViewBinder());
+
+        mBindBooleanCallbackHelper.waitForCallback(0);
+        mBindFloatCallbackHelper.waitForCallback(0);
+        mBindIntCallbackHelper.waitForCallback(0);
+        mBindObjectCallbackHelper.waitForCallback(0);
+    }
+
+    @Test
+    public void testBindNewModel_NonNullOldModel_UnsetPropertyValues()
+            throws TimeoutException, InterruptedException {
+        PropertyModel oldModel =
+                new PropertyModel(BOOLEAN_PROPERTY, FLOAT_PROPERTY, INT_PROPERTY, OBJECT_PROPERTY);
+
+        oldModel.set(BOOLEAN_PROPERTY, true);
+        oldModel.set(FLOAT_PROPERTY, 1.2f);
+        oldModel.set(INT_PROPERTY, 3);
+        oldModel.set(OBJECT_PROPERTY, new TestObject("Test"));
+
+        ModelListAdapter.bindNewModel(
+                mModel, oldModel, new View(RuntimeEnvironment.application), new TestViewBinder());
+
+        mBindBooleanCallbackHelper.waitForCallback(0);
+        mBindFloatCallbackHelper.waitForCallback(0);
+        mBindIntCallbackHelper.waitForCallback(0);
+        mBindObjectCallbackHelper.waitForCallback(0);
+    }
+
+    @Test(expected = AssertionError.class)
+    public void testBindNewModel_RewriteReadOnlyProperty() {
+        PropertyModel oldModel = new PropertyModel.Builder(READONLY_BOOLEAN_PROPERTY)
+                                         .with(READONLY_BOOLEAN_PROPERTY, false)
+                                         .build();
+        PropertyModel newModel = new PropertyModel.Builder(READONLY_BOOLEAN_PROPERTY)
+                                         .with(READONLY_BOOLEAN_PROPERTY, true)
+                                         .build();
+        ModelListAdapter.bindNewModel(
+                newModel, oldModel, new View(RuntimeEnvironment.application), new TestViewBinder());
+    }
+}
diff --git a/ui/android/junit/src/org/chromium/ui/modelutil/PropertyModelTest.java b/ui/android/junit/src/org/chromium/ui/modelutil/PropertyModelTest.java
index 4f68cf4..9c7b127 100644
--- a/ui/android/junit/src/org/chromium/ui/modelutil/PropertyModelTest.java
+++ b/ui/android/junit/src/org/chromium/ui/modelutil/PropertyModelTest.java
@@ -9,6 +9,7 @@
 import static org.hamcrest.Matchers.equalTo;
 import static org.mockito.Mockito.verify;
 
+import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -54,6 +55,8 @@
             new WritableObjectPropertyKey<>();
     public static WritableObjectPropertyKey<List<Integer>> OBJECT_PROPERTY_C =
             new WritableObjectPropertyKey<>();
+    public static WritableObjectPropertyKey<Object> OBJECT_PROPERTY_SKIP_EQUALITY =
+            new WritableObjectPropertyKey<>(true);
 
     @Test
     public void getAllSetProperties() {
@@ -216,4 +219,106 @@
     public void preventsDuplicateKeys() {
         new PropertyModel(BOOLEAN_PROPERTY_A, BOOLEAN_PROPERTY_A);
     }
+
+    @Test
+    public void testCompareValue_Boolean() {
+        PropertyModel model1 =
+                new PropertyModel(BOOLEAN_PROPERTY_A, BOOLEAN_PROPERTY_B, BOOLEAN_PROPERTY_C);
+        model1.set(BOOLEAN_PROPERTY_A, true);
+        model1.set(BOOLEAN_PROPERTY_B, true);
+        model1.set(BOOLEAN_PROPERTY_C, false);
+
+        PropertyModel model2 =
+                new PropertyModel(BOOLEAN_PROPERTY_A, BOOLEAN_PROPERTY_B, BOOLEAN_PROPERTY_C);
+        model2.set(BOOLEAN_PROPERTY_A, true);
+        model2.set(BOOLEAN_PROPERTY_B, false);
+
+        Assert.assertTrue("BOOLEAN_PROPERTY_A should be equal",
+                model1.compareValue(model2, BOOLEAN_PROPERTY_A));
+        Assert.assertFalse("BOOLEAN_PROPERTY_B should not be equal",
+                model1.compareValue(model2, BOOLEAN_PROPERTY_B));
+        Assert.assertFalse("BOOLEAN_PROPERTY_C should not be equal",
+                model1.compareValue(model2, BOOLEAN_PROPERTY_C));
+    }
+
+    @Test
+    public void testCompareValue_Integer() {
+        PropertyModel model1 = new PropertyModel(INT_PROPERTY_A, INT_PROPERTY_B, INT_PROPERTY_C);
+        model1.set(INT_PROPERTY_A, 1);
+        model1.set(INT_PROPERTY_B, 2);
+        model1.set(INT_PROPERTY_C, 3);
+
+        PropertyModel model2 = new PropertyModel(INT_PROPERTY_A, INT_PROPERTY_B, INT_PROPERTY_C);
+        model2.set(INT_PROPERTY_A, 1);
+        model2.set(INT_PROPERTY_B, 3);
+
+        Assert.assertTrue(
+                "INT_PROPERTY_A should be equal", model1.compareValue(model2, INT_PROPERTY_A));
+        Assert.assertFalse(
+                "INT_PROPERTY_B should not be equal", model1.compareValue(model2, INT_PROPERTY_B));
+        Assert.assertFalse(
+                "INT_PROPERTY_C should not be equal", model1.compareValue(model2, INT_PROPERTY_C));
+    }
+
+    @Test
+    public void testCompareValue_Float() {
+        PropertyModel model1 =
+                new PropertyModel(FLOAT_PROPERTY_A, FLOAT_PROPERTY_B, FLOAT_PROPERTY_C);
+        model1.set(FLOAT_PROPERTY_A, 1.2f);
+        model1.set(FLOAT_PROPERTY_B, 2.2f);
+        model1.set(FLOAT_PROPERTY_C, 3.2f);
+
+        PropertyModel model2 =
+                new PropertyModel(FLOAT_PROPERTY_A, FLOAT_PROPERTY_B, FLOAT_PROPERTY_C);
+        model2.set(FLOAT_PROPERTY_A, 1.2f);
+        model2.set(FLOAT_PROPERTY_B, 3.2f);
+
+        Assert.assertTrue(
+                "FLOAT_PROPERTY_A should be equal", model1.compareValue(model2, FLOAT_PROPERTY_A));
+        Assert.assertFalse("FLOAT_PROPERTY_B should not be equal",
+                model1.compareValue(model2, FLOAT_PROPERTY_B));
+        Assert.assertFalse("FLOAT_PROPERTY_C should not be equal",
+                model1.compareValue(model2, FLOAT_PROPERTY_C));
+    }
+
+    @Test
+    public void testCompareValue_Object() {
+        Object sharedObject = new Object();
+
+        PropertyModel model1 =
+                new PropertyModel(OBJECT_PROPERTY_A, OBJECT_PROPERTY_B, OBJECT_PROPERTY_C);
+        model1.set(OBJECT_PROPERTY_A, sharedObject);
+        model1.set(OBJECT_PROPERTY_B, "Test");
+        model1.set(OBJECT_PROPERTY_C, new ArrayList<>());
+
+        PropertyModel model2 =
+                new PropertyModel(OBJECT_PROPERTY_A, OBJECT_PROPERTY_B, OBJECT_PROPERTY_C);
+        model2.set(OBJECT_PROPERTY_A, sharedObject);
+        model2.set(OBJECT_PROPERTY_B, "Test");
+
+        Assert.assertTrue("OBJECT_PROPERTY_A should be equal",
+                model1.compareValue(model2, OBJECT_PROPERTY_A));
+        Assert.assertTrue("OBJECT_PROPERTY_B should be equal",
+                model1.compareValue(model2, OBJECT_PROPERTY_B));
+        Assert.assertFalse("OBJECT_PROPERTY_C should not be equal",
+                model1.compareValue(model2, OBJECT_PROPERTY_C));
+
+        model2.set(OBJECT_PROPERTY_B, "Test2");
+        Assert.assertFalse("OBJECT_PROPERTY_B should not be equal",
+                model1.compareValue(model2, OBJECT_PROPERTY_B));
+    }
+
+    @Test
+    public void testCompareValue_Object_SkipEquality() {
+        Object sharedObject = new Object();
+
+        PropertyModel model1 = new PropertyModel(OBJECT_PROPERTY_SKIP_EQUALITY);
+        model1.set(OBJECT_PROPERTY_SKIP_EQUALITY, sharedObject);
+
+        PropertyModel model2 = new PropertyModel(OBJECT_PROPERTY_SKIP_EQUALITY);
+        model2.set(OBJECT_PROPERTY_SKIP_EQUALITY, sharedObject);
+
+        Assert.assertFalse("OBJECT_PROPERTY_A should not be equal",
+                model1.compareValue(model2, OBJECT_PROPERTY_SKIP_EQUALITY));
+    }
 }
diff --git a/ui/base/accelerators/global_media_keys_listener_win.h b/ui/base/accelerators/global_media_keys_listener_win.h
index 4b95a20..8de4946 100644
--- a/ui/base/accelerators/global_media_keys_listener_win.h
+++ b/ui/base/accelerators/global_media_keys_listener_win.h
@@ -32,6 +32,7 @@
   // MediaKeysListener implementation.
   bool StartWatchingMediaKey(KeyboardCode key_code) override;
   void StopWatchingMediaKey(KeyboardCode key_code) override;
+  void SetIsMediaPlaying(bool is_playing) override {}
 
  private:
   // Called by SingletonHwndObserver.
diff --git a/ui/base/accelerators/media_keys_listener.h b/ui/base/accelerators/media_keys_listener.h
index 9072523c..997718d 100644
--- a/ui/base/accelerators/media_keys_listener.h
+++ b/ui/base/accelerators/media_keys_listener.h
@@ -49,6 +49,13 @@
   virtual bool StartWatchingMediaKey(KeyboardCode key_code) = 0;
   // Stop listening for a given media key.
   virtual void StopWatchingMediaKey(KeyboardCode key_code) = 0;
+
+  // Informs the listener whether or not media is currently playing. In some
+  // implementations this will prevent us from calling PlayPause unnecessarily.
+  // TODO(https://crbug.com/974035): Once the MediaKeysListenerManager has been
+  // refactored to work with system media controls this should no longer be
+  // needed and should be deleted.
+  virtual void SetIsMediaPlaying(bool is_playing) = 0;
 };
 
 }  // namespace ui
diff --git a/ui/base/accelerators/media_keys_listener_mac.mm b/ui/base/accelerators/media_keys_listener_mac.mm
index 71b417ee..f4e3126 100644
--- a/ui/base/accelerators/media_keys_listener_mac.mm
+++ b/ui/base/accelerators/media_keys_listener_mac.mm
@@ -46,6 +46,7 @@
   // MediaKeysListener:
   bool StartWatchingMediaKey(KeyboardCode key_code) override;
   void StopWatchingMediaKey(KeyboardCode key_code) override;
+  void SetIsMediaPlaying(bool is_playing) override {}
 
  private:
   // Callback on media key event.
diff --git a/ui/base/accelerators/mpris_media_keys_listener.cc b/ui/base/accelerators/mpris_media_keys_listener.cc
index 7edd491..81653849 100644
--- a/ui/base/accelerators/mpris_media_keys_listener.cc
+++ b/ui/base/accelerators/mpris_media_keys_listener.cc
@@ -88,6 +88,10 @@
   }
 }
 
+void MprisMediaKeysListener::SetIsMediaPlaying(bool is_playing) {
+  is_media_playing_ = is_playing;
+}
+
 void MprisMediaKeysListener::OnNext() {
   MaybeSendKeyCode(VKEY_MEDIA_NEXT_TRACK);
 }
@@ -97,7 +101,8 @@
 }
 
 void MprisMediaKeysListener::OnPause() {
-  MaybeSendKeyCode(VKEY_MEDIA_PLAY_PAUSE);
+  if (is_media_playing_)
+    MaybeSendKeyCode(VKEY_MEDIA_PLAY_PAUSE);
 }
 
 void MprisMediaKeysListener::OnPlayPause() {
@@ -109,7 +114,8 @@
 }
 
 void MprisMediaKeysListener::OnPlay() {
-  MaybeSendKeyCode(VKEY_MEDIA_PLAY_PAUSE);
+  if (!is_media_playing_)
+    MaybeSendKeyCode(VKEY_MEDIA_PLAY_PAUSE);
 }
 
 void MprisMediaKeysListener::MaybeSendKeyCode(KeyboardCode key_code) {
diff --git a/ui/base/accelerators/mpris_media_keys_listener.h b/ui/base/accelerators/mpris_media_keys_listener.h
index 166b249..ff95808 100644
--- a/ui/base/accelerators/mpris_media_keys_listener.h
+++ b/ui/base/accelerators/mpris_media_keys_listener.h
@@ -35,6 +35,7 @@
   // MediaKeysListener implementation.
   bool StartWatchingMediaKey(KeyboardCode key_code) override;
   void StopWatchingMediaKey(KeyboardCode key_code) override;
+  void SetIsMediaPlaying(bool is_playing) override;
 
   // mpris::MprisServiceObserver implementation.
   void OnNext() override;
@@ -57,6 +58,7 @@
   MediaKeysListener::Delegate* delegate_;
   base::flat_set<KeyboardCode> key_codes_;
   mpris::MprisService* service_ = nullptr;
+  bool is_media_playing_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(MprisMediaKeysListener);
 };
diff --git a/ui/base/accelerators/mpris_media_keys_listener_unittest.cc b/ui/base/accelerators/mpris_media_keys_listener_unittest.cc
index 29b570e..5f4eb42 100644
--- a/ui/base/accelerators/mpris_media_keys_listener_unittest.cc
+++ b/ui/base/accelerators/mpris_media_keys_listener_unittest.cc
@@ -118,4 +118,32 @@
   listener()->OnPrevious();
 }
 
+TEST_F(MprisMediaKeysListenerTest, DoesNotFirePlayPauseOnPauseEventWhenPaused) {
+  // Should be set to true when we start listening for the key.
+  EXPECT_CALL(mock_mpris_service(), SetCanPlay(true));
+  EXPECT_CALL(mock_mpris_service(), SetCanPause(true));
+  EXPECT_CALL(delegate(), OnMediaKeysAccelerator(_)).Times(0);
+
+  listener()->Initialize();
+  listener()->StartWatchingMediaKey(ui::VKEY_MEDIA_PLAY_PAUSE);
+  listener()->SetIsMediaPlaying(false);
+
+  // Simulate media key press.
+  listener()->OnPause();
+}
+
+TEST_F(MprisMediaKeysListenerTest, DoesNotFirePlayPauseOnPlayEventWhenPlaying) {
+  // Should be set to true when we start listening for the key.
+  EXPECT_CALL(mock_mpris_service(), SetCanPlay(true));
+  EXPECT_CALL(mock_mpris_service(), SetCanPause(true));
+  EXPECT_CALL(delegate(), OnMediaKeysAccelerator(_)).Times(0);
+
+  listener()->Initialize();
+  listener()->StartWatchingMediaKey(ui::VKEY_MEDIA_PLAY_PAUSE);
+  listener()->SetIsMediaPlaying(true);
+
+  // Simulate media key press.
+  listener()->OnPlay();
+}
+
 }  // namespace ui
diff --git a/ui/base/accelerators/remote_command_media_keys_listener_mac.h b/ui/base/accelerators/remote_command_media_keys_listener_mac.h
index e077175..192c921 100644
--- a/ui/base/accelerators/remote_command_media_keys_listener_mac.h
+++ b/ui/base/accelerators/remote_command_media_keys_listener_mac.h
@@ -37,6 +37,7 @@
   // MediaKeysListener implementation.
   bool StartWatchingMediaKey(KeyboardCode key_code) override;
   void StopWatchingMediaKey(KeyboardCode key_code) override;
+  void SetIsMediaPlaying(bool is_playing) override;
 
   // now_playing::RemoteCommandCenterDelegateObserver implementation.
   void OnNext() override;
@@ -61,6 +62,7 @@
   base::flat_set<KeyboardCode> key_codes_;
   now_playing::RemoteCommandCenterDelegate* remote_command_center_delegate_ =
       nullptr;
+  bool is_media_playing_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(RemoteCommandMediaKeysListenerMac);
 };
diff --git a/ui/base/accelerators/remote_command_media_keys_listener_mac.mm b/ui/base/accelerators/remote_command_media_keys_listener_mac.mm
index ddcc5442..0f7265d6 100644
--- a/ui/base/accelerators/remote_command_media_keys_listener_mac.mm
+++ b/ui/base/accelerators/remote_command_media_keys_listener_mac.mm
@@ -91,6 +91,10 @@
   }
 }
 
+void RemoteCommandMediaKeysListenerMac::SetIsMediaPlaying(bool is_playing) {
+  is_media_playing_ = is_playing;
+}
+
 void RemoteCommandMediaKeysListenerMac::OnNext() {
   MaybeSend(VKEY_MEDIA_NEXT_TRACK);
 }
@@ -100,7 +104,8 @@
 }
 
 void RemoteCommandMediaKeysListenerMac::OnPause() {
-  MaybeSend(VKEY_MEDIA_PLAY_PAUSE);
+  if (is_media_playing_)
+    MaybeSend(VKEY_MEDIA_PLAY_PAUSE);
 }
 
 void RemoteCommandMediaKeysListenerMac::OnPlayPause() {
@@ -112,7 +117,8 @@
 }
 
 void RemoteCommandMediaKeysListenerMac::OnPlay() {
-  MaybeSend(VKEY_MEDIA_PLAY_PAUSE);
+  if (!is_media_playing_)
+    MaybeSend(VKEY_MEDIA_PLAY_PAUSE);
 }
 
 void RemoteCommandMediaKeysListenerMac::MaybeSend(KeyboardCode key_code) {
diff --git a/ui/base/accelerators/remote_command_media_keys_listener_mac_unittest.mm b/ui/base/accelerators/remote_command_media_keys_listener_mac_unittest.mm
index 123b3f00..c5860602 100644
--- a/ui/base/accelerators/remote_command_media_keys_listener_mac_unittest.mm
+++ b/ui/base/accelerators/remote_command_media_keys_listener_mac_unittest.mm
@@ -118,4 +118,50 @@
   }
 }
 
+TEST_F(RemoteCommandMediaKeysListenerMacTest,
+       DoesNotFirePlayPauseOnPauseEventWhenPaused) {
+  if (@available(macOS 10.12.2, *)) {
+    now_playing::MockRemoteCommandCenterDelegate rcc_delegate;
+    MockMediaKeysListenerDelegate delegate;
+    RemoteCommandMediaKeysListenerMac listener(&delegate);
+    listener.SetRemoteCommandCenterDelegateForTesting(&rcc_delegate);
+
+    EXPECT_CALL(rcc_delegate, AddObserver(&listener));
+    EXPECT_CALL(rcc_delegate, SetCanPlayPause(true));
+    EXPECT_CALL(rcc_delegate, SetCanPlay(true));
+    EXPECT_CALL(rcc_delegate, SetCanPause(true));
+    EXPECT_CALL(delegate, OnMediaKeysAccelerator(_)).Times(0);
+
+    listener.Initialize();
+    listener.StartWatchingMediaKey(ui::VKEY_MEDIA_PLAY_PAUSE);
+    listener.SetIsMediaPlaying(false);
+
+    // Simulate media key press.
+    listener.OnPause();
+  }
+}
+
+TEST_F(RemoteCommandMediaKeysListenerMacTest,
+       DoesNotFirePlayPauseOnPlayEventWhenPlaying) {
+  if (@available(macOS 10.12.2, *)) {
+    now_playing::MockRemoteCommandCenterDelegate rcc_delegate;
+    MockMediaKeysListenerDelegate delegate;
+    RemoteCommandMediaKeysListenerMac listener(&delegate);
+    listener.SetRemoteCommandCenterDelegateForTesting(&rcc_delegate);
+
+    EXPECT_CALL(rcc_delegate, AddObserver(&listener));
+    EXPECT_CALL(rcc_delegate, SetCanPlayPause(true));
+    EXPECT_CALL(rcc_delegate, SetCanPlay(true));
+    EXPECT_CALL(rcc_delegate, SetCanPause(true));
+    EXPECT_CALL(delegate, OnMediaKeysAccelerator(_)).Times(0);
+
+    listener.Initialize();
+    listener.StartWatchingMediaKey(ui::VKEY_MEDIA_PLAY_PAUSE);
+    listener.SetIsMediaPlaying(true);
+
+    // Simulate media key press.
+    listener.OnPlay();
+  }
+}
+
 }  // namespace ui
diff --git a/ui/base/accelerators/system_media_controls_media_keys_listener.cc b/ui/base/accelerators/system_media_controls_media_keys_listener.cc
index a07e0eb..f4de72a 100644
--- a/ui/base/accelerators/system_media_controls_media_keys_listener.cc
+++ b/ui/base/accelerators/system_media_controls_media_keys_listener.cc
@@ -104,6 +104,10 @@
   }
 }
 
+void SystemMediaControlsMediaKeysListener::SetIsMediaPlaying(bool is_playing) {
+  is_media_playing_ = is_playing;
+}
+
 void SystemMediaControlsMediaKeysListener::OnNext() {
   MaybeSendKeyCode(VKEY_MEDIA_NEXT_TRACK);
 }
@@ -113,7 +117,8 @@
 }
 
 void SystemMediaControlsMediaKeysListener::OnPause() {
-  MaybeSendKeyCode(VKEY_MEDIA_PLAY_PAUSE);
+  if (is_media_playing_)
+    MaybeSendKeyCode(VKEY_MEDIA_PLAY_PAUSE);
 }
 
 void SystemMediaControlsMediaKeysListener::OnStop() {
@@ -121,7 +126,8 @@
 }
 
 void SystemMediaControlsMediaKeysListener::OnPlay() {
-  MaybeSendKeyCode(VKEY_MEDIA_PLAY_PAUSE);
+  if (!is_media_playing_)
+    MaybeSendKeyCode(VKEY_MEDIA_PLAY_PAUSE);
 }
 
 void SystemMediaControlsMediaKeysListener::MaybeSendKeyCode(
diff --git a/ui/base/accelerators/system_media_controls_media_keys_listener.h b/ui/base/accelerators/system_media_controls_media_keys_listener.h
index c900d841..1bc133b7 100644
--- a/ui/base/accelerators/system_media_controls_media_keys_listener.h
+++ b/ui/base/accelerators/system_media_controls_media_keys_listener.h
@@ -35,6 +35,7 @@
   // MediaKeysListener implementation.
   bool StartWatchingMediaKey(KeyboardCode key_code) override;
   void StopWatchingMediaKey(KeyboardCode key_code) override;
+  void SetIsMediaPlaying(bool is_playing) override;
 
   // system_media_controls::SystemMediaControlsServiceObserver implementation.
   void OnNext() override;
@@ -61,6 +62,8 @@
 
   system_media_controls::SystemMediaControlsService* service_ = nullptr;
 
+  bool is_media_playing_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(SystemMediaControlsMediaKeysListener);
 };
 
diff --git a/ui/base/accelerators/system_media_controls_media_keys_listener_unittest.cc b/ui/base/accelerators/system_media_controls_media_keys_listener_unittest.cc
index b07c9a59..f4d20b0 100644
--- a/ui/base/accelerators/system_media_controls_media_keys_listener_unittest.cc
+++ b/ui/base/accelerators/system_media_controls_media_keys_listener_unittest.cc
@@ -132,4 +132,36 @@
   listener()->OnPrevious();
 }
 
+TEST_F(SystemMediaControlsMediaKeysListenerTest,
+       DoesNotFirePlayPauseOnPauseEventWhenPaused) {
+  // Should be set to true when we start listening for the key.
+  EXPECT_CALL(mock_system_media_controls_service(), SetIsPlayEnabled(true));
+  EXPECT_CALL(mock_system_media_controls_service(), SetIsPauseEnabled(true));
+
+  EXPECT_CALL(delegate(), OnMediaKeysAccelerator(_)).Times(0);
+
+  ASSERT_TRUE(listener()->Initialize());
+  listener()->StartWatchingMediaKey(ui::VKEY_MEDIA_PLAY_PAUSE);
+  listener()->SetIsMediaPlaying(false);
+
+  // Simulate media key press.
+  listener()->OnPause();
+}
+
+TEST_F(SystemMediaControlsMediaKeysListenerTest,
+       DoesNotFirePlayPauseOnPlayEventWhenPlaying) {
+  // Should be set to true when we start listening for the key.
+  EXPECT_CALL(mock_system_media_controls_service(), SetIsPlayEnabled(true));
+  EXPECT_CALL(mock_system_media_controls_service(), SetIsPauseEnabled(true));
+
+  EXPECT_CALL(delegate(), OnMediaKeysAccelerator(_)).Times(0);
+
+  ASSERT_TRUE(listener()->Initialize());
+  listener()->StartWatchingMediaKey(ui::VKEY_MEDIA_PLAY_PAUSE);
+  listener()->SetIsMediaPlaying(true);
+
+  // Simulate media key press.
+  listener()->OnPlay();
+}
+
 }  // namespace ui
diff --git a/ui/base/clipboard/clipboard_format_type.h b/ui/base/clipboard/clipboard_format_type.h
index 07d8e08..98c06de 100644
--- a/ui/base/clipboard/clipboard_format_type.h
+++ b/ui/base/clipboard/clipboard_format_type.h
@@ -26,6 +26,7 @@
 namespace ui {
 
 // Platform neutral holder for native data representation of a clipboard type.
+// Copyable and assignable, since this is an opaque value type.
 struct COMPONENT_EXPORT(BASE_CLIPBOARD_TYPES) ClipboardFormatType {
   ClipboardFormatType();
   ~ClipboardFormatType();
@@ -93,7 +94,7 @@
   friend struct ClipboardFormatType;
 
   // Platform-specific glue used internally by the ClipboardFormatType struct.
-  // Each platform should define,at least one of each of the following:
+  // Each platform should define at least one of each of the following:
   // 1. A constructor that wraps that native clipboard format descriptor.
   // 2. An accessor to retrieve the wrapped descriptor.
   // 3. A data member to hold the wrapped descriptor.
@@ -125,8 +126,6 @@
 #else
 #error No ClipboardFormatType definition.
 #endif
-
-  // Copyable and assignable, since this is essentially an opaque value type.
 };
 
 }  // namespace ui
diff --git a/ui/base/clipboard/clipboard_format_type_win.cc b/ui/base/clipboard/clipboard_format_type_win.cc
index 5d300923..e5210b9e3 100644
--- a/ui/base/clipboard/clipboard_format_type_win.cc
+++ b/ui/base/clipboard/clipboard_format_type_win.cc
@@ -56,7 +56,7 @@
   return data_.cfFormat == other.data_.cfFormat;
 }
 
-// Various predefined ClipboardFormatTypes.
+// Predefined ClipboardFormatTypes.
 
 // static
 ClipboardFormatType ClipboardFormatType::GetType(
@@ -207,9 +207,6 @@
     LONG index) {
   auto& index_to_type_map = GetFileContentTypeMap();
 
-  // Use base::WrapUnique instead of std::make_unique here since
-  // ClipboardFormatType constructor is private. See
-  // https://chromium.googlesource.com/chromium/src/+/HEAD/styleguide/c++/c++-dos-and-donts.md.
   auto insert_or_assign_result = index_to_type_map.insert(
       {index,
        ClipboardFormatType(::RegisterClipboardFormat(CFSTR_FILECONTENTS), index,
diff --git a/ui/base/clipboard/clipboard_types.h b/ui/base/clipboard/clipboard_types.h
index 8929d92..dc12e88 100644
--- a/ui/base/clipboard/clipboard_types.h
+++ b/ui/base/clipboard/clipboard_types.h
@@ -7,13 +7,11 @@
 
 namespace ui {
 
-// This type designates which clipboard the action should be applied to.
-// Only platforms that use the X Window System support the selection buffer.
-// Drag type is only supported on Mac OS X.
+// |ClipboardType| designates which clipboard the action should be applied to.
 enum ClipboardType {
   CLIPBOARD_TYPE_COPY_PASTE,
-  CLIPBOARD_TYPE_SELECTION,
-  CLIPBOARD_TYPE_DRAG,
+  CLIPBOARD_TYPE_SELECTION,  // Only supported on systems running X11.
+  CLIPBOARD_TYPE_DRAG,       // Only supported on Mac OS X.
   CLIPBOARD_TYPE_LAST = CLIPBOARD_TYPE_DRAG
 };
 
diff --git a/ui/base/clipboard/clipboard_util_win.h b/ui/base/clipboard/clipboard_util_win.h
index bfcea85c..0a02c28 100644
--- a/ui/base/clipboard/clipboard_util_win.h
+++ b/ui/base/clipboard/clipboard_util_win.h
@@ -1,8 +1,6 @@
 // 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.
-//
-// Some helper functions for working with the clipboard and IDataObjects.
 
 #ifndef UI_BASE_CLIPBOARD_CLIPBOARD_UTIL_WIN_H_
 #define UI_BASE_CLIPBOARD_CLIPBOARD_UTIL_WIN_H_
@@ -22,6 +20,7 @@
 
 namespace ui {
 
+// Contains helper functions for working with the clipboard and IDataObjects.
 class COMPONENT_EXPORT(BASE_CLIPBOARD) ClipboardUtil {
  public:
   /////////////////////////////////////////////////////////////////////////////
diff --git a/ui/base/clipboard/custom_data_helper.h b/ui/base/clipboard/custom_data_helper.h
index 7d980f0..fc1bf96 100644
--- a/ui/base/clipboard/custom_data_helper.h
+++ b/ui/base/clipboard/custom_data_helper.h
@@ -1,11 +1,6 @@
 // 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.
-//
-// Due to restrictions of most operating systems, we don't directly map each
-// type of custom data to a native data transfer type. Instead, we serialize
-// each key-value pair into the pickle as a pair of string objects, and then
-// write the binary data in the pickle to the native data transfer object.
 
 #ifndef UI_BASE_CLIPBOARD_CUSTOM_DATA_HELPER_H_
 #define UI_BASE_CLIPBOARD_CUSTOM_DATA_HELPER_H_
@@ -20,6 +15,10 @@
 #include "base/strings/string16.h"
 #include "build/build_config.h"
 
+// Due to restrictions of most operating systems, we don't directly map each
+// type of custom data to a native data transfer type. Instead, we serialize
+// each key-value pair into the pickle as a pair of string objects, and then
+// write the binary data in the pickle to the native data transfer object.
 namespace base {
 class Pickle;
 }
diff --git a/ui/base/clipboard/scoped_clipboard_writer.cc b/ui/base/clipboard/scoped_clipboard_writer.cc
index 6e11dab..b41c7095 100644
--- a/ui/base/clipboard/scoped_clipboard_writer.cc
+++ b/ui/base/clipboard/scoped_clipboard_writer.cc
@@ -2,10 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// This file implements the ScopedClipboardWriter class. Documentation on its
-// purpose can be found in our header. Documentation on the format of the
-// parameters for each clipboard target can be found in clipboard.h.
-
 #include "ui/base/clipboard/scoped_clipboard_writer.h"
 
 #include "base/pickle.h"
@@ -14,6 +10,8 @@
 #include "ui/base/clipboard/clipboard_format_type.h"
 #include "ui/gfx/geometry/size.h"
 
+// Documentation on the format of the parameters for each clipboard target can
+// be found in clipboard.h.
 namespace ui {
 
 ScopedClipboardWriter::ScopedClipboardWriter(ClipboardType type) : type_(type) {
diff --git a/ui/base/clipboard/scoped_clipboard_writer.h b/ui/base/clipboard/scoped_clipboard_writer.h
index 084d5fe1..178833f3 100644
--- a/ui/base/clipboard/scoped_clipboard_writer.h
+++ b/ui/base/clipboard/scoped_clipboard_writer.h
@@ -2,12 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// This file declares the ScopedClipboardWriter class, a wrapper around
-// the Clipboard class which simplifies writing data to the system clipboard.
-// Upon deletion the class atomically writes all data to the clipboard,
-// avoiding any potential race condition with other processes that are also
-// writing to the system clipboard.
-
 #ifndef UI_BASE_CLIPBOARD_SCOPED_CLIPBOARD_WRITER_H_
 #define UI_BASE_CLIPBOARD_SCOPED_CLIPBOARD_WRITER_H_
 
@@ -25,8 +19,14 @@
 
 namespace ui {
 
-// This class is a wrapper for |Clipboard| that handles packing data
-// into a Clipboard::ObjectMap.
+// |ScopedClipboardWriter|:
+// - is a wrapper for |Clipboard|.
+// - simplifies writing data to the system clipboard.
+// - handles packing data into a Clipboard::ObjectMap.
+//
+// Upon deletion, the class atomically writes all data to the clipboard,
+// avoiding any potential race condition with other processes that are also
+// writing to the system clipboard.
 class COMPONENT_EXPORT(BASE_CLIPBOARD) ScopedClipboardWriter {
  public:
   // Create an instance that is a simple wrapper around the clipboard of the
diff --git a/ui/views/controls/label.cc b/ui/views/controls/label.cc
index 29635eab..90a54d5 100644
--- a/ui/views/controls/label.cc
+++ b/ui/views/controls/label.cc
@@ -414,6 +414,11 @@
   return base::string16();
 }
 
+void Label::OnHandlePropertyChangeEffects(PropertyEffects property_effects) {
+  if (property_effects & kPropertyEffectsLayout)
+    ResetLayout();
+}
+
 std::unique_ptr<gfx::RenderText> Label::CreateRenderText() const {
   // Multi-line labels only support NO_ELIDE and ELIDE_TAIL for now.
   // TODO(warx): Investigate more elide text support.
diff --git a/ui/views/controls/label.h b/ui/views/controls/label.h
index 4d9c4a4..36117ce 100644
--- a/ui/views/controls/label.h
+++ b/ui/views/controls/label.h
@@ -235,6 +235,7 @@
   WordLookupClient* GetWordLookupClient() override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
   base::string16 GetTooltipText(const gfx::Point& p) const override;
+  void OnHandlePropertyChangeEffects(PropertyEffects property_effects) override;
 
  protected:
   // Create a single RenderText instance to actually be painted.
diff --git a/ui/views/view.cc b/ui/views/view.cc
index f0d165b..5df93cb 100644
--- a/ui/views/view.cc
+++ b/ui/views/view.cc
@@ -1955,6 +1955,7 @@
     InvalidateLayout();
   if (effects & kPropertyEffectsPaint)
     SchedulePaint();
+  OnHandlePropertyChangeEffects(effects);
 }
 
 PropertyChangedSubscription View::AddPropertyChangedCallback(
diff --git a/ui/views/view.h b/ui/views/view.h
index 94dcf00d..78b42e0 100644
--- a/ui/views/view.h
+++ b/ui/views/view.h
@@ -1572,6 +1572,11 @@
   void OnPropertyChanged(PropertyKey property,
                          PropertyEffects property_effects);
 
+  // Empty function called in HandlePropertyChangeEffects to be overridden in
+  // subclasses if they have custom functions for property changes.
+  virtual void OnHandlePropertyChangeEffects(PropertyEffects property_effects) {
+  }
+
  private:
   friend class internal::PreEventDispatchHandler;
   friend class internal::PostEventDispatchHandler;
diff --git a/ui/webui/resources/cr_elements/BUILD.gn b/ui/webui/resources/cr_elements/BUILD.gn
index af9dbbf..d551ea4f 100644
--- a/ui/webui/resources/cr_elements/BUILD.gn
+++ b/ui/webui/resources/cr_elements/BUILD.gn
@@ -7,8 +7,6 @@
 group("closure_compile") {
   deps = [
     ":cr_elements_resources",
-    "chromeos/cr_picture:closure_compile",
-    "chromeos/network:closure_compile",
     "cr_action_menu:closure_compile",
     "cr_button:closure_compile",
     "cr_checkbox:closure_compile",
@@ -21,7 +19,6 @@
     "cr_profile_avatar_selector:closure_compile",
     "cr_radio_button:closure_compile",
     "cr_radio_group:closure_compile",
-    "cr_searchable_drop_down:closure_compile",
     "cr_slider:closure_compile",
     "cr_tabs:closure_compile",
     "cr_toast:closure_compile",
@@ -29,6 +26,16 @@
     "cr_view_manager:closure_compile",
     "policy:closure_compile",
   ]
+
+  if (is_chromeos) {
+    deps += [
+      "chromeos:closure_compile",
+
+      # cr-searchable-drop-down is only used in smb and cups dialogs, both of
+      # which are chromeos only.
+      "cr_searchable_drop_down:closure_compile",
+    ]
+  }
 }
 
 js_type_check("cr_elements_resources") {
diff --git a/ui/webui/resources/cr_elements/chromeos/BUILD.gn b/ui/webui/resources/cr_elements/chromeos/BUILD.gn
new file mode 100644
index 0000000..b73e6bf5
--- /dev/null
+++ b/ui/webui/resources/cr_elements/chromeos/BUILD.gn
@@ -0,0 +1,15 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/closure_compiler/compile_js.gni")
+
+assert(is_chromeos, "Only ChromeOS components belong here.")
+
+group("closure_compile") {
+  deps = [
+    "cr_picture:closure_compile",
+    "fingerprint:closure_compile",
+    "network:closure_compile",
+  ]
+}
diff --git a/ui/webui/resources/cr_elements/chromeos/network/BUILD.gn b/ui/webui/resources/cr_elements/chromeos/network/BUILD.gn
index c70cdd8e..92effd9 100644
--- a/ui/webui/resources/cr_elements/chromeos/network/BUILD.gn
+++ b/ui/webui/resources/cr_elements/chromeos/network/BUILD.gn
@@ -4,63 +4,71 @@
 
 import("//third_party/closure_compiler/compile_js.gni")
 
+# Note: This file is referenced in settings_ui for :cr_onc_types.
+
 js_type_check("closure_compile") {
   deps = [
-    ":cr_network_icon",
-    ":cr_network_list",
-    ":cr_network_list_item",
-    ":cr_network_list_types",
-    ":cr_network_listener_behavior",
-    ":cr_network_select",
     ":cr_onc_types",
   ]
+  if (is_chromeos) {
+    deps += [
+      ":cr_network_icon",
+      ":cr_network_list",
+      ":cr_network_list_item",
+      ":cr_network_list_types",
+      ":cr_network_listener_behavior",
+      ":cr_network_select",
+    ]
+  }
 }
 
-js_library("cr_network_icon") {
-  deps = [
-    ":cr_onc_types",
-    "//ui/webui/resources/js:assert",
-  ]
-}
+if (is_chromeos) {
+  js_library("cr_network_icon") {
+    deps = [
+      ":cr_onc_types",
+      "//ui/webui/resources/js:assert",
+    ]
+  }
 
-js_library("cr_network_list") {
-  deps = [
-    ":cr_network_list_types",
-    ":cr_onc_types",
-    "//ui/webui/resources/cr_elements:cr_scrollable_behavior",
-  ]
-}
+  js_library("cr_network_list") {
+    deps = [
+      ":cr_network_list_types",
+      ":cr_onc_types",
+      "//ui/webui/resources/cr_elements:cr_scrollable_behavior",
+    ]
+  }
 
-js_library("cr_network_list_item") {
-  deps = [
-    ":cr_network_list_types",
-    ":cr_onc_types",
-    "//ui/webui/resources/cr_elements/policy:cr_policy_network_behavior",
-    "//ui/webui/resources/js:assert",
-  ]
-}
+  js_library("cr_network_list_item") {
+    deps = [
+      ":cr_network_list_types",
+      ":cr_onc_types",
+      "//ui/webui/resources/cr_elements/policy:cr_policy_network_behavior",
+      "//ui/webui/resources/js:assert",
+    ]
+  }
 
-js_library("cr_network_list_types") {
-  deps = [
-    ":cr_onc_types",
-  ]
-}
+  js_library("cr_network_list_types") {
+    deps = [
+      ":cr_onc_types",
+    ]
+  }
 
-js_library("cr_network_listener_behavior") {
-  deps = [
-    "//ui/webui/resources/js:assert",
-  ]
-  externs_list = [ "$externs_path/networking_private.js" ]
-  extra_sources = [ "$interfaces_path/networking_private_interface.js" ]
-}
+  js_library("cr_network_listener_behavior") {
+    deps = [
+      "//ui/webui/resources/js:assert",
+    ]
+    externs_list = [ "$externs_path/networking_private.js" ]
+    extra_sources = [ "$interfaces_path/networking_private_interface.js" ]
+  }
 
-js_library("cr_network_select") {
-  deps = [
-    ":cr_network_list_types",
-    ":cr_onc_types",
-    "//ui/webui/resources/js:util",
-  ]
-  externs_list = [ "$externs_path/networking_private.js" ]
+  js_library("cr_network_select") {
+    deps = [
+      ":cr_network_list_types",
+      ":cr_onc_types",
+      "//ui/webui/resources/js:util",
+    ]
+    externs_list = [ "$externs_path/networking_private.js" ]
+  }
 }
 
 js_library("cr_onc_types") {
diff --git a/ui/webui/resources/cr_elements/cr_button/cr_button.html b/ui/webui/resources/cr_elements/cr_button/cr_button.html
index ae6392de..58c3d13 100644
--- a/ui/webui/resources/cr_elements/cr_button/cr_button.html
+++ b/ui/webui/resources/cr_elements/cr_button/cr_button.html
@@ -12,7 +12,7 @@
     <style include="cr-hidden-style">
       :host {
         --active-shadow-rgb: var(--google-grey-800-rgb);
-        --active-shadow-action-rgb: var(--google-blue-500-rgb);
+        --active-shadow-action-rgb: var(--google-blue-refresh-500-rgb);
         --bg-action: var(--google-blue-600);
         --border-color: var(--google-grey-refresh-300);
         --disabled-bg-action: var(--google-grey-refresh-100);
@@ -20,7 +20,9 @@
         --disabled-border-color: var(--google-grey-refresh-100);
         --focus-shadow-color: rgba(var(--google-blue-600-rgb), .4);
         --hover-bg-action: rgba(var(--google-blue-600-rgb), .9);
-        --hover-bg-color: rgba(var(--google-blue-500-rgb), .04);
+        --hover-bg-color: rgba(var(--google-blue-refresh-500-rgb), .04);
+        --hover-border-color: var(--google-blue-refresh-100);
+        --hover-shadow-action-rgb: var(--google-blue-refresh-500-rgb);
         --ink-color-action: white;
         /* Blue-ish color used either as a background or as a text color,
          * depending on the type of button. */
@@ -95,7 +97,7 @@
 
       @media (prefers-color-scheme: light) {
         :host(:hover) {
-          border-color: var(--google-blue-refresh-100);
+          border-color: var(--hover-border-color);
         }
       }
 
@@ -117,10 +119,11 @@
         background: var(--hover-bg-action);
       }
 
-      @media (prefers-color-scheme: dark) {
-        :host(.action-button:hover) {
-          box-shadow: 0 1px 2px 0 rgba(var(--google-blue-500-rgb), .3),
-              0 1px 3px 1px rgba(var(--google-blue-500-rgb), .15);
+      @media (prefers-color-scheme: light) {
+        :host(.action-button:not(:active):hover) {
+          box-shadow:
+              0 1px 2px 0 rgba(var(--hover-shadow-action-rgb), .3),
+              0 1px 3px 1px rgba(var(--hover-shadow-action-rgb), .15);
         }
       }
 
diff --git a/ui/webui/resources/cr_elements/policy/BUILD.gn b/ui/webui/resources/cr_elements/policy/BUILD.gn
index 743f989..e758d2e 100644
--- a/ui/webui/resources/cr_elements/policy/BUILD.gn
+++ b/ui/webui/resources/cr_elements/policy/BUILD.gn
@@ -8,12 +8,16 @@
   deps = [
     ":cr_policy_indicator",
     ":cr_policy_indicator_behavior",
-    ":cr_policy_network_behavior",
-    ":cr_policy_network_indicator",
     ":cr_policy_pref_behavior",
     ":cr_policy_pref_indicator",
     ":cr_tooltip_icon",
   ]
+  if (is_chromeos) {
+    deps += [
+      ":cr_policy_network_behavior",
+      ":cr_policy_network_indicator",
+    ]
+  }
 }
 
 js_library("cr_policy_indicator") {
@@ -43,20 +47,22 @@
   externs_list = [ "$externs_path/settings_private.js" ]
 }
 
-js_library("cr_policy_network_behavior") {
-  deps = [
-    ":cr_policy_indicator_behavior",
-    "../chromeos/network:cr_onc_types",
-  ]
-}
+if (is_chromeos) {
+  js_library("cr_policy_network_behavior") {
+    deps = [
+      ":cr_policy_indicator_behavior",
+      "../chromeos/network:cr_onc_types",
+    ]
+  }
 
-js_library("cr_policy_network_indicator") {
-  deps = [
-    ":cr_policy_indicator_behavior",
-    ":cr_policy_network_behavior",
-    ":cr_tooltip_icon",
-    "../chromeos/network:cr_onc_types",
-  ]
+  js_library("cr_policy_network_indicator") {
+    deps = [
+      ":cr_policy_indicator_behavior",
+      ":cr_policy_network_behavior",
+      ":cr_tooltip_icon",
+      "../chromeos/network:cr_onc_types",
+    ]
+  }
 }
 
 js_library("cr_tooltip_icon") {
diff --git a/url/gurl.cc b/url/gurl.cc
index 84e7bf1..91b0f24 100644
--- a/url/gurl.cc
+++ b/url/gurl.cc
@@ -8,6 +8,7 @@
 
 #include <algorithm>
 #include <ostream>
+#include <utility>
 
 #include "base/lazy_instance.h"
 #include "base/logging.h"
@@ -339,16 +340,11 @@
 }
 
 bool GURL::IsAboutBlank() const {
-  if (!SchemeIs(url::kAboutScheme))
-    return false;
+  return IsAboutUrl(url::kAboutBlankPath);
+}
 
-  if (has_host() || has_username() || has_password() || has_port())
-    return false;
-
-  if (path() != url::kAboutBlankPath && path() != url::kAboutBlankWithHashPath)
-    return false;
-
-  return true;
+bool GURL::IsAboutSrcdoc() const {
+  return IsAboutUrl(url::kAboutSrcdocPath);
 }
 
 bool GURL::SchemeIs(base::StringPiece lower_ascii_scheme) const {
@@ -487,6 +483,30 @@
          (parsed_.inner_parsed() ? sizeof(url::Parsed) : 0);
 }
 
+bool GURL::IsAboutUrl(base::StringPiece allowed_path) const {
+  if (!SchemeIs(url::kAboutScheme))
+    return false;
+
+  if (has_host() || has_username() || has_password() || has_port())
+    return false;
+
+  if (!path_piece().starts_with(allowed_path))
+    return false;
+
+  if (path_piece().size() == allowed_path.size()) {
+    DCHECK_EQ(path_piece(), allowed_path);
+    return true;
+  }
+
+  if ((path_piece().size() == allowed_path.size() + 1) &&
+      path_piece().back() == '/') {
+    DCHECK_EQ(path_piece(), allowed_path.as_string() + '/');
+    return true;
+  }
+
+  return false;
+}
+
 std::ostream& operator<<(std::ostream& out, const GURL& url) {
   return out << url.possibly_invalid_spec();
 }
diff --git a/url/gurl.h b/url/gurl.h
index 1b0669e..9c680e0 100644
--- a/url/gurl.h
+++ b/url/gurl.h
@@ -217,6 +217,10 @@
   // about:blank/#foo.
   bool IsAboutBlank() const;
 
+  // Returns true when the url is of the form about:srcdoc, about:srcdoc?foo or
+  // about:srcdoc/#foo.
+  bool IsAboutSrcdoc() const;
+
   // Returns true if the given parameter (should be lower-case ASCII to match
   // the canonicalized scheme) is the scheme for this URL. Do not include a
   // colon.
@@ -445,6 +449,9 @@
 
   void InitializeFromCanonicalSpec();
 
+  // Helper used by IsAboutBlank and IsAboutSrcdoc.
+  bool IsAboutUrl(base::StringPiece allowed_path) const;
+
   // Returns the substring of the input identified by the given component.
   std::string ComponentString(const url::Component& comp) const {
     if (comp.len <= 0)
diff --git a/url/gurl_unittest.cc b/url/gurl_unittest.cc
index 379c04f..0d7b65b 100644
--- a/url/gurl_unittest.cc
+++ b/url/gurl_unittest.cc
@@ -863,11 +863,34 @@
   const std::string kNotAboutBlankUrls[] = {
       "http:blank",      "about:blan",          "about://blank",
       "about:blank/foo", "about://:8000/blank", "about://foo:foo@/blank",
-      "foo@about:blank", "foo:bar@about:blank", "about:blank:8000"};
+      "foo@about:blank", "foo:bar@about:blank", "about:blank:8000",
+      "about:blANk"};
   for (const auto& url : kNotAboutBlankUrls)
     EXPECT_FALSE(GURL(url).IsAboutBlank()) << url;
 }
 
+TEST(GURLTest, IsAboutSrcdoc) {
+  const std::string kAboutSrcdocUrls[] = {
+      "about:srcdoc", "about:srcdoc/", "about:srcdoc?foo", "about:srcdoc/#foo",
+      "about:srcdoc?foo#foo"};
+  for (const auto& url : kAboutSrcdocUrls)
+    EXPECT_TRUE(GURL(url).IsAboutSrcdoc()) << url;
+
+  const std::string kNotAboutSrcdocUrls[] = {"http:srcdoc",
+                                             "about:srcdo",
+                                             "about://srcdoc",
+                                             "about://srcdoc\\",
+                                             "about:srcdoc/foo",
+                                             "about://:8000/srcdoc",
+                                             "about://foo:foo@/srcdoc",
+                                             "foo@about:srcdoc",
+                                             "foo:bar@about:srcdoc",
+                                             "about:srcdoc:8000",
+                                             "about:srCDOc"};
+  for (const auto& url : kNotAboutSrcdocUrls)
+    EXPECT_FALSE(GURL(url).IsAboutSrcdoc()) << url;
+}
+
 TEST(GURLTest, EqualsIgnoringRef) {
   const struct {
     const char* url_a;
diff --git a/url/url_constants.cc b/url/url_constants.cc
index 110c6a7b..38f86bbb 100644
--- a/url/url_constants.cc
+++ b/url/url_constants.cc
@@ -9,7 +9,7 @@
 const char kAboutBlankURL[] = "about:blank";
 
 const char kAboutBlankPath[] = "blank";
-const char kAboutBlankWithHashPath[] = "blank/";
+const char kAboutSrcdocPath[] = "srcdoc";
 
 const char kAboutScheme[] = "about";
 const char kBlobScheme[] = "blob";
diff --git a/url/url_constants.h b/url/url_constants.h
index 38a0e38..7f322f89 100644
--- a/url/url_constants.h
+++ b/url/url_constants.h
@@ -14,7 +14,7 @@
 COMPONENT_EXPORT(URL) extern const char kAboutBlankURL[];
 
 COMPONENT_EXPORT(URL) extern const char kAboutBlankPath[];
-COMPONENT_EXPORT(URL) extern const char kAboutBlankWithHashPath[];
+COMPONENT_EXPORT(URL) extern const char kAboutSrcdocPath[];
 
 COMPONENT_EXPORT(URL) extern const char kAboutScheme[];
 COMPONENT_EXPORT(URL) extern const char kBlobScheme[];