diff --git a/DEPS b/DEPS
index d82411c..068a4339 100644
--- a/DEPS
+++ b/DEPS
@@ -121,11 +121,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': 'eceb19f37e0ab68a77618239c33e163ea697bb04',
+  'skia_revision': 'd8a90f9be1f068dd8231cc6c9e9d490d17e7ece6',
   # 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': '6bf2d7999b7d4193221f058a70962b181abf58d3',
+  'v8_revision': '6e89f8f552fe46cbf81a5bbb40066f881b2e7e65',
   # 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.
@@ -133,7 +133,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '4c94788c9c8533ae40aeb87f26d45fd7ca112f23',
+  'angle_revision': '0f073667daf00e894e17cbe7d40187c881552648',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling build tools
   # and whatever else without interference from each other.
@@ -145,7 +145,7 @@
   # 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': 'b600f15d3895afac7e6a39b309d59c4229b177f3',
+  'pdfium_revision': '57a2246b527fcdcd434aad381067c62fd9d583af',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -181,11 +181,11 @@
   # 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': '556d7714fda3de581bcc7b18e612a4aa63fdd244',
+  'catapult_revision': 'eae881c2a8ba3cea65d9e610ce347373486f91b4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
-  'libfuzzer_revision': 'ee7a5b85c7cf017a138920ae4fe8cc65cb2b5625',
+  'libfuzzer_revision': '6134addcf3523f4fda9e3add9fa1850f8a3ee48b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-node-modules
   # and whatever else without interference from each other.
@@ -245,7 +245,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '1c0b0474c1a01a88e7e51a0b5a18771d96aacf88',
+  'dawn_revision': '79ff8bead9091f8041f623c1109c290154211f21',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -679,7 +679,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'd403a66dc72a6484c0a6e8693be9c44eea2c0d26',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '2900d34d5975cc467e393fcf26bad649706e858c',
       'condition': 'checkout_linux',
   },
 
@@ -704,7 +704,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'b19e8dff155d6bb50ddbb8b7ddd12b4b28fec7b4',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '3f812d07b2aa257fcdb7860e6cdd53e24b283bca',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -1240,7 +1240,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@6ca723bad79bad35876ae2fa540536f51b14084a',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@56f3eeaa1714342e2fed47845f821dfa923a5d28',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 7e2302fd..afa1de18 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1518,7 +1518,6 @@
 
   deps = [
     ":ash_shell_lib",
-    ":manifest",
     ":test_support",
     "//ash/components/quick_launch:lib",
     "//ash/components/quick_launch:manifest",
@@ -1530,6 +1529,8 @@
     "//ash/components/tap_visualizer:manifest",
     "//ash/components/tap_visualizer/public/mojom",
     "//ash/public/cpp",
+    "//ash/public/cpp:manifest",
+    "//ash/public/cpp:manifest_for_tests",
     "//base:i18n",
     "//chrome:packed_resources",
     "//chromeos",
@@ -1892,7 +1893,6 @@
   deps = [
     ":ash",
     ":ash_service_resources",
-    ":manifest",
     ":test_support",
     "//ash/app_list:test_support",
     "//ash/app_list/presenter",
@@ -1908,6 +1908,8 @@
     "//ash/components/tap_visualizer:unit_tests",
     "//ash/keyboard/arc",
     "//ash/public/cpp",
+    "//ash/public/cpp:manifest",
+    "//ash/public/cpp:manifest_for_tests",
     "//ash/public/cpp:test_support",
     "//ash/public/cpp:unit_tests",
     "//ash/public/cpp/vector_icons",
@@ -2288,12 +2290,6 @@
   ]
 }
 
-service_manifest("manifest") {
-  name = "ash"
-  source = "manifest.json"
-  packaged_services = [ "//services/ws:manifest" ]
-}
-
 # TODO: Load locale-specific strings.
 # TODO: Avoid duplication between Mash and Chrome pak files: crbug.com/628715.
 repack("ash_service_resources") {
diff --git a/ash/accelerators/accelerator_controller_unittest.cc b/ash/accelerators/accelerator_controller_unittest.cc
index 5f85b081..18a7ba8 100644
--- a/ash/accelerators/accelerator_controller_unittest.cc
+++ b/ash/accelerators/accelerator_controller_unittest.cc
@@ -943,7 +943,7 @@
 
 namespace {
 
-// Tests the four combinations of the TOGGLE_CAPS_LOCK accelerator.
+// Tests the TOGGLE_CAPS_LOCK accelerator.
 TEST_F(AcceleratorControllerTest, ToggleCapsLockAccelerators) {
   ImeController* controller = Shell::Get()->ime_controller();
 
@@ -1041,6 +1041,20 @@
     EXPECT_TRUE(controller->IsCapsLockEnabled());
     controller->UpdateCapsLockState(false);
   }
+
+  // 7. Toggle CapsLock shortcut should still work after fake events generated.
+  // (https://crbug.com/918317).
+  generator->PressKey(ui::VKEY_PROCESSKEY, ui::EF_IME_FABRICATED_KEY);
+  generator->ReleaseKey(ui::VKEY_UNKNOWN, ui::EF_IME_FABRICATED_KEY);
+
+  // Press Search, Press Alt, Release Search, Release Alt. CapsLock should be
+  // triggered.
+  EXPECT_FALSE(ProcessInController(press_search_then_alt));
+  EXPECT_TRUE(ProcessInController(release_search_before_alt));
+  controller->FlushMojoForTesting();
+  EXPECT_EQ(6, client.set_caps_lock_count_);
+  EXPECT_TRUE(controller->IsCapsLockEnabled());
+  controller->UpdateCapsLockState(false);
 }
 
 class PreferredReservedAcceleratorsTest : public AshTestBase {
diff --git a/ash/accelerators/exit_warning_handler.h b/ash/accelerators/exit_warning_handler.h
index c678f84d..75884c8a 100644
--- a/ash/accelerators/exit_warning_handler.h
+++ b/ash/accelerators/exit_warning_handler.h
@@ -56,6 +56,7 @@
 
  private:
   friend class AcceleratorControllerTest;
+  friend class OverviewSessionTest;
 
   enum State { IDLE, WAIT_FOR_DOUBLE_PRESS, EXITING };
 
diff --git a/ash/accessibility/accessibility_focus_ring_controller.cc b/ash/accessibility/accessibility_focus_ring_controller.cc
index 66e9880..abc3a496 100644
--- a/ash/accessibility/accessibility_focus_ring_controller.cc
+++ b/ash/accessibility/accessibility_focus_ring_controller.cc
@@ -66,14 +66,14 @@
     SkColor color,
     const std::string& caller_id) {
   AccessibilityFocusRingGroup* focus_ring_group =
-      GetFocusRingGroupForCallerId(caller_id, /* Create if missing */ true);
+      GetFocusRingGroupForCallerId(caller_id, true /* Create if missing */);
   focus_ring_group->SetColor(color, this);
 }
 
 void AccessibilityFocusRingController::ResetFocusRingColor(
     const std::string& caller_id) {
   AccessibilityFocusRingGroup* focus_ring_group =
-      GetFocusRingGroupForCallerId(caller_id, /* Do not create */ false);
+      GetFocusRingGroupForCallerId(caller_id, false /* Do not create */);
   if (!focus_ring_group)
     return;
   focus_ring_group->ResetColor(this);
@@ -84,7 +84,7 @@
     mojom::FocusRingBehavior focus_ring_behavior,
     const std::string& caller_id) {
   AccessibilityFocusRingGroup* focus_ring_group =
-      GetFocusRingGroupForCallerId(caller_id, /* Create if missing */ true);
+      GetFocusRingGroupForCallerId(caller_id, true /* Create if missing */);
   if (focus_ring_group->SetFocusRectsAndBehavior(rects, focus_ring_behavior,
                                                  this))
     OnLayerChange(focus_ring_group->focus_animation_info());
@@ -93,7 +93,7 @@
 void AccessibilityFocusRingController::HideFocusRing(
     const std::string& caller_id) {
   AccessibilityFocusRingGroup* focus_ring_group =
-      GetFocusRingGroupForCallerId(caller_id, /* Do not create */ false);
+      GetFocusRingGroupForCallerId(caller_id, false /* Do not create */);
   if (!focus_ring_group)
     return;
   focus_ring_group->ClearFocusRects(this);
@@ -114,6 +114,23 @@
   UpdateHighlightFromHighlightRects();
 }
 
+void AccessibilityFocusRingController::EnableDoubleFocusRing(
+    SkColor color,
+    const std::string& caller_id) {
+  AccessibilityFocusRingGroup* focus_ring_group =
+      GetFocusRingGroupForCallerId(caller_id, true /* Create if missing */);
+  focus_ring_group->EnableDoubleFocusRing(color, this);
+}
+
+void AccessibilityFocusRingController::DisableDoubleFocusRing(
+    const std::string& caller_id) {
+  AccessibilityFocusRingGroup* focus_ring_group =
+      GetFocusRingGroupForCallerId(caller_id, false /* Do not create */);
+  if (!focus_ring_group)
+    return;
+  focus_ring_group->DisableDoubleFocusRing(this);
+}
+
 void AccessibilityFocusRingController::UpdateHighlightFromHighlightRects() {
   if (!highlight_layer_)
     highlight_layer_ = std::make_unique<AccessibilityHighlightLayer>(this);
diff --git a/ash/accessibility/accessibility_focus_ring_controller.h b/ash/accessibility/accessibility_focus_ring_controller.h
index 0cf323cf4..a7464bf 100644
--- a/ash/accessibility/accessibility_focus_ring_controller.h
+++ b/ash/accessibility/accessibility_focus_ring_controller.h
@@ -49,6 +49,9 @@
   void SetHighlights(const std::vector<gfx::Rect>& rects,
                      SkColor color) override;
   void HideHighlights() override;
+  void EnableDoubleFocusRing(SkColor color,
+                             const std::string& caller_id) override;
+  void DisableDoubleFocusRing(const std::string& caller_id) override;
 
   // Draw a ring around the mouse cursor. It fades out automatically.
   void SetCursorRing(const gfx::Point& location);
diff --git a/ash/accessibility/accessibility_focus_ring_group.cc b/ash/accessibility/accessibility_focus_ring_group.cc
index 1cda6af1..ffd7811 100644
--- a/ash/accessibility/accessibility_focus_ring_group.cc
+++ b/ash/accessibility/accessibility_focus_ring_group.cc
@@ -67,6 +67,19 @@
   UpdateFocusRingsFromFocusRects(delegate);
 }
 
+void AccessibilityFocusRingGroup::EnableDoubleFocusRing(
+    SkColor color,
+    AccessibilityLayerDelegate* delegate) {
+  focus_ring_secondary_color_ = color;
+  UpdateFocusRingsFromFocusRects(delegate);
+}
+
+void AccessibilityFocusRingGroup::DisableDoubleFocusRing(
+    AccessibilityLayerDelegate* delegate) {
+  focus_ring_secondary_color_.reset();
+  UpdateFocusRingsFromFocusRects(delegate);
+}
+
 void AccessibilityFocusRingGroup::UpdateFocusRingsFromFocusRects(
     AccessibilityLayerDelegate* delegate) {
   previous_focus_rings_.swap(focus_rings_);
@@ -96,10 +109,15 @@
   }
 
   for (size_t i = 0; i < focus_rings_.size(); ++i) {
-    if (focus_ring_color_) {
+    if (focus_ring_color_)
       focus_layers_[i]->SetColor(*(focus_ring_color_));
-    } else
+    else
       focus_layers_[i]->ResetColor();
+
+    if (focus_ring_secondary_color_)
+      focus_layers_[i]->EnableDoubleFocusRing(*(focus_ring_secondary_color_));
+    else
+      focus_layers_[i]->DisableDoubleFocusRing();
   }
 }
 
diff --git a/ash/accessibility/accessibility_focus_ring_group.h b/ash/accessibility/accessibility_focus_ring_group.h
index 7f1c3ae..e74895e0 100644
--- a/ash/accessibility/accessibility_focus_ring_group.h
+++ b/ash/accessibility/accessibility_focus_ring_group.h
@@ -31,6 +31,10 @@
   void SetColor(SkColor color, AccessibilityLayerDelegate* delegate);
   void ResetColor(AccessibilityLayerDelegate* delegate);
 
+  void EnableDoubleFocusRing(SkColor secondary_color,
+                             AccessibilityLayerDelegate* delegate);
+  void DisableDoubleFocusRing(AccessibilityLayerDelegate* delegate);
+
   void UpdateFocusRingsFromFocusRects(AccessibilityLayerDelegate* delegate);
   bool CanAnimate() const;
   void AnimateFocusRings(base::TimeTicks timestamp);
@@ -77,6 +81,7 @@
 
   std::vector<gfx::Rect> focus_rects_;
   base::Optional<SkColor> focus_ring_color_;
+  base::Optional<SkColor> focus_ring_secondary_color_;
   std::vector<AccessibilityFocusRing> previous_focus_rings_;
   std::vector<std::unique_ptr<AccessibilityFocusRingLayer>> focus_layers_;
   std::vector<AccessibilityFocusRing> focus_rings_;
diff --git a/ash/app_launch_unittest.cc b/ash/app_launch_unittest.cc
index 8813629..cad40f3 100644
--- a/ash/app_launch_unittest.cc
+++ b/ash/app_launch_unittest.cc
@@ -4,7 +4,8 @@
 
 #include "ash/components/quick_launch/manifest.h"
 #include "ash/components/quick_launch/public/mojom/constants.mojom.h"
-#include "ash/manifest.h"
+#include "ash/public/cpp/manifest.h"
+#include "ash/public/cpp/test_manifest.h"
 #include "ash/public/interfaces/constants.mojom.h"
 #include "base/bind.h"
 #include "base/command_line.h"
@@ -32,7 +33,9 @@
  public:
   AppLaunchTest()
       : test_service_manager_(
-            {GetManifest(), quick_launch_app::GetManifest(),
+            {service_manager::Manifest(GetManifest())
+                 .Amend(GetManifestOverlayForTesting()),
+             quick_launch_app::GetManifest(),
              service_manager::ManifestBuilder()
                  .WithServiceName(kTestServiceName)
                  .RequireCapability(mojom::kServiceName, "")
diff --git a/ash/ash_service_unittest.cc b/ash/ash_service_unittest.cc
index 92c20b9..9bd86bd 100644
--- a/ash/ash_service_unittest.cc
+++ b/ash/ash_service_unittest.cc
@@ -6,7 +6,8 @@
 #include <memory>
 #include <vector>
 
-#include "ash/manifest.h"
+#include "ash/public/cpp/manifest.h"
+#include "ash/public/cpp/test_manifest.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/public/interfaces/constants.mojom.h"
 #include "ash/public/interfaces/window_properties.mojom.h"
@@ -78,7 +79,8 @@
  public:
   AshServiceTest()
       : test_service_manager_(
-            {GetManifest(),
+            {service_manager::Manifest(GetManifest())
+                 .Amend(GetManifestOverlayForTesting()),
              service_manager::ManifestBuilder()
                  .WithServiceName(kTestServiceName)
                  .RequireCapability(ws::mojom::kServiceName, "app")
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 06d8fd3..c16bd1e 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -1268,9 +1268,6 @@
       <message name="IDS_ASH_DISPLAY_FAILURE_SEND_FEEDBACK" desc="Button text below error report to allow user to send feedback report.">
         Send feedback
       </message>
-      <message name="IDS_ASH_DISPLAY_MIRRORING_NOT_SUPPORTED" desc="A message used to explain that mirroring with more than two displays is not supported.">
-        Mirroring with more than two displays is not supported.
-      </message>
       <message name="IDS_ASH_DISPLAY_RESOLUTION_CHANGE_ACCEPT" desc="A button label shown in the notification for the resolution change to accept the change">
         Accept
       </message>
diff --git a/ash/display/cursor_window_controller.cc b/ash/display/cursor_window_controller.cc
index 6e3a85b8..a8e65c5 100644
--- a/ash/display/cursor_window_controller.cc
+++ b/ash/display/cursor_window_controller.cc
@@ -133,8 +133,7 @@
   // early outing when there isn't a PrefService yet.
   Shell* shell = Shell::Get();
   display::DisplayManager* display_manager = shell->display_manager();
-  if ((display_manager->is_multi_mirroring_enabled() &&
-       display_manager->IsInSoftwareMirrorMode()) ||
+  if ((display_manager->IsInSoftwareMirrorMode()) ||
       display_manager->IsInUnifiedMode() ||
       display_manager->screen_capture_is_active()) {
     return true;
diff --git a/ash/display/display_configuration_controller.cc b/ash/display/display_configuration_controller.cc
index 7ad578a..ec6f43e 100644
--- a/ash/display/display_configuration_controller.cc
+++ b/ash/display/display_configuration_controller.cc
@@ -130,13 +130,6 @@
 }
 
 void DisplayConfigurationController::SetMirrorMode(bool mirror, bool throttle) {
-  if (!display_manager_->is_multi_mirroring_enabled() &&
-      display_manager_->num_connected_displays() > 2) {
-    ShowDisplayErrorNotification(
-        l10n_util::GetStringUTF16(IDS_ASH_DISPLAY_MIRRORING_NOT_SUPPORTED),
-        false);
-    return;
-  }
   if (display_manager_->num_connected_displays() <= 1 ||
       display_manager_->IsInMirrorMode() == mirror ||
       (throttle && IsLimited())) {
diff --git a/ash/display/display_manager_unittest.cc b/ash/display/display_manager_unittest.cc
index 7d0fce9..bb19f75 100644
--- a/ash/display/display_manager_unittest.cc
+++ b/ash/display/display_manager_unittest.cc
@@ -183,22 +183,6 @@
   DISALLOW_COPY_AND_ASSIGN(DisplayManagerTest);
 };
 
-class DisplayManagerTestDisableMultiMirroring : public DisplayManagerTest {
- public:
-  DisplayManagerTestDisableMultiMirroring() = default;
-  ~DisplayManagerTestDisableMultiMirroring() override = default;
-
-  // DisplayManagerTest:
-  void SetUp() override {
-    base::CommandLine::ForCurrentProcess()->AppendSwitch(
-        ::switches::kDisableMultiMirroring);
-    DisplayManagerTest::SetUp();
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(DisplayManagerTestDisableMultiMirroring);
-};
-
 TEST_F(DisplayManagerTest, UpdateDisplayTest) {
   EXPECT_EQ(1U, display_manager()->GetNumDisplays());
 
@@ -1032,17 +1016,6 @@
   EXPECT_TRUE(layout.HasSamePlacementList(*(expected_layout_builder.Build())));
 }
 
-// TODO(weidongg/774795) Remove test when multi mirroring is enabled by default.
-TEST_F(DisplayManagerTestDisableMultiMirroring, NoMirrorInThreeDisplays) {
-  UpdateDisplay("640x480,320x200,400x300");
-  ash::Shell::Get()->display_configuration_controller()->SetMirrorMode(true,
-                                                                       false);
-  EXPECT_FALSE(display_manager()->IsInMirrorMode());
-  EXPECT_EQ(3u, display_manager()->GetNumDisplays());
-  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_DISPLAY_MIRRORING_NOT_SUPPORTED),
-            GetDisplayErrorNotificationMessageForTest());
-}
-
 TEST_F(DisplayManagerTest, OverscanInsetsTest) {
   UpdateDisplay("0+0-500x500,0+501-400x400");
   reset();
@@ -2240,41 +2213,6 @@
   SetSoftwareMirrorMode(false);
 }
 
-// TODO(weidongg/774795) Remove test when multi mirroring is enabled by default.
-// Make sure this does not cause any crashes. See http://crbug.com/412910
-TEST_F(DisplayManagerTestDisableMultiMirroring,
-       SoftwareMirroringWithCompositingCursor) {
-  UpdateDisplay("300x400,400x500");
-
-  MirrorWindowTestApi test_api;
-  EXPECT_TRUE(test_api.GetHosts().empty());
-
-  display::ManagedDisplayInfo secondary_info =
-      display_manager()->GetDisplayInfo(
-          display_manager()->GetSecondaryDisplay().id());
-
-  display_manager()->SetSoftwareMirroring(true);
-  display_manager()->UpdateDisplays();
-
-  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
-  EXPECT_FALSE(root_windows[0]->Contains(test_api.GetCursorWindow()));
-
-  Shell::Get()->SetCursorCompositingEnabled(true);
-
-  EXPECT_TRUE(root_windows[0]->Contains(test_api.GetCursorWindow()));
-
-  // Removes the first display and keeps the second one.
-  display_manager()->SetSoftwareMirroring(false);
-  std::vector<display::ManagedDisplayInfo> new_info_list;
-  new_info_list.push_back(secondary_info);
-  display_manager()->OnNativeDisplaysChanged(new_info_list);
-
-  root_windows = Shell::GetAllRootWindows();
-  EXPECT_TRUE(root_windows[0]->Contains(test_api.GetCursorWindow()));
-
-  Shell::Get()->SetCursorCompositingEnabled(false);
-}
-
 TEST_F(DisplayManagerTest, InvertLayout) {
   EXPECT_EQ("left, 0",
             display::DisplayPlacement(display::DisplayPlacement::RIGHT, 0)
diff --git a/ash/display/window_tree_host_manager.cc b/ash/display/window_tree_host_manager.cc
index d039ad6..217055a 100644
--- a/ash/display/window_tree_host_manager.cc
+++ b/ash/display/window_tree_host_manager.cc
@@ -782,8 +782,7 @@
 
   // Enable cursor compositing, so that cursor could be mirrored to destination
   // displays along with other display content through reflector.
-  if (display_manager->is_multi_mirroring_enabled())
-    Shell::Get()->UpdateCursorCompositingEnabled();
+  Shell::Get()->UpdateCursorCompositingEnabled();
 }
 
 display::DisplayConfigurator* WindowTreeHostManager::display_configurator() {
diff --git a/ash/keyboard/ash_keyboard_controller.cc b/ash/keyboard/ash_keyboard_controller.cc
index 73997f2a..4a437fa 100644
--- a/ash/keyboard/ash_keyboard_controller.cc
+++ b/ash/keyboard/ash_keyboard_controller.cc
@@ -53,10 +53,6 @@
       keyboard_ui_factory_ ? keyboard_ui_factory_->CreateKeyboardUI()
                            : std::make_unique<AshKeyboardUI>(this),
       virtual_keyboard_controller_.get());
-
-  // Start preloading the virtual keyboard UI in the background, so that it
-  // shows up faster when needed.
-  keyboard_controller_->LoadKeyboardWindowInBackground();
 }
 
 void AshKeyboardController::DisableKeyboard() {
diff --git a/ash/keyboard/virtual_keyboard_controller_unittest.cc b/ash/keyboard/virtual_keyboard_controller_unittest.cc
index 81a3a64b..fec9ece 100644
--- a/ash/keyboard/virtual_keyboard_controller_unittest.cc
+++ b/ash/keyboard/virtual_keyboard_controller_unittest.cc
@@ -593,11 +593,6 @@
        ShowKeyboardInSecondaryDisplay) {
   UpdateDisplay("500x500,500x500");
 
-  // Load in the primary display.
-  keyboard_controller_->LoadKeyboardWindowInBackground();
-  // Wait for the keyboard window to load.
-  base::RunLoop().RunUntilIdle();
-
   // Show in secondary display.
   keyboard_controller_->ShowKeyboardInDisplay(GetSecondaryDisplay());
   EXPECT_EQ(GetSecondaryRootWindow(), keyboard_controller_->GetRootWindow());
diff --git a/ash/manifest.json b/ash/manifest.json
deleted file mode 100644
index 390317b6..0000000
--- a/ash/manifest.json
+++ /dev/null
@@ -1,93 +0,0 @@
-{
-  "name": "ash",
-  "display_name": "Ash Window Manager and Shell",
-  "sandbox_type": "none",
-  "options" : {
-    "instance_sharing" : "singleton"
-  },
-  "interface_provider_specs": {
-    "service_manager:connector": {
-      "provides": {
-        // Modifications here should correspond with changes to
-        // chrome_content_browser_manifest_overlay.json.
-        "system_ui": [
-          "app_list.mojom.AppList",
-          "ash.mojom.AcceleratorController",
-          "ash.mojom.AccessibilityController",
-          "ash.mojom.AccessibilityFocusRingController",
-          "ash.mojom.AppListController",
-          "ash.mojom.AshMessageCenterController",
-          "ash.mojom.AssistantAlarmTimerController",
-          "ash.mojom.AssistantController",
-          "ash.mojom.AssistantNotificationController",
-          "ash.mojom.AssistantScreenContextController",
-          "ash.mojom.AssistantSetupController",
-          "ash.mojom.AssistantVolumeControl",
-          "ash.mojom.CastConfig",
-          "ash.mojom.ContainedShellController",
-          "ash.mojom.CrosDisplayConfigController",
-          "ash.mojom.DockedMagnifierController",
-          "ash.mojom.EventRewriterController",
-          "ash.mojom.FirstRunHelper",
-          "ash.mojom.HighlighterController",
-          "ash.mojom.ImeController",
-          "ash.mojom.KeyboardController",
-          "ash.mojom.LocaleUpdateController",
-          "ash.mojom.LoginScreen",
-          "ash.mojom.MediaController",
-          "ash.mojom.NewWindowController",
-          "ash.mojom.NightLightController",
-          "ash.mojom.NoteTakingController",
-          "ash.mojom.ProcessCreationTimeRecorder",
-          "ash.mojom.SessionController",
-          "ash.mojom.ShelfController",
-          "ash.mojom.ShellState",
-          "ash.mojom.ShutdownController",
-          "ash.mojom.SplitViewController",
-          "ash.mojom.SystemTray",
-          "ash.mojom.TabletModeController",
-          "ash.mojom.TrayAction",
-          "ash.mojom.VoiceInteractionController",
-          "ash.mojom.VpnList",
-          "ash.mojom.WallpaperController"
-        ],
-        // Test-only interfaces.
-        "test": [
-          "ash.mojom.LoginScreenTestApi",
-          "ash.mojom.ShelfTestApi",
-          "ash.mojom.ShellTestApi",
-          "ash.mojom.StatusAreaWidgetTestApi",
-          "ash.mojom.SystemTrayTestApi",
-          "ash.mojom.TimeToFirstPresentRecorderTestApi"
-        ],
-        // Only chrome is allowed to use this (required as dbus runs in Chrome).
-        "display": [
-          "ash.mojom.AshDisplayController",
-          "ash.mojom.DisplayOutputProtection"
-        ],
-        "mus:window_manager": [ "ui.mojom.AcceleratorRegistrar" ],
-        "service_manager:service_factory": [
-          "service_manager.mojom.ServiceFactory"
-        ]
-      },
-      "requires": {
-        "*": [ "accessibility", "app" ],
-        "ash_pref_connector": [ "pref_connector" ],
-        "catalog": [ "directory" ],
-        "content": [ "navigation" ],
-        "device": [
-          "device:bluetooth_system",
-          "device:fingerprint"
-        ],
-        "local_state": [ "pref_client" ],
-        "multidevice_setup": [ "multidevice_setup" ],
-        "ui": [
-          "ozone",
-          "window_manager"
-        ],
-        "data_decoder": [ "image_decoder" ],
-        "viz": [ "viz_host" ]
-      }
-    }
-  }
-}
diff --git a/ash/media/media_controller.cc b/ash/media/media_controller.cc
index b3bb505c..3a36b3f6 100644
--- a/ash/media/media_controller.cc
+++ b/ash/media/media_controller.cc
@@ -117,7 +117,7 @@
 void MediaController::SetMediaSessionControllerForTest(
     media_session::mojom::MediaControllerPtr controller) {
   media_session_controller_ptr_ = std::move(controller);
-  BindMediaSessionObserver();
+  BindMediaControllerObserver();
 }
 
 void MediaController::FlushForTesting() {
@@ -139,7 +139,7 @@
         base::BindRepeating(&MediaController::OnMediaSessionControllerError,
                             base::Unretained(this)));
 
-    BindMediaSessionObserver();
+    BindMediaControllerObserver();
   }
 
   return media_session_controller_ptr_.get();
@@ -150,12 +150,12 @@
   supported_media_session_action_ = false;
 }
 
-void MediaController::BindMediaSessionObserver() {
+void MediaController::BindMediaControllerObserver() {
   if (!media_session_controller_ptr_.is_bound())
     return;
 
-  media_session::mojom::MediaSessionObserverPtr observer;
-  media_session_observer_binding_.Bind(mojo::MakeRequest(&observer));
+  media_session::mojom::MediaControllerObserverPtr observer;
+  media_controller_observer_binding_.Bind(mojo::MakeRequest(&observer));
   media_session_controller_ptr_->AddObserver(std::move(observer));
 }
 
diff --git a/ash/media/media_controller.h b/ash/media/media_controller.h
index ac5c3ef..16a706e7 100644
--- a/ash/media/media_controller.h
+++ b/ash/media/media_controller.h
@@ -13,7 +13,6 @@
 #include "mojo/public/cpp/bindings/associated_binding.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
 #include "services/media_session/public/mojom/media_controller.mojom.h"
-#include "services/media_session/public/mojom/media_session.mojom.h"
 
 namespace service_manager {
 class Connector;
@@ -38,7 +37,7 @@
 // been provided to us.
 class ASH_EXPORT MediaController
     : public mojom::MediaController,
-      public media_session::mojom::MediaSessionObserver {
+      public media_session::mojom::MediaControllerObserver {
  public:
   // |connector| can be null in tests.
   explicit MediaController(service_manager::Connector* connector);
@@ -66,7 +65,7 @@
   void RequestCaptureState();
   void SuspendMediaSessions();
 
-  // media_session::mojom::MediaSessionObserver:
+  // media_session::mojom::MediaControllerObserver:
   void MediaSessionInfoChanged(
       media_session::mojom::MediaSessionInfoPtr session_info) override {}
   void MediaSessionMetadataChanged(
@@ -99,7 +98,7 @@
 
   void OnMediaSessionControllerError();
 
-  void BindMediaSessionObserver();
+  void BindMediaControllerObserver();
 
   // Returns true if we should use the media session service for key handling.
   bool ShouldUseMediaSession();
@@ -113,8 +112,8 @@
 
   service_manager::Connector* const connector_;
 
-  mojo::Binding<media_session::mojom::MediaSessionObserver>
-      media_session_observer_binding_{this};
+  mojo::Binding<media_session::mojom::MediaControllerObserver>
+      media_controller_observer_binding_{this};
 
   // Bindings for users of the mojo interface.
   mojo::BindingSet<mojom::MediaController> bindings_;
diff --git a/ash/media/media_notification_item.cc b/ash/media/media_notification_item.cc
index 6ebc4c4..f044267b 100644
--- a/ash/media/media_notification_item.cc
+++ b/ash/media/media_notification_item.cc
@@ -40,9 +40,9 @@
       session_info_(std::move(session_info)) {
   // Bind an observer to the associated media session.
   if (media_controller_ptr_.is_bound()) {
-    media_session::mojom::MediaSessionObserverPtr media_session_observer;
-    observer_binding_.Bind(mojo::MakeRequest(&media_session_observer));
-    media_controller_ptr_->AddObserver(std::move(media_session_observer));
+    media_session::mojom::MediaControllerObserverPtr media_controller_observer;
+    observer_binding_.Bind(mojo::MakeRequest(&media_controller_observer));
+    media_controller_ptr_->AddObserver(std::move(media_controller_observer));
   }
 
   MaybeHideOrShowNotification();
diff --git a/ash/media/media_notification_item.h b/ash/media/media_notification_item.h
index 6b5b2781..38ea5f3 100644
--- a/ash/media/media_notification_item.h
+++ b/ash/media/media_notification_item.h
@@ -21,14 +21,14 @@
 // MediaNotificationItem manages hiding/showing a media notification and
 // updating the metadata for a single media session.
 class ASH_EXPORT MediaNotificationItem
-    : public media_session::mojom::MediaSessionObserver {
+    : public media_session::mojom::MediaControllerObserver {
  public:
   MediaNotificationItem(const std::string& id,
                         media_session::mojom::MediaControllerPtr controller,
                         media_session::mojom::MediaSessionInfoPtr session_info);
   ~MediaNotificationItem() override;
 
-  // media_session::mojom::MediaSessionObserver:
+  // media_session::mojom::MediaControllerObserver:
   void MediaSessionInfoChanged(
       media_session::mojom::MediaSessionInfoPtr session_info) override;
   void MediaSessionMetadataChanged(
@@ -69,8 +69,8 @@
 
   std::set<media_session::mojom::MediaSessionAction> session_actions_;
 
-  mojo::Binding<media_session::mojom::MediaSessionObserver> observer_binding_{
-      this};
+  mojo::Binding<media_session::mojom::MediaControllerObserver>
+      observer_binding_{this};
 
   base::WeakPtrFactory<MediaNotificationItem> weak_ptr_factory_{this};
 
diff --git a/ash/metrics/demo_session_metrics_recorder.cc b/ash/metrics/demo_session_metrics_recorder.cc
index f27ee656..4378eb6d 100644
--- a/ash/metrics/demo_session_metrics_recorder.cc
+++ b/ash/metrics/demo_session_metrics_recorder.cc
@@ -4,6 +4,7 @@
 
 #include "ash/metrics/demo_session_metrics_recorder.h"
 
+#include <iostream>
 #include <string>
 #include <utility>
 
@@ -12,6 +13,7 @@
 #include "ash/shelf/shelf_window_watcher.h"
 #include "ash/shell.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/scoped_observer.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "extensions/common/constants.h"
@@ -20,6 +22,7 @@
 #include "ui/aura/window.h"
 #include "ui/base/ui_base_features.h"
 #include "ui/base/user_activity/user_activity_detector.h"
+#include "ui/wm/core/focus_controller.h"
 #include "ui/wm/public/activation_client.h"
 
 namespace ash {
@@ -46,6 +49,13 @@
     return DemoModeApp::kHighlights;
   }
 
+  // Each version of the Screensaver app is bucketed into the same value.
+  if (app_id == extension_misc::kScreensaverAppId ||
+      app_id == extension_misc::kScreensaverAlt1AppId ||
+      app_id == extension_misc::kScreensaverAlt2AppId) {
+    return DemoModeApp::kScreensaver;
+  }
+
   if (app_id == extension_misc::kCameraAppId)
     return DemoModeApp::kCamera;
   if (app_id == extension_misc::kFilesManagerAppId)
@@ -86,21 +96,30 @@
   return DemoModeApp::kOtherArcApp;
 }
 
+const std::string* GetArcPackageName(const aura::Window* window) {
+  return window->GetProperty(kArcPackageNameKey);
+}
+
+const ShelfID GetShelfID(const aura::Window* window) {
+  return ShelfID::Deserialize(window->GetProperty(kShelfIDKey));
+}
+
+AppType GetAppType(const aura::Window* window) {
+  return static_cast<AppType>(window->GetProperty(aura::client::kAppType));
+}
+
 // Maps the app-like thing in |window| to a DemoModeApp value for metrics.
 DemoModeApp GetAppFromWindow(const aura::Window* window) {
-  ash::AppType app_type =
-      static_cast<ash::AppType>(window->GetProperty(aura::client::kAppType));
-  if (app_type == ash::AppType::ARC_APP) {
+  AppType app_type = GetAppType(window);
+  if (app_type == AppType::ARC_APP) {
     // The ShelfID app id isn't used to identify ARC++ apps since it's a hash of
     // both the package name and the activity.
-    const std::string* package_name =
-        static_cast<std::string*>(window->GetProperty(kArcPackageNameKey));
+    const std::string* package_name = GetArcPackageName(window);
     return package_name ? GetAppFromPackageName(*package_name)
                         : DemoModeApp::kOtherArcApp;
   }
 
-  std::string app_id =
-      ShelfID::Deserialize(window->GetProperty(kShelfIDKey)).app_id;
+  std::string app_id = GetShelfID(window).app_id;
 
   // The Chrome "app" in the shelf is just the browser.
   if (app_id == extension_misc::kChromeAppId)
@@ -116,33 +135,141 @@
 
   // If the window is the "browser" type, having an app ID other than the
   // default indicates a hosted/bookmark app.
-  if (app_type == ash::AppType::CHROME_APP ||
-      (app_type == ash::AppType::BROWSER && !is_default(app_id))) {
+  if (app_type == AppType::CHROME_APP ||
+      (app_type == AppType::BROWSER && !is_default(app_id))) {
     return GetAppFromAppId(app_id);
   }
 
-  if (app_type == ash::AppType::BROWSER)
+  if (app_type == AppType::BROWSER)
     return DemoModeApp::kBrowser;
   return DemoModeApp::kOtherWindow;
 }
 
 }  // namespace
 
+// Observes for changes in a window's ArcPackageName property for the purpose of
+// logging of unique launches of ARC apps.
+class DemoSessionMetricsRecorder::UniqueAppsLaunchedArcPackageNameObserver
+    : public aura::WindowObserver {
+ public:
+  explicit UniqueAppsLaunchedArcPackageNameObserver(
+      DemoSessionMetricsRecorder* metrics_recorder)
+      : metrics_recorder_(metrics_recorder), scoped_observer_(this) {}
+
+  // aura::WindowObserver
+  void OnWindowPropertyChanged(aura::Window* window,
+                               const void* key,
+                               intptr_t old) override {
+    if (key != kArcPackageNameKey)
+      return;
+
+    const std::string* package_name = GetArcPackageName(window);
+
+    if (package_name != nullptr)
+      metrics_recorder_->RecordAppLaunch(*package_name);
+    else
+      VLOG(1) << "Got null ARC package name";
+
+    window->RemoveObserver(this);
+  }
+
+  void ObserveWindow(aura::Window* window) { scoped_observer_.Add(window); }
+
+  void OnWindowDestroyed(aura::Window* window) override {
+    window->RemoveObserver(this);
+  }
+
+ private:
+  DemoSessionMetricsRecorder* metrics_recorder_;
+  ScopedObserver<aura::Window, UniqueAppsLaunchedArcPackageNameObserver>
+      scoped_observer_;
+
+  DISALLOW_COPY_AND_ASSIGN(UniqueAppsLaunchedArcPackageNameObserver);
+};
+
 DemoSessionMetricsRecorder::DemoSessionMetricsRecorder(
     std::unique_ptr<base::RepeatingTimer> timer)
-    : timer_(std::move(timer)), observer_(this) {
+    : timer_(std::move(timer)),
+      observer_(this),
+      unique_apps_arc_package_name_observer_(
+          std::make_unique<UniqueAppsLaunchedArcPackageNameObserver>(this)) {
   // Outside of tests, use a normal repeating timer.
   if (!timer_.get())
     timer_ = std::make_unique<base::RepeatingTimer>();
 
   StartRecording();
   observer_.Add(ui::UserActivityDetector::Get());
+
+  // Subscribe to window activation updates.  Even though this gets us
+  // notifications for all window activations, we ignore the ARC
+  // notifications because they don't contain the app_id.  We handle
+  // accounting for ARC windows with OnTaskCreated.
+  if (Shell::Get()->GetPrimaryRootWindow()) {
+    activation_client_ = Shell::Get()->focus_controller();
+    activation_client_->AddObserver(this);
+  }
 }
 
 DemoSessionMetricsRecorder::~DemoSessionMetricsRecorder() {
   // Report any remaining stored samples on exit. (If the user went idle, there
   // won't be any.)
   ReportSamples();
+
+  // Unsubscribe from window activation events.
+  activation_client_->RemoveObserver(this);
+
+  ReportUniqueAppsLaunched();
+}
+
+// This method will only record 1 launch for each unique app_id, regardless of
+// how many times it is called with that app_id.
+void DemoSessionMetricsRecorder::RecordAppLaunch(const std::string& app_id) {
+  if (unique_apps_launched_recording_enabled_ &&
+      GetAppFromAppId(app_id) != DemoModeApp::kHighlights &&
+      GetAppFromAppId(app_id) != DemoModeApp::kScreensaver) {
+    unique_apps_launched_.insert(app_id);
+  }
+}
+
+void DemoSessionMetricsRecorder::OnWindowActivated(ActivationReason reason,
+                                                   aura::Window* gained_active,
+                                                   aura::Window* lost_active) {
+  if (!gained_active)
+    return;
+
+  // Don't count popup windows.
+  if (gained_active->type() != aura::client::WINDOW_TYPE_NORMAL)
+    return;
+
+  AppType app_type = GetAppType(gained_active);
+
+  std::string app_id;
+  if (app_type == AppType::ARC_APP) {
+    const std::string* package_name = GetArcPackageName(gained_active);
+
+    if (!package_name) {
+      // The package name property for the window has not been set yet.
+      // Listen for changes to the window properties so we can
+      // be informed when the package name gets set.
+      if (!gained_active->HasObserver(
+              unique_apps_arc_package_name_observer_.get())) {
+        unique_apps_arc_package_name_observer_->ObserveWindow(gained_active);
+      }
+      return;
+    }
+    app_id = *package_name;
+  } else {
+    // This is a non-ARC window, so we just get the shelf ID, which should
+    // be unique per app.
+    app_id = GetShelfID(gained_active).app_id;
+  }
+
+  // Some app_ids are empty, i.e the "You will be signed out
+  // in X seconds" modal dialog in Demo Mode, so skip those.
+  if (app_id.empty())
+    return;
+
+  RecordAppLaunch(app_id);
 }
 
 void DemoSessionMetricsRecorder::OnUserActivity(const ui::Event* event) {
@@ -156,6 +283,7 @@
 }
 
 void DemoSessionMetricsRecorder::StartRecording() {
+  unique_apps_launched_recording_enabled_ = true;
   timer_->Start(FROM_HERE, kSamplePeriod, this,
                 &DemoSessionMetricsRecorder::TakeSampleOrPause);
 }
@@ -170,7 +298,7 @@
   }
 
   const aura::Window* window =
-      ash::Shell::Get()->activation_client()->GetActiveWindow();
+      Shell::Get()->activation_client()->GetActiveWindow();
   if (!window)
     return;
 
@@ -186,4 +314,12 @@
   unreported_samples_.clear();
 }
 
+void DemoSessionMetricsRecorder::ReportUniqueAppsLaunched() {
+  if (unique_apps_launched_recording_enabled_)
+    UMA_HISTOGRAM_COUNTS_100("DemoMode.UniqueAppsLaunched",
+                             unique_apps_launched_.size());
+
+  unique_apps_launched_.clear();
+}
+
 }  // namespace ash
diff --git a/ash/metrics/demo_session_metrics_recorder.h b/ash/metrics/demo_session_metrics_recorder.h
index d21fbd0..635bafaf 100644
--- a/ash/metrics/demo_session_metrics_recorder.h
+++ b/ash/metrics/demo_session_metrics_recorder.h
@@ -6,12 +6,17 @@
 #define ASH_METRICS_DEMO_SESSION_METRICS_RECORDER_H_
 
 #include <memory>
+#include <string>
 #include <vector>
 
 #include "ash/ash_export.h"
+#include "base/containers/flat_set.h"
 #include "base/macros.h"
 #include "base/scoped_observer.h"
+#include "ui/aura/window_observer.h"
 #include "ui/base/user_activity/user_activity_observer.h"
+#include "ui/wm/public/activation_change_observer.h"
+#include "ui/wm/public/activation_client.h"
 
 namespace base {
 class RepeatingTimer;
@@ -25,7 +30,9 @@
 
 // A metrics recorder for demo sessions that samples the active window's app or
 // window type. Only used when the device is in Demo Mode.
-class ASH_EXPORT DemoSessionMetricsRecorder : public ui::UserActivityObserver {
+class ASH_EXPORT DemoSessionMetricsRecorder
+    : public ui::UserActivityObserver,
+      public wm::ActivationChangeObserver {
  public:
   // These apps are preinstalled in Demo Mode. This list is not exhaustive, and
   // includes first- and third-party Chrome and ARC apps.
@@ -52,9 +59,10 @@
     kSquid = 16,  // Android note-taking app.
     kWebStore = 17,
     kYouTube = 18,
+    kScreensaver = 19,  // Demo Mode screensaver app.
     // Add future entries above this comment, in sync with enums.xml.
     // Update kMaxValue to the last value.
-    kMaxValue = kYouTube,
+    kMaxValue = kScreensaver,
   };
 
   // The recorder will create a normal timer by default. Tests should provide a
@@ -66,6 +74,11 @@
   // ui::UserActivityObserver:
   void OnUserActivity(const ui::Event* event) override;
 
+  // wm::ActivationChangeObserver:
+  void OnWindowActivated(wm::ActivationChangeObserver::ActivationReason reason,
+                         aura::Window* gained_active,
+                         aura::Window* lost_active) override;
+
  private:
   // Starts the timer for periodic sampling.
   void StartRecording();
@@ -77,10 +90,30 @@
   // Emits histograms for recorded samples.
   void ReportSamples();
 
+  // Indicates whether the specified app_id should be recorded for
+  // the unique-apps-launched stat.
+  bool ShouldRecordAppLaunch(const std::string& app_id);
+
+  // Records the specified app_id's launch, subject to the
+  // restrictions of ShouldRecordAppLaunch().
+  void RecordAppLaunch(const std::string& app_id);
+
+  // Emits histograms for the number of unique apps launched.
+  void ReportUniqueAppsLaunched();
+
   // Stores samples as they are collected. Report to UMA if we see user
   // activity soon after. Guaranteed not to grow too large.
   std::vector<DemoModeApp> unreported_samples_;
 
+  // Indicates whether the unique-app-launch stats recording has been enabled.
+  bool unique_apps_launched_recording_enabled_ = false;
+
+  // Tracks the ids of apps that have been launched in Demo Mode.
+  base::flat_set<std::string> unique_apps_launched_;
+
+  // Used for subscribing to window activation events.
+  wm::ActivationClient* activation_client_ = nullptr;
+
   // How many periods have elapsed since the last user activity.
   int periods_since_activity_ = 0;
 
@@ -89,9 +122,14 @@
   ScopedObserver<ui::UserActivityDetector, DemoSessionMetricsRecorder>
       observer_;
 
+  class UniqueAppsLaunchedArcPackageNameObserver;
+
+  std::unique_ptr<UniqueAppsLaunchedArcPackageNameObserver>
+      unique_apps_arc_package_name_observer_;
+
   DISALLOW_COPY_AND_ASSIGN(DemoSessionMetricsRecorder);
 };
 
 }  // namespace ash
 
-#endif  // ASH_METRICS_POINTER_METRICS_RECORDER_H_
+#endif  // ASH_METRICS_DEMO_SESSION_METRICS_RECORDER_H_
diff --git a/ash/metrics/demo_session_metrics_recorder_unittest.cc b/ash/metrics/demo_session_metrics_recorder_unittest.cc
index ad50a8d4..71a4628 100644
--- a/ash/metrics/demo_session_metrics_recorder_unittest.cc
+++ b/ash/metrics/demo_session_metrics_recorder_unittest.cc
@@ -129,7 +129,8 @@
     window->SetProperty(
         kShelfIDKey,
         new std::string(ShelfID(app_id, std::string()).Serialize()));
-    window->SetProperty(kArcPackageNameKey, new std::string(package_name));
+    if (!package_name.empty())
+      window->SetProperty(kArcPackageNameKey, new std::string(package_name));
     return window;
   }
 
@@ -445,5 +446,79 @@
   histogram_tester_->ExpectTotalCount("DemoMode.ActiveApp", 0);
 }
 
+TEST_F(DemoSessionMetricsRecorderTest, UniqueAppsLaunchedOnDeletion) {
+  // Activate each window twice.  Despite activating each twice,
+  // the count should only be incremented once per unique app.
+  auto chrome_app_window = CreateChromeAppWindow(extension_misc::kCameraAppId);
+  wm::ActivateWindow(chrome_app_window.get());
+  wm::DeactivateWindow(chrome_app_window.get());
+  wm::ActivateWindow(chrome_app_window.get());
+
+  auto chrome_browser_window =
+      CreateChromeAppWindow(extension_misc::kChromeAppId);
+  wm::ActivateWindow(chrome_browser_window.get());
+  wm::DeactivateWindow(chrome_browser_window.get());
+  wm::ActivateWindow(chrome_browser_window.get());
+
+  auto arc_window_1 = CreateArcWindow("com.google.Photos");
+  wm::ActivateWindow(arc_window_1.get());
+  wm::DeactivateWindow(arc_window_1.get());
+  wm::ActivateWindow(arc_window_1.get());
+
+  auto arc_window_2 = CreateArcWindow("com.google.Maps");
+  wm::ActivateWindow(arc_window_2.get());
+  wm::DeactivateWindow(arc_window_2.get());
+  wm::ActivateWindow(arc_window_2.get());
+
+  // Popup windows shouldn't be counted at all.
+  auto popup_window = CreatePopupWindow();
+  wm::ActivateWindow(popup_window.get());
+  wm::DeactivateWindow(popup_window.get());
+  wm::ActivateWindow(popup_window.get());
+
+  DeleteMetricsRecorder();
+
+  histogram_tester_->ExpectUniqueSample("DemoMode.UniqueAppsLaunched", 4, 1);
+}
+
+TEST_F(DemoSessionMetricsRecorderTest,
+       NoUniqueAppsLaunchedOnMissingArcPackageName) {
+  // Create an ARC window with no package name set yet
+  auto arc_window_1 = CreateArcWindow("");
+  wm::ActivateWindow(arc_window_1.get());
+
+  DeleteMetricsRecorder();
+
+  // There shuld be no unique apps reported if there was no package name.
+  histogram_tester_->ExpectUniqueSample("DemoMode.UniqueAppsLaunched", 0, 1);
+}
+
+TEST_F(DemoSessionMetricsRecorderTest,
+       UniqueAppsLaunchedOnDelayedArcPackageName) {
+  // Create an ARC window with no package name set yet.
+  auto arc_window_1 = CreateArcWindow("");
+  wm::ActivateWindow(arc_window_1.get());
+
+  // Set the package name after window creation/activation.
+  arc_window_1->SetProperty(kArcPackageNameKey,
+                            new std::string("com.google.Photos"));
+
+  auto arc_window_2 = CreateArcWindow("com.google.Maps");
+  wm::ActivateWindow(arc_window_2.get());
+
+  DeleteMetricsRecorder();
+
+  // There should be 2 unique apps reported.
+  histogram_tester_->ExpectUniqueSample("DemoMode.UniqueAppsLaunched", 2, 1);
+}
+
+TEST_F(DemoSessionMetricsRecorderTest, NoUniqueAppsLaunchedOnDeletion) {
+  DeleteMetricsRecorder();
+
+  // There should be no samples if the recorder is deleted with 0 unique apps
+  // launched.
+  histogram_tester_->ExpectUniqueSample("DemoMode.UniqueAppsLaunched", 0, 1);
+}
+
 }  // namespace
 }  // namespace ash
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index 735660b5..4adb466 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -158,6 +158,46 @@
   output_name = "ash_public_cpp"
 }
 
+source_set("manifest") {
+  sources = [
+    "manifest.cc",
+    "manifest.h",
+  ]
+
+  deps = [
+    "//ash/public/interfaces:interfaces_internal",
+    "//base",
+    "//chromeos/services/multidevice_setup/public/mojom",
+    "//services/catalog/public/mojom",
+    "//services/content/public/mojom",
+    "//services/data_decoder/public/mojom",
+    "//services/device/public/mojom",
+    "//services/preferences/public/mojom",
+    "//services/service_manager/public/cpp",
+    "//services/service_manager/public/mojom",
+    "//services/viz/public/interfaces",
+    "//services/ws:manifest",
+    "//services/ws/public/mojom",
+  ]
+}
+
+source_set("manifest_for_tests") {
+  testonly = true
+
+  sources = [
+    "test_manifest.cc",
+    "test_manifest.h",
+  ]
+
+  deps = [
+    ":manifest",
+    "//ash/public/interfaces:test_interfaces",
+    "//base",
+    "//services/service_manager/public/cpp",
+    "//services/service_manager/public/mojom",
+  ]
+}
+
 # Using a test service because the traits need to pass handles around. Revisit
 # this after Deserialize(Serialize()) API works with handles.
 mojom("test_interfaces") {
diff --git a/ash/public/cpp/DEPS b/ash/public/cpp/DEPS
index dab1430..5465f0b 100644
--- a/ash/public/cpp/DEPS
+++ b/ash/public/cpp/DEPS
@@ -1,5 +1,8 @@
 include_rules = [
   "+components/prefs",
+  "+services/catalog/public",
+  "+services/data_decoder/public",
+  "+services/device/public",
   "+skia/public/interfaces",
   "+ui/display",
 ]
diff --git a/ash/public/cpp/OWNERS b/ash/public/cpp/OWNERS
index 09b138f0..978703ff 100644
--- a/ash/public/cpp/OWNERS
+++ b/ash/public/cpp/OWNERS
@@ -1,3 +1,8 @@
+per-file manifest.cc=set noparent
+per-file manifest.cc=file://ipc/SECURITY_OWNERS
+per-file manifest.h=set noparent
+per-file manifest.h=file://ipc/SECURITY_OWNERS
+
 per-file *shelf*=file://ash/shelf/OWNERS
 
 per-file *.mojom=set noparent
diff --git a/ash/public/cpp/manifest.cc b/ash/public/cpp/manifest.cc
new file mode 100644
index 0000000..03989db
--- /dev/null
+++ b/ash/public/cpp/manifest.cc
@@ -0,0 +1,134 @@
+// 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 "ash/public/cpp/manifest.h"
+
+#include "ash/public/interfaces/accelerator_controller.mojom.h"
+#include "ash/public/interfaces/accessibility_controller.mojom.h"
+#include "ash/public/interfaces/accessibility_focus_ring_controller.mojom.h"
+#include "ash/public/interfaces/app_list.mojom.h"
+#include "ash/public/interfaces/ash_display_controller.mojom.h"
+#include "ash/public/interfaces/ash_message_center_controller.mojom.h"
+#include "ash/public/interfaces/assistant_controller.mojom.h"
+#include "ash/public/interfaces/assistant_volume_control.mojom.h"
+#include "ash/public/interfaces/cast_config.mojom.h"
+#include "ash/public/interfaces/constants.mojom.h"
+#include "ash/public/interfaces/contained_shell.mojom.h"
+#include "ash/public/interfaces/cros_display_config.mojom.h"
+#include "ash/public/interfaces/display_output_protection.mojom.h"
+#include "ash/public/interfaces/docked_magnifier_controller.mojom.h"
+#include "ash/public/interfaces/event_rewriter_controller.mojom.h"
+#include "ash/public/interfaces/first_run_helper.mojom.h"
+#include "ash/public/interfaces/highlighter_controller.mojom.h"
+#include "ash/public/interfaces/ime_controller.mojom.h"
+#include "ash/public/interfaces/keyboard_controller.mojom.h"
+#include "ash/public/interfaces/locale.mojom.h"
+#include "ash/public/interfaces/login_screen.mojom.h"
+#include "ash/public/interfaces/media.mojom.h"
+#include "ash/public/interfaces/new_window.mojom.h"
+#include "ash/public/interfaces/night_light_controller.mojom.h"
+#include "ash/public/interfaces/note_taking_controller.mojom.h"
+#include "ash/public/interfaces/pref_connector.mojom.h"
+#include "ash/public/interfaces/process_creation_time_recorder.mojom.h"
+#include "ash/public/interfaces/session_controller.mojom.h"
+#include "ash/public/interfaces/shelf.mojom.h"
+#include "ash/public/interfaces/shutdown.mojom.h"
+#include "ash/public/interfaces/split_view.mojom.h"
+#include "ash/public/interfaces/system_tray.mojom.h"
+#include "ash/public/interfaces/tablet_mode.mojom.h"
+#include "ash/public/interfaces/tray_action.mojom.h"
+#include "ash/public/interfaces/voice_interaction_controller.mojom.h"
+#include "ash/public/interfaces/vpn_list.mojom.h"
+#include "ash/public/interfaces/wallpaper.mojom.h"
+#include "base/no_destructor.h"
+#include "chromeos/services/multidevice_setup/public/mojom/constants.mojom.h"
+#include "services/catalog/public/mojom/constants.mojom.h"
+#include "services/content/public/mojom/constants.mojom.h"
+#include "services/data_decoder/public/mojom/constants.mojom.h"
+#include "services/device/public/mojom/constants.mojom.h"
+#include "services/preferences/public/mojom/preferences.mojom.h"
+#include "services/service_manager/public/cpp/manifest_builder.h"
+#include "services/service_manager/public/mojom/service_factory.mojom.h"
+#include "services/viz/public/interfaces/constants.mojom.h"
+#include "services/ws/manifest.h"
+#include "services/ws/public/mojom/constants.mojom.h"
+
+namespace ash {
+
+service_manager::Manifest& GetAmendmentForTesting() {
+  static base::NoDestructor<service_manager::Manifest> amendment;
+  return *amendment;
+}
+
+const service_manager::Manifest& GetManifest() {
+  static base::NoDestructor<service_manager::Manifest> manifest{
+      service_manager::ManifestBuilder()
+          .WithServiceName(mojom::kServiceName)
+          .WithDisplayName("Ash Window Manager and Shell")
+          .WithOptions(service_manager::ManifestOptionsBuilder()
+                           .WithSandboxType("none")
+                           .WithInstanceSharingPolicy(
+                               service_manager::Manifest::
+                                   InstanceSharingPolicy::kSingleton)
+                           .Build())
+          .ExposeCapability("service_manager:service_factory",
+                            service_manager::Manifest::InterfaceList<
+                                service_manager::mojom::ServiceFactory>())
+          .ExposeCapability(
+              "system_ui",
+              service_manager::Manifest::InterfaceList<
+                  mojom::AcceleratorController, mojom::AccessibilityController,
+                  mojom::AccessibilityFocusRingController,
+                  mojom::AppListController, mojom::AshMessageCenterController,
+                  mojom::AssistantAlarmTimerController,
+                  mojom::AssistantController,
+                  mojom::AssistantNotificationController,
+                  mojom::AssistantScreenContextController,
+                  mojom::AssistantSetupController,
+                  mojom::AssistantVolumeControl, mojom::CastConfig,
+                  mojom::ContainedShellController,
+                  mojom::CrosDisplayConfigController,
+                  mojom::DockedMagnifierController,
+                  mojom::EventRewriterController, mojom::FirstRunHelper,
+                  mojom::HighlighterController, mojom::ImeController,
+                  mojom::KeyboardController, mojom::LocaleUpdateController,
+                  mojom::LoginScreen, mojom::MediaController,
+                  mojom::NewWindowController, mojom::NightLightController,
+                  mojom::NoteTakingController,
+                  mojom::ProcessCreationTimeRecorder, mojom::SessionController,
+                  mojom::ShelfController, mojom::ShutdownController,
+                  mojom::SplitViewController, mojom::SystemTray,
+                  mojom::TabletModeController, mojom::TrayAction,
+                  mojom::VoiceInteractionController, mojom::VpnList,
+                  mojom::WallpaperController>())
+          .ExposeCapability("display", service_manager::Manifest::InterfaceList<
+                                           mojom::AshDisplayController,
+                                           mojom::DisplayOutputProtection>())
+          .RequireCapability("*", "accessibility")
+          .RequireCapability("*", "app")
+          .RequireCapability(prefs::mojom::kLocalStateServiceName,
+                             "pref_client")
+          .RequireCapability(content::mojom::kServiceName, "navigation")
+          .RequireCapability(data_decoder::mojom::kServiceName, "image_decoder")
+          .RequireCapability(mojom::kPrefConnectorServiceName, "pref_connector")
+          .RequireCapability(viz::mojom::kVizServiceName, "viz_host")
+          .RequireCapability(catalog::mojom::kServiceName, "directory")
+          .RequireCapability(ws::mojom::kServiceName, "ozone")
+          .RequireCapability(ws::mojom::kServiceName, "window_manager")
+          .RequireCapability(device::mojom::kServiceName,
+                             "device:bluetooth_system")
+          .RequireCapability(device::mojom::kServiceName, "device:fingerprint")
+          .RequireCapability(chromeos::multidevice_setup::mojom::kServiceName,
+                             "multidevice_setup")
+          .PackageService(ui::GetManifest())
+          .Build()
+          .Amend(GetAmendmentForTesting())};
+  return *manifest;
+}
+
+void AmendManifestForTesting(const service_manager::Manifest& amendment) {
+  GetAmendmentForTesting() = amendment;
+}
+
+}  // namespace ash
diff --git a/ash/public/cpp/manifest.h b/ash/public/cpp/manifest.h
new file mode 100644
index 0000000..124306b
--- /dev/null
+++ b/ash/public/cpp/manifest.h
@@ -0,0 +1,25 @@
+// 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 ASH_PUBLIC_CPP_MANIFEST_H_
+#define ASH_PUBLIC_CPP_MANIFEST_H_
+
+#include "services/service_manager/public/cpp/manifest.h"
+
+namespace ash {
+
+// Returns the service manifest used for the ash service in a production
+// environment.
+const service_manager::Manifest& GetManifest();
+
+// Sets a global overlay with which to amend the manifest returned by
+// |GetManifest()|. Must be called before the first call to |GetManifest()| in
+// order to have any effect. This is useful for e.g. browser test environments
+// where |GetManifest()| is called by production code and the test environment
+// needs to affect its behavior with test-only dependencies.
+void AmendManifestForTesting(const service_manager::Manifest& amendment);
+
+}  // namespace ash
+
+#endif  // ASH_PUBLIC_CPP_MANIFEST_H_
diff --git a/ash/public/cpp/test_manifest.cc b/ash/public/cpp/test_manifest.cc
new file mode 100644
index 0000000..6bacb0f1e0
--- /dev/null
+++ b/ash/public/cpp/test_manifest.cc
@@ -0,0 +1,31 @@
+// 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 "ash/public/cpp/test_manifest.h"
+
+#include "ash/public/interfaces/login_screen_test_api.test-mojom.h"
+#include "ash/public/interfaces/shelf_test_api.test-mojom.h"
+#include "ash/public/interfaces/shell_test_api.test-mojom.h"
+#include "ash/public/interfaces/status_area_widget_test_api.test-mojom.h"
+#include "ash/public/interfaces/system_tray_test_api.test-mojom.h"
+#include "ash/public/interfaces/time_to_first_present_recorder_test_api.test-mojom.h"
+#include "base/no_destructor.h"
+#include "services/service_manager/public/cpp/manifest_builder.h"
+
+namespace ash {
+
+const service_manager::Manifest& GetManifestOverlayForTesting() {
+  static base::NoDestructor<service_manager::Manifest> manifest{
+      service_manager::ManifestBuilder()
+          .ExposeCapability(
+              "test", service_manager::Manifest::InterfaceList<
+                          mojom::LoginScreenTestApi, mojom::ShelfTestApi,
+                          mojom::ShellTestApi, mojom::StatusAreaWidgetTestApi,
+                          mojom::SystemTrayTestApi,
+                          mojom::TimeToFirstPresentRecorderTestApi>())
+          .Build()};
+  return *manifest;
+}
+
+}  // namespace ash
diff --git a/ash/public/cpp/test_manifest.h b/ash/public/cpp/test_manifest.h
new file mode 100644
index 0000000..d67a307
--- /dev/null
+++ b/ash/public/cpp/test_manifest.h
@@ -0,0 +1,21 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_PUBLIC_CPP_TEST_MANIFEST_H_
+#define ASH_PUBLIC_CPP_TEST_MANIFEST_H_
+
+#include "services/service_manager/public/cpp/manifest.h"
+
+namespace ash {
+
+// A manifest for the ash service with additional capabilities exposed for
+// testing APIs. This can be amended to the normal manifest by passing it to
+// ash::AmendManifestForTesting (in manifest.h) in a test environment before
+// initializing the Service Manager, or by explicitly a mending a copy of the
+// normal manifest (in e.g. unit tests using TestServiceManager).
+const service_manager::Manifest& GetManifestOverlayForTesting();
+
+}  // namespace ash
+
+#endif  // ASH_PUBLIC_CPP_MANIFEST_H_
diff --git a/ash/public/interfaces/accessibility_focus_ring_controller.mojom b/ash/public/interfaces/accessibility_focus_ring_controller.mojom
index 3cd96ce1..ae4ff29 100644
--- a/ash/public/interfaces/accessibility_focus_ring_controller.mojom
+++ b/ash/public/interfaces/accessibility_focus_ring_controller.mojom
@@ -38,4 +38,10 @@
   // Hides highlight on screen.
   // TODO(katie): Add |caller_id| to highlights as well.
   HideHighlights();
+
+  // Enables double focus rings and sets the second color for the given caller.
+  EnableDoubleFocusRing(uint32 skcolor, string caller_id);
+
+  // Disables double focus rings for the given caller.
+  DisableDoubleFocusRing(string caller_id);
 };
diff --git a/ash/rotator/screen_rotation_animator.cc b/ash/rotator/screen_rotation_animator.cc
index 4914a797..4aa542ee 100644
--- a/ash/rotator/screen_rotation_animator.cc
+++ b/ash/rotator/screen_rotation_animator.cc
@@ -27,6 +27,7 @@
 #include "ui/compositor/layer_animator.h"
 #include "ui/compositor/layer_owner.h"
 #include "ui/compositor/layer_tree_owner.h"
+#include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/display/display.h"
 #include "ui/display/manager/display_manager.h"
 #include "ui/display/manager/managed_display_info.h"
@@ -292,6 +293,12 @@
 
   old_layer_tree_owner_ = CopyLayerTree(std::move(result));
   AddLayerAtTopOfWindowLayers(root_window_, old_layer_tree_owner_->root());
+
+  // TODO(oshima): We need a better way to control animation and other
+  // activities during system wide animation.
+  animation_scale_mode_ =
+      std::make_unique<ui::ScopedAnimationDurationScaleMode>(
+          ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
   SetRotation(rotation_request->display_id, rotation_request->old_rotation,
               rotation_request->new_rotation, rotation_request->source);
 
@@ -304,6 +311,7 @@
 void ScreenRotationAnimator::OnScreenRotationContainerLayerCopiedAfterRotation(
     std::unique_ptr<ScreenRotationRequest> rotation_request,
     std::unique_ptr<viz::CopyOutputResult> result) {
+  animation_scale_mode_.reset();
   if (IgnoreCopyResult(rotation_request->id, rotation_request_id_))
     return;
   // In the following cases, abort animation:
diff --git a/ash/rotator/screen_rotation_animator.h b/ash/rotator/screen_rotation_animator.h
index d17e454..d11aa37 100644
--- a/ash/rotator/screen_rotation_animator.h
+++ b/ash/rotator/screen_rotation_animator.h
@@ -6,6 +6,7 @@
 #define ASH_ROTATOR_SCREEN_ROTATION_ANIMATOR_H_
 
 #include <stdint.h>
+#include <memory>
 
 #include "ash/ash_export.h"
 #include "ash/display/display_configuration_controller.h"
@@ -28,6 +29,7 @@
 namespace ui {
 class AnimationMetricsReporter;
 class LayerTreeOwner;
+class ScopedAnimationDurationScaleMode;
 }  // namespace ui
 
 namespace ash {
@@ -189,6 +191,7 @@
   std::unique_ptr<ScreenRotationRequest> last_pending_request_;
   base::Optional<ScreenRotationRequest> current_async_rotation_request_;
   display::Display::Rotation target_rotation_ = display::Display::ROTATE_0;
+  std::unique_ptr<ui::ScopedAnimationDurationScaleMode> animation_scale_mode_;
   base::WeakPtrFactory<ScreenRotationAnimator> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(ScreenRotationAnimator);
diff --git a/ash/shelf/shelf_widget_unittest.cc b/ash/shelf/shelf_widget_unittest.cc
index d27fa6fb..b813596 100644
--- a/ash/shelf/shelf_widget_unittest.cc
+++ b/ash/shelf/shelf_widget_unittest.cc
@@ -521,10 +521,7 @@
         keyboard::switches::kEnableVirtualKeyboard);
     AshTestBase::SetUp();
     ASSERT_TRUE(keyboard::IsKeyboardEnabled());
-
-    keyboard_controller()->LoadKeyboardWindowInBackground();
-    // Wait for the keyboard window to load.
-    base::RunLoop().RunUntilIdle();
+    keyboard::test::WaitUntilLoaded();
 
     // These tests only apply to the floating virtual keyboard, as it is the
     // only case where both the virtual keyboard and the shelf are visible.
diff --git a/ash/shell/content/client/shell_content_browser_client.cc b/ash/shell/content/client/shell_content_browser_client.cc
index ce8fe44..5b289a8d 100644
--- a/ash/shell/content/client/shell_content_browser_client.cc
+++ b/ash/shell/content/client/shell_content_browser_client.cc
@@ -13,7 +13,8 @@
 #include "ash/components/shortcut_viewer/public/mojom/shortcut_viewer.mojom.h"
 #include "ash/components/tap_visualizer/manifest.h"
 #include "ash/components/tap_visualizer/public/mojom/tap_visualizer.mojom.h"
-#include "ash/manifest.h"
+#include "ash/public/cpp/manifest.h"
+#include "ash/public/cpp/test_manifest.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/public/interfaces/constants.mojom.h"
 #include "ash/shell.h"
@@ -59,7 +60,8 @@
 const service_manager::Manifest& GetAshShellPackagedServicesOverlayManifest() {
   static base::NoDestructor<service_manager::Manifest> manifest{
       service_manager::ManifestBuilder()
-          .PackageService(ash::GetManifest())
+          .PackageService(service_manager::Manifest(ash::GetManifest())
+                              .Amend(ash::GetManifestOverlayForTesting()))
           .PackageService(quick_launch_app::GetManifest())
           .PackageService(shortcut_viewer_app::GetManifest())
           .PackageService(tap_visualizer_app::GetManifest())
diff --git a/ash/system/cast/tray_cast.cc b/ash/system/cast/tray_cast.cc
index 47baa65..4644ee4 100644
--- a/ash/system/cast/tray_cast.cc
+++ b/ash/system/cast/tray_cast.cc
@@ -80,16 +80,6 @@
 
 CastDetailedView::~CastDetailedView() = default;
 
-void CastDetailedView::SimulateViewClickedForTest(
-    const std::string& receiver_id) {
-  for (const auto& it : view_to_sink_map_) {
-    if (it.second->id == receiver_id) {
-      HandleViewClicked(it.first);
-      break;
-    }
-  }
-}
-
 void CastDetailedView::CreateItems() {
   CreateScrollableList();
   CreateTitleRow(IDS_ASH_STATUS_TRAY_CAST);
diff --git a/ash/system/cast/tray_cast.h b/ash/system/cast/tray_cast.h
index d82f3f7..0803d80 100644
--- a/ash/system/cast/tray_cast.h
+++ b/ash/system/cast/tray_cast.h
@@ -25,10 +25,6 @@
                    const std::vector<mojom::SinkAndRoutePtr>& sinks_and_routes);
   ~CastDetailedView() override;
 
-  // Makes the detail view think the view associated with the given receiver_id
-  // was clicked. This will start a cast.
-  void SimulateViewClickedForTest(const std::string& receiver_id);
-
   // Updates the list of available receivers.
   void UpdateReceiverList(
       const std::vector<mojom::SinkAndRoutePtr>& sinks_routes);
diff --git a/ash/system/screen_layout_observer.cc b/ash/system/screen_layout_observer.cc
index 0d950d3..2d724820 100644
--- a/ash/system/screen_layout_observer.cc
+++ b/ash/system/screen_layout_observer.cc
@@ -411,39 +411,9 @@
 bool ScreenLayoutObserver::GetExitMirrorModeMessage(
     base::string16* out_message,
     base::string16* out_additional_message) {
-  if (GetDisplayManager()->is_multi_mirroring_enabled()) {
-    *out_message =
-        l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_MIRROR_EXIT);
-    return true;
-  }
-  switch (current_display_mode_) {
-    case DisplayMode::EXTENDED_3_PLUS:
-      // Mirror mode was turned off due to having more than two displays.
-      // Show a message that mirror mode for 3+ displays is not supported.
-      *out_message =
-          l10n_util::GetStringUTF16(IDS_ASH_DISPLAY_MIRRORING_NOT_SUPPORTED);
-      return true;
-
-    case DisplayMode::DOCKED:
-      // Handle disabling mirror mode as a result of going to docked mode
-      // when we only have a single display (this means we actually have two
-      // physical displays, one of which is the internal display, but they
-      // were in mirror mode, and hence considered as one. Closing the
-      // internal display disables mirror mode and we still have a single
-      // active display).
-      // Falls through.
-    case DisplayMode::SINGLE:
-      // We're exiting mirror mode because we removed one of the two
-      // displays.
-      *out_message =
-          l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_MIRROR_EXIT);
-      return true;
-
-    default:
-      // Mirror mode was turned off; other messages should be shown e.g.
-      // extended mode is on, ... etc.
-      return false;
-  }
+  *out_message =
+      l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_MIRROR_EXIT);
+  return true;
 }
 
 }  // namespace ash
diff --git a/ash/system/screen_layout_observer_unittest.cc b/ash/system/screen_layout_observer_unittest.cc
index dd44960..e5ec071c 100644
--- a/ash/system/screen_layout_observer_unittest.cc
+++ b/ash/system/screen_layout_observer_unittest.cc
@@ -128,34 +128,7 @@
   return nullptr;
 }
 
-class ScreenLayoutObserverTestMultiMirroring
-    : public ScreenLayoutObserverTest,
-      public testing::WithParamInterface<bool> {
- public:
-  ScreenLayoutObserverTestMultiMirroring() = default;
-  ~ScreenLayoutObserverTestMultiMirroring() override = default;
-
- protected:
-  void SetUp() override {
-    bool should_disable_multi_mirroring = GetParam();
-    if (should_disable_multi_mirroring) {
-      base::CommandLine::ForCurrentProcess()->AppendSwitch(
-          ::switches::kDisableMultiMirroring);
-    }
-    ScreenLayoutObserverTest::SetUp();
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(ScreenLayoutObserverTestMultiMirroring);
-};
-
-// Instantiate the boolean which is used to enable/disable multi-mirroring in
-// the parameterized tests.
-INSTANTIATE_TEST_CASE_P(,
-                        ScreenLayoutObserverTestMultiMirroring,
-                        testing::Bool());
-
-TEST_P(ScreenLayoutObserverTestMultiMirroring, DisplayNotifications) {
+TEST_F(ScreenLayoutObserverTest, DisplayNotifications) {
   Shell::Get()->screen_layout_observer()->set_show_notifications_for_testing(
       true);
 
@@ -235,15 +208,8 @@
   // Turn off mirror mode.
   CloseNotification();
   display_manager()->SetMirrorMode(display::MirrorMode::kOff, base::nullopt);
-  if (display_manager()->is_multi_mirroring_enabled()) {
-    EXPECT_EQ(
-        l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_MIRROR_EXIT),
-        GetDisplayNotificationText());
-  } else {
-    EXPECT_EQ(l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_EXTENDED,
-                                         GetSecondDisplayName()),
-              GetDisplayNotificationText());
-  }
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_MIRROR_EXIT),
+            GetDisplayNotificationText());
   EXPECT_TRUE(GetDisplayNotificationAdditionalText().empty());
 
   // Rotate the second.
@@ -395,37 +361,6 @@
             GetDisplayNotificationText());
 }
 
-// TODO(crbug.com/774795) Remove this test when multi mirroring is enabled by
-// default.
-// Tests that exiting mirror mode because of adding a third display shows the
-// correct "3+ displays mirror mode is not supported" message.
-TEST_P(ScreenLayoutObserverTestMultiMirroring,
-       ExitMirrorModeBecauseOfThirdDisplayMessage) {
-  if (display_manager()->is_multi_mirroring_enabled()) {
-    // This test is not neccessary when mirroring across 3+ displays is
-    // supported.
-    return;
-  }
-  Shell::Get()->screen_layout_observer()->set_show_notifications_for_testing(
-      true);
-  UpdateDisplay("400x400,200x200");
-  display::Display::SetInternalDisplayId(
-      display_manager()->GetSecondaryDisplay().id());
-
-  // Mirroring.
-  UpdateDisplay("400x400,200x200");
-  display_manager()->SetMirrorMode(display::MirrorMode::kNormal, base::nullopt);
-  EXPECT_EQ(l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_DISPLAY_MIRRORING,
-                                       GetMirroringDisplayNames()),
-            GetDisplayNotificationText());
-
-  // Adding a third display. Mirror mode for 3+ displays is not supported.
-  CloseNotification();
-  UpdateDisplay("400x400,200x200,100x100");
-  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_DISPLAY_MIRRORING_NOT_SUPPORTED),
-            GetDisplayNotificationText());
-}
-
 // Special case: tests that exiting mirror mode by removing a display shows the
 // correct message.
 TEST_F(ScreenLayoutObserverTest,
diff --git a/ash/system/status_area_widget_unittest.cc b/ash/system/status_area_widget_unittest.cc
index 1457fa9c..64f3de5 100644
--- a/ash/system/status_area_widget_unittest.cc
+++ b/ash/system/status_area_widget_unittest.cc
@@ -250,10 +250,7 @@
         keyboard::switches::kEnableVirtualKeyboard);
     AshTestBase::SetUp();
     ASSERT_TRUE(keyboard::IsKeyboardEnabled());
-
-    keyboard_controller()->LoadKeyboardWindowInBackground();
-    // Wait for the keyboard window to load.
-    base::RunLoop().RunUntilIdle();
+    keyboard::test::WaitUntilLoaded();
 
     // These tests only apply to the floating virtual keyboard, as it is the
     // only case where both the virtual keyboard and the shelf are visible.
diff --git a/ash/system/virtual_keyboard/virtual_keyboard_tray_unittest.cc b/ash/system/virtual_keyboard/virtual_keyboard_tray_unittest.cc
index d5c13c0..811e5fee 100644
--- a/ash/system/virtual_keyboard/virtual_keyboard_tray_unittest.cc
+++ b/ash/system/virtual_keyboard/virtual_keyboard_tray_unittest.cc
@@ -23,12 +23,10 @@
         keyboard::switches::kEnableVirtualKeyboard);
     AshTestBase::SetUp();
     ASSERT_TRUE(keyboard::IsKeyboardEnabled());
+    keyboard::test::WaitUntilLoaded();
 
     // These tests only apply to the floating virtual keyboard, as it is the
     // only case where both the virtual keyboard and the shelf are visible.
-    keyboard_controller()->LoadKeyboardWindowInBackground();
-    // Wait for the keyboard window to load.
-    base::RunLoop().RunUntilIdle();
     keyboard_controller()->SetContainerType(
         keyboard::mojom::ContainerType::kFloating, base::nullopt,
         base::DoNothing());
diff --git a/ash/utility/screenshot_controller.cc b/ash/utility/screenshot_controller.cc
index ef07130..7941420 100644
--- a/ash/utility/screenshot_controller.cc
+++ b/ash/utility/screenshot_controller.cc
@@ -24,6 +24,7 @@
 #include "ui/events/event_handler.h"
 #include "ui/gfx/canvas.h"
 #include "ui/views/widget/widget.h"
+#include "ui/wm/core/accelerator_filter.h"
 #include "ui/wm/core/cursor_manager.h"
 
 namespace ash {
@@ -464,8 +465,7 @@
 
   // Key event is blocked. So have to record current accelerator here.
   if (event->stopped_propagation()) {
-    // Filter accelerators in the same way with AcceleratorFilter::OnKeyEvent.
-    if (event->is_char() || !event->target())
+    if (::wm::AcceleratorFilter::ShouldFilter(event))
       return;
 
     ui::Accelerator accelerator(*event);
diff --git a/ash/wm/overview/overview_controller_unittest.cc b/ash/wm/overview/overview_controller_unittest.cc
index a834e99..c1f8be66 100644
--- a/ash/wm/overview/overview_controller_unittest.cc
+++ b/ash/wm/overview/overview_controller_unittest.cc
@@ -378,8 +378,8 @@
     TabletModeControllerTestApi().EnterTabletMode();
     base::RunLoop().RunUntilIdle();
     ASSERT_TRUE(keyboard::IsKeyboardEnabled());
+    keyboard::test::WaitUntilLoaded();
 
-    keyboard_controller()->LoadKeyboardWindowInBackground();
     keyboard_controller()->GetKeyboardWindow()->SetBounds(
         keyboard::KeyboardBoundsFromRootBounds(
             Shell::GetPrimaryRootWindow()->bounds(), 100));
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index fa59fd9..8357e06c 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -432,7 +432,7 @@
     bool animate,
     OverviewItem* ignored_item,
     OverviewSession::OverviewTransition transition) {
-  if (!overview_session_)
+  if (!overview_session_ || suspend_reposition_)
     return;
 
   DCHECK_NE(transition, OverviewSession::OverviewTransition::kExit);
diff --git a/ash/wm/overview/overview_grid.h b/ash/wm/overview/overview_grid.h
index 54f7cea8..b2cbe74 100644
--- a/ash/wm/overview/overview_grid.h
+++ b/ash/wm/overview/overview_grid.h
@@ -237,6 +237,8 @@
     return drop_target_widget_.get();
   }
 
+  void set_suspend_reposition(bool value) { suspend_reposition_ = value; }
+
  private:
   class ShieldView;
   class TargetWindowObserver;
@@ -371,6 +373,10 @@
   // non-empty if a nudge is in progress.
   std::vector<NudgeData> nudge_data_;
 
+  // True to skip |PositionWindows()|. Used to avoid O(n^2) layout
+  // when reposition windows in tablet overview mode.
+  bool suspend_reposition_ = false;
+
   DISALLOW_COPY_AND_ASSIGN(OverviewGrid);
 };
 
diff --git a/ash/wm/overview/overview_session.cc b/ash/wm/overview/overview_session.cc
index bfdfeeb..8159a59 100644
--- a/ash/wm/overview/overview_session.cc
+++ b/ash/wm/overview/overview_session.cc
@@ -646,6 +646,16 @@
   return nullptr;
 }
 
+void OverviewSession::SuspendReposition() {
+  for (auto& grid : grid_list_)
+    grid->set_suspend_reposition(true);
+}
+
+void OverviewSession::ResumeReposition() {
+  for (auto& grid : grid_list_)
+    grid->set_suspend_reposition(false);
+}
+
 void OverviewSession::OnDisplayRemoved(const display::Display& display) {
   // TODO(flackr): Keep window selection active on remaining displays.
   CancelSelection();
@@ -755,7 +765,7 @@
       SelectWindow(grid_list_[selected_grid_index_]->SelectedWindow());
       break;
     default:
-      break;
+      return;
   }
 
   event->SetHandled();
diff --git a/ash/wm/overview/overview_session.h b/ash/wm/overview/overview_session.h
index d1f1c745..7f21b8e7 100644
--- a/ash/wm/overview/overview_session.h
+++ b/ash/wm/overview/overview_session.h
@@ -221,6 +221,10 @@
   // Gets the window which keeps focus for the duration of overview mode.
   aura::Window* GetOverviewFocusWindow();
 
+  // Suspends/Resumes window re-positiong in overview.
+  void SuspendReposition();
+  void ResumeReposition();
+
   OverviewDelegate* delegate() { return delegate_; }
 
   SplitViewDragIndicators* split_view_drag_indicators() {
diff --git a/ash/wm/overview/overview_session_unittest.cc b/ash/wm/overview/overview_session_unittest.cc
index 92d92bc00..711d078 100644
--- a/ash/wm/overview/overview_session_unittest.cc
+++ b/ash/wm/overview/overview_session_unittest.cc
@@ -6,6 +6,8 @@
 #include <memory>
 #include <vector>
 
+#include "ash/accelerators/accelerator_controller.h"
+#include "ash/accelerators/exit_warning_handler.h"
 #include "ash/accessibility/accessibility_controller.h"
 #include "ash/accessibility/test_accessibility_controller_client.h"
 #include "ash/app_list/app_list_controller_impl.h"
@@ -321,6 +323,11 @@
     return !!item->transform_window_.mask_;
   }
 
+  static void StubForTest(ExitWarningHandler* ewh) {
+    ewh->stub_timer_for_test_ = true;
+  }
+  static bool is_ui_shown(ExitWarningHandler* ewh) { return !!ewh->widget_; }
+
  private:
   std::unique_ptr<ShelfViewTestAPI> shelf_view_test_api_;
 
@@ -1204,6 +1211,22 @@
   EXPECT_EQ(GetSelectedWindow(), overview_windows[0]->GetWindow());
 }
 
+TEST_F(OverviewSessionTest, AcceleratorInOverviewSession) {
+  ToggleOverview();
+  auto* accelerator_controller = Shell::Get()->accelerator_controller();
+  auto* ewh = accelerator_controller->GetExitWarningHandlerForTest();
+  ASSERT_TRUE(ewh);
+  StubForTest(ewh);
+  EXPECT_FALSE(is_ui_shown(ewh));
+
+  ui::test::EventGenerator event_generator(Shell::GetPrimaryRootWindow());
+  event_generator.PressKey(ui::VKEY_Q, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
+  event_generator.ReleaseKey(ui::VKEY_Q,
+                             ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
+
+  EXPECT_TRUE(is_ui_shown(ewh));
+}
+
 // Tests that pressing Ctrl+W while a window is selected in overview closes it.
 TEST_F(OverviewSessionTest, CloseWindowWithKey) {
   std::unique_ptr<views::Widget> widget(CreateTestWidget());
diff --git a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
index fdf6d6f5..8cec93b 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
@@ -12,6 +12,7 @@
 #include "ash/accelerometer/accelerometer_types.h"
 #include "ash/app_list/app_list_controller_impl.h"
 #include "ash/display/screen_orientation_controller.h"
+#include "ash/public/cpp/app_types.h"
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/tablet_mode.h"
 #include "ash/shell.h"
@@ -27,6 +28,7 @@
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/fake_power_manager_client.h"
 #include "services/ws/public/cpp/input_devices/input_device_client_test_api.h"
+#include "ui/aura/client/aura_constants.h"
 #include "ui/display/manager/display_manager.h"
 #include "ui/display/screen.h"
 #include "ui/display/test/display_manager_test_api.h"
@@ -1138,6 +1140,47 @@
   EXPECT_FALSE(Shell::Get()->overview_controller()->IsSelecting());
 }
 
+// Test that if before tablet mode, the active window is an ARC window snapped
+// on the left and the previous window is snapped on the right, then split view
+// is not activated.
+TEST_F(TabletModeControllerTest,
+       StartTabletActiveArcLeftSnapPreviousRightSnap) {
+  SplitViewController* split_view_controller =
+      Shell::Get()->split_view_controller();
+  std::unique_ptr<aura::Window> left_window = CreateDesktopWindowSnappedLeft();
+  left_window->SetProperty(aura::client::kAppType,
+                           static_cast<int>(AppType::ARC_APP));
+  std::unique_ptr<aura::Window> right_window =
+      CreateDesktopWindowSnappedRight();
+  ::wm::ActivateWindow(left_window.get());
+  tablet_mode_controller()->EnableTabletModeWindowManager(true);
+  EXPECT_EQ(SplitViewController::NO_SNAP, split_view_controller->state());
+  EXPECT_FALSE(Shell::Get()->overview_controller()->IsSelecting());
+}
+
+// Test that if before tablet mode, the active window is snapped on the left,
+// the previous window is an ARC window snapped on the right, and the third
+// window is snapped on the right (just to test that it is ignored after the ARC
+// window), then split view is activated with the active window on the left.
+TEST_F(TabletModeControllerTest,
+       StartTabletActiveLeftSnapPreviousArcRightSnap) {
+  SplitViewController* split_view_controller =
+      Shell::Get()->split_view_controller();
+  std::unique_ptr<aura::Window> left_window = CreateDesktopWindowSnappedLeft();
+  std::unique_ptr<aura::Window> right_window =
+      CreateDesktopWindowSnappedRight();
+  right_window->SetProperty(aura::client::kAppType,
+                            static_cast<int>(AppType::ARC_APP));
+  std::unique_ptr<aura::Window> extra_right_window =
+      CreateDesktopWindowSnappedRight();
+  ::wm::ActivateWindow(right_window.get());
+  ::wm::ActivateWindow(left_window.get());
+  tablet_mode_controller()->EnableTabletModeWindowManager(true);
+  EXPECT_EQ(SplitViewController::LEFT_SNAPPED, split_view_controller->state());
+  EXPECT_EQ(left_window.get(), split_view_controller->left_window());
+  EXPECT_TRUE(Shell::Get()->overview_controller()->IsSelecting());
+}
+
 // Test that if overview is triggered on entering tablet mode, then the app list
 // can still be successfully shown and actually seen.
 TEST_F(TabletModeControllerTest, AppListWorksAfterEnteringTabletForOverview) {
diff --git a/ash/wm/tablet_mode/tablet_mode_window_manager.cc b/ash/wm/tablet_mode/tablet_mode_window_manager.cc
index 52978d4e..daa265a8 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_manager.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_manager.cc
@@ -6,12 +6,14 @@
 
 #include <memory>
 
+#include "ash/public/cpp/app_types.h"
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/root_window_controller.h"
 #include "ash/shell.h"
 #include "ash/wm/mru_window_tracker.h"
 #include "ash/wm/overview/overview_controller.h"
+#include "ash/wm/overview/overview_session.h"
 #include "ash/wm/tablet_mode/scoped_skip_user_session_blocked_check.h"
 #include "ash/wm/tablet_mode/tablet_mode_backdrop_delegate_impl.h"
 #include "ash/wm/tablet_mode/tablet_mode_event_handler.h"
@@ -202,11 +204,18 @@
     ui::PropertyChangeReason reason) {
   if (!IsContainerWindow(window))
     return;
+
+  auto* session = Shell::Get()->overview_controller()->overview_session();
+  if (session)
+    session->SuspendReposition();
+
   // Reposition all non maximizeable windows.
   for (auto& pair : window_state_map_) {
     pair.second->UpdateWindowPosition(wm::GetWindowState(pair.first),
                                       /*animate=*/false);
   }
+  if (session)
+    session->ResumeReposition();
 }
 
 void TabletModeWindowManager::OnWindowVisibilityChanged(aura::Window* window,
@@ -299,9 +308,11 @@
   const mojom::WindowStateType active_window_state_type =
       wm::GetWindowState(windows[0])->GetStateType();
 
-  // If the active window is not snapped, then just maximize all windows.
-  if (active_window_state_type != mojom::WindowStateType::LEFT_SNAPPED &&
-      active_window_state_type != mojom::WindowStateType::RIGHT_SNAPPED) {
+  // If the active window is ARC or not snapped, then just maximize all windows.
+  if (static_cast<ash::AppType>(windows[0]->GetProperty(
+          aura::client::kAppType)) == AppType::ARC_APP ||
+      (active_window_state_type != mojom::WindowStateType::LEFT_SNAPPED &&
+       active_window_state_type != mojom::WindowStateType::RIGHT_SNAPPED)) {
     for (auto* window : windows)
       MaximizeAndTrackWindow(window);
     return;
@@ -310,6 +321,9 @@
   // The snapped active window will be represented by split view, which will be
   // activated after maximizing all windows. The split view layout is decided
   // here by examining window states before all those states become maximized.
+  const bool prev_win_not_arc =
+      windows.size() > 1u && static_cast<ash::AppType>(windows[1]->GetProperty(
+                                 aura::client::kAppType)) != AppType::ARC_APP;
   SplitViewController::SnapPosition curr_win_snap_pos =
       SplitViewController::NONE;
   SplitViewController::SnapPosition prev_win_snap_pos =
@@ -318,8 +332,8 @@
     // The active window snapped on the left shall go there in split view.
     curr_win_snap_pos = SplitViewController::LEFT;
 
-    if (windows.size() > 1u && wm::GetWindowState(windows[1])->GetStateType() ==
-                                   mojom::WindowStateType::RIGHT_SNAPPED) {
+    if (prev_win_not_arc && wm::GetWindowState(windows[1])->GetStateType() ==
+                                mojom::WindowStateType::RIGHT_SNAPPED) {
       // The previous window snapped on the right shall go there in split view.
       prev_win_snap_pos = SplitViewController::RIGHT;
     }
@@ -329,8 +343,8 @@
     // The active window snapped on the right shall go there in split view.
     curr_win_snap_pos = SplitViewController::RIGHT;
 
-    if (windows.size() > 1u && wm::GetWindowState(windows[1])->GetStateType() ==
-                                   mojom::WindowStateType::LEFT_SNAPPED) {
+    if (prev_win_not_arc && wm::GetWindowState(windows[1])->GetStateType() ==
+                                mojom::WindowStateType::LEFT_SNAPPED) {
       // The previous window snapped on the left shall go there in split view.
       prev_win_snap_pos = SplitViewController::LEFT;
     }
diff --git a/base/android/scoped_hardware_buffer_fence_sync.h b/base/android/scoped_hardware_buffer_fence_sync.h
index 74e0499b..2bd2cfb 100644
--- a/base/android/scoped_hardware_buffer_fence_sync.h
+++ b/base/android/scoped_hardware_buffer_fence_sync.h
@@ -26,6 +26,12 @@
   ScopedHardwareBufferHandle TakeBuffer();
   ScopedFD TakeFence();
 
+  // Provides fence which is signaled when the reads for this buffer are done
+  // and it can be reused. The method assumes a current GLContext and will only
+  // synchronize the reads with this context.
+  // Must only be called once.
+  virtual void SetReadFence(base::ScopedFD fence_fd) = 0;
+
  private:
   ScopedHardwareBufferHandle handle_;
   ScopedFD fence_fd_;
diff --git a/base/files/file_descriptor_watcher_posix_unittest.cc b/base/files/file_descriptor_watcher_posix_unittest.cc
index a2f657f..b2191e35 100644
--- a/base/files/file_descriptor_watcher_posix_unittest.cc
+++ b/base/files/file_descriptor_watcher_posix_unittest.cc
@@ -301,12 +301,12 @@
   controller = nullptr;
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MessagePumpForIOOnMainThread,
     FileDescriptorWatcherTest,
     ::testing::Values(
         FileDescriptorWatcherTestType::MESSAGE_PUMP_FOR_IO_ON_MAIN_THREAD));
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MessagePumpForIOOnOtherThread,
     FileDescriptorWatcherTest,
     ::testing::Values(
diff --git a/base/memory/platform_shared_memory_region_fuchsia.cc b/base/memory/platform_shared_memory_region_fuchsia.cc
index 3fcf584..049d8cf2a 100644
--- a/base/memory/platform_shared_memory_region_fuchsia.cc
+++ b/base/memory/platform_shared_memory_region_fuchsia.cc
@@ -112,9 +112,9 @@
                                                void** memory,
                                                size_t* mapped_size) const {
   uintptr_t addr;
-  zx_vm_option_t options = ZX_VM_REQUIRE_NON_RESIZABLE | ZX_VM_FLAG_PERM_READ;
+  zx_vm_option_t options = ZX_VM_REQUIRE_NON_RESIZABLE | ZX_VM_PERM_READ;
   if (mode_ != Mode::kReadOnly)
-    options |= ZX_VM_FLAG_PERM_WRITE;
+    options |= ZX_VM_PERM_WRITE;
   zx_status_t status = zx::vmar::root_self()->map(
       /*vmar_offset=*/0, handle_, offset, size, options, &addr);
   if (status != ZX_OK) {
diff --git a/base/memory/platform_shared_memory_region_unittest.cc b/base/memory/platform_shared_memory_region_unittest.cc
index 13a82e9..3193993 100644
--- a/base/memory/platform_shared_memory_region_unittest.cc
+++ b/base/memory/platform_shared_memory_region_unittest.cc
@@ -272,9 +272,9 @@
   DWORD old_protection;
   return VirtualProtect(addr, len, PAGE_READWRITE, &old_protection);
 #elif defined(OS_FUCHSIA)
-  zx_status_t status = zx::vmar::root_self()->protect(
-      reinterpret_cast<uintptr_t>(addr), len,
-      ZX_VM_FLAG_PERM_READ | ZX_VM_FLAG_PERM_WRITE);
+  zx_status_t status =
+      zx::vmar::root_self()->protect(reinterpret_cast<uintptr_t>(addr), len,
+                                     ZX_VM_PERM_READ | ZX_VM_PERM_WRITE);
   return status == ZX_OK;
 #else
   return false;
diff --git a/base/memory/shared_memory_fuchsia.cc b/base/memory/shared_memory_fuchsia.cc
index e27752d..9cef989 100644
--- a/base/memory/shared_memory_fuchsia.cc
+++ b/base/memory/shared_memory_fuchsia.cc
@@ -85,9 +85,9 @@
   if (memory_)
     return false;
 
-  zx_vm_option_t options = ZX_VM_REQUIRE_NON_RESIZABLE | ZX_VM_FLAG_PERM_READ;
+  zx_vm_option_t options = ZX_VM_REQUIRE_NON_RESIZABLE | ZX_VM_PERM_READ;
   if (!read_only_)
-    options |= ZX_VM_FLAG_PERM_WRITE;
+    options |= ZX_VM_PERM_WRITE;
   uintptr_t addr;
   zx_status_t status = zx::vmar::root_self()->map(
       /*vmar_offset=*/0, *zx::unowned_vmo(shm_.GetHandle()), offset, bytes,
diff --git a/base/memory/shared_memory_unittest.cc b/base/memory/shared_memory_unittest.cc
index ae6bcd2..f958c264 100644
--- a/base/memory/shared_memory_unittest.cc
+++ b/base/memory/shared_memory_unittest.cc
@@ -416,7 +416,7 @@
   uintptr_t addr;
   EXPECT_NE(ZX_OK, zx::vmar::root_self()->map(
                        0, *zx::unowned_vmo(handle.GetHandle()), 0,
-                       contents.size(), ZX_VM_FLAG_PERM_WRITE, &addr))
+                       contents.size(), ZX_VM_PERM_WRITE, &addr))
       << "Shouldn't be able to map as writable.";
 
   zx::vmo duped_handle;
diff --git a/base/message_loop/message_loop_perftest.cc b/base/message_loop/message_loop_perftest.cc
index 867e8fe..b45db66b 100644
--- a/base/message_loop/message_loop_perftest.cc
+++ b/base/message_loop/message_loop_perftest.cc
@@ -247,8 +247,8 @@
                          "us/task", true);
 }
 
-INSTANTIATE_TEST_CASE_P(,
-                        MessageLoopPerfTest,
-                        ::testing::Values(1, 5, 10),
-                        MessageLoopPerfTest::ParamInfoToString);
+INSTANTIATE_TEST_SUITE_P(,
+                         MessageLoopPerfTest,
+                         ::testing::Values(1, 5, 10),
+                         MessageLoopPerfTest::ParamInfoToString);
 }  // namespace base
diff --git a/base/message_loop/message_loop_unittest.cc b/base/message_loop/message_loop_unittest.cc
index c766cc2f..20d07a280 100644
--- a/base/message_loop/message_loop_unittest.cc
+++ b/base/message_loop/message_loop_unittest.cc
@@ -1564,7 +1564,7 @@
   EXPECT_TRUE(loop->IsIdleForTesting());
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ,
     MessageLoopTypedTest,
     ::testing::Values(TestType{MessageLoop::TYPE_DEFAULT,
diff --git a/base/message_loop/message_pump_unittest.cc b/base/message_loop/message_pump_unittest.cc
index 66bd710..14250f0 100644
--- a/base/message_loop/message_pump_unittest.cc
+++ b/base/message_loop/message_pump_unittest.cc
@@ -165,11 +165,11 @@
   message_pump_->Run(&delegate);
 }
 
-INSTANTIATE_TEST_CASE_P(,
-                        MessagePumpTest,
-                        ::testing::Values(MessageLoop::TYPE_DEFAULT,
-                                          MessageLoop::TYPE_UI,
-                                          MessageLoop::TYPE_IO));
+INSTANTIATE_TEST_SUITE_P(,
+                         MessagePumpTest,
+                         ::testing::Values(MessageLoop::TYPE_DEFAULT,
+                                           MessageLoop::TYPE_UI,
+                                           MessageLoop::TYPE_IO));
 
 }  // namespace
 }  // namespace base
\ No newline at end of file
diff --git a/base/run_loop_unittest.cc b/base/run_loop_unittest.cc
index fcc59fe..0431601 100644
--- a/base/run_loop_unittest.cc
+++ b/base/run_loop_unittest.cc
@@ -559,12 +559,12 @@
   run_loop_.RunUntilIdle();
 }
 
-INSTANTIATE_TEST_CASE_P(Real,
-                        RunLoopTest,
-                        testing::Values(RunLoopTestType::kRealEnvironment));
-INSTANTIATE_TEST_CASE_P(Mock,
-                        RunLoopTest,
-                        testing::Values(RunLoopTestType::kTestDelegate));
+INSTANTIATE_TEST_SUITE_P(Real,
+                         RunLoopTest,
+                         testing::Values(RunLoopTestType::kRealEnvironment));
+INSTANTIATE_TEST_SUITE_P(Mock,
+                         RunLoopTest,
+                         testing::Values(RunLoopTestType::kTestDelegate));
 
 TEST(ScopedRunTimeoutForTestTest, TimesOut) {
   test::ScopedTaskEnvironment task_environment;
diff --git a/base/synchronization/waitable_event_watcher_unittest.cc b/base/synchronization/waitable_event_watcher_unittest.cc
index c1cb720..5acb5e16 100644
--- a/base/synchronization/waitable_event_watcher_unittest.cc
+++ b/base/synchronization/waitable_event_watcher_unittest.cc
@@ -419,11 +419,11 @@
   EXPECT_FALSE(did_callback);
 }
 
-INSTANTIATE_TEST_CASE_P(,
-                        WaitableEventWatcherTest,
-                        testing::ValuesIn(testing_main_threads));
+INSTANTIATE_TEST_SUITE_P(,
+                         WaitableEventWatcherTest,
+                         testing::ValuesIn(testing_main_threads));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ,
     WaitableEventWatcherDeletionTest,
     testing::Combine(testing::ValuesIn(testing_main_threads), testing::Bool()));
diff --git a/base/test/scoped_task_environment_unittest.cc b/base/test/scoped_task_environment_unittest.cc
index ce2bbcb..c1c0441 100644
--- a/base/test/scoped_task_environment_unittest.cc
+++ b/base/test/scoped_task_environment_unittest.cc
@@ -384,27 +384,27 @@
   EXPECT_EQ(RunLoop::ScopedRunTimeoutForTest::Current(), old_run_timeout);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MainThreadDefault,
     ScopedTaskEnvironmentTest,
     ::testing::Values(ScopedTaskEnvironment::MainThreadType::DEFAULT));
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MainThreadMockTime,
     ScopedTaskEnvironmentTest,
     ::testing::Values(ScopedTaskEnvironment::MainThreadType::MOCK_TIME));
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MainThreadUIMockTime,
     ScopedTaskEnvironmentTest,
     ::testing::Values(ScopedTaskEnvironment::MainThreadType::UI_MOCK_TIME));
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MainThreadUI,
     ScopedTaskEnvironmentTest,
     ::testing::Values(ScopedTaskEnvironment::MainThreadType::UI));
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MainThreadIO,
     ScopedTaskEnvironmentTest,
     ::testing::Values(ScopedTaskEnvironment::MainThreadType::IO));
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MainThreadIOMockTime,
     ScopedTaskEnvironmentTest,
     ::testing::Values(ScopedTaskEnvironment::MainThreadType::IO_MOCK_TIME));
@@ -628,15 +628,15 @@
   EXPECT_EQ(TimeTicks::Now(), start_time + delay);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MainThreadMockTime,
     ScopedTaskEnvironmentMockedTime,
     ::testing::Values(ScopedTaskEnvironment::MainThreadType::MOCK_TIME));
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MainThreadUIMockTime,
     ScopedTaskEnvironmentMockedTime,
     ::testing::Values(ScopedTaskEnvironment::MainThreadType::UI_MOCK_TIME));
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MainThreadIOMockTime,
     ScopedTaskEnvironmentMockedTime,
     ::testing::Values(ScopedTaskEnvironment::MainThreadType::IO_MOCK_TIME));
diff --git a/base/test/sequenced_task_runner_test_template.h b/base/test/sequenced_task_runner_test_template.h
index a510030..96c7bf8 100644
--- a/base/test/sequenced_task_runner_test_template.h
+++ b/base/test/sequenced_task_runner_test_template.h
@@ -122,7 +122,7 @@
   TaskRunnerTestDelegate delegate_;
 };
 
-TYPED_TEST_CASE_P(SequencedTaskRunnerTest);
+TYPED_TEST_SUITE_P(SequencedTaskRunnerTest);
 
 // This test posts N non-nestable tasks in sequence, and expects them to run
 // in FIFO order, with no part of any two tasks' execution
@@ -303,20 +303,20 @@
 
 // The SequencedTaskRunnerTest test case verifies behaviour that is expected
 // from a sequenced task runner in order to be conformant.
-REGISTER_TYPED_TEST_CASE_P(SequencedTaskRunnerTest,
-                           SequentialNonNestable,
-                           SequentialNestable,
-                           SequentialDelayedNonNestable,
-                           NonNestablePostFromNonNestableTask,
-                           DelayedTasksSameDelay,
-                           DelayedTaskAfterLongTask,
-                           DelayedTaskAfterManyLongTasks);
+REGISTER_TYPED_TEST_SUITE_P(SequencedTaskRunnerTest,
+                            SequentialNonNestable,
+                            SequentialNestable,
+                            SequentialDelayedNonNestable,
+                            NonNestablePostFromNonNestableTask,
+                            DelayedTasksSameDelay,
+                            DelayedTaskAfterLongTask,
+                            DelayedTaskAfterManyLongTasks);
 
 template <typename TaskRunnerTestDelegate>
 class SequencedTaskRunnerDelayedTest
     : public SequencedTaskRunnerTest<TaskRunnerTestDelegate> {};
 
-TYPED_TEST_CASE_P(SequencedTaskRunnerDelayedTest);
+TYPED_TEST_SUITE_P(SequencedTaskRunnerDelayedTest);
 
 // This test posts a delayed task, and checks that the task is run later than
 // the specified time.
@@ -343,7 +343,7 @@
 // SequencedTaskRunnerDelayedTest tests that the |delay| parameter of
 // is used to actually wait for |delay| ms before executing the task.
 // This is not mandatory for a SequencedTaskRunner to be compliant.
-REGISTER_TYPED_TEST_CASE_P(SequencedTaskRunnerDelayedTest, DelayedTaskBasic);
+REGISTER_TYPED_TEST_SUITE_P(SequencedTaskRunnerDelayedTest, DelayedTaskBasic);
 
 }  // namespace base
 
diff --git a/base/test/task_runner_test_template.h b/base/test/task_runner_test_template.h
index 4670522..09d707b 100644
--- a/base/test/task_runner_test_template.h
+++ b/base/test/task_runner_test_template.h
@@ -39,7 +39,7 @@
 // Then you simply #include this file as well as gtest.h and add the
 // following statement to my_task_runner_unittest.cc:
 //
-//   INSTANTIATE_TYPED_TEST_CASE_P(
+//   INSTANTIATE_TYPED_TEST_SUITE_P(
 //       MyTaskRunner, TaskRunnerTest, MyTaskRunnerTestDelegate);
 //
 // Easy!
@@ -47,10 +47,9 @@
 // The optional test harnesses TaskRunnerAffinityTest can be
 // instanciated in the same way, using the same delegate:
 //
-//   INSTANTIATE_TYPED_TEST_CASE_P(
+//   INSTANTIATE_TYPED_TEST_SUITE_P(
 //       MyTaskRunner, TaskRunnerAffinityTest, MyTaskRunnerTestDelegate);
 
-
 #ifndef BASE_TEST_TASK_RUNNER_TEST_TEMPLATE_H_
 #define BASE_TEST_TASK_RUNNER_TEST_TEMPLATE_H_
 
@@ -115,7 +114,7 @@
   TaskRunnerTestDelegate delegate_;
 };
 
-TYPED_TEST_CASE_P(TaskRunnerTest);
+TYPED_TEST_SUITE_P(TaskRunnerTest);
 
 // We can't really test much, since TaskRunner provides very few
 // guarantees.
@@ -168,7 +167,7 @@
 
 // The TaskRunnerTest test case verifies behaviour that is expected from a
 // task runner in order to be conformant.
-REGISTER_TYPED_TEST_CASE_P(TaskRunnerTest, Basic, Delayed);
+REGISTER_TYPED_TEST_SUITE_P(TaskRunnerTest, Basic, Delayed);
 
 namespace test {
 
@@ -182,7 +181,7 @@
 template <typename TaskRunnerTestDelegate>
 class TaskRunnerAffinityTest : public TaskRunnerTest<TaskRunnerTestDelegate> {};
 
-TYPED_TEST_CASE_P(TaskRunnerAffinityTest);
+TYPED_TEST_SUITE_P(TaskRunnerAffinityTest);
 
 // Post a bunch of tasks to the task runner as well as to a separate
 // thread, each checking the value of RunsTasksInCurrentSequence(),
@@ -223,7 +222,7 @@
 
 // TaskRunnerAffinityTest tests that the TaskRunner implementation
 // can determine if tasks will never be run on a specific thread.
-REGISTER_TYPED_TEST_CASE_P(TaskRunnerAffinityTest, RunsTasksInCurrentSequence);
+REGISTER_TYPED_TEST_SUITE_P(TaskRunnerAffinityTest, RunsTasksInCurrentSequence);
 
 }  // namespace base
 
diff --git a/base/test/test_shared_memory_util.cc b/base/test/test_shared_memory_util.cc
index 5093e67c..82b695a 100644
--- a/base/test/test_shared_memory_util.cc
+++ b/base/test/test_shared_memory_util.cc
@@ -71,7 +71,7 @@
 #if defined(OS_FUCHSIA)
 // Fuchsia specific implementation.
 bool CheckReadOnlySharedMemoryFuchsiaHandle(zx::unowned_vmo handle) {
-  const uint32_t flags = ZX_VM_FLAG_PERM_READ | ZX_VM_FLAG_PERM_WRITE;
+  const uint32_t flags = ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
   uintptr_t addr;
   const zx_status_t status =
       zx::vmar::root_self()->map(0, *handle, 0U, kDataSize, flags, &addr);
diff --git a/base/timer/timer_unittest.cc b/base/timer/timer_unittest.cc
index bcfde1c..ed0c1ee 100644
--- a/base/timer/timer_unittest.cc
+++ b/base/timer/timer_unittest.cc
@@ -700,8 +700,8 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(,
-                        TimerTestWithThreadType,
-                        testing::ValuesIn(testing_main_threads));
+INSTANTIATE_TEST_SUITE_P(,
+                         TimerTestWithThreadType,
+                         testing::ValuesIn(testing_main_threads));
 
 }  // namespace base
diff --git a/build/config/BUILD.gn b/build/config/BUILD.gn
index e07318e..ffa036e 100644
--- a/build/config/BUILD.gn
+++ b/build/config/BUILD.gn
@@ -293,6 +293,10 @@
   if (is_win && generate_order_files && !is_nacl) {
     public_deps += [ "//tools/cygprofile_win" ]
   }
+
+  if (is_fuchsia) {
+    public_deps += [ "//third_party/fuchsia-sdk:runtime_library" ]
+  }
 }
 
 group("executable_deps") {
diff --git a/build/config/compiler/compiler.gni b/build/config/compiler/compiler.gni
index 817efb4..5b60633c 100644
--- a/build/config/compiler/compiler.gni
+++ b/build/config/compiler/compiler.gni
@@ -171,9 +171,11 @@
 declare_args() {
   # Set to true to use lld, the LLVM linker.
   # https://crbug.com/911658 for using lld on 32-bit linux.
+  # https://crbug.com/917504 for arm chromeos
   use_lld = is_clang &&
             (is_win || is_fuchsia || is_android ||
-             (is_linux && target_os != "chromeos" && current_cpu != "x86"))
+             (is_linux && target_os != "chromeos" && current_cpu != "x86") ||
+             (target_os == "chromeos" && current_cpu != "arm"))
 }
 
 declare_args() {
diff --git a/build/config/fuchsia/build_manifest.py b/build/config/fuchsia/build_manifest.py
index 0fac7f88..ee13568 100644
--- a/build/config/fuchsia/build_manifest.py
+++ b/build/config/fuchsia/build_manifest.py
@@ -13,73 +13,13 @@
 import tempfile
 
 
-def ReadDynamicLibDeps(paths):
-  """Returns a list of NEEDED libraries read from a binary's ELF header."""
-
-  LIBRARY_RE = re.compile(r'.*\(NEEDED\)\s+Shared library: \[(?P<lib>.*)\]')
-  elfinfo = subprocess.check_output(['readelf', '-d'] + paths,
-                                    stderr=open(os.devnull, 'w'))
-  libs = []
-  for line in elfinfo.split('\n'):
-    match = LIBRARY_RE.match(line.rstrip())
-    if match:
-      lib = match.group('lib')
-
-      # libc.so is an alias for ld.so.1 .
-      if lib == 'libc.so':
-        lib = 'ld.so.1'
-
-      # Skip libzircon.so, as it is supplied by the OS loader.
-      if lib != 'libzircon.so':
-        libs.append(lib)
-
-  return libs
-
-
-def ComputeTransitiveLibDeps(executable_path, available_libs):
-  """Returns a set representing the library dependencies of |executable_path|,
-  the dependencies of its dependencies, and so on.
-
-  A list of candidate library filesystem paths is passed using |available_libs|
-  to help with resolving full paths from the short ELF header filenames."""
-
-  # Stack of binaries (libraries, executables) awaiting traversal.
-  to_visit = [executable_path]
-
-  # The computed set of visited transitive dependencies.
-  deps = set()
-
-  while to_visit:
-    deps = deps.union(to_visit)
-
-    # Resolve the full paths for all of |cur_path|'s NEEDED libraries.
-    dep_paths = {available_libs[dep]
-                 for dep in ReadDynamicLibDeps(list(to_visit))}
-
-    # Add newly discovered dependencies to the pending traversal stack.
-    to_visit = dep_paths.difference(deps)
-
-  return deps
-
-
-def EnumerateDirectoryFiles(path):
-  """Returns a flattened list of all files contained under |path|."""
-
-  output = set()
-  for dirname, _, files in os.walk(path):
-    output = output.union({os.path.join(dirname, f) for f in files})
-  return output
-
-
 def MakePackagePath(file_path, roots):
   """Computes a path for |file_path| that is relative to one of the directory
   paths in |roots|.
 
-  file_path: The absolute file path to relativize.
-  roots: A list of absolute directory paths which may serve as a relative root
-         for |file_path|. At least one path must contain |file_path|.
-         Overlapping roots are permitted; the deepest matching root will be
-         chosen.
+  file_path: The file path to relativize.
+  roots: A list of directory paths which may serve as a relative root
+         for |file_path|.
 
   Examples:
 
@@ -104,28 +44,17 @@
     if file_path.startswith(next_root):
       relative_path = file_path[len(next_root):]
 
-      # Move all dynamic libraries (ending in .so or .so.<number>) to lib/.
-      if re.search('.*\.so(\.\d+)?$', file_path):
-        relative_path = 'lib/' + os.path.basename(relative_path)
-
       return relative_path
 
-  raise Exception('Error: no matching root paths found for \'%s\'.' % file_path)
+  return file_path
 
 
 def _GetStrippedPath(bin_path):
   """Finds the stripped version of the binary |bin_path| in the build
   output directory."""
 
-  # Skip the resolution step for binaries that don't have stripped counterparts,
-  # like system libraries or other libraries built outside the Chromium build.
-  if not '.unstripped' in bin_path:
-    return bin_path
-
-  return os.path.normpath(os.path.join(bin_path,
-                                       os.path.pardir,
-                                       os.path.pardir,
-                                       os.path.basename(bin_path)))
+  return bin_path.replace('lib.unstripped/', 'lib/').replace(
+      'exe.unstripped/', '')
 
 
 def _IsBinary(path):
@@ -141,9 +70,9 @@
   with open(args.output_path, 'w') as manifest, \
        open(args.depfile_path, 'w') as depfile:
     # Process the runtime deps file for file paths, recursively walking
-    # directories as needed. File paths are stored in absolute form,
-    # so that MakePackagePath() may relativize to either the source root or
-    # output directory.
+    # directories as needed.
+    # MakePackagePath() may relativize to either the source root or output
+    # directory.
     # runtime_deps may contain duplicate paths, so use a set for
     # de-duplication.
     expanded_files = set()
@@ -154,37 +83,20 @@
           for current_file in files:
             if current_file.startswith('.'):
               continue
-            expanded_files.add(os.path.abspath(
-                os.path.join(root, current_file)))
+            expanded_files.add(
+                os.path.join(root, current_file))
       else:
-        expanded_files.add(os.path.abspath(next_path))
-
-    # Get set of dist libraries available for dynamic linking.
-    dist_libs = set()
-    for next_dir in args.dynlib_path:
-      dist_libs = dist_libs.union(EnumerateDirectoryFiles(next_dir))
-
-    # Compute the set of dynamic libraries used by the application or its
-    # transitive dependencies (dist libs and components), and merge the result
-    # with |expanded_files| so that they are included in the manifest.
-    #
-    # TODO(crbug.com/861931): Make sure that deps of the files in data_deps
-    # (binaries and libraries) are included as well.
-    expanded_files = expanded_files.union(
-       ComputeTransitiveLibDeps(
-           args.app_filename,
-           {os.path.basename(f): f for f in expanded_files.union(dist_libs)}))
+        expanded_files.add(next_path)
 
     # Format and write out the manifest contents.
-    gen_dir = os.path.join(args.out_dir, "gen")
+    gen_dir = os.path.normpath(os.path.join(args.out_dir, "gen"))
     app_found = False
     excluded_files_set = set(args.exclude_file)
     for current_file in expanded_files:
       if _IsBinary(current_file):
         current_file = _GetStrippedPath(current_file)
 
-      absolute_file_path = os.path.join(args.out_dir, current_file)
-      in_package_path = MakePackagePath(absolute_file_path,
+      in_package_path = MakePackagePath(current_file,
                                         [gen_dir, args.root_dir, args.out_dir])
       if in_package_path == args.app_filename:
         app_found = True
@@ -193,11 +105,7 @@
         excluded_files_set.remove(in_package_path)
         continue
 
-      # The source path is relativized so that it can be used on multiple
-      # environments with differing parent directory structures,
-      # e.g. builder bots and swarming clients.
-      manifest.write('%s=%s\n' % (in_package_path,
-                                  os.path.relpath(current_file, args.out_dir)))
+      manifest.write('%s=%s\n' % (in_package_path, current_file))
 
     if len(excluded_files_set) > 0:
       raise Exception('Some files were excluded with --exclude-file, but '
@@ -249,8 +157,6 @@
       help='Path to write GN deps file.')
   parser.add_argument('--exclude-file', action='append', default=[],
       help='Package-relative file path to exclude from the package.')
-  parser.add_argument('--dynlib-path', action='append', default=[],
-      help='Paths for the dynamic libraries relative to the output dir.')
   parser.add_argument('--output-path', required=True, help='Output file path.')
 
   args = parser.parse_args()
diff --git a/build/config/fuchsia/config.gni b/build/config/fuchsia/config.gni
index 165153b..cdf684e2 100644
--- a/build/config/fuchsia/config.gni
+++ b/build/config/fuchsia/config.gni
@@ -11,9 +11,9 @@
 
 # Compute the arch-specific path to packages' dynamic library dependencies.
 if (current_cpu == "arm64") {
-  dist_libroot = fuchsia_sdk + "/arch/arm64/dist/"
+  dist_libroot = fuchsia_sdk + "/arch/arm64/dist"
 } else if (current_cpu == "x64") {
-  dist_libroot = fuchsia_sdk + "/arch/x64/dist/"
+  dist_libroot = fuchsia_sdk + "/arch/x64/dist"
 } else {
   assert(false, "No libraries available for architecture: $current_cpu")
 }
diff --git a/build/config/fuchsia/package.gni b/build/config/fuchsia/package.gni
index 2e71029..924eb0a 100644
--- a/build/config/fuchsia/package.gni
+++ b/build/config/fuchsia/package.gni
@@ -85,25 +85,21 @@
 
     args = [
       "--root-dir",
-      rebase_path("//"),
+      rebase_path("//", root_build_dir),
       "--out-dir",
-      rebase_path(root_out_dir),
+      rebase_path(root_out_dir, root_build_dir),
       "--app-name",
       pkg.package_name,
       "--app-filename",
       get_label_info(pkg.binary, "name"),
       "--sandbox-policy-path",
-      rebase_path(pkg.sandbox_policy),
+      rebase_path(pkg.sandbox_policy, root_build_dir),
       "--runtime-deps-file",
-      rebase_path(_runtime_deps_file),
+      rebase_path(_runtime_deps_file, root_build_dir),
       "--depfile-path",
-      rebase_path(_depfile),
-      "--dynlib-path",
-      rebase_path(dist_libroot),
-      "--dynlib-path",
-      rebase_path("${sysroot}/dist"),
+      rebase_path(_depfile, root_build_dir),
       "--output-path",
-      rebase_path(_archive_manifest),
+      rebase_path(_archive_manifest, root_build_dir),
     ]
 
     if (defined(pkg.excluded_files)) {
diff --git a/build/toolchain/fuchsia/BUILD.gn b/build/toolchain/fuchsia/BUILD.gn
index 7ab3a2d0..654073b 100644
--- a/build/toolchain/fuchsia/BUILD.gn
+++ b/build/toolchain/fuchsia/BUILD.gn
@@ -2,8 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/toolchain/gcc_toolchain.gni")
 import("//build/config/fuchsia/config.gni")
+import("//build/toolchain/gcc_toolchain.gni")
 
 # Fuchsia builds using the Clang toolchain, with most parameters common across
 # the different target architectures.
@@ -21,6 +21,8 @@
       use_unstripped_as_runtime_outputs = true
     }
 
+    default_shlib_subdir = "/lib"
+
     toolchain_args = invoker.toolchain_args
     toolchain_args.current_os = "fuchsia"
   }
diff --git a/build/toolchain/gcc_toolchain.gni b/build/toolchain/gcc_toolchain.gni
index d3bf0ae0..42686fb 100644
--- a/build/toolchain/gcc_toolchain.gni
+++ b/build/toolchain/gcc_toolchain.gni
@@ -241,6 +241,12 @@
       default_shlib_extension = shlib_extension
     }
 
+    if (defined(invoker.default_shlib_subdir)) {
+      default_shlib_subdir = invoker.default_shlib_subdir
+    } else {
+      default_shlib_subdir = ""
+    }
+
     if (defined(invoker.executable_extension)) {
       default_executable_extension = invoker.executable_extension
     } else {
@@ -434,7 +440,7 @@
       # specifies).
       default_output_extension = default_shlib_extension
 
-      default_output_dir = "{{root_out_dir}}"
+      default_output_dir = "{{root_out_dir}}${default_shlib_subdir}"
 
       output_prefix = "lib"
 
@@ -494,7 +500,7 @@
         default_output_extension = default_shlib_extension
       }
 
-      default_output_dir = "{{root_out_dir}}"
+      default_output_dir = "{{root_out_dir}}${default_shlib_subdir}"
 
       output_prefix = "lib"
 
@@ -622,6 +628,7 @@
                            [
                              "strip",
                              "is_clang_analysis_supported",
+                             "default_shlib_subdir",
                              "enable_linker_map",
                              "use_unstripped_as_runtime_outputs",
                            ])
diff --git a/build/toolchain/toolchain.gni b/build/toolchain/toolchain.gni
index c0e1dbe..f9fb7e7 100644
--- a/build/toolchain/toolchain.gni
+++ b/build/toolchain/toolchain.gni
@@ -78,6 +78,13 @@
   shlib_prefix = ""
 }
 
+# Directory for shared library files.
+if (is_fuchsia) {
+  shlib_subdir = "/lib"
+} else {
+  shlib_subdir = ""
+}
+
 # While other "tool"s in a toolchain are specific to the target of that
 # toolchain, the "stamp" and "copy" tools are really generic to the host;
 # but each toolchain must define them separately.  GN doesn't allow a
diff --git a/cc/test/layer_tree_test.cc b/cc/test/layer_tree_test.cc
index 7943fd4..a1bf3e6 100644
--- a/cc/test/layer_tree_test.cc
+++ b/cc/test/layer_tree_test.cc
@@ -439,11 +439,10 @@
     test_hooks_->BeginMainFrame(args);
   }
 
+  void RecordStartOfFrameMetrics() override {}
   void RecordEndOfFrameMetrics(base::TimeTicks) override {}
 
-  void UpdateLayerTreeHost(bool record_main_frame_metrics) override {
-    test_hooks_->UpdateLayerTreeHost();
-  }
+  void UpdateLayerTreeHost() override { test_hooks_->UpdateLayerTreeHost(); }
 
   void ApplyViewportChanges(const ApplyViewportChangesArgs& args) override {
     test_hooks_->ApplyViewportChanges(args);
diff --git a/cc/test/stub_layer_tree_host_client.h b/cc/test/stub_layer_tree_host_client.h
index 021d747a..47c43574 100644
--- a/cc/test/stub_layer_tree_host_client.h
+++ b/cc/test/stub_layer_tree_host_client.h
@@ -18,10 +18,11 @@
   void WillBeginMainFrame() override {}
   void DidBeginMainFrame() override {}
   void BeginMainFrame(const viz::BeginFrameArgs& args) override {}
+  void RecordStartOfFrameMetrics() override {}
   void RecordEndOfFrameMetrics(base::TimeTicks) override {}
   void BeginMainFrameNotExpectedSoon() override {}
   void BeginMainFrameNotExpectedUntil(base::TimeTicks time) override {}
-  void UpdateLayerTreeHost(bool record_main_frame_metrics) override {}
+  void UpdateLayerTreeHost() override {}
   void ApplyViewportChanges(const ApplyViewportChangesArgs&) override {}
   void RecordWheelAndTouchScrollingCount(bool has_scrolled_by_wheel,
                                          bool has_scrolled_by_touch) override {}
diff --git a/cc/tiles/paint_worklet_image_cache.cc b/cc/tiles/paint_worklet_image_cache.cc
index 47f7773..791cbb3f 100644
--- a/cc/tiles/paint_worklet_image_cache.cc
+++ b/cc/tiles/paint_worklet_image_cache.cc
@@ -50,12 +50,13 @@
 // then there is no need to call the Paint() function.
 void PaintWorkletImageCache::PaintImageInTask(const PaintImage& paint_image) {
   sk_sp<PaintRecord> record = painter_->Paint();
-  records_[paint_image.paint_worklet_input()] = record;
+  records_[paint_image.paint_worklet_input()] =
+      std::make_pair(std::move(record), 0);
 }
 
 PaintRecord* PaintWorkletImageCache::GetPaintRecordForTest(
     PaintWorkletInput* input) {
-  return records_[input].get();
+  return records_[input].first.get();
 }
 
 }  // namespace cc
diff --git a/cc/tiles/paint_worklet_image_cache.h b/cc/tiles/paint_worklet_image_cache.h
index d857dc3..7b7b23df 100644
--- a/cc/tiles/paint_worklet_image_cache.h
+++ b/cc/tiles/paint_worklet_image_cache.h
@@ -5,6 +5,8 @@
 #ifndef CC_TILES_PAINT_WORKLET_IMAGE_CACHE_H_
 #define CC_TILES_PAINT_WORKLET_IMAGE_CACHE_H_
 
+#include <utility>
+
 #include "base/containers/flat_map.h"
 #include "cc/cc_export.h"
 #include "cc/paint/draw_image.h"
@@ -32,15 +34,19 @@
   void PaintImageInTask(const PaintImage& paint_image);
 
   PaintRecord* GetPaintRecordForTest(PaintWorkletInput* input);
-  const base::flat_map<PaintWorkletInput*, sk_sp<PaintRecord>>&
+  const base::flat_map<PaintWorkletInput*,
+                       std::pair<sk_sp<PaintRecord>, size_t>>&
   GetRecordsForTest() {
     return records_;
   }
 
  private:
-  // The PaintRecord is produced by PaintWorkletLayerPainter::Paint(), and used
-  // for raster.
-  base::flat_map<PaintWorkletInput*, sk_sp<PaintRecord>> records_;
+  // This is a map of paint worklet inputs to a pair of paint record and a
+  // reference count. The paint record is the representation of the worklet
+  // output based on the input, and the reference count is the number of times
+  // that it is used for tile rasterization.
+  base::flat_map<PaintWorkletInput*, std::pair<sk_sp<PaintRecord>, size_t>>
+      records_;
   // The PaintWorkletImageCache is owned by ImageController, which has the same
   // life time as the LayerTreeHostImpl, that guarantees that the painter will
   // live as long as the LayerTreeHostImpl.
diff --git a/cc/tiles/paint_worklet_image_cache_unittest.cc b/cc/tiles/paint_worklet_image_cache_unittest.cc
index 18355d4..5b448d6 100644
--- a/cc/tiles/paint_worklet_image_cache_unittest.cc
+++ b/cc/tiles/paint_worklet_image_cache_unittest.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <utility>
+
 #include "cc/tiles/paint_worklet_image_cache.h"
 
 #include "cc/paint/draw_image.h"
@@ -87,15 +89,17 @@
   TestTileTaskRunner::ProcessTask(task1.get());
   TestTileTaskRunner::ProcessTask(task2.get());
 
-  base::flat_map<PaintWorkletInput*, sk_sp<PaintRecord>> records =
-      cache.GetRecordsForTest();
+  base::flat_map<PaintWorkletInput*, std::pair<sk_sp<PaintRecord>, size_t>>
+      records = cache.GetRecordsForTest();
   EXPECT_EQ(records.size(), 2u);
 
-  PaintRecord* record1 = records[paint_image1.paint_worklet_input()].get();
+  PaintRecord* record1 =
+      records[paint_image1.paint_worklet_input()].first.get();
   EXPECT_TRUE(record1);
   TestPaintRecord(record1);
 
-  PaintRecord* record2 = records[paint_image2.paint_worklet_input()].get();
+  PaintRecord* record2 =
+      records[paint_image2.paint_worklet_input()].first.get();
   EXPECT_TRUE(record2);
   TestPaintRecord(record2);
 }
diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc
index 7e0328a..5aca1c6 100644
--- a/cc/trees/layer_tree_host.cc
+++ b/cc/trees/layer_tree_host.cc
@@ -277,8 +277,8 @@
   return debug_state_;
 }
 
-void LayerTreeHost::RequestMainFrameUpdate(bool record_main_frame_metrics) {
-  client_->UpdateLayerTreeHost(record_main_frame_metrics);
+void LayerTreeHost::RequestMainFrameUpdate() {
+  client_->UpdateLayerTreeHost();
 }
 
 // This function commits the LayerTreeHost to an impl tree. When modifying
@@ -644,7 +644,7 @@
   DCHECK(IsSingleThreaded());
   // This function is only valid when not using the scheduler.
   DCHECK(!settings_.single_thread_proxy_scheduler);
-  RequestMainFrameUpdate(false /* record_main_frame_metrics */);
+  RequestMainFrameUpdate();
   UpdateLayers();
 }
 
@@ -942,6 +942,10 @@
   RecordWheelAndTouchScrollingCount(*info);
 }
 
+void LayerTreeHost::RecordStartOfFrameMetrics() {
+  client_->RecordStartOfFrameMetrics();
+}
+
 void LayerTreeHost::RecordEndOfFrameMetrics(base::TimeTicks frame_begin_time) {
   client_->RecordEndOfFrameMetrics(frame_begin_time);
 }
diff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h
index d98608d..fdfbdad 100644
--- a/cc/trees/layer_tree_host.h
+++ b/cc/trees/layer_tree_host.h
@@ -578,7 +578,7 @@
   void BeginMainFrameNotExpectedSoon();
   void BeginMainFrameNotExpectedUntil(base::TimeTicks time);
   void AnimateLayers(base::TimeTicks monotonic_frame_begin_time);
-  void RequestMainFrameUpdate(bool record_main_frame_metrics);
+  void RequestMainFrameUpdate();
   void FinishCommitOnImplThread(LayerTreeHostImpl* host_impl);
   void WillCommit();
   void CommitComplete();
@@ -602,6 +602,7 @@
   // Called when the compositor completed page scale animation.
   void DidCompletePageScaleAnimation();
   void ApplyScrollAndScale(ScrollAndScaleSet* info);
+  void RecordStartOfFrameMetrics();
   void RecordEndOfFrameMetrics(base::TimeTicks frame_begin_time);
 
   LayerTreeHostClient* client() { return client_; }
diff --git a/cc/trees/layer_tree_host_client.h b/cc/trees/layer_tree_host_client.h
index a282dae8..9ac8155a 100644
--- a/cc/trees/layer_tree_host_client.h
+++ b/cc/trees/layer_tree_host_client.h
@@ -91,11 +91,8 @@
   // (Blink's notions of) style, layout, paint invalidation and compositing
   // state. (The "compositing state" will result in a mutated layer tree on the
   // LayerTreeHost via additional interface indirections which lead back to
-  // mutations on the LayerTreeHost.) The |record_main_frame_metrics| flag
-  // determines whether Blink will compute metrics related to main frame update
-  // time. If true, the caller must ensure that RecordEndOfFrameMetrics is
-  // called when this method returns and the total main frame time is known.
-  virtual void UpdateLayerTreeHost(bool record_main_frame_metrics) = 0;
+  // mutations on the LayerTreeHost.)
+  virtual void UpdateLayerTreeHost() = 0;
 
   // Notifies the client of viewport-related changes that occured in the
   // LayerTreeHost since the last commit. This typically includes things
@@ -129,8 +126,9 @@
   virtual void DidPresentCompositorFrame(
       uint32_t frame_token,
       const gfx::PresentationFeedback& feedback) = 0;
-  // Record UMA and UKM metrics that require the time from the start of
-  // BeginMainFrame to the Commit, or early out.
+  // Mark the frame start and end time for UMA and UKM metrics that require
+  // the time from the start of BeginMainFrame to the Commit, or early out.
+  virtual void RecordStartOfFrameMetrics() = 0;
   virtual void RecordEndOfFrameMetrics(base::TimeTicks frame_begin_time) = 0;
   virtual void DidGenerateLocalSurfaceIdAllocation(
       const viz::LocalSurfaceIdAllocation& allocation) = 0;
diff --git a/cc/trees/proxy_main.cc b/cc/trees/proxy_main.cc
index bce8661..2459f4a 100644
--- a/cc/trees/proxy_main.cc
+++ b/cc/trees/proxy_main.cc
@@ -207,6 +207,7 @@
       begin_main_frame_state->scroll_info.get());
 
   layer_tree_host_->WillBeginMainFrame();
+  layer_tree_host_->RecordStartOfFrameMetrics();
 
   // See LayerTreeHostClient::BeginMainFrame for more documentation on
   // what this does.
@@ -225,8 +226,7 @@
 
   // See LayerTreeHostClient::MainFrameUpdate for more documentation on
   // what this does.
-  layer_tree_host_->RequestMainFrameUpdate(
-      true /* record_main_frame_metrics */);
+  layer_tree_host_->RequestMainFrameUpdate();
 
   // TODO(schenney) This will be changed to defer_commits_ when we introduce
   // the separation between deferring of main frame updates and deferring of
diff --git a/cc/trees/single_thread_proxy.cc b/cc/trees/single_thread_proxy.cc
index 68986683..9518bbc 100644
--- a/cc/trees/single_thread_proxy.cc
+++ b/cc/trees/single_thread_proxy.cc
@@ -809,8 +809,7 @@
   layer_tree_host_->WillBeginMainFrame();
   layer_tree_host_->BeginMainFrame(begin_frame_args);
   layer_tree_host_->AnimateLayers(begin_frame_args.frame_time);
-  layer_tree_host_->RequestMainFrameUpdate(
-      false /* record_main_frame_metrics */);
+  layer_tree_host_->RequestMainFrameUpdate();
 }
 
 void SingleThreadProxy::DoPainting() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreJavaUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreJavaUtils.java
index 1ab95f71..a05f950 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreJavaUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreJavaUtils.java
@@ -203,8 +203,8 @@
             public boolean onInfoBarButtonClicked(boolean isPrimary) {
                 try {
                     assert sRequestInstallInstance == null;
-                    ArCoreShim.InstallStatus installStatus =
-                            getArCoreShimInstance().requestInstall(activity, true);
+                    @ArCoreShim.InstallStatus
+                    int installStatus = getArCoreShimInstance().requestInstall(activity, true);
 
                     if (installStatus == ArCoreShim.InstallStatus.INSTALL_REQUESTED) {
                         // Install flow will resume in onArCoreRequestInstallReturned, mark that
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreShim.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreShim.java
index 2f26a8c0..04c5deb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreShim.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreShim.java
@@ -24,7 +24,12 @@
      * For detailed description, please see:
      * https://developers.google.com/ar/reference/java/arcore/reference/com/google/ar/core/ArCoreApk.InstallStatus
      */
-    public enum InstallStatus { INSTALLED, INSTALL_REQUESTED }
+    @IntDef({InstallStatus.INSTALLED, InstallStatus.INSTALL_REQUESTED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface InstallStatus {
+        int INSTALLED = 0;
+        int INSTALL_REQUESTED = 1;
+    }
 
     /**
      * Equivalent of ArCoreApk.Availability enum.
@@ -55,7 +60,7 @@
     /**
      * Equivalent of ArCoreApk.requestInstall.
      */
-    public InstallStatus requestInstall(Activity activity, boolean userRequestedInstall)
+    public @InstallStatus int requestInstall(Activity activity, boolean userRequestedInstall)
             throws UnavailableDeviceNotCompatibleException,
                    UnavailableUserDeclinedInstallationException;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreShimImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreShimImpl.java
index 901945f..1b6696f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreShimImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/ArCoreShimImpl.java
@@ -19,7 +19,7 @@
     public ArCoreShimImpl() {}
 
     @Override
-    public InstallStatus requestInstall(Activity activity, boolean userRequestedInstall)
+    public @InstallStatus int requestInstall(Activity activity, boolean userRequestedInstall)
             throws UnavailableDeviceNotCompatibleException,
                    UnavailableUserDeclinedInstallationException {
         try {
@@ -40,7 +40,7 @@
         return mapArCoreApkAvailability(availability);
     }
 
-    private InstallStatus mapArCoreApkInstallStatus(ArCoreApk.InstallStatus installStatus) {
+    private @InstallStatus int mapArCoreApkInstallStatus(ArCoreApk.InstallStatus installStatus) {
         switch (installStatus) {
             case INSTALLED:
                 return InstallStatus.INSTALLED;
diff --git a/chrome/app/BUILD.gn b/chrome/app/BUILD.gn
index 9fcd111..f7320581 100644
--- a/chrome/app/BUILD.gn
+++ b/chrome/app/BUILD.gn
@@ -9,7 +9,6 @@
 import("//device/vr/buildflags/buildflags.gni")
 import("//ppapi/buildflags/buildflags.gni")
 import("//printing/buildflags/buildflags.gni")
-import("//services/catalog/public/tools/catalog.gni")
 import("//services/service_manager/public/service_manifest.gni")
 import("//tools/grit/grit_rule.gni")
 import("//tools/ipc_fuzzer/ipc_fuzzer.gni")
@@ -589,10 +588,10 @@
 
   if (is_chromeos) {
     deps += [
-      "//ash:manifest",
       "//ash/components/quick_launch:manifest",
       "//ash/components/shortcut_viewer:manifest",
       "//ash/components/tap_visualizer:manifest",
+      "//ash/public/cpp:manifest",
       "//chrome/browser/chromeos:ash_pref_connector_manifest",
       "//chrome/services/cups_ipp_parser:manifest",
       "//chromeos/services/ime:manifest",
diff --git a/chrome/app/DEPS b/chrome/app/DEPS
index 376ea29..8902a820 100644
--- a/chrome/app/DEPS
+++ b/chrome/app/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
   "+ash/components/quick_launch/public",
+  "+ash/public",
   "+chrome/browser",
   "+chrome/child",
   "+chrome/chrome_watcher",
@@ -79,7 +80,6 @@
     "+ash/components/quick_launch",
     "+ash/components/shortcut_viewer",
     "+ash/components/tap_visualizer",
-    "+ash/manifest.h",
     "+chrome/services/cups_ipp_parser",
     "+chrome/services/file_util",
     "+chrome/services/isolated_xr_device",
diff --git a/chrome/app/chrome_packaged_service_manifests.cc b/chrome/app/chrome_packaged_service_manifests.cc
index c25964a..114bced 100644
--- a/chrome/app/chrome_packaged_service_manifests.cc
+++ b/chrome/app/chrome_packaged_service_manifests.cc
@@ -25,7 +25,7 @@
 #include "ash/components/quick_launch/manifest.h"
 #include "ash/components/shortcut_viewer/manifest.h"
 #include "ash/components/tap_visualizer/manifest.h"
-#include "ash/manifest.h"
+#include "ash/public/cpp/manifest.h"
 #include "chrome/browser/chromeos/ash_pref_connector_manifest.h"
 #include "chrome/services/cups_ipp_parser/manifest.h"
 #include "chromeos/services/ime/manifest.h"
diff --git a/chrome/app/printing_strings.grdp b/chrome/app/printing_strings.grdp
index 6abc12c..71329f5f 100644
--- a/chrome/app/printing_strings.grdp
+++ b/chrome/app/printing_strings.grdp
@@ -208,7 +208,7 @@
       Manage
     </message>
     <message name="IDS_PRINT_PREVIEW_SEE_MORE" desc="Dropdown option to view more print destinations in a dialog." meaning="Label text">
-      See more
+      See more...
     </message>
     <message name="IDS_PRINT_PREVIEW_SEE_MORE_DESTINATIONS_LABEL" desc="Aria label for the dropdown option to view more print destinations in a dialog. Expansion of SEE_MORE above to add context for screen readers." meaning="Label text">
       See more destinations
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index bc57528..1d1207c 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -3658,6 +3658,12 @@
     <message name="IDS_SETTINGS_SYNC_SETTINGS_CANCEL_SYNC" desc="The label of button that allows user to cancel sync.">
       Cancel sync
     </message>
+    <message name="IDS_SETTINGS_SYNC_SETUP_CANCEL_DIALOG_TITLE" desc="The title of the dialog to confirm the sync cancellation.">
+      Cancel sync?
+    </message>
+    <message name="IDS_SETTINGS_SYNC_SETUP_CANCEL_DIALOG_BODY" desc="The body text of the dialog to confirm the sync cancellation.">
+      You can turn on sync anytime in settings
+    </message>
   </if>
   <message name="IDS_SETTINGS_SYNC_TIMEOUT" desc="Text explaining what to do if sync times out.">
     Check your internet connection. If the problem continues, try signing out and signing in again.
diff --git a/chrome/app/vector_icons/trash_can.icon b/chrome/app/vector_icons/trash_can.icon
index 1ac9ed1..0b6316b 100644
--- a/chrome/app/vector_icons/trash_can.icon
+++ b/chrome/app/vector_icons/trash_can.icon
@@ -1,21 +1,21 @@
-// 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.

-

-CANVAS_DIMENSIONS, 16,

-MOVE_TO, 4, 12.67f,

-CUBIC_TO, 4, 13.4f, 4.6f, 14, 5.33f, 14,

-R_H_LINE_TO, 5.33f,

-CUBIC_TO, 11.4f, 14, 12, 13.4f, 12, 12.67f,

-R_V_LINE_TO, -8,

-H_LINE_TO, 4,

-CLOSE,

-R_MOVE_TO, 8.67f, -10,

-R_H_LINE_TO, -2.33f,

-LINE_TO, 9.67f, 2,

-H_LINE_TO, 6.33f,

-R_LINE_TO, -0.67f, 0.67f,

-H_LINE_TO, 3.33f,

-V_LINE_TO, 4,

-R_H_LINE_TO, 9.33f,

-CLOSE

+// 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.
+
+CANVAS_DIMENSIONS, 16,
+MOVE_TO, 4, 12.67f,
+CUBIC_TO, 4, 13.4f, 4.6f, 14, 5.33f, 14,
+R_H_LINE_TO, 5.33f,
+CUBIC_TO, 11.4f, 14, 12, 13.4f, 12, 12.67f,
+R_V_LINE_TO, -8,
+H_LINE_TO, 4,
+CLOSE,
+R_MOVE_TO, 8.67f, -10,
+R_H_LINE_TO, -2.33f,
+LINE_TO, 9.67f, 2,
+H_LINE_TO, 6.33f,
+R_LINE_TO, -0.67f, 0.67f,
+H_LINE_TO, 3.33f,
+V_LINE_TO, 4,
+R_H_LINE_TO, 9.33f,
+CLOSE
diff --git a/chrome/app/vector_icons/vr_headset.icon b/chrome/app/vector_icons/vr_headset.icon
index 91ccffb..1a43c384 100644
--- a/chrome/app/vector_icons/vr_headset.icon
+++ b/chrome/app/vector_icons/vr_headset.icon
@@ -1,79 +1,79 @@
-// 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.

-

-CANVAS_DIMENSIONS, 20,

-MOVE_TO, 17.4f, 5.57f,

-ARC_TO, 2.12f, 2.12f, 0, 0, 0, 15.94f, 5,

-H_LINE_TO, 4.06f,

-R_CUBIC_TO, -0.55f, 0, -1.07f, 0.2f, -1.46f, 0.56f,

-R_CUBIC_TO, -0.39f, 0.37f, -0.6f, 0.85f, -0.6f, 1.36f,

-R_V_LINE_TO, 6.14f,

-R_CUBIC_TO, 0, 0.52f, 0.21f, 1, 0.6f, 1.36f,

-R_CUBIC_TO, 0.39f, 0.37f, 0.91f, 0.56f, 1.46f, 0.56f,

-R_H_LINE_TO, 2.85f,

-R_CUBIC_TO, 0.37f, 0, 0.74f, -0.09f, 1.06f, -0.28f,

-R_CUBIC_TO, 0.32f, -0.18f, 0.58f, -0.43f, 0.76f, -0.74f,

-R_LINE_TO, 0.81f, -1.4f,

-R_ARC_TO, 0.48f, 0.48f, 0, 0, 1, 0.13f, -0.62f,

-R_ARC_TO, 0.56f, 0.56f, 0, 0, 1, 0.67f, 0,

-R_CUBIC_TO, 0.2f, 0.15f, 0.25f, 0.41f, 0.13f, 0.62f,

-R_LINE_TO, 0.81f, 1.4f,

-R_CUBIC_TO, 0.18f, 0.3f, 0.44f, 0.56f, 0.76f, 0.74f,

-R_CUBIC_TO, 0.32f, 0.18f, 0.68f, 0.28f, 1.06f, 0.28f,

-R_H_LINE_TO, 2.85f,

-R_CUBIC_TO, 0.55f, 0, 1.07f, -0.2f, 1.46f, -0.56f,

-R_CUBIC_TO, 0.39f, -0.36f, 0.6f, -0.85f, 0.6f, -1.36f,

-V_LINE_TO, 6.93f,

-R_CUBIC_TO, 0, -0.51f, -0.21f, -1, -0.6f, -1.36f,

-CLOSE,

-MOVE_TO, 6.5f, 11.5f,

-R_CUBIC_TO, -1.11f, 0, -2, -0.9f, -2, -2,

-R_CUBIC_TO, 0, -1.1f, 0.9f, -2, 2, -2,

-R_CUBIC_TO, 1.1f, 0, 2, 0.9f, 2, 2,

-R_CUBIC_TO, 0, 1.1f, -0.89f, 2, -2, 2,

-CLOSE,

-R_MOVE_TO, 7, 0,

-R_CUBIC_TO, -1.11f, 0, -2, -0.9f, -2, -2,

-R_CUBIC_TO, 0, -1.1f, 0.9f, -2, 2, -2,

-R_CUBIC_TO, 1.1f, 0, 2, 0.9f, 2, 2,

-R_CUBIC_TO, 0, 1.1f, -0.89f, 2, -2, 2,

-CLOSE

-

-CANVAS_DIMENSIONS, 16,

-MOVE_TO, 14.47f, 4.45f,

-ARC_TO, 1.95f, 1.95f, 0, 0, 0, 13.2f, 4,

-H_LINE_TO, 2.8f,

-R_CUBIC_TO, -0.48f, 0, -0.93f, 0.16f, -1.27f, 0.45f,

-R_CUBIC_TO, -0.34f, 0.29f, -0.53f, 0.68f, -0.53f, 1.09f,

-R_V_LINE_TO, 4.91f,

-R_CUBIC_TO, 0, 0.41f, 0.19f, 0.8f, 0.53f, 1.09f,

-R_CUBIC_TO, 0.34f, 0.29f, 0.79f, 0.45f, 1.27f, 0.45f,

-R_H_LINE_TO, 2.5f,

-R_CUBIC_TO, 0.33f, 0, 0.64f, -0.08f, 0.92f, -0.22f,

-R_CUBIC_TO, 0.28f, -0.14f, 0.51f, -0.35f, 0.66f, -0.6f,

-R_LINE_TO, 0.7f, -1.12f,

-R_CUBIC_TO, -0.1f, -0.17f, -0.05f, -0.37f, 0.12f, -0.49f,

-R_ARC_TO, 0.53f, 0.53f, 0, 0, 1, 0.59f, 0,

-R_CUBIC_TO, 0.17f, 0.12f, 0.22f, 0.33f, 0.12f, 0.49f,

-R_LINE_TO, 0.7f, 1.12f,

-R_CUBIC_TO, 0.15f, 0.24f, 0.38f, 0.45f, 0.66f, 0.6f,

-R_CUBIC_TO, 0.28f, 0.14f, 0.6f, 0.22f, 0.92f, 0.22f,

-R_H_LINE_TO, 2.5f,

-R_CUBIC_TO, 0.48f, 0, 0.93f, -0.16f, 1.27f, -0.45f,

-R_CUBIC_TO, 0.34f, -0.29f, 0.53f, -0.68f, 0.53f, -1.09f,

-V_LINE_TO, 5.54f,

-R_CUBIC_TO, 0, -0.41f, -0.19f, -0.8f, -0.53f, -1.09f,

-CLOSE,

-MOVE_TO, 5, 9,

-R_CUBIC_TO, -0.83f, 0, -1.5f, -0.67f, -1.5f, -1.5f,

-CUBIC_TO_SHORTHAND, 4.18f, 6, 5, 6,

-R_CUBIC_TO, 0.82f, 0, 1.5f, 0.68f, 1.5f, 1.5f,

-CUBIC_TO_SHORTHAND, 5.83f, 9, 5, 9,

-CLOSE,

-R_MOVE_TO, 6, 0,

-R_CUBIC_TO, -0.83f, 0, -1.5f, -0.67f, -1.5f, -1.5f,

-CUBIC_TO_SHORTHAND, 10.18f, 6, 11, 6,

-R_CUBIC_TO, 0.82f, 0, 1.5f, 0.68f, 1.5f, 1.5f,

-CUBIC_TO_SHORTHAND, 11.83f, 9, 11, 9,

-CLOSE

+// 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.
+
+CANVAS_DIMENSIONS, 20,
+MOVE_TO, 17.4f, 5.57f,
+ARC_TO, 2.12f, 2.12f, 0, 0, 0, 15.94f, 5,
+H_LINE_TO, 4.06f,
+R_CUBIC_TO, -0.55f, 0, -1.07f, 0.2f, -1.46f, 0.56f,
+R_CUBIC_TO, -0.39f, 0.37f, -0.6f, 0.85f, -0.6f, 1.36f,
+R_V_LINE_TO, 6.14f,
+R_CUBIC_TO, 0, 0.52f, 0.21f, 1, 0.6f, 1.36f,
+R_CUBIC_TO, 0.39f, 0.37f, 0.91f, 0.56f, 1.46f, 0.56f,
+R_H_LINE_TO, 2.85f,
+R_CUBIC_TO, 0.37f, 0, 0.74f, -0.09f, 1.06f, -0.28f,
+R_CUBIC_TO, 0.32f, -0.18f, 0.58f, -0.43f, 0.76f, -0.74f,
+R_LINE_TO, 0.81f, -1.4f,
+R_ARC_TO, 0.48f, 0.48f, 0, 0, 1, 0.13f, -0.62f,
+R_ARC_TO, 0.56f, 0.56f, 0, 0, 1, 0.67f, 0,
+R_CUBIC_TO, 0.2f, 0.15f, 0.25f, 0.41f, 0.13f, 0.62f,
+R_LINE_TO, 0.81f, 1.4f,
+R_CUBIC_TO, 0.18f, 0.3f, 0.44f, 0.56f, 0.76f, 0.74f,
+R_CUBIC_TO, 0.32f, 0.18f, 0.68f, 0.28f, 1.06f, 0.28f,
+R_H_LINE_TO, 2.85f,
+R_CUBIC_TO, 0.55f, 0, 1.07f, -0.2f, 1.46f, -0.56f,
+R_CUBIC_TO, 0.39f, -0.36f, 0.6f, -0.85f, 0.6f, -1.36f,
+V_LINE_TO, 6.93f,
+R_CUBIC_TO, 0, -0.51f, -0.21f, -1, -0.6f, -1.36f,
+CLOSE,
+MOVE_TO, 6.5f, 11.5f,
+R_CUBIC_TO, -1.11f, 0, -2, -0.9f, -2, -2,
+R_CUBIC_TO, 0, -1.1f, 0.9f, -2, 2, -2,
+R_CUBIC_TO, 1.1f, 0, 2, 0.9f, 2, 2,
+R_CUBIC_TO, 0, 1.1f, -0.89f, 2, -2, 2,
+CLOSE,
+R_MOVE_TO, 7, 0,
+R_CUBIC_TO, -1.11f, 0, -2, -0.9f, -2, -2,
+R_CUBIC_TO, 0, -1.1f, 0.9f, -2, 2, -2,
+R_CUBIC_TO, 1.1f, 0, 2, 0.9f, 2, 2,
+R_CUBIC_TO, 0, 1.1f, -0.89f, 2, -2, 2,
+CLOSE
+
+CANVAS_DIMENSIONS, 16,
+MOVE_TO, 14.47f, 4.45f,
+ARC_TO, 1.95f, 1.95f, 0, 0, 0, 13.2f, 4,
+H_LINE_TO, 2.8f,
+R_CUBIC_TO, -0.48f, 0, -0.93f, 0.16f, -1.27f, 0.45f,
+R_CUBIC_TO, -0.34f, 0.29f, -0.53f, 0.68f, -0.53f, 1.09f,
+R_V_LINE_TO, 4.91f,
+R_CUBIC_TO, 0, 0.41f, 0.19f, 0.8f, 0.53f, 1.09f,
+R_CUBIC_TO, 0.34f, 0.29f, 0.79f, 0.45f, 1.27f, 0.45f,
+R_H_LINE_TO, 2.5f,
+R_CUBIC_TO, 0.33f, 0, 0.64f, -0.08f, 0.92f, -0.22f,
+R_CUBIC_TO, 0.28f, -0.14f, 0.51f, -0.35f, 0.66f, -0.6f,
+R_LINE_TO, 0.7f, -1.12f,
+R_CUBIC_TO, -0.1f, -0.17f, -0.05f, -0.37f, 0.12f, -0.49f,
+R_ARC_TO, 0.53f, 0.53f, 0, 0, 1, 0.59f, 0,
+R_CUBIC_TO, 0.17f, 0.12f, 0.22f, 0.33f, 0.12f, 0.49f,
+R_LINE_TO, 0.7f, 1.12f,
+R_CUBIC_TO, 0.15f, 0.24f, 0.38f, 0.45f, 0.66f, 0.6f,
+R_CUBIC_TO, 0.28f, 0.14f, 0.6f, 0.22f, 0.92f, 0.22f,
+R_H_LINE_TO, 2.5f,
+R_CUBIC_TO, 0.48f, 0, 0.93f, -0.16f, 1.27f, -0.45f,
+R_CUBIC_TO, 0.34f, -0.29f, 0.53f, -0.68f, 0.53f, -1.09f,
+V_LINE_TO, 5.54f,
+R_CUBIC_TO, 0, -0.41f, -0.19f, -0.8f, -0.53f, -1.09f,
+CLOSE,
+MOVE_TO, 5, 9,
+R_CUBIC_TO, -0.83f, 0, -1.5f, -0.67f, -1.5f, -1.5f,
+CUBIC_TO_SHORTHAND, 4.18f, 6, 5, 6,
+R_CUBIC_TO, 0.82f, 0, 1.5f, 0.68f, 1.5f, 1.5f,
+CUBIC_TO_SHORTHAND, 5.83f, 9, 5, 9,
+CLOSE,
+R_MOVE_TO, 6, 0,
+R_CUBIC_TO, -0.83f, 0, -1.5f, -0.67f, -1.5f, -1.5f,
+CUBIC_TO_SHORTHAND, 10.18f, 6, 11, 6,
+R_CUBIC_TO, 0.82f, 0, 1.5f, 0.68f, 1.5f, 1.5f,
+CUBIC_TO_SHORTHAND, 11.83f, 9, 11, 9,
+CLOSE
diff --git a/chrome/app_shim/chrome_main_app_mode_mac.mm b/chrome/app_shim/chrome_main_app_mode_mac.mm
index bc20426c..026678d 100644
--- a/chrome/app_shim/chrome_main_app_mode_mac.mm
+++ b/chrome/app_shim/chrome_main_app_mode_mac.mm
@@ -49,6 +49,14 @@
 
 }  // namespace
 
+// The NSApplication for app shims is a vanilla NSApplication, but sub-class it
+// so that we can DCHECK that we know precisely when it is initialized.
+@interface AppShimApplication : NSApplication
+@end
+
+@implementation AppShimApplication
+@end
+
 // A ReplyEventHandler is a helper class to send an Apple Event to a process
 // and call a callback when the reply returns.
 //
@@ -165,6 +173,12 @@
 
 }  // extern "C"
 
+void PostRepeatingDelayedTask() {
+  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+      FROM_HERE, base::BindOnce(&PostRepeatingDelayedTask),
+      base::TimeDelta::FromDays(1));
+}
+
 int ChromeAppModeStart_v5(const app_mode::ChromeAppModeInfo* info) {
   base::CommandLine::Init(info->argc, info->argv);
 
@@ -254,11 +268,24 @@
       pid = [[existing_chrome objectAtIndex:0] processIdentifier];
   }
 
+  // Initialize the NSApplication (and ensure that it was not previously
+  // initialized).
+  [AppShimApplication sharedApplication];
+  CHECK([NSApp isKindOfClass:[AppShimApplication class]]);
+
   base::MessageLoopForUI main_message_loop;
   ui::WindowResizeHelperMac::Get()->Init(main_message_loop.task_runner());
   base::PlatformThread::SetName("CrAppShimMain");
   AppShimController controller(info);
 
+  // TODO(https://crbug.com/925998): This workaround ensures that there is
+  // always delayed work enqueued. If there is ever not enqueued delayed work,
+  // then NSMenus and NSAlerts can start misbehaving (see
+  // https://crbug.com/920795 for examples). This workaround is not an
+  // appropriate solution to the problem, and should be replaced by a fix in
+  // the relevant message pump code.
+  PostRepeatingDelayedTask();
+
   // In tests, launching Chrome does nothing, and we won't get a ping response,
   // so just assume the socket exists.
   if (pid == -1 &&
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index c0710bf..8b98f6fb 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3049,6 +3049,12 @@
       "search/search_suggest/search_suggest_service_factory.cc",
       "search/search_suggest/search_suggest_service_factory.h",
       "search/search_suggest/search_suggest_service_observer.h",
+      "serial/chrome_serial_delegate.cc",
+      "serial/chrome_serial_delegate.h",
+      "serial/serial_chooser_context.cc",
+      "serial/serial_chooser_context.h",
+      "serial/serial_chooser_context_factory.cc",
+      "serial/serial_chooser_context_factory.h",
       "signin/signin_promo.cc",
       "signin/signin_promo.h",
       "signin/signin_ui_util.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 4556bda..6180851 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3431,12 +3431,6 @@
      flag_descriptions::kEnableNetworkLoggingToFileDescription, kOsAll,
      SINGLE_VALUE_TYPE(network::switches::kLogNetLog)},
 
-#if defined(OS_CHROMEOS)
-    {"disable-multi-mirroring", flag_descriptions::kDisableMultiMirroringName,
-     flag_descriptions::kDisableMultiMirroringDescription, kOsCrOS,
-     SINGLE_VALUE_TYPE(switches::kDisableMultiMirroring)},
-#endif  // defined(OS_CHROMEOS)
-
 #if defined(OS_ANDROID)
     {"grant-notifications-to-dse",
      flag_descriptions::kGrantNotificationsToDSEName,
diff --git a/chrome/browser/apps/app_service/app_service_proxy.cc b/chrome/browser/apps/app_service/app_service_proxy.cc
index e841cbd..056cc1c 100644
--- a/chrome/browser/apps/app_service/app_service_proxy.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy.cc
@@ -108,6 +108,13 @@
   });
 }
 
+void AppServiceProxy::Shutdown() {
+#if defined(OS_CHROMEOS)
+  extension_apps_.Shutdown();
+  extension_web_apps_.Shutdown();
+#endif  // OS_CHROMEOS
+}
+
 void AppServiceProxy::OnApps(std::vector<apps::mojom::AppPtr> deltas) {
   cache_.OnApps(std::move(deltas));
 }
diff --git a/chrome/browser/apps/app_service/app_service_proxy.h b/chrome/browser/apps/app_service/app_service_proxy.h
index 264676f..2efc254 100644
--- a/chrome/browser/apps/app_service/app_service_proxy.h
+++ b/chrome/browser/apps/app_service/app_service_proxy.h
@@ -56,6 +56,9 @@
   void OpenNativeSettings(const std::string& app_id);
 
  private:
+  // KeyedService overrides.
+  void Shutdown() override;
+
   // apps::mojom::Subscriber overrides.
   void OnApps(std::vector<apps::mojom::AppPtr> deltas) override;
   void Clone(apps::mojom::SubscriberRequest request) override;
diff --git a/chrome/browser/apps/app_service/arc_apps.cc b/chrome/browser/apps/app_service/arc_apps.cc
index 0728843e..d52cb379 100644
--- a/chrome/browser/apps/app_service/arc_apps.cc
+++ b/chrome/browser/apps/app_service/arc_apps.cc
@@ -18,9 +18,11 @@
 #include "chrome/browser/ui/app_list/arc/arc_app_icon_descriptor.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
 #include "chrome/grit/component_extension_resources.h"
+#include "components/arc/app_permissions/arc_app_permissions_bridge.h"
 #include "components/arc/arc_bridge_service.h"
 #include "components/arc/arc_service_manager.h"
 #include "components/arc/common/app.mojom.h"
+#include "components/arc/common/app_permissions.mojom.h"
 #include "content/public/common/service_manager_connection.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
 #include "services/data_decoder/public/cpp/decode_image.h"
@@ -221,7 +223,38 @@
 
 void ArcApps::SetPermission(const std::string& app_id,
                             apps::mojom::PermissionPtr permission) {
-  NOTIMPLEMENTED();
+  const std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
+      prefs_->GetApp(app_id);
+  if (!app_info) {
+    LOG(ERROR) << "SetPermission failed, could not find app with id " << app_id;
+    return;
+  }
+
+  auto* arc_service_manager = arc::ArcServiceManager::Get();
+  if (!arc_service_manager) {
+    LOG(WARNING) << "SetPermission failed, ArcServiceManager not available.";
+    return;
+  }
+
+  auto permission_type =
+      static_cast<arc::mojom::AppPermission>(permission->permission_id);
+  if (permission->value) {
+    auto* permissions_instance = ARC_GET_INSTANCE_FOR_METHOD(
+        arc_service_manager->arc_bridge_service()->app_permissions(),
+        GrantPermission);
+    if (permissions_instance) {
+      permissions_instance->GrantPermission(app_info->package_name,
+                                            permission_type);
+    }
+  } else {
+    auto* permissions_instance = ARC_GET_INSTANCE_FOR_METHOD(
+        arc_service_manager->arc_bridge_service()->app_permissions(),
+        RevokePermission);
+    if (permissions_instance) {
+      permissions_instance->RevokePermission(app_info->package_name,
+                                             permission_type);
+    }
+  }
 }
 
 void ArcApps::Uninstall(const std::string& app_id) {
diff --git a/chrome/browser/apps/app_service/extension_apps.cc b/chrome/browser/apps/app_service/extension_apps.cc
index 61ce1fc..b1230ef 100644
--- a/chrome/browser/apps/app_service/extension_apps.cc
+++ b/chrome/browser/apps/app_service/extension_apps.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/apps/app_service/extension_apps.h"
 
+#include <memory>
 #include <utility>
 #include <vector>
 
@@ -24,6 +25,8 @@
 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
 #include "chrome/services/app_service/public/mojom/types.mojom.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/content_settings/core/common/content_settings_pattern.h"
+#include "components/content_settings/core/common/content_settings_types.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
@@ -93,8 +96,15 @@
   app_service->RegisterPublisher(std::move(publisher), app_type_);
 
   profile_ = profile;
+  DCHECK(profile_);
+  observer_.Add(extensions::ExtensionRegistry::Get(profile_));
+  HostContentSettingsMapFactory::GetForProfile(profile_)->AddObserver(this);
+}
+
+void ExtensionApps::Shutdown() {
   if (profile_) {
-    observer_.Add(extensions::ExtensionRegistry::Get(profile_));
+    HostContentSettingsMapFactory::GetForProfile(profile_)->RemoveObserver(
+        this);
   }
 }
 
@@ -292,6 +302,43 @@
   }
 }
 
+void ExtensionApps::OnContentSettingChanged(
+    const ContentSettingsPattern& primary_pattern,
+    const ContentSettingsPattern& secondary_pattern,
+    ContentSettingsType content_type,
+    const std::string& resource_identifier) {
+  // If content_type is not one of the supported permissions, do nothing.
+  if (!base::ContainsValue(kSupportedPermissionTypes, content_type)) {
+    return;
+  }
+
+  DCHECK(profile_);
+
+  extensions::ExtensionRegistry* registry =
+      extensions::ExtensionRegistry::Get(profile_);
+
+  std::unique_ptr<extensions::ExtensionSet> extensions =
+      registry->GenerateInstalledExtensionsSet(
+          extensions::ExtensionRegistry::ENABLED |
+          extensions::ExtensionRegistry::DISABLED |
+          extensions::ExtensionRegistry::TERMINATED);
+
+  for (const auto& extension : *extensions) {
+    const GURL url =
+        extensions::AppLaunchInfo::GetFullLaunchURL(extension.get());
+
+    if (extension->from_bookmark() && primary_pattern.Matches(url) &&
+        Accepts(extension.get())) {
+      apps::mojom::AppPtr app = apps::mojom::App::New();
+      app->app_type = apps::mojom::AppType::kWeb;
+      app->app_id = extension->id();
+      PopulatePermissions(extension.get(), &app->permissions);
+
+      Publish(std::move(app));
+    }
+  }
+}
+
 void ExtensionApps::OnExtensionInstalled(
     content::BrowserContext* browser_context,
     const extensions::Extension* extension,
@@ -303,13 +350,7 @@
 
   // TODO(crbug.com/826982): Does the is_update case need to be handled
   // differently? E.g. by only passing through fields that have changed.
-  apps::mojom::AppPtr app = Convert(extension, apps::mojom::Readiness::kReady);
-
-  subscribers_.ForAllPtrs([&app](apps::mojom::Subscriber* subscriber) {
-    std::vector<apps::mojom::AppPtr> apps;
-    apps.push_back(app.Clone());
-    subscriber->OnApps(std::move(apps));
-  });
+  Publish(Convert(extension, apps::mojom::Readiness::kReady));
 }
 
 void ExtensionApps::OnExtensionUninstalled(
@@ -328,6 +369,10 @@
   app->app_id = extension->id();
   app->readiness = apps::mojom::Readiness::kUninstalledByUser;
 
+  Publish(std::move(app));
+}
+
+void ExtensionApps::Publish(apps::mojom::AppPtr app) {
   subscribers_.ForAllPtrs([&app](apps::mojom::Subscriber* subscriber) {
     std::vector<apps::mojom::AppPtr> apps;
     apps.push_back(app.Clone());
@@ -357,6 +402,44 @@
   return app_id == arc::kPlayStoreAppId;
 }
 
+void ExtensionApps::PopulatePermissions(
+    const extensions::Extension* extension,
+    std::vector<mojom::PermissionPtr>* target) {
+  const GURL url = extensions::AppLaunchInfo::GetFullLaunchURL(extension);
+
+  auto* host_content_settings_map =
+      HostContentSettingsMapFactory::GetForProfile(profile_);
+  DCHECK(host_content_settings_map);
+
+  for (ContentSettingsType type : kSupportedPermissionTypes) {
+    ContentSetting setting = host_content_settings_map->GetContentSetting(
+        url, url, type, std::string() /* resource_identifier */);
+
+    // Map ContentSettingsType to an apps::mojom::TriState value
+    apps::mojom::TriState setting_val;
+    switch (setting) {
+      case CONTENT_SETTING_ALLOW:
+        setting_val = apps::mojom::TriState::kAllow;
+        break;
+      case CONTENT_SETTING_ASK:
+        setting_val = apps::mojom::TriState::kAsk;
+        break;
+      case CONTENT_SETTING_BLOCK:
+        setting_val = apps::mojom::TriState::kBlock;
+        break;
+      default:
+        setting_val = apps::mojom::TriState::kAsk;
+    }
+
+    auto permission = apps::mojom::Permission::New();
+    permission->permission_id = static_cast<uint32_t>(type);
+    permission->value_type = apps::mojom::PermissionValueType::kTriState;
+    permission->value = static_cast<uint32_t>(setting_val);
+
+    target->push_back(std::move(permission));
+  }
+}
+
 apps::mojom::AppPtr ExtensionApps::Convert(
     const extensions::Extension* extension,
     apps::mojom::Readiness readiness) {
@@ -375,38 +458,7 @@
   // Extensions where |from_bookmark| is true wrap websites and use web
   // permissions.
   if (extension->from_bookmark()) {
-    const GURL url = extensions::AppLaunchInfo::GetFullLaunchURL(extension);
-    auto* host_content_settings_map =
-        HostContentSettingsMapFactory::GetForProfile(profile_);
-    DCHECK(host_content_settings_map);
-
-    for (ContentSettingsType type : kSupportedPermissionTypes) {
-      ContentSetting setting = host_content_settings_map->GetContentSetting(
-          url, url, type, std::string() /* resource_identifier */);
-
-      // Map ContentSettingsType to an apps::mojom::TriState value
-      apps::mojom::TriState setting_val;
-      switch (setting) {
-        case CONTENT_SETTING_ALLOW:
-          setting_val = apps::mojom::TriState::kAllow;
-          break;
-        case CONTENT_SETTING_ASK:
-          setting_val = apps::mojom::TriState::kAsk;
-          break;
-        case CONTENT_SETTING_BLOCK:
-          setting_val = apps::mojom::TriState::kBlock;
-          break;
-        default:
-          setting_val = apps::mojom::TriState::kAsk;
-      }
-
-      auto permission = apps::mojom::Permission::New();
-      permission->permission_id = static_cast<uint32_t>(type);
-      permission->value_type = apps::mojom::PermissionValueType::kTriState;
-      permission->value = static_cast<uint32_t>(setting_val);
-
-      app->permissions.push_back(std::move(permission));
-    }
+    PopulatePermissions(extension, &app->permissions);
   }
 
   // TODO(crbug.com/826982): does this catch default installed web apps?
diff --git a/chrome/browser/apps/app_service/extension_apps.h b/chrome/browser/apps/app_service/extension_apps.h
index 6079d84a..51477ef2 100644
--- a/chrome/browser/apps/app_service/extension_apps.h
+++ b/chrome/browser/apps/app_service/extension_apps.h
@@ -10,6 +10,7 @@
 #include "base/macros.h"
 #include "base/scoped_observer.h"
 #include "chrome/services/app_service/public/mojom/app_service.mojom.h"
+#include "components/content_settings/core/browser/content_settings_observer.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/interface_ptr_set.h"
@@ -30,7 +31,8 @@
 //
 // See chrome/services/app_service/README.md.
 class ExtensionApps : public apps::mojom::Publisher,
-                      public extensions::ExtensionRegistryObserver {
+                      public extensions::ExtensionRegistryObserver,
+                      public content_settings::Observer {
  public:
   ExtensionApps();
   ~ExtensionApps() override;
@@ -38,6 +40,7 @@
   void Initialize(const apps::mojom::AppServicePtr& app_service,
                   Profile* profile,
                   apps::mojom::AppType type);
+  void Shutdown();
 
  private:
   // Determines whether the given extension should be treated as type app_type_,
@@ -61,6 +64,12 @@
   void Uninstall(const std::string& app_id) override;
   void OpenNativeSettings(const std::string& app_id) override;
 
+  // content_settings::Observer overrides.
+  void OnContentSettingChanged(const ContentSettingsPattern& primary_pattern,
+                               const ContentSettingsPattern& secondary_pattern,
+                               ContentSettingsType content_type,
+                               const std::string& resource_identifier) override;
+
   // extensions::ExtensionRegistryObserver overrides.
   void OnExtensionInstalled(content::BrowserContext* browser_context,
                             const extensions::Extension* extension,
@@ -69,12 +78,16 @@
                               const extensions::Extension* extension,
                               extensions::UninstallReason reason) override;
 
+  void Publish(apps::mojom::AppPtr app);
+
   // Checks if extension is disabled and if enable flow should be started.
   // Returns true if extension enable flow is started or there is already one
   // running.
   bool RunExtensionEnableFlow(const std::string& app_id);
 
   static bool IsBlacklisted(const std::string& app_id);
+  void PopulatePermissions(const extensions::Extension* extension,
+                           std::vector<mojom::PermissionPtr>* target);
   apps::mojom::AppPtr Convert(const extensions::Extension* extension,
                               apps::mojom::Readiness readiness);
   void ConvertVector(const extensions::ExtensionSet& extensions,
diff --git a/chrome/browser/apps/platform_apps/app_window_interactive_uitest.cc b/chrome/browser/apps/platform_apps/app_window_interactive_uitest.cc
index 15fb04b..eabd3a2a 100644
--- a/chrome/browser/apps/platform_apps/app_window_interactive_uitest.cc
+++ b/chrome/browser/apps/platform_apps/app_window_interactive_uitest.cc
@@ -15,6 +15,7 @@
 #include "extensions/browser/app_window/native_app_window.h"
 #include "extensions/test/extension_test_message_listener.h"
 #include "extensions/test/result_catcher.h"
+#include "ui/base/ui_base_features.h"
 
 #if defined(OS_MACOSX)
 #include "base/mac/mac_util.h"
@@ -457,6 +458,9 @@
 #define MAYBE_TestFullscreen TestFullscreen
 #endif
 IN_PROC_BROWSER_TEST_F(AppWindowInteractiveTest, MAYBE_TestFullscreen) {
+  // Flaky on CrOS + IsUsingWindowService. https://crbug.com/926007
+  if (features::IsUsingWindowService())
+    return;
   ASSERT_TRUE(RunAppWindowInteractiveTest("testFullscreen")) << message_;
 }
 
diff --git a/chrome/browser/autofill/autofill_uitest_util.cc b/chrome/browser/autofill/autofill_uitest_util.cc
index b4dd07b..eb515840 100644
--- a/chrome/browser/autofill/autofill_uitest_util.cc
+++ b/chrome/browser/autofill/autofill_uitest_util.cc
@@ -90,6 +90,15 @@
   observer.Wait();
 }
 
+void AddTestServerCreditCard(Browser* browser, const CreditCard& card) {
+  PdmChangeWaiter observer(browser);
+  GetPersonalDataManager(browser->profile())->AddFullServerCreditCard(card);
+
+  // AddCreditCard is asynchronous. Wait for it to finish before continuing the
+  // tests.
+  observer.Wait();
+}
+
 void AddTestAutofillData(Browser* browser,
                          const AutofillProfile& profile,
                          const CreditCard& card) {
diff --git a/chrome/browser/autofill/autofill_uitest_util.h b/chrome/browser/autofill/autofill_uitest_util.h
index 6140db1c..89ce6d15 100644
--- a/chrome/browser/autofill/autofill_uitest_util.h
+++ b/chrome/browser/autofill/autofill_uitest_util.h
@@ -18,6 +18,7 @@
 void SetTestProfile(Browser* browser, const AutofillProfile& profile);
 void SetTestProfiles(Browser* browser, std::vector<AutofillProfile>* profiles);
 void AddTestCreditCard(Browser* browser, const CreditCard& card);
+void AddTestServerCreditCard(Browser* browser, const CreditCard& card);
 void AddTestAutofillData(Browser* browser,
                          const AutofillProfile& profile,
                          const CreditCard& card);
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 cbfe25d..3ebfb6d 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
@@ -690,6 +690,10 @@
     content::HostZoomMap* zoom_map =
         content::HostZoomMap::GetDefaultForBrowserContext(profile_);
     zoom_map->ClearZoomLevels(delete_begin_, delete_end_);
+
+    host_content_settings_map_->ClearSettingsForOneTypeWithPredicate(
+        CONTENT_SETTINGS_TYPE_SERIAL_CHOOSER_DATA, delete_begin_, delete_end_,
+        HostContentSettingsMap::PatternSourcePredicate());
 #else
     // Reset the Default Search Engine permissions to their default.
     SearchPermissionsService* search_permissions_service =
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index c75d5088..99e16d50 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -116,6 +116,7 @@
 #include "chrome/browser/safe_browsing/ui_manager.h"
 #include "chrome/browser/safe_browsing/url_checker_delegate_impl.h"
 #include "chrome/browser/search/search.h"
+#include "chrome/browser/serial/chrome_serial_delegate.h"
 #include "chrome/browser/sessions/session_tab_helper.h"
 #include "chrome/browser/signin/chrome_signin_proxying_url_loader_factory.h"
 #include "chrome/browser/signin/chrome_signin_url_loader_throttle.h"
@@ -4922,6 +4923,14 @@
   tab_helper->CreateWebUsbService(render_frame_host, std::move(request));
 }
 
+#if !defined(OS_ANDROID)
+content::SerialDelegate* ChromeContentBrowserClient::GetSerialDelegate() {
+  if (!serial_delegate_)
+    serial_delegate_ = std::make_unique<ChromeSerialDelegate>();
+  return serial_delegate_.get();
+}
+#endif
+
 std::unique_ptr<content::AuthenticatorRequestClientDelegate>
 ChromeContentBrowserClient::GetWebAuthenticationRequestDelegate(
     content::RenderFrameHost* render_frame_host) {
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 18ff27e7e..b2fd7010 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -74,6 +74,8 @@
 class Origin;
 }
 
+class ChromeSerialDelegate;
+
 // Returns the user agent of Chrome.
 std::string GetUserAgent();
 
@@ -490,6 +492,9 @@
   void CreateWebUsbService(
       content::RenderFrameHost* render_frame_host,
       mojo::InterfaceRequest<blink::mojom::WebUsbService> request) override;
+#if !defined(OS_ANDROID)
+  content::SerialDelegate* GetSerialDelegate() override;
+#endif
   bool ShowPaymentHandlerWindow(
       content::BrowserContext* browser_context,
       const GURL& url,
@@ -642,6 +647,10 @@
 
   ChromeFeatureListCreator* chrome_feature_list_creator_;
 
+#if !defined(OS_ANDROID)
+  std::unique_ptr<ChromeSerialDelegate> serial_delegate_;
+#endif
+
   base::WeakPtrFactory<ChromeContentBrowserClient> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(ChromeContentBrowserClient);
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index eb4c973..085d7c1c 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -2046,6 +2046,8 @@
   testonly = true
 
   sources = [
+    "accessibility/test_accessibility_focus_ring_controller.cc",
+    "accessibility/test_accessibility_focus_ring_controller.h",
     "android_sms/fake_android_sms_app_manager.cc",
     "android_sms/fake_android_sms_app_manager.h",
     "android_sms/fake_android_sms_app_setup_controller.cc",
@@ -2322,6 +2324,7 @@
     "login/error_screens_histogram_helper_unittest.cc",
     "login/existing_user_controller_auto_login_unittest.cc",
     "login/hwid_checker_unittest.cc",
+    "login/lock/screen_locker_unittest.cc",
     "login/profile_auth_data_unittest.cc",
     "login/quick_unlock/fingerprint_storage_unittest.cc",
     "login/quick_unlock/pin_storage_prefs_unittest.cc",
diff --git a/chrome/browser/chromeos/accessibility/test_accessibility_focus_ring_controller.cc b/chrome/browser/chromeos/accessibility/test_accessibility_focus_ring_controller.cc
new file mode 100644
index 0000000..d59c8f8
--- /dev/null
+++ b/chrome/browser/chromeos/accessibility/test_accessibility_focus_ring_controller.cc
@@ -0,0 +1,66 @@
+// 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/accessibility/test_accessibility_focus_ring_controller.h"
+
+#include <utility>
+
+#include "ash/public/interfaces/constants.mojom.h"
+#include "content/public/common/service_manager_connection.h"
+#include "services/service_manager/public/cpp/connector.h"
+#include "services/service_manager/public/cpp/service_filter.h"
+
+TestAccessibilityFocusRingController::TestAccessibilityFocusRingController() {
+  CHECK(content::ServiceManagerConnection::GetForProcess())
+      << "ServiceManager is uninitialized. Did you forget to create a "
+         "content::TestServiceManagerContext?";
+  content::ServiceManagerConnection::GetForProcess()
+      ->GetConnector()
+      ->OverrideBinderForTesting(
+          service_manager::ServiceFilter::ByName(ash::mojom::kServiceName),
+          ash::mojom::AccessibilityFocusRingController::Name_,
+          base::BindRepeating(&TestAccessibilityFocusRingController::Bind,
+                              base::Unretained(this)));
+}
+
+TestAccessibilityFocusRingController::~TestAccessibilityFocusRingController() {
+  content::ServiceManagerConnection::GetForProcess()
+      ->GetConnector()
+      ->ClearBinderOverrideForTesting(
+          service_manager::ServiceFilter::ByName(ash::mojom::kServiceName),
+          ash::mojom::AccessibilityFocusRingController::Name_);
+}
+
+void TestAccessibilityFocusRingController::SetFocusRingColor(
+    uint32_t skcolor,
+    const std::string& caller_id) {}
+
+void TestAccessibilityFocusRingController::ResetFocusRingColor(
+    const std::string& caller_id) {}
+
+void TestAccessibilityFocusRingController::SetFocusRing(
+    const std::vector<gfx::Rect>& rects_in_screen,
+    ash::mojom::FocusRingBehavior focus_ring_behavior,
+    const std::string& caller_id) {}
+
+void TestAccessibilityFocusRingController::HideFocusRing(
+    const std::string& caller_id) {}
+
+void TestAccessibilityFocusRingController::SetHighlights(
+    const std::vector<gfx::Rect>& rects_in_screen,
+    uint32_t skcolor) {}
+
+void TestAccessibilityFocusRingController::HideHighlights() {}
+
+void TestAccessibilityFocusRingController::Bind(
+    mojo::ScopedMessagePipeHandle handle) {
+  binding_.Bind(
+      ash::mojom::AccessibilityFocusRingControllerRequest(std::move(handle)));
+}
+void TestAccessibilityFocusRingController::EnableDoubleFocusRing(
+    uint32_t skcolor,
+    const std::string& caller_id) {}
+
+void TestAccessibilityFocusRingController::DisableDoubleFocusRing(
+    const std::string& caller_id) {}
diff --git a/chrome/browser/chromeos/accessibility/test_accessibility_focus_ring_controller.h b/chrome/browser/chromeos/accessibility/test_accessibility_focus_ring_controller.h
new file mode 100644
index 0000000..173b7f2a
--- /dev/null
+++ b/chrome/browser/chromeos/accessibility/test_accessibility_focus_ring_controller.h
@@ -0,0 +1,50 @@
+// 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_ACCESSIBILITY_TEST_ACCESSIBILITY_FOCUS_RING_CONTROLLER_H_
+#define CHROME_BROWSER_CHROMEOS_ACCESSIBILITY_TEST_ACCESSIBILITY_FOCUS_RING_CONTROLLER_H_
+
+#include <string>
+#include <vector>
+
+#include "ash/public/interfaces/accessibility_focus_ring_controller.mojom.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/binding.h"
+
+// Test implementation of ash's mojo AccessibilityFocusRingController interface.
+//
+// Registers itself to ServiceManager on construction and deregisters
+// on destruction.
+//
+// Note: A ServiceManagerConnection must be initialized before constructing this
+// object. Consider using content::TestServiceManagerContext on your tests.
+class TestAccessibilityFocusRingController
+    : public ash::mojom::AccessibilityFocusRingController {
+ public:
+  TestAccessibilityFocusRingController();
+  ~TestAccessibilityFocusRingController() override;
+
+  // ash::mojom::AccessibilityFocusRingController:
+  void SetFocusRingColor(uint32_t skcolor,
+                         const std::string& caller_id) override;
+  void ResetFocusRingColor(const std::string& caller_id) override;
+  void SetFocusRing(const std::vector<gfx::Rect>& rects_in_screen,
+                    ash::mojom::FocusRingBehavior focus_ring_behavior,
+                    const std::string& caller_id) override;
+  void HideFocusRing(const std::string& caller_id) override;
+  void SetHighlights(const std::vector<gfx::Rect>& rects_in_screen,
+                     uint32_t skcolor) override;
+  void HideHighlights() override;
+  void EnableDoubleFocusRing(uint32_t skcolor,
+                             const std::string& caller_id) override;
+  void DisableDoubleFocusRing(const std::string& caller_id) override;
+
+ private:
+  void Bind(mojo::ScopedMessagePipeHandle handle);
+  mojo::Binding<ash::mojom::AccessibilityFocusRingController> binding_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(TestAccessibilityFocusRingController);
+};
+
+#endif  // CHROME_BROWSER_CHROMEOS_ACCESSIBILITY_TEST_ACCESSIBILITY_FOCUS_RING_CONTROLLER_H_
diff --git a/chrome/browser/chromeos/android_sms/android_sms_service_factory.cc b/chrome/browser/chromeos/android_sms/android_sms_service_factory.cc
index a9213c0..6982d723 100644
--- a/chrome/browser/chromeos/android_sms/android_sms_service_factory.cc
+++ b/chrome/browser/chromeos/android_sms/android_sms_service_factory.cc
@@ -9,8 +9,10 @@
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/notifications/notification_display_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
 #include "chrome/browser/web_applications/web_app_provider_factory.h"
+#include "chrome/browser/web_applications/web_app_utils.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "chromeos/services/multidevice_setup/public/cpp/prefs.h"
 #include "chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
@@ -23,10 +25,29 @@
 
 namespace {
 
-bool IsFeatureAllowed(content::BrowserContext* context) {
-  return multidevice_setup::IsFeatureAllowed(
-      multidevice_setup::mojom::Feature::kMessages,
-      Profile::FromBrowserContext(context)->GetPrefs());
+bool ShouldStartAndroidSmsService(Profile* profile) {
+  const bool multidevice_feature_allowed = multidevice_setup::IsFeatureAllowed(
+      multidevice_setup::mojom::Feature::kMessages, profile->GetPrefs());
+
+  const bool android_messages_integration_enabled =
+      base::FeatureList::IsEnabled(
+          chromeos::features::kAndroidMessagesIntegration);
+
+  const bool has_user_for_profile =
+      !!ProfileHelper::Get()->GetUserByProfile(profile);
+
+  return web_app::AreWebAppsEnabled(profile) && !profile->IsGuestSession() &&
+         multidevice_feature_allowed && android_messages_integration_enabled &&
+         has_user_for_profile;
+}
+
+content::BrowserContext* GetBrowserContextForAndroidSms(
+    content::BrowserContext* context) {
+  // Use original profile to create only one KeyedService instance.
+  Profile* original_profile =
+      Profile::FromBrowserContext(context)->GetOriginalProfile();
+  return ShouldStartAndroidSmsService(original_profile) ? original_profile
+                                                        : nullptr;
 }
 
 }  // namespace
@@ -61,15 +82,7 @@
 
 KeyedService* AndroidSmsServiceFactory::BuildServiceInstanceFor(
     content::BrowserContext* context) const {
-  if (!IsFeatureAllowed(context) ||
-      !base::FeatureList::IsEnabled(
-          chromeos::features::kAndroidMessagesIntegration)) {
-    return nullptr;
-  }
-
   Profile* profile = Profile::FromBrowserContext(context);
-  if (ProfileHelper::Get()->GetUserByProfile(profile) == nullptr)
-    return nullptr;
 
   return new AndroidSmsService(
       profile, HostContentSettingsMapFactory::GetForProfile(profile),
@@ -79,6 +92,11 @@
       app_list::AppListSyncableServiceFactory::GetForProfile(profile));
 }
 
+content::BrowserContext* AndroidSmsServiceFactory::GetBrowserContextToUse(
+    content::BrowserContext* context) const {
+  return GetBrowserContextForAndroidSms(context);
+}
+
 bool AndroidSmsServiceFactory::ServiceIsCreatedWithBrowserContext() const {
   return true;
 }
diff --git a/chrome/browser/chromeos/android_sms/android_sms_service_factory.h b/chrome/browser/chromeos/android_sms/android_sms_service_factory.h
index f584067..daabe7a 100644
--- a/chrome/browser/chromeos/android_sms/android_sms_service_factory.h
+++ b/chrome/browser/chromeos/android_sms/android_sms_service_factory.h
@@ -33,6 +33,8 @@
   // BrowserContextKeyedServiceFactory:
   KeyedService* BuildServiceInstanceFor(
       content::BrowserContext* context) const override;
+  content::BrowserContext* GetBrowserContextToUse(
+      content::BrowserContext* context) const override;
   bool ServiceIsCreatedWithBrowserContext() const override;
   bool ServiceIsNULLWhileTesting() const override;
   void RegisterProfilePrefs(
diff --git a/chrome/browser/chromeos/arc/accessibility/arc_accessibility_util.cc b/chrome/browser/chromeos/arc/accessibility/arc_accessibility_util.cc
index bf1efc2..aa30d8d 100644
--- a/chrome/browser/chromeos/arc/accessibility/arc_accessibility_util.cc
+++ b/chrome/browser/chromeos/arc/accessibility/arc_accessibility_util.cc
@@ -32,6 +32,7 @@
     case mojom::AccessibilityEventType::VIEW_SCROLLED:
       return ax::mojom::Event::kScrollPositionChanged;
     case mojom::AccessibilityEventType::VIEW_SELECTED:
+      return ax::mojom::Event::kValueChanged;
     case mojom::AccessibilityEventType::VIEW_HOVER_EXIT:
     case mojom::AccessibilityEventType::TOUCH_EXPLORATION_GESTURE_START:
     case mojom::AccessibilityEventType::TOUCH_EXPLORATION_GESTURE_END:
diff --git a/chrome/browser/chromeos/dbus/org.chromium.ChromeFeaturesService.conf b/chrome/browser/chromeos/dbus/org.chromium.ChromeFeaturesService.conf
index 9048bde..bf11685 100644
--- a/chrome/browser/chromeos/dbus/org.chromium.ChromeFeaturesService.conf
+++ b/chrome/browser/chromeos/dbus/org.chromium.ChromeFeaturesService.conf
@@ -6,13 +6,14 @@
   found in the LICENSE file.
 -->
 <busconfig>
+  <!-- vmc runs as chronos -->
   <policy user="chronos">
     <allow own="org.chromium.ChromeFeaturesService"/>
   </policy>
 
-  <!-- vmc runs as chronos -->
-  <policy user="chronos">
+  <!-- upstart and tast run as root -->
+  <policy user="root">
     <allow send_destination="org.chromium.ChromeFeaturesService"
-           send_interface="org.chromium.ChromeFeaturesService"/>
+           send_interface="org.chromium.ChromeFeaturesServiceInterface"/>
   </policy>
 </busconfig>
diff --git a/chrome/browser/chromeos/file_manager/audio_player_browsertest.cc b/chrome/browser/chromeos/file_manager/audio_player_browsertest.cc
index 771b96a..ed9852c7 100644
--- a/chrome/browser/chromeos/file_manager/audio_player_browsertest.cc
+++ b/chrome/browser/chromeos/file_manager/audio_player_browsertest.cc
@@ -4,6 +4,9 @@
 
 #include "chrome/browser/chromeos/file_manager/file_manager_browsertest_base.h"
 
+#include "base/test/scoped_feature_list.h"
+#include "media/base/media_switches.h"
+
 namespace file_manager {
 
 template <GuestMode MODE>
@@ -79,12 +82,12 @@
   StartTest();
 }
 
-#if defined(OS_CHROMEOS) && defined(MEMORY_SANITIZER)
-#define MAYBE_NativeMediaKey DISABLED_NativeMediaKey
-#else
-#define MAYBE_NativeMediaKey NativeMediaKey
-#endif
-IN_PROC_BROWSER_TEST_F(AudioPlayerBrowserTest, MAYBE_NativeMediaKey) {
+IN_PROC_BROWSER_TEST_F(AudioPlayerBrowserTest, NativeMediaKey) {
+  // The HardwareMediaKeyHandling feature makes key handling flaky.
+  // See https://crbug.com/902519.
+  base::test::ScopedFeatureList disable_media_key_handling;
+  disable_media_key_handling.InitAndDisableFeature(
+      media::kHardwareMediaKeyHandling);
   set_test_case_name("mediaKeyNative");
   StartTest();
 }
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
index 0bc3831..67cab0d 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
@@ -493,6 +493,8 @@
 #if !defined(MEMORY_SANITIZER)
                       TestCase("openQuickViewPdf"),
 #endif
+                      TestCase("openQuickViewKeyboardUpDownChangesView"),
+                      TestCase("openQuickViewKeyboardLeftRightChangesView"),
                       TestCase("openQuickViewScrollText"),
                       TestCase("openQuickViewScrollHtml"),
                       TestCase("openQuickViewBackgroundColorText"),
@@ -583,6 +585,8 @@
     ::testing::Values(
         TestCase("transferFromDriveToDownloads").DisableDriveFs(),
         TestCase("transferFromDriveToDownloads").EnableDriveFs(),
+        TestCase("transferFromDownloadsToMyFiles").EnableMyFilesVolume(),
+        TestCase("transferFromDownloadsToMyFilesMove").EnableMyFilesVolume(),
         TestCase("transferFromDownloadsToDrive").DisableDriveFs(),
         TestCase("transferFromDownloadsToDrive").EnableDriveFs(),
         TestCase("transferFromSharedToDownloads").DisableDriveFs(),
diff --git a/chrome/browser/chromeos/file_manager/video_player_browsertest.cc b/chrome/browser/chromeos/file_manager/video_player_browsertest.cc
index 9dad913..53f0719 100644
--- a/chrome/browser/chromeos/file_manager/video_player_browsertest.cc
+++ b/chrome/browser/chromeos/file_manager/video_player_browsertest.cc
@@ -4,7 +4,9 @@
 
 #include "chrome/browser/chromeos/file_manager/file_manager_browsertest_base.h"
 
+#include "base/test/scoped_feature_list.h"
 #include "chromeos/constants/chromeos_switches.h"
+#include "media/base/media_switches.h"
 
 namespace file_manager {
 
@@ -73,8 +75,14 @@
   StartTest();
 }
 
-// Flaky see https://crbug.com/921418.
+// Flaky. Suspect due to a race when loading Chromecast integration.
+// See https://crbug.com/926035.
 IN_PROC_BROWSER_TEST_F(VideoPlayerBrowserTest, DISABLED_NativeMediaKey) {
+  // The HardwareMediaKeyHandling feature makes key handling flaky.
+  // See https://crbug.com/902519.
+  base::test::ScopedFeatureList disable_media_key_handling;
+  disable_media_key_handling.InitAndDisableFeature(
+      media::kHardwareMediaKeyHandling);
   set_test_case_name("mediaKeyNative");
   StartTest();
 }
diff --git a/chrome/browser/chromeos/login/lock/screen_locker_unittest.cc b/chrome/browser/chromeos/login/lock/screen_locker_unittest.cc
new file mode 100644
index 0000000..2ad1344
--- /dev/null
+++ b/chrome/browser/chromeos/login/lock/screen_locker_unittest.cc
@@ -0,0 +1,138 @@
+// 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/login/lock/screen_locker.h"
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
+#include "chrome/browser/chromeos/accessibility/test_accessibility_focus_ring_controller.h"
+#include "chrome/browser/chromeos/input_method/mock_input_method_manager_impl.h"
+#include "chrome/browser/chromeos/lock_screen_apps/state_controller.h"
+#include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
+#include "chrome/browser/chromeos/settings/device_settings_test_helper.h"
+#include "chrome/browser/chromeos/settings/scoped_testing_cros_settings.h"
+#include "chrome/browser/chromeos/settings/stub_install_attributes.h"
+#include "chrome/browser/ui/ash/accessibility/fake_accessibility_controller.h"
+#include "chrome/browser/ui/ash/login_screen_client.h"
+#include "chrome/browser/ui/ash/session_controller_client.h"
+#include "chrome/browser/ui/ash/test_login_screen.h"
+#include "chrome/browser/ui/ash/test_session_controller.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "chromeos/audio/cras_audio_handler.h"
+#include "chromeos/cryptohome/system_salt_getter.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/login/login_state/login_state.h"
+#include "chromeos/system/fake_statistics_provider.h"
+#include "components/account_id/account_id.h"
+#include "components/session_manager/core/session_manager.h"
+#include "components/user_manager/scoped_user_manager.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "content/public/test/test_service_manager_context.h"
+#include "device/bluetooth/dbus/bluez_dbus_manager.h"
+#include "media/audio/audio_manager.h"
+#include "media/audio/sounds/sounds_manager.h"
+#include "media/audio/test_audio_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+
+class ScreenLockerUnitTest : public testing::Test {
+ public:
+  ScreenLockerUnitTest() = default;
+  ~ScreenLockerUnitTest() override = default;
+
+  void SetUp() override {
+    DBusThreadManager::Initialize();
+
+    // MojoSystemInfoDispatcher dependency:
+    bluez::BluezDBusManager::GetSetterForTesting();
+
+    // Initialize SessionControllerClient and dependencies:
+    chromeos::LoginState::Initialize();
+    CHECK(testing_profile_manager_.SetUp());
+    session_controller_client_ = std::make_unique<SessionControllerClient>();
+    session_controller_client_->Init();
+
+    // Initialize AccessibilityManager and dependencies:
+    media::SoundsManager::Create();
+    input_method::InputMethodManager::Initialize(
+        // Owned by InputMethodManager
+        new input_method::MockInputMethodManagerImpl());
+    CrasAudioHandler::InitializeForTesting();
+    chromeos::AccessibilityManager::Initialize();
+
+    // Initialize ScreenLocker dependencies:
+    SystemSaltGetter::Initialize();
+    const AccountId account_id =
+        AccountId::FromUserEmail("testemail@example.com");
+    fake_user_manager_->AddUser(account_id);
+    fake_user_manager_->LoginUser(account_id);
+    CHECK(user_manager::UserManager::Get()->GetPrimaryUser());
+    session_manager::SessionManager::Get()->CreateSession(
+        account_id, account_id.GetUserEmail(), false);
+  }
+
+  void TearDown() override {
+    input_method::InputMethodManager::Shutdown();
+    media::SoundsManager::Shutdown();
+    media::AudioManager::Get()->Shutdown();
+    session_controller_client_.reset();
+    chromeos::LoginState::Shutdown();
+  }
+
+ protected:
+  // Needed for main loop and posting async tasks.
+  content::TestBrowserThreadBundle thread_bundle_;
+
+  // Needed to set up Service Manager and create mojo fakes.
+  content::TestServiceManagerContext context_;
+
+  // ViewsScreenLocker dependencies:
+  lock_screen_apps::StateController state_controller_;
+  // * MojoSystemInfoDispatcher dependencies:
+  ScopedTestingCrosSettings scoped_testing_cros_settings_;
+  system::ScopedFakeStatisticsProvider fake_statictics_provider_;
+  // * ChromeUserSelectionScreen dependencies:
+  chromeos::ScopedStubInstallAttributes test_install_attributes_;
+
+  // ScreenLocker dependencies:
+  // * AccessibilityManager dependencies:
+  FakeAccessibilityController fake_accessibility_controller_;
+  TestAccessibilityFocusRingController
+      test_accessibility_focus_ring_controller_;
+  std::unique_ptr<media::AudioManager> audio_manager_{
+      media::AudioManager::CreateForTesting(
+          std::make_unique<media::TestAudioThread>())};
+  // * LoginScreenClient dependencies:
+  session_manager::SessionManager session_manager_;
+  TestLoginScreen test_login_screen_;
+  LoginScreenClient login_screen_client_;
+  // * SessionControllerClient dependencies:
+  FakeChromeUserManager* fake_user_manager_{new FakeChromeUserManager()};
+  user_manager::ScopedUserManager scoped_user_manager_{
+      base::WrapUnique(fake_user_manager_)};
+  TestingProfileManager testing_profile_manager_{
+      TestingBrowserProcess::GetGlobal()};
+  ScopedDeviceSettingsTestHelper device_settings_test_helper_;
+  TestSessionController test_session_controller_;
+  std::unique_ptr<SessionControllerClient> session_controller_client_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScreenLockerUnitTest);
+};
+
+// Chrome notifies Ash when screen is locked. Ash is responsible for suspending
+// the device.
+TEST_F(ScreenLockerUnitTest, VerifyAshIsNotifiedOfScreenLocked) {
+  EXPECT_EQ(0, test_session_controller_.lock_animation_complete_call_count());
+  ScreenLocker::Show();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(1, test_session_controller_.lock_animation_complete_call_count());
+  ScreenLocker::Hide();
+  // Needed to perform internal cleanup scheduled in ScreenLocker::Hide()
+  base::RunLoop().RunUntilIdle();
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/login_manager_test.cc b/chrome/browser/chromeos/login/login_manager_test.cc
index 8a242db..417bb707 100644
--- a/chrome/browser/chromeos/login/login_manager_test.cc
+++ b/chrome/browser/chromeos/login/login_manager_test.cc
@@ -96,11 +96,11 @@
   ASSERT_TRUE(gaia_https_forwarder_.Initialize(
       kGAIAHost, embedded_test_server()->base_url()));
 
-  MixinBasedBrowserTest::SetUp();
+  MixinBasedInProcessBrowserTest::SetUp();
 }
 
 void LoginManagerTest::TearDownOnMainThread() {
-  MixinBasedBrowserTest::TearDownOnMainThread();
+  MixinBasedInProcessBrowserTest::TearDownOnMainThread();
 
   EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
 }
@@ -117,7 +117,7 @@
 
   fake_gaia_.Initialize();
 
-  MixinBasedBrowserTest::SetUpCommandLine(command_line);
+  MixinBasedInProcessBrowserTest::SetUpCommandLine(command_line);
 }
 
 void LoginManagerTest::SetUpOnMainThread() {
@@ -153,7 +153,7 @@
       should_launch_browser_);
   session_manager_test_api.SetShouldObtainTokenHandleInTests(false);
 
-  MixinBasedBrowserTest::SetUpOnMainThread();
+  MixinBasedInProcessBrowserTest::SetUpOnMainThread();
 }
 
 void LoginManagerTest::RegisterUser(const AccountId& account_id) {
diff --git a/chrome/browser/chromeos/login/login_manager_test.h b/chrome/browser/chromeos/login/login_manager_test.h
index a527e85..45742d5 100644
--- a/chrome/browser/chromeos/login/login_manager_test.h
+++ b/chrome/browser/chromeos/login/login_manager_test.h
@@ -8,7 +8,7 @@
 #include <string>
 
 #include "base/macros.h"
-#include "chrome/browser/chromeos/login/mixin_based_browser_test.h"
+#include "chrome/browser/chromeos/login/mixin_based_in_process_browser_test.h"
 #include "chrome/browser/chromeos/login/test/https_forwarder.h"
 #include "chrome/browser/chromeos/login/test/js_checker.h"
 #include "google_apis/gaia/fake_gaia.h"
@@ -25,7 +25,7 @@
 // out-of-box as completed.
 // Guarantees that WebUI has been initialized by waiting for
 // NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE notification.
-class LoginManagerTest : public MixinBasedBrowserTest {
+class LoginManagerTest : public MixinBasedInProcessBrowserTest {
  public:
   LoginManagerTest(bool should_launch_browser,
                    bool should_initialize_webui);
diff --git a/chrome/browser/chromeos/login/login_ui_browsertest.cc b/chrome/browser/chromeos/login/login_ui_browsertest.cc
index c2247d6..af44f5b 100644
--- a/chrome/browser/chromeos/login/login_ui_browsertest.cc
+++ b/chrome/browser/chromeos/login/login_ui_browsertest.cc
@@ -42,18 +42,18 @@
           kTestUsers[i].email, kTestUsers[i].gaia_id));
     }
 
-    screenshot_testing_ = new ScreenshotTestingMixin;
-    screenshot_testing_->IgnoreArea(areas::kClockArea);
-    screenshot_testing_->IgnoreArea(areas::kFirstUserpod);
-    screenshot_testing_->IgnoreArea(areas::kSecondUserpod);
-    AddMixin(base::WrapUnique(screenshot_testing_));
+    screenshot_testing_.IgnoreArea(areas::kClockArea);
+    screenshot_testing_.IgnoreArea(areas::kFirstUserpod);
+    screenshot_testing_.IgnoreArea(areas::kSecondUserpod);
   }
   ~LoginUITest() override {}
 
  protected:
   std::vector<AccountId> test_users_;
 
-  ScreenshotTestingMixin* screenshot_testing_;
+  ScreenshotTestingMixin screenshot_testing_{&mixin_host_};
+
+  DISALLOW_COPY_AND_ASSIGN(LoginUITest);
 };
 
 IN_PROC_BROWSER_TEST_F(LoginUITest, PRE_LoginUIVisible) {
@@ -77,7 +77,7 @@
       "document.querySelectorAll('.pod:not(#user-pod-template)')[1]"
       ".user.emailAddress == '" +
       test_users_[1].GetUserEmail() + "'");
-  screenshot_testing_->RunScreenshotTesting("LoginUITest-LoginUIVisible");
+  screenshot_testing_.RunScreenshotTesting("LoginUITest-LoginUIVisible");
 }
 
 IN_PROC_BROWSER_TEST_F(LoginUITest, PRE_InterruptedAutoStartEnrollment) {
diff --git a/chrome/browser/chromeos/login/mixin_based_browser_test.cc b/chrome/browser/chromeos/login/mixin_based_browser_test.cc
deleted file mode 100644
index 7847cdc4..0000000
--- a/chrome/browser/chromeos/login/mixin_based_browser_test.cc
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (c) 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/chromeos/login/mixin_based_browser_test.h"
-
-#include <utility>
-
-#include "base/containers/adapters.h"
-
-namespace chromeos {
-
-MixinBasedBrowserTest::MixinBasedBrowserTest() : setup_was_launched_(false) {}
-
-MixinBasedBrowserTest::~MixinBasedBrowserTest() {}
-
-void MixinBasedBrowserTest::SetUpCommandLine(base::CommandLine* command_line) {
-  setup_was_launched_ = true;
-  for (const auto& mixin : mixins_)
-    mixin->SetUpCommandLine(command_line);
-}
-
-void MixinBasedBrowserTest::SetUpInProcessBrowserTestFixture() {
-  setup_was_launched_ = true;
-  for (const auto& mixin : mixins_)
-    mixin->SetUpInProcessBrowserTestFixture();
-}
-
-void MixinBasedBrowserTest::SetUpOnMainThread() {
-  setup_was_launched_ = true;
-  for (const auto& mixin : mixins_)
-    mixin->SetUpOnMainThread();
-}
-
-void MixinBasedBrowserTest::TearDownOnMainThread() {
-  for (const auto& mixin : base::Reversed(mixins_))
-    mixin->TearDownInProcessBrowserTestFixture();
-}
-
-void MixinBasedBrowserTest::TearDownInProcessBrowserTestFixture() {
-  for (const auto& mixin : base::Reversed(mixins_))
-    mixin->TearDownInProcessBrowserTestFixture();
-}
-
-void MixinBasedBrowserTest::AddMixin(std::unique_ptr<Mixin> mixin) {
-  CHECK(!setup_was_launched_)
-      << "You are trying to add a mixin after setting up has already started.";
-  mixins_.push_back(std::move(mixin));
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/mixin_based_browser_test.h b/chrome/browser/chromeos/login/mixin_based_browser_test.h
deleted file mode 100644
index 9d32795..0000000
--- a/chrome/browser/chromeos/login/mixin_based_browser_test.h
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (c) 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_CHROMEOS_LOGIN_MIXIN_BASED_BROWSER_TEST_H_
-#define CHROME_BROWSER_CHROMEOS_LOGIN_MIXIN_BASED_BROWSER_TEST_H_
-
-#include <memory>
-#include <vector>
-
-#include "chrome/test/base/in_process_browser_test.h"
-
-namespace chromeos {
-
-class MixinBasedBrowserTest : public InProcessBrowserTest {
- public:
-  // A class that can be used to add some features not related directly to the
-  // testing process in order not to make the test class too complicated and to
-  // set up them in a proper time (at the same time when the corresponding set
-  // ups for the main test are run).
-  //
-  // To used this you need to derive a class from from
-  // MixinBasedBrowserTest::Mixin, e.g. MixinYouWantToUse, and declare all
-  // the methods you'd like in this new class. You also can reload setups and
-  // teardowns if you need so. Test which wants to use some mixin should call
-  // AddMixin(mixin_) from its constructor, where mixin_ should be an instance
-  // of MixinYouWantToUse.
-  //
-  // All methods in Mixin are complete analogs of those in InProcessBrowserTest,
-  // so if some usecases are unclear, take a look at in_process_browser_test.h
-  class Mixin {
-   public:
-    Mixin() {}
-    virtual ~Mixin() {}
-
-    // Is called before creating the browser and running
-    // SetUpInProcessBrowserTestFixture.
-    // Should be used for setting up the command line.
-    virtual void SetUpCommandLine(base::CommandLine* command_line) {}
-
-    // Is called before creating the browser.
-    // Should be used to set up the environment for running the browser.
-    virtual void SetUpInProcessBrowserTestFixture() {}
-
-    // Is called after creating the browser and before executing test code.
-    // Should be used for setting up things related to the browser object.
-    virtual void SetUpOnMainThread() {}
-
-    // Is called after executing the test code and before the browser is torn
-    // down.
-    // Should be used to do the necessary cleanup on the working browser.
-    virtual void TearDownOnMainThread() {}
-
-    // Is called after the browser is torn down.
-    // Should be used to do the remaining cleanup.
-    virtual void TearDownInProcessBrowserTestFixture() {}
-  };
-
-  MixinBasedBrowserTest();
-  ~MixinBasedBrowserTest() override;
-
-  // Override from InProcessBrowserTest.
-  void SetUpCommandLine(base::CommandLine* command_line) override;
-  void SetUpInProcessBrowserTestFixture() override;
-  void SetUpOnMainThread() override;
-  void TearDownOnMainThread() override;
-  void TearDownInProcessBrowserTestFixture() override;
-
- protected:
-  // Adds |mixin| as an mixin for this test, passing ownership
-  // for it to MixinBasedBrowserTest.
-  // Should be called in constructor of the test (should be already completed
-  // before running set ups).
-  void AddMixin(std::unique_ptr<Mixin> mixin);
-
- private:
-  // Keeps all the mixins for this test,
-  std::vector<std::unique_ptr<Mixin>> mixins_;
-
-  // Is false initially, becomes true when any of SetUp* methods is called.
-  // Required to check that AddMixin is always called before setting up.
-  bool setup_was_launched_;
-};
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_LOGIN_MIXIN_BASED_BROWSER_TEST_H_
diff --git a/chrome/browser/chromeos/login/mixin_based_in_process_browser_test.cc b/chrome/browser/chromeos/login/mixin_based_in_process_browser_test.cc
new file mode 100644
index 0000000..e181424
--- /dev/null
+++ b/chrome/browser/chromeos/login/mixin_based_in_process_browser_test.cc
@@ -0,0 +1,130 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/login/mixin_based_in_process_browser_test.h"
+
+#include <utility>
+
+#include "base/containers/adapters.h"
+
+namespace chromeos {
+
+InProcessBrowserTestMixin::InProcessBrowserTestMixin(
+    InProcessBrowserTestMixinHost* host) {
+  host->mixins_.push_back(this);
+}
+
+InProcessBrowserTestMixin::~InProcessBrowserTestMixin() = default;
+
+void InProcessBrowserTestMixin::SetUp() {}
+
+void InProcessBrowserTestMixin::SetUpCommandLine(
+    base::CommandLine* command_line) {}
+
+void InProcessBrowserTestMixin::SetUpDefaultCommandLine(
+    base::CommandLine* command_line) {}
+
+void InProcessBrowserTestMixin::SetUpInProcessBrowserTestFixture() {}
+
+void InProcessBrowserTestMixin::SetUpOnMainThread() {}
+
+void InProcessBrowserTestMixin::TearDownOnMainThread() {}
+
+void InProcessBrowserTestMixin::TearDownInProcessBrowserTestFixture() {}
+
+void InProcessBrowserTestMixin::TearDown() {}
+
+InProcessBrowserTestMixinHost::InProcessBrowserTestMixinHost() = default;
+
+InProcessBrowserTestMixinHost::~InProcessBrowserTestMixinHost() = default;
+
+void InProcessBrowserTestMixinHost::SetUp() {
+  for (InProcessBrowserTestMixin* mixin : mixins_)
+    mixin->SetUp();
+}
+
+void InProcessBrowserTestMixinHost::SetUpCommandLine(
+    base::CommandLine* command_line) {
+  for (InProcessBrowserTestMixin* mixin : mixins_)
+    mixin->SetUpCommandLine(command_line);
+}
+
+void InProcessBrowserTestMixinHost::SetUpDefaultCommandLine(
+    base::CommandLine* command_line) {
+  for (InProcessBrowserTestMixin* mixin : mixins_)
+    mixin->SetUpDefaultCommandLine(command_line);
+}
+
+void InProcessBrowserTestMixinHost::SetUpInProcessBrowserTestFixture() {
+  for (InProcessBrowserTestMixin* mixin : mixins_)
+    mixin->SetUpInProcessBrowserTestFixture();
+}
+
+void InProcessBrowserTestMixinHost::SetUpOnMainThread() {
+  for (InProcessBrowserTestMixin* mixin : mixins_)
+    mixin->SetUpOnMainThread();
+}
+
+void InProcessBrowserTestMixinHost::TearDownOnMainThread() {
+  for (InProcessBrowserTestMixin* mixin : base::Reversed(mixins_))
+    mixin->TearDownOnMainThread();
+}
+
+void InProcessBrowserTestMixinHost::TearDownInProcessBrowserTestFixture() {
+  for (InProcessBrowserTestMixin* mixin : base::Reversed(mixins_))
+    mixin->TearDownInProcessBrowserTestFixture();
+}
+
+void InProcessBrowserTestMixinHost::TearDown() {
+  for (InProcessBrowserTestMixin* mixin : base::Reversed(mixins_))
+    mixin->TearDown();
+}
+
+MixinBasedInProcessBrowserTest::MixinBasedInProcessBrowserTest() = default;
+
+MixinBasedInProcessBrowserTest::~MixinBasedInProcessBrowserTest() = default;
+
+void MixinBasedInProcessBrowserTest::SetUp() {
+  mixin_host_.SetUp();
+  InProcessBrowserTest::SetUp();
+}
+
+void MixinBasedInProcessBrowserTest::SetUpCommandLine(
+    base::CommandLine* command_line) {
+  mixin_host_.SetUpCommandLine(command_line);
+  InProcessBrowserTest::SetUpCommandLine(command_line);
+}
+
+void MixinBasedInProcessBrowserTest::SetUpDefaultCommandLine(
+    base::CommandLine* command_line) {
+  mixin_host_.SetUpDefaultCommandLine(command_line);
+  InProcessBrowserTest::SetUpDefaultCommandLine(command_line);
+}
+
+void MixinBasedInProcessBrowserTest::SetUpInProcessBrowserTestFixture() {
+  mixin_host_.SetUpInProcessBrowserTestFixture();
+  InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
+}
+
+void MixinBasedInProcessBrowserTest::SetUpOnMainThread() {
+  mixin_host_.SetUpOnMainThread();
+  InProcessBrowserTest::SetUpOnMainThread();
+}
+
+void MixinBasedInProcessBrowserTest::TearDownOnMainThread() {
+  mixin_host_.TearDownOnMainThread();
+  InProcessBrowserTest::TearDownOnMainThread();
+}
+
+void MixinBasedInProcessBrowserTest::TearDownInProcessBrowserTestFixture() {
+  mixin_host_.TearDownInProcessBrowserTestFixture();
+  InProcessBrowserTest::TearDownInProcessBrowserTestFixture();
+}
+
+void MixinBasedInProcessBrowserTest::TearDown() {
+  mixin_host_.TearDown();
+  InProcessBrowserTest::TearDown();
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/mixin_based_in_process_browser_test.h b/chrome/browser/chromeos/login/mixin_based_in_process_browser_test.h
new file mode 100644
index 0000000..f3089439
--- /dev/null
+++ b/chrome/browser/chromeos/login/mixin_based_in_process_browser_test.h
@@ -0,0 +1,146 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_LOGIN_MIXIN_BASED_IN_PROCESS_BROWSER_TEST_H_
+#define CHROME_BROWSER_CHROMEOS_LOGIN_MIXIN_BASED_IN_PROCESS_BROWSER_TEST_H_
+
+#include <memory>
+#include <vector>
+
+#include "chrome/test/base/in_process_browser_test.h"
+
+namespace chromeos {
+
+//
+// InProcessBrowserTestMixin enables writing isolated test helpers which depend
+// on the standard test lifecycle but should not be test bases.
+//
+// A new mixin is created by deriving from InProcessBrowserTestMixin and
+// overriding methods as needed.
+//
+//   class MyMixin : public InProcessBrowserTestMixin {
+//    public:
+//     explicit MyMixin(InProcessBrowserTestMixinHost* host)
+//         : InProcessBrowserTestMixin(host) {}
+//     ~MyMixin() override = default;
+//
+//     // InProcessBrowserTestMixin:
+//     void SetUpCommandLine(base::CommandLine* command_line) { /* ... */ }
+//
+//    private:
+//     DISALLOW_COPY_AND_ASSIGN(MyMixin);
+//   };
+//
+//
+// To use the mixin, declare it as a member variable on the class and call the
+// constructor with the InProcessBrowserTestMixinHost also declared on the class
+// (or parent class). The mixin will register itself with the host and the host
+// will invoke all registered mixin methods.
+//
+// For example, here is how to use MixinBasedInProcessBrowserTest:
+//
+//   class SimpleUsage : public MixinBasedInProcessBrowserTest {
+//    public:
+//     SimpleUsage() = default;
+//     ~SimpleUsage() override = default;
+//
+//    private:
+//     MyMixin my_mixin_{&mixin_host_};
+//     SomeOtherMixin some_other_mixin_{&mixin_host_};
+//
+//     DISALLOW_COPY_AND_ASSIGN(SimpleUsage);
+//   };
+//
+//
+// See WizardInProcessBrowserTest for an example of how to correctly embed a
+// mixin host.
+//
+
+class InProcessBrowserTestMixinHost;
+
+// Derive from this type to create a class which depends on the test lifecycle
+// without also becoming a test base.
+class InProcessBrowserTestMixin {
+ public:
+  explicit InProcessBrowserTestMixin(InProcessBrowserTestMixinHost* host);
+  virtual ~InProcessBrowserTestMixin();
+
+  // See InProcessBrowserTest for docs. The call order is:
+  //
+  // SetUp
+  //   SetUpCommandLine
+  //   SetUpDefaultCommandLine
+  //   SetUpInProcessBrowserTestFixture
+  //   SetUpOnMainThread
+  //   TearDownOnMainThread
+  //   TearDownInProcessBrowserTestFixture
+  // TearDown
+  //
+  // SetUp is the function which calls SetUpCommandLine,
+  // SetUpDefaultCommandLine, etc.
+  virtual void SetUp();
+  virtual void SetUpCommandLine(base::CommandLine* command_line);
+  virtual void SetUpDefaultCommandLine(base::CommandLine* command_line);
+  virtual void SetUpInProcessBrowserTestFixture();
+  virtual void SetUpOnMainThread();
+  virtual void TearDownOnMainThread();
+  virtual void TearDownInProcessBrowserTestFixture();
+  virtual void TearDown();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(InProcessBrowserTestMixin);
+};
+
+// The mixin host executes the callbacks on the mixin instances.
+class InProcessBrowserTestMixinHost final {
+ public:
+  InProcessBrowserTestMixinHost();
+  ~InProcessBrowserTestMixinHost();
+
+  void SetUp();
+  void SetUpCommandLine(base::CommandLine* command_line);
+  void SetUpDefaultCommandLine(base::CommandLine* command_line);
+  void SetUpInProcessBrowserTestFixture();
+  void SetUpOnMainThread();
+  void TearDownOnMainThread();
+  void TearDownInProcessBrowserTestFixture();
+  void TearDown();
+
+ private:
+  // The constructor of InProcessBrowserTestMixin injects itself directly into
+  // mixins_. This is done instead of an explicit AddMixin to make API usage
+  // simpler.
+  friend class InProcessBrowserTestMixin;
+
+  std::vector<InProcessBrowserTestMixin*> mixins_;
+
+  DISALLOW_COPY_AND_ASSIGN(InProcessBrowserTestMixinHost);
+};
+
+// An InProcessBrowserTest which supports mixins.
+class MixinBasedInProcessBrowserTest : public InProcessBrowserTest {
+ public:
+  MixinBasedInProcessBrowserTest();
+  ~MixinBasedInProcessBrowserTest() override;
+
+  // InProcessBrowserTest:
+  void SetUp() override;
+  void SetUpCommandLine(base::CommandLine* command_line) override;
+  void SetUpDefaultCommandLine(base::CommandLine* command_line) override;
+  void SetUpInProcessBrowserTestFixture() override;
+  void SetUpOnMainThread() override;
+  void TearDownOnMainThread() override;
+  void TearDownInProcessBrowserTestFixture() override;
+  void TearDown() override;
+
+ protected:
+  InProcessBrowserTestMixinHost mixin_host_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MixinBasedInProcessBrowserTest);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_LOGIN_MIXIN_BASED_IN_PROCESS_BROWSER_TEST_H_
diff --git a/chrome/browser/chromeos/login/screenshot_testing/screenshot_testing_mixin.cc b/chrome/browser/chromeos/login/screenshot_testing/screenshot_testing_mixin.cc
index 0817afb..88a009a 100644
--- a/chrome/browser/chromeos/login/screenshot_testing/screenshot_testing_mixin.cc
+++ b/chrome/browser/chromeos/login/screenshot_testing/screenshot_testing_mixin.cc
@@ -16,20 +16,11 @@
 
 namespace chromeos {
 
-ScreenshotTestingMixin::ScreenshotTestingMixin()
-    : enable_test_screenshots_(false) {}
+ScreenshotTestingMixin::ScreenshotTestingMixin(
+    InProcessBrowserTestMixinHost* host)
+    : InProcessBrowserTestMixin(host) {}
 
-ScreenshotTestingMixin::~ScreenshotTestingMixin() {}
-
-void ScreenshotTestingMixin::SetUpInProcessBrowserTestFixture() {
-  enable_test_screenshots_ = screenshot_tester_.TryInitialize();
-}
-
-void ScreenshotTestingMixin::SetUpCommandLine(base::CommandLine* command_line) {
-  if (enable_test_screenshots_) {
-    command_line->AppendSwitch(switches::kEnablePixelOutputInTests);
-  }
-}
+ScreenshotTestingMixin::~ScreenshotTestingMixin() = default;
 
 void ScreenshotTestingMixin::RunScreenshotTesting(
     const std::string& test_name) {
@@ -43,6 +34,16 @@
   screenshot_tester_.IgnoreArea(area);
 }
 
+void ScreenshotTestingMixin::SetUpInProcessBrowserTestFixture() {
+  enable_test_screenshots_ = screenshot_tester_.TryInitialize();
+}
+
+void ScreenshotTestingMixin::SetUpCommandLine(base::CommandLine* command_line) {
+  if (enable_test_screenshots_) {
+    command_line->AppendSwitch(switches::kEnablePixelOutputInTests);
+  }
+}
+
 // Current implementation is a mockup.
 // It simply waits for 5 seconds, assuming that this time is enough for
 // animation to load completely.
diff --git a/chrome/browser/chromeos/login/screenshot_testing/screenshot_testing_mixin.h b/chrome/browser/chromeos/login/screenshot_testing/screenshot_testing_mixin.h
index b8cdd8b..e93e27a 100644
--- a/chrome/browser/chromeos/login/screenshot_testing/screenshot_testing_mixin.h
+++ b/chrome/browser/chromeos/login/screenshot_testing/screenshot_testing_mixin.h
@@ -9,7 +9,7 @@
 
 #include "base/command_line.h"
 #include "base/timer/timer.h"
-#include "chrome/browser/chromeos/login/mixin_based_browser_test.h"
+#include "chrome/browser/chromeos/login/mixin_based_in_process_browser_test.h"
 #include "chrome/browser/chromeos/login/screenshot_testing/screenshot_tester.h"
 #include "content/public/test/browser_test_base.h"
 
@@ -19,23 +19,18 @@
 // Sets up everything required for taking screenshots.
 // Provides functionality to deal with animation load: screenshots
 // should be taken only when all the animation is loaded.
-class ScreenshotTestingMixin : public MixinBasedBrowserTest::Mixin {
+class ScreenshotTestingMixin : public InProcessBrowserTestMixin {
  public:
-  ScreenshotTestingMixin();
+  explicit ScreenshotTestingMixin(InProcessBrowserTestMixinHost* host);
   ~ScreenshotTestingMixin() override;
 
-  // Override from BrowsertestBase::Mixin.
-  void SetUpInProcessBrowserTestFixture() override;
-
-  // Override from BrowsertestBase::Mixin.
-  void SetUpCommandLine(base::CommandLine* command_line) override;
-
-  // Runs screenshot testing if it is turned on by command line switches.
   void RunScreenshotTesting(const std::string& test_name);
-
-  // Remembers that area |area| should be ignored during comparison.
   void IgnoreArea(const SkIRect& area);
 
+  // InProcessBrowserTestMixin:
+  void SetUpInProcessBrowserTestFixture() override;
+  void SetUpCommandLine(base::CommandLine* command_line) override;
+
  private:
   // It turns out that it takes some more time for the animation
   // to finish loading even after all the notifications have been sent.
@@ -54,11 +49,13 @@
   base::Closure animation_waiter_quitter_;
 
   // Is true if testing with screenshots is turned on with all proper switches.
-  bool enable_test_screenshots_;
+  bool enable_test_screenshots_ = false;
 
   // |screenshot_tester_ | does everything connected with taking, loading and
   // comparing screenshots
   ScreenshotTester screenshot_tester_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScreenshotTestingMixin);
 };
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/test/oobe_base_test.cc b/chrome/browser/chromeos/login/test/oobe_base_test.cc
index 5f401e9..608276b 100644
--- a/chrome/browser/chromeos/login/test/oobe_base_test.cc
+++ b/chrome/browser/chromeos/login/test/oobe_base_test.cc
@@ -83,15 +83,45 @@
   // SetUpCommandLine().
   InitHttpsForwarders();
 
+  mixin_host_.SetUp();
   extensions::ExtensionApiTest::SetUp();
 }
 
+void OobeBaseTest::SetUpCommandLine(base::CommandLine* command_line) {
+  extensions::ExtensionApiTest::SetUpCommandLine(command_line);
+  mixin_host_.SetUpCommandLine(command_line);
+
+  if (ShouldForceWebUiLogin())
+    command_line->AppendSwitch(ash::switches::kShowWebUiLogin);
+  command_line->AppendSwitch(chromeos::switches::kLoginManager);
+  command_line->AppendSwitch(chromeos::switches::kForceLoginManagerInTests);
+  if (!needs_background_networking_)
+    command_line->AppendSwitch(::switches::kDisableBackgroundNetworking);
+  command_line->AppendSwitchASCII(chromeos::switches::kLoginProfile, "user");
+
+  GURL gaia_url = gaia_https_forwarder_.GetURLForSSLHost(std::string());
+  command_line->AppendSwitchASCII(::switches::kGaiaUrl, gaia_url.spec());
+  command_line->AppendSwitchASCII(::switches::kLsoUrl, gaia_url.spec());
+  command_line->AppendSwitchASCII(::switches::kGoogleApisUrl, gaia_url.spec());
+  command_line->AppendSwitchASCII(::switches::kOAuthAccountManagerUrl,
+                                  gaia_url.spec());
+
+  fake_gaia_->Initialize();
+  fake_gaia_->set_issue_oauth_code_cookie(true);
+}
+
+void OobeBaseTest::SetUpDefaultCommandLine(base::CommandLine* command_line) {
+  mixin_host_.SetUpDefaultCommandLine(command_line);
+  extensions::ExtensionApiTest::SetUpDefaultCommandLine(command_line);
+}
+
 void OobeBaseTest::SetUpInProcessBrowserTestFixture() {
   network_portal_detector_ = new NetworkPortalDetectorTestImpl();
   network_portal_detector::InitializeForTesting(network_portal_detector_);
   network_portal_detector_->SetDefaultNetworkForTesting(
       FakeShillManagerClient::kFakeEthernetNetworkGuid);
 
+  mixin_host_.SetUpInProcessBrowserTestFixture();
   extensions::ExtensionApiTest::SetUpInProcessBrowserTestFixture();
 }
 
@@ -123,35 +153,25 @@
     run_loop.Run();
   }
 
+  mixin_host_.SetUpOnMainThread();
   extensions::ExtensionApiTest::SetUpOnMainThread();
 }
 
 void OobeBaseTest::TearDownOnMainThread() {
   EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
 
+  mixin_host_.TearDownOnMainThread();
   extensions::ExtensionApiTest::TearDownOnMainThread();
 }
 
-void OobeBaseTest::SetUpCommandLine(base::CommandLine* command_line) {
-  extensions::ExtensionApiTest::SetUpCommandLine(command_line);
+void OobeBaseTest::TearDownInProcessBrowserTestFixture() {
+  mixin_host_.TearDownInProcessBrowserTestFixture();
+  extensions::ExtensionApiTest::TearDownInProcessBrowserTestFixture();
+}
 
-  if (ShouldForceWebUiLogin())
-    command_line->AppendSwitch(ash::switches::kShowWebUiLogin);
-  command_line->AppendSwitch(chromeos::switches::kLoginManager);
-  command_line->AppendSwitch(chromeos::switches::kForceLoginManagerInTests);
-  if (!needs_background_networking_)
-    command_line->AppendSwitch(::switches::kDisableBackgroundNetworking);
-  command_line->AppendSwitchASCII(chromeos::switches::kLoginProfile, "user");
-
-  GURL gaia_url = gaia_https_forwarder_.GetURLForSSLHost(std::string());
-  command_line->AppendSwitchASCII(::switches::kGaiaUrl, gaia_url.spec());
-  command_line->AppendSwitchASCII(::switches::kLsoUrl, gaia_url.spec());
-  command_line->AppendSwitchASCII(::switches::kGoogleApisUrl, gaia_url.spec());
-  command_line->AppendSwitchASCII(::switches::kOAuthAccountManagerUrl,
-                                  gaia_url.spec());
-
-  fake_gaia_->Initialize();
-  fake_gaia_->set_issue_oauth_code_cookie(true);
+void OobeBaseTest::TearDown() {
+  mixin_host_.TearDown();
+  extensions::ExtensionApiTest::TearDown();
 }
 
 void OobeBaseTest::InitHttpsForwarders() {
diff --git a/chrome/browser/chromeos/login/test/oobe_base_test.h b/chrome/browser/chromeos/login/test/oobe_base_test.h
index 224a3752..0276090d 100644
--- a/chrome/browser/chromeos/login/test/oobe_base_test.h
+++ b/chrome/browser/chromeos/login/test/oobe_base_test.h
@@ -10,6 +10,7 @@
 #include "base/callback.h"
 #include "base/command_line.h"
 #include "base/macros.h"
+#include "chrome/browser/chromeos/login/mixin_based_in_process_browser_test.h"
 #include "chrome/browser/chromeos/login/test/https_forwarder.h"
 #include "chrome/browser/chromeos/login/test/js_checker.h"
 #include "chrome/browser/extensions/extension_apitest.h"
@@ -49,12 +50,15 @@
   virtual void RegisterAdditionalRequestHandlers();
 
  protected:
-  // InProcessBrowserTest overrides.
+  // InProcessBrowserTest:
   void SetUp() override;
+  void SetUpCommandLine(base::CommandLine* command_line) override;
+  void SetUpDefaultCommandLine(base::CommandLine* command_line) override;
   void SetUpInProcessBrowserTestFixture() override;
   void SetUpOnMainThread() override;
   void TearDownOnMainThread() override;
-  void SetUpCommandLine(base::CommandLine* command_line) override;
+  void TearDownInProcessBrowserTestFixture() override;
+  void TearDown() override;
 
   virtual void InitHttpsForwarders();
 
@@ -102,6 +106,8 @@
                              const std::string& gaia_id,
                              const std::string& refresh_token);
 
+  InProcessBrowserTestMixinHost mixin_host_;
+
   std::unique_ptr<FakeGaia> fake_gaia_;
   NetworkPortalDetectorTestImpl* network_portal_detector_ = nullptr;
 
diff --git a/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc b/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc
index e9fb591..0ca250f0 100644
--- a/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc
+++ b/chrome/browser/chromeos/login/users/chrome_user_manager_impl.cc
@@ -9,6 +9,7 @@
 #include <cstddef>
 #include <set>
 #include <utility>
+#include <vector>
 
 #include "ash/public/cpp/ash_pref_names.h"
 #include "ash/public/interfaces/constants.mojom.h"
@@ -847,9 +848,23 @@
 }
 
 void ChromeUserManagerImpl::PerformPostUserListLoadingActions() {
-  for (user_manager::UserList::iterator ui = users_.begin(), ue = users_.end();
-       ui != ue; ++ui) {
-    GetUserImageManager((*ui)->GetAccountId())->LoadUserImage();
+  std::vector<user_manager::User*> users_to_remove;
+
+  for (user_manager::User* user : users_) {
+    // TODO(http://crbug/866790): Remove supervised user accounts. After we have
+    // enough confidence that there are no more supervised users on devices in
+    // the wild, remove this.
+    if (base::FeatureList::IsEnabled(
+            features::kRemoveSupervisedUsersOnStartup) &&
+        user->IsSupervised()) {
+      users_to_remove.push_back(user);
+    } else {
+      GetUserImageManager(user->GetAccountId())->LoadUserImage();
+    }
+  }
+
+  for (user_manager::User* user : users_to_remove) {
+    RemoveUser(user->GetAccountId(), nullptr);
   }
 }
 
diff --git a/chrome/browser/chromeos/login/users/remove_supervised_users_browsertest.cc b/chrome/browser/chromeos/login/users/remove_supervised_users_browsertest.cc
new file mode 100644
index 0000000..b15f6f7
--- /dev/null
+++ b/chrome/browser/chromeos/login/users/remove_supervised_users_browsertest.cc
@@ -0,0 +1,122 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/chromeos/login/login_manager_test.h"
+#include "chrome/browser/chromeos/login/users/chrome_user_manager.h"
+#include "chrome/browser/chromeos/login/users/chrome_user_manager_impl.h"
+#include "chrome/browser/chromeos/settings/cros_settings.h"
+#include "chrome/browser/chromeos/settings/scoped_testing_cros_settings.h"
+#include "chrome/common/chrome_features.h"
+#include "components/account_id/account_id.h"
+#include "components/user_manager/scoped_user_manager.h"
+#include "components/user_manager/user_manager.h"
+#include "components/user_manager/user_manager_base.h"
+
+namespace chromeos {
+
+namespace {
+
+struct {
+  const char* email;
+  const char* gaia_id;
+} const kTestUsers[] = {
+    {"test-user1@gmail.com", "1111111111"},
+    {"test-user2@gmail.com", "2222222222"},
+    // Test Supervised User.
+    // The domain is defined in user_manager::kSupervisedUserDomain.
+    // That const isn't directly referenced here to keep this code readable by
+    // avoiding std::string concatenations.
+    {"test-superviseduser@locally-managed.localhost", "3333333333"},
+};
+
+}  // namespace
+
+class RemoveSupervisedUsersBrowserTest : public LoginManagerTest {
+ public:
+  RemoveSupervisedUsersBrowserTest() : LoginManagerTest(false, false) {}
+
+  ~RemoveSupervisedUsersBrowserTest() override = default;
+
+  void SetUpOnMainThread() override {
+    LoginManagerTest::SetUpOnMainThread();
+    InitializeTestUsers();
+  }
+
+  void TearDownOnMainThread() override {
+    LoginManagerTest::TearDownOnMainThread();
+    scoped_user_manager_.reset();
+  }
+
+ protected:
+  std::vector<AccountId> test_users_;
+
+ private:
+  void InitializeTestUsers() {
+    for (size_t i = 0; i < base::size(kTestUsers); ++i) {
+      test_users_.emplace_back(AccountId::FromUserEmailGaiaId(
+          kTestUsers[i].email, kTestUsers[i].gaia_id));
+      RegisterUser(test_users_[i]);
+    }
+
+    // Setup a scoped real ChromeUserManager, since this is an end-to-end
+    // test.  This will read the users registered to the local state just
+    // before this in RegisterUser.
+    scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
+        chromeos::ChromeUserManagerImpl::CreateChromeUserManager());
+  }
+
+  std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
+  ScopedTestingCrosSettings scoped_testing_cros_settings_;
+
+  DISALLOW_COPY_AND_ASSIGN(RemoveSupervisedUsersBrowserTest);
+};
+
+class RemoveSupervisedUsersBrowserEnabledTest
+    : public RemoveSupervisedUsersBrowserTest {
+ protected:
+  void SetUpInProcessBrowserTestFixture() override {
+    scoped_feature_list_.InitAndEnableFeature(
+        features::kRemoveSupervisedUsersOnStartup);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+class RemoveSupervisedUsersBrowserDisabledTest
+    : public RemoveSupervisedUsersBrowserTest {
+ protected:
+  void SetUpInProcessBrowserTestFixture() override {
+    scoped_feature_list_.InitAndDisableFeature(
+        features::kRemoveSupervisedUsersOnStartup);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(RemoveSupervisedUsersBrowserEnabledTest,
+                       SupervisedUserRemoved) {
+  // When Supervised users are removed, only 2 test users should be returned.
+  EXPECT_EQ(2U, user_manager::UserManager::Get()->GetUsers().size());
+
+  // None of the non-supervised users should have been removed.
+  for (user_manager::User* user :
+       user_manager::UserManager::Get()->GetUsers()) {
+    EXPECT_EQ(false, user->IsSupervised());
+  }
+}
+
+IN_PROC_BROWSER_TEST_F(RemoveSupervisedUsersBrowserDisabledTest,
+                       SupervisedUserNotRemoved) {
+  // When Supervised users are not removed, all 3 test users should be returned.
+  EXPECT_EQ(3U, user_manager::UserManager::Get()->GetUsers().size());
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/extensions/api/identity/identity_apitest.cc b/chrome/browser/extensions/api/identity/identity_apitest.cc
index e6e3a9f..a9ca97ae 100644
--- a/chrome/browser/extensions/api/identity/identity_apitest.cc
+++ b/chrome/browser/extensions/api/identity/identity_apitest.cc
@@ -32,7 +32,6 @@
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/account_fetcher_service_factory.h"
-#include "chrome/browser/signin/account_tracker_service_factory.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
 #include "chrome/browser/ui/browser.h"
@@ -467,20 +466,6 @@
     return account_info.account_id;
   }
 
-  std::string AddAccount(const std::string& email) {
-    std::string account_id = SeedAccountInfo(email);
-    identity_test_env()->SetRefreshTokenForAccount(account_id);
-    return account_id;
-  }
-
-  std::string SeedAccountInfo(const std::string& email) {
-    std::string gaia = "gaia_id_for_" + email;
-    std::replace(gaia.begin(), gaia.end(), '@', '_');
-    AccountTrackerService* account_tracker =
-        AccountTrackerServiceFactory::GetForProfile(profile());
-    return account_tracker->SeedAccountInfo(gaia, email);
-  }
-
   IdentityAPI* id_api() {
     return IdentityAPI::GetFactoryInstance()->Get(browser()->profile());
   }
@@ -589,7 +574,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(IdentityGetAccountsFunctionTest, NoPrimaryAccount) {
-  AddAccount("secondary@example.com");
+  identity_test_env()->MakeAccountAvailable("secondary@example.com");
   EXPECT_TRUE(ExpectGetAccounts({}));
 }
 
@@ -608,7 +593,7 @@
 
 IN_PROC_BROWSER_TEST_F(IdentityGetAccountsFunctionTest, TwoAccountsSignedIn) {
   SignIn("primary@example.com");
-  AddAccount("secondary@example.com");
+  identity_test_env()->MakeAccountAvailable("secondary@example.com");
   if (!id_api()->AreExtensionsRestrictedToPrimaryAccount()) {
     EXPECT_TRUE(ExpectGetAccounts({"gaia_id_for_primary_example.com",
                                    "gaia_id_for_secondary_example.com"}));
@@ -639,7 +624,7 @@
 IN_PROC_BROWSER_TEST_F(IdentityOldProfilesGetAccountsFunctionTest,
                        TwoAccountsSignedIn) {
   SignIn("primary@example.com");
-  AddAccount("secondary@example.com");
+  identity_test_env()->MakeAccountAvailable("secondary@example.com");
   EXPECT_TRUE(ExpectGetAccounts({"gaia_id_for_primary_example.com"}));
 }
 
@@ -1706,7 +1691,7 @@
 IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
                        MultiDefaultUserManuallyIssueToken) {
   std::string primary_account_id = SignIn("primary@example.com");
-  SeedAccountInfo("secondary@example.com");
+  identity_test_env()->MakeAccountAvailable("secondary@example.com");
 
   scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction());
   scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES));
@@ -1734,7 +1719,7 @@
 IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
                        MultiPrimaryUserManuallyIssueToken) {
   std::string primary_account_id = SignIn("primary@example.com");
-  AddAccount("secondary@example.com");
+  identity_test_env()->MakeAccountAvailable("secondary@example.com");
 
   scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction());
   scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES));
@@ -1768,7 +1753,10 @@
     return;
 
   std::string primary_account_id = SignIn("primary@example.com");
-  std::string secondary_account_id = AddAccount("secondary@example.com");
+  std::string secondary_account_id =
+      identity_test_env()
+          ->MakeAccountAvailable("secondary@example.com")
+          .account_id;
 
   scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction());
   scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES));
@@ -1798,7 +1786,7 @@
 IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
                        MultiUnknownUserGetTokenFromTokenServiceFailure) {
   SignIn("primary@example.com");
-  AddAccount("secondary@example.com");
+  identity_test_env()->MakeAccountAvailable("secondary@example.com");
 
   scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction());
   scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES));
@@ -1818,7 +1806,7 @@
     return;
 
   SignIn("primary@example.com");
-  AddAccount("secondary@example.com");
+  identity_test_env()->MakeAccountAvailable("secondary@example.com");
 
   scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction());
   func->set_extension(CreateExtension(CLIENT_ID | SCOPES));
@@ -1840,7 +1828,7 @@
     return;
 
   SignIn("primary@example.com");
-  AddAccount("secondary@example.com");
+  identity_test_env()->MakeAccountAvailable("secondary@example.com");
 
   scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction());
   func->set_extension(CreateExtension(CLIENT_ID | SCOPES));
@@ -1860,7 +1848,7 @@
     return;
 
   SignIn("primary@example.com");
-  AddAccount("secondary@example.com");
+  identity_test_env()->MakeAccountAvailable("secondary@example.com");
 
   scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction());
   func->set_extension(CreateExtension(CLIENT_ID | SCOPES));
@@ -2361,10 +2349,8 @@
   account_info.id = "gaia_id_for_primary_example.com";
   AddExpectedEvent(api::identity::OnSignInChanged::Create(account_info, true));
 
-  // Make the primary account's refresh token available and check that the
-  // callback fires. Note that we must call AddAccount() here as the account's
-  // information must be present in the AccountTrackerService as well.
-  AddAccount("primary@example.com");
+  // Make the primary account available again and check that the callback fires.
+  identity_test_env()->MakeAccountAvailable("primary@example.com");
   EXPECT_FALSE(HasExpectedEvent());
 }
 
@@ -2378,10 +2364,11 @@
   account_info.id = "gaia_id_for_secondary_example.com";
   AddExpectedEvent(api::identity::OnSignInChanged::Create(account_info, true));
 
-  // Make a secondary account's refresh token available and check that the
-  // callback fires. Note that we must call AddAccount() here as the account's
-  // information must be present in the AccountTrackerService as well.
-  std::string secondary_account_id = AddAccount("secondary@example.com");
+  // Make a secondary account available again and check that the callback fires.
+  std::string secondary_account_id =
+      identity_test_env()
+          ->MakeAccountAvailable("secondary@example.com")
+          .account_id;
   EXPECT_FALSE(HasExpectedEvent());
 
   // Revoke the secondary account's refresh token and check that the callback
diff --git a/chrome/browser/extensions/api/tab_capture/tab_capture_performancetest.cc b/chrome/browser/extensions/api/tab_capture/tab_capture_performancetest.cc
index 63f04e32..89cd9cf4 100644
--- a/chrome/browser/extensions/api/tab_capture/tab_capture_performancetest.cc
+++ b/chrome/browser/extensions/api/tab_capture/tab_capture_performancetest.cc
@@ -271,9 +271,9 @@
 
 // Note: First argument is optional and intentionally left blank.
 // (it's a prefix for the generated test cases)
-INSTANTIATE_TEST_CASE_P(,
-                        TabCapturePerformanceTest,
-                        testing::Values(0,
-                                        kUseGpu,
-                                        kTestThroughWebRTC,
-                                        kTestThroughWebRTC | kUseGpu));
+INSTANTIATE_TEST_SUITE_P(,
+                         TabCapturePerformanceTest,
+                         testing::Values(0,
+                                         kUseGpu,
+                                         kTestThroughWebRTC,
+                                         kTestThroughWebRTC | kUseGpu));
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index cfb4f59..03349c6 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -659,11 +659,6 @@
     "If enabled, the chrome://policy-tool URL loads a page for managing "
     "policies.";
 
-const char kDisableMultiMirroringName[] =
-    "Display mirroring across multiple displays.";
-const char kDisableMultiMirroringDescription[] =
-    "Disable Display mirroring across multiple displays.";
-
 const char kEnableNavigationTracingName[] = "Enable navigation tracing";
 const char kEnableNavigationTracingDescription[] =
     "This is to be used in conjunction with the trace-upload-url flag. "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index d03fab1..22703094 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -427,9 +427,6 @@
 extern const char kEnablePolicyToolName[];
 extern const char kEnablePolicyToolDescription[];
 
-extern const char kDisableMultiMirroringName[];
-extern const char kDisableMultiMirroringDescription[];
-
 extern const char kEnableNavigationTracingName[];
 extern const char kEnableNavigationTracingDescription[];
 
diff --git a/chrome/browser/media/cast_remoting_connector_unittest.cc b/chrome/browser/media/cast_remoting_connector_unittest.cc
index fe6d0d8..3008d9b 100644
--- a/chrome/browser/media/cast_remoting_connector_unittest.cc
+++ b/chrome/browser/media/cast_remoting_connector_unittest.cc
@@ -625,13 +625,13 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(,
-                        CastRemotingConnectorFullSessionTest,
-                        ::testing::Values(SOURCE_TERMINATES,
-                                          MOJO_PIPE_CLOSES,
-                                          ROUTE_TERMINATES,
-                                          EXTERNAL_FAILURE,
-                                          USER_DISABLED));
+INSTANTIATE_TEST_SUITE_P(,
+                         CastRemotingConnectorFullSessionTest,
+                         ::testing::Values(SOURCE_TERMINATES,
+                                           MOJO_PIPE_CLOSES,
+                                           ROUTE_TERMINATES,
+                                           EXTERNAL_FAILURE,
+                                           USER_DISABLED));
 
 // TODO(xjz): Remove the following tests after Mirroring Service is launched.
 TEST_F(CastRemotingConnectorTest,
@@ -936,10 +936,10 @@
   }
 }
 
-INSTANTIATE_TEST_CASE_P(,
-                        DeprecatedCastRemotingConnectorFullSessionTest,
-                        ::testing::Values(SOURCE_TERMINATES,
-                                          MOJO_PIPE_CLOSES,
-                                          ROUTE_TERMINATES,
-                                          EXTERNAL_FAILURE,
-                                          USER_DISABLED));
+INSTANTIATE_TEST_SUITE_P(,
+                         DeprecatedCastRemotingConnectorFullSessionTest,
+                         ::testing::Values(SOURCE_TERMINATES,
+                                           MOJO_PIPE_CLOSES,
+                                           ROUTE_TERMINATES,
+                                           EXTERNAL_FAILURE,
+                                           USER_DISABLED));
diff --git a/chrome/browser/media/webrtc/webrtc_text_log_handler.cc b/chrome/browser/media/webrtc/webrtc_text_log_handler.cc
index 361d8697..f6d5c88 100644
--- a/chrome/browser/media/webrtc/webrtc_text_log_handler.cc
+++ b/chrome/browser/media/webrtc/webrtc_text_log_handler.cc
@@ -27,6 +27,7 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/gpu_data_manager.h"
+#include "content/public/browser/network_service_instance.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/webrtc_log.h"
 #include "content/public/common/content_features.h"
@@ -36,6 +37,7 @@
 #include "net/base/ip_address.h"
 #include "net/base/network_change_notifier.h"
 #include "net/base/network_interfaces.h"
+#include "services/network/public/mojom/network_service.mojom.h"
 #include "services/service_manager/sandbox/features.h"
 
 #if defined(OS_LINUX)
@@ -100,13 +102,6 @@
 #endif
 }
 
-net::NetworkInterfaceList GetNetworkInterfaceList() {
-  net::NetworkInterfaceList network_list;
-  net::GetNetworkList(&network_list,
-                      net::EXCLUDE_HOST_SCOPE_VIRTUAL_INTERFACES);
-  return network_list;
-}
-
 }  // namespace
 
 WebRtcLogBuffer::WebRtcLogBuffer()
@@ -224,11 +219,10 @@
   if (!meta_data_)
     meta_data_.reset(new MetaDataMap());
 
-  base::PostTaskWithTraitsAndReplyWithResult(
-      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
-      base::BindOnce(&GetNetworkInterfaceList),
-      base::BindOnce(&WebRtcTextLogHandler::LogInitialInfoOnIOThread, this,
-                     callback));
+  base::PostTaskWithTraits(
+      FROM_HERE, {BrowserThread::UI},
+      base::BindOnce(&WebRtcTextLogHandler::GetNetworkInterfaceListOnUIThread,
+                     this, std::move(callback)));
   return true;
 }
 
@@ -420,6 +414,26 @@
   web_app_id_ = web_app_id;
 }
 
+void WebRtcTextLogHandler::GetNetworkInterfaceListOnUIThread(
+    const GenericDoneCallback& callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  content::GetNetworkService()->GetNetworkList(
+      net::EXCLUDE_HOST_SCOPE_VIRTUAL_INTERFACES,
+      base::BindOnce(&WebRtcTextLogHandler::OnGetNetworkInterfaceList, this,
+                     std::move(callback)));
+}
+
+void WebRtcTextLogHandler::OnGetNetworkInterfaceList(
+    const GenericDoneCallback& callback,
+    const base::Optional<net::NetworkInterfaceList>& networks) {
+  base::PostTaskWithTraits(
+      FROM_HERE, {BrowserThread::IO},
+      base::BindOnce(
+          &WebRtcTextLogHandler::LogInitialInfoOnIOThread, this,
+          std::move(callback),
+          networks.has_value() ? *networks : net::NetworkInterfaceList()));
+}
+
 void WebRtcTextLogHandler::LogInitialInfoOnIOThread(
     const GenericDoneCallback& callback,
     const net::NetworkInterfaceList& network_list) {
diff --git a/chrome/browser/media/webrtc/webrtc_text_log_handler.h b/chrome/browser/media/webrtc/webrtc_text_log_handler.h
index 3e03eee..23d66812 100644
--- a/chrome/browser/media/webrtc/webrtc_text_log_handler.h
+++ b/chrome/browser/media/webrtc/webrtc_text_log_handler.h
@@ -144,6 +144,12 @@
 
   void LogToCircularBuffer(const std::string& message);
 
+  void GetNetworkInterfaceListOnUIThread(const GenericDoneCallback& callback);
+
+  void OnGetNetworkInterfaceList(
+      const GenericDoneCallback& callback,
+      const base::Optional<net::NetworkInterfaceList>& networks);
+
   void LogInitialInfoOnIOThread(const GenericDoneCallback& callback,
                                 const net::NetworkInterfaceList& network_list);
 
diff --git a/chrome/browser/profiling_host/memlog_browsertest.cc b/chrome/browser/profiling_host/memlog_browsertest.cc
index 74da3ad..09f81a1 100644
--- a/chrome/browser/profiling_host/memlog_browsertest.cc
+++ b/chrome/browser/profiling_host/memlog_browsertest.cc
@@ -174,9 +174,9 @@
   return params;
 }
 
-INSTANTIATE_TEST_CASE_P(Memlog,
-                        MemlogBrowserTest,
-                        ::testing::ValuesIn(GetParams()));
+INSTANTIATE_TEST_SUITE_P(Memlog,
+                         MemlogBrowserTest,
+                         ::testing::ValuesIn(GetParams()));
 
 }  // namespace heap_profiling
 
diff --git a/chrome/browser/resources/app_management/BUILD.gn b/chrome/browser/resources/app_management/BUILD.gn
index 9be310c..99daf0e 100644
--- a/chrome/browser/resources/app_management/BUILD.gn
+++ b/chrome/browser/resources/app_management/BUILD.gn
@@ -117,6 +117,7 @@
       ":browser_proxy",
       ":constants",
       ":fake_page_handler",
+      ":store_client",
       ":types",
     ]
   }
@@ -125,12 +126,15 @@
     deps = [
       ":app_item",
       ":fake_page_handler",
+      ":store_client",
     ]
   }
 
   js_library("permission_item") {
     deps = [
       ":fake_page_handler",
+      ":store_client",
+      ":util",
     ]
   }
 
diff --git a/chrome/browser/resources/app_management/api_listener.html b/chrome/browser/resources/app_management/api_listener.html
index 0320371..751d6c4 100644
--- a/chrome/browser/resources/app_management/api_listener.html
+++ b/chrome/browser/resources/app_management/api_listener.html
@@ -2,4 +2,4 @@
 <link rel="import" href="actions.html">
 <link rel="import" href="store.html">
 <link rel="import" href="util.html">
-<script src="chrome://apps/api_listener.js"></script>
+<script src="api_listener.js"></script>
diff --git a/chrome/browser/resources/app_management/app.html b/chrome/browser/resources/app_management/app.html
index 4a71ef5..6078b89 100644
--- a/chrome/browser/resources/app_management/app.html
+++ b/chrome/browser/resources/app_management/app.html
@@ -11,7 +11,7 @@
 <link rel="import" href="store_client.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_toolbar/cr_toolbar.html">
 
-<link rel="import" href="chrome://apps/api_listener.html">
+<link rel="import" href="api_listener.html">
 
 <dom-module id="app-management-app">
   <template>
diff --git a/chrome/browser/resources/app_management/chrome_app_permission_view.html b/chrome/browser/resources/app_management/chrome_app_permission_view.html
index bd9ab947..b79cf5b 100644
--- a/chrome/browser/resources/app_management/chrome_app_permission_view.html
+++ b/chrome/browser/resources/app_management/chrome_app_permission_view.html
@@ -30,7 +30,7 @@
         padding-inline-start: 24px;
       }
     </style>
-    <app-management-permission-view-header app="[[app_]]">
+    <app-management-permission-view-header>
     </app-management-permission-view-header>
     <div class="card-container">
       <div id="app-info">
diff --git a/chrome/browser/resources/app_management/chrome_app_permission_view.js b/chrome/browser/resources/app_management/chrome_app_permission_view.js
index 01697a6d..e6eff99 100644
--- a/chrome/browser/resources/app_management/chrome_app_permission_view.js
+++ b/chrome/browser/resources/app_management/chrome_app_permission_view.js
@@ -24,26 +24,11 @@
   },
 
   attached: function() {
-    this.watch('app_', (state) => {
-      const selectedAppId = state.currentPage.selectedAppId;
-      if (selectedAppId) {
-        return state.apps[selectedAppId];
-      }
-    });
-
+    this.watch('app_', state => app_management.util.getSelectedApp(state));
     this.updateFromStore();
   },
 
   /**
-   * @param {App} app
-   * @return {string}
-   * @private
-   */
-  iconUrlFromId_: function(app) {
-    return app_management.util.getAppIcon(app);
-  },
-
-  /**
    * @private
    */
   onAppChanged_: async function() {
diff --git a/chrome/browser/resources/app_management/fake_page_handler.js b/chrome/browser/resources/app_management/fake_page_handler.js
index b21bd5e..2d62f96 100644
--- a/chrome/browser/resources/app_management/fake_page_handler.js
+++ b/chrome/browser/resources/app_management/fake_page_handler.js
@@ -9,15 +9,15 @@
   class FakePageHandler {
     /**
      * @param {number} permissionId
-     * @param {Object=} config
+     * @param {Object=} optConfig
      * @return {!Permission}
      */
-    static createPermission(permissionId, config) {
+    static createPermission(permissionId, optConfig) {
       const permission = app_management.util.createPermission(
           permissionId, PermissionValueType.kTriState, TriState.kBlock);
 
-      if (config) {
-        Object.assign(permission, config);
+      if (optConfig) {
+        Object.assign(permission, optConfig);
       }
 
       return permission;
@@ -25,10 +25,10 @@
 
     /**
      * @param {string} id
-     * @param {Object=} config
+     * @param {Object=} optConfig
      * @return {!App}
      */
-    static createApp(id, config) {
+    static createApp(id, optConfig) {
       const permissionIds = [
         PwaPermissionType.CONTENT_SETTINGS_TYPE_GEOLOCATION,
         PwaPermissionType.CONTENT_SETTINGS_TYPE_NOTIFICATIONS,
@@ -46,24 +46,25 @@
         id: id,
         type: apps.mojom.AppType.kWeb,
         title: 'App Title',
+        description: '',
         version: '5.1',
         size: '9.0MB',
-        isPinned: apps.mojom.OptionalBool.kUnknown,
+        isPinned: apps.mojom.OptionalBool.kFalse,
         permissions: permissions,
       };
 
-      if (config) {
-        Object.assign(app, config);
+      if (optConfig) {
+        Object.assign(app, optConfig);
       }
 
       return app;
     }
 
     /**
-     * @param {appManagement.mojom.PageInterface} page
+     * @param {appManagement.mojom.PageProxy} page
      */
     constructor(page) {
-      /** @type {appManagement.mojom.PageInterface} */
+      /** @type {appManagement.mojom.PageProxy} */
       this.page = page;
 
       /** @type {!Array<App>} */
@@ -128,6 +129,31 @@
      * @param {string} appId
      */
     openNativeSettings(appId) {}
+
+    /**
+     * @param {string} id
+     * @param {Object=} optConfig
+     */
+    async addApp(id, optConfig) {
+      this.page.onAppAdded(FakePageHandler.createApp(id, optConfig));
+      await this.flushForTesting();
+    }
+
+    /**
+     * Takes an app id and an object mapping app fields to the values they
+     * should be changed to, and dispatches an action to carry out these
+     * changes.
+     * @param {string} id
+     * @param {Object} changes
+     */
+    async changeApp(id, changes) {
+      this.page.onAppChanged(FakePageHandler.createApp(id, changes));
+      await this.flushForTesting();
+    }
+
+    async flushForTesting() {
+      await this.page.flushForTesting();
+    }
   }
 
   return {FakePageHandler: FakePageHandler};
diff --git a/chrome/browser/resources/app_management/metadata_view.html b/chrome/browser/resources/app_management/metadata_view.html
index 8ada6e7d..9ca5003 100644
--- a/chrome/browser/resources/app_management/metadata_view.html
+++ b/chrome/browser/resources/app_management/metadata_view.html
@@ -36,19 +36,20 @@
       justify-content: space-around;
     }
     </style>
-    <template is="dom-if" if="[[pinToShelfToggleVisible_(app)]]">
+    <template is="dom-if" if="[[pinToShelfToggleVisible_(app_)]]">
       <div id="shelf-switch-row">
         <span id="shelf-switch" class="header-text">
           $i18n{pinToShelf}
-          <cr-toggle checked="[[isPinned_(app)]]" on-change="togglePinned_">
+          <cr-toggle id="pin-to-shelf-toggle" checked="[[isPinned_(app_)]]"
+              on-change="togglePinned_">
           </cr-toggle>
         </span>
       </div>
     </template>
 
     <div id="metadata-overview" class="secondary-text">
-      <span>[[versionString_(app)]]</span>
-      <span>[[sizeString_(app)]]</span>
+      <span>[[versionString_(app_)]]</span>
+      <span>[[sizeString_(app_)]]</span>
       <!--TODO(ceciliani): Placeholder for legal declaration-->
     </div>
   </template>
diff --git a/chrome/browser/resources/app_management/metadata_view.js b/chrome/browser/resources/app_management/metadata_view.js
index 891c13d..8914ee5 100644
--- a/chrome/browser/resources/app_management/metadata_view.js
+++ b/chrome/browser/resources/app_management/metadata_view.js
@@ -5,20 +5,29 @@
 Polymer({
   is: 'app-management-metadata-view',
 
+  behaviors: [
+    app_management.StoreClient,
+  ],
+
   properties: {
     /** @type {App} */
-    app: {
+    app_: {
       type: Object,
     },
   },
 
+  attached: function() {
+    this.watch('app_', state => app_management.util.getSelectedApp(state));
+    this.updateFromStore();
+  },
+
   /**
    * @param {App} app
    * @return bool
    * @private
    */
   pinToShelfToggleVisible_: function(app) {
-    return !(app.isPinned === OptionalBool.kUnknown);
+    return app.isPinned !== OptionalBool.kUnknown;
   },
 
   /**
@@ -33,11 +42,9 @@
   },
 
   togglePinned_: function() {
-    assert(this.app);
-
     let newPinnedValue;
 
-    switch (this.app.isPinned) {
+    switch (this.app_.isPinned) {
       case OptionalBool.kFalse:
         newPinnedValue = OptionalBool.kTrue;
         break;
@@ -49,12 +56,12 @@
     }
 
     app_management.BrowserProxy.getInstance().handler.setPinned(
-        this.app.id, newPinnedValue);
+        this.app_.id, newPinnedValue);
   },
 
   /**
    * @param {App} app
-   * @return {string?}
+   * @return {?string}
    * @private
    */
   versionString_: function(app) {
@@ -67,7 +74,7 @@
 
   /**
    * @param {App} app
-   * @return {string?}
+   * @return {?string}
    * @private
    */
   sizeString_: function(app) {
diff --git a/chrome/browser/resources/app_management/permission_item.js b/chrome/browser/resources/app_management/permission_item.js
index aa31805..1a885f5 100644
--- a/chrome/browser/resources/app_management/permission_item.js
+++ b/chrome/browser/resources/app_management/permission_item.js
@@ -4,6 +4,10 @@
 Polymer({
   is: 'app-management-permission-item',
 
+  behaviors: [
+    app_management.StoreClient,
+  ],
+
   properties: {
     /**
      * The name of the permission, to be displayed to the user.
@@ -21,14 +25,14 @@
     /**
      * @type {App}
      */
-    app: Object,
+    app_: Object,
 
     /**
      * @private {PermissionValueType}
      */
     permissionValueType_: {
       type: Number,
-      computed: 'getPermissionValueType_(app)',
+      computed: 'getPermissionValueType_(app_)',
     },
 
     /**
@@ -38,13 +42,18 @@
      */
     permissionValue_: {
       type: Number,
-      computed: 'getPermissionValue_(app, permissionType)',
+      computed: 'getPermissionValue_(app_, permissionType)',
     },
 
     /** @type {string} */
     icon: String,
   },
 
+  attached: function() {
+    this.watch('app_', state => app_management.util.getSelectedApp(state));
+    this.updateFromStore();
+  },
+
   /**
    * @param {App} app
    * @return {number}
@@ -112,7 +121,7 @@
     }
 
     app_management.BrowserProxy.getInstance().handler.setPermission(
-        this.app.id, newPermission);
+        this.app_.id, newPermission);
   },
 
   /**
@@ -154,7 +163,11 @@
         newPermissionValue = TriState.kAllow;
         break;
       case TriState.kAllow:
-        newPermissionValue = TriState.kAsk;
+        // TODO(rekanorman): Eventually TriState.kAsk, but currently changing a
+        // permission to kAsk then opening the site settings page for the app
+        // produces the error:
+        // "Only extensions or enterprise policy can change the setting to ASK."
+        newPermissionValue = TriState.kBlock;
         break;
       default:
         assertNotReached();
diff --git a/chrome/browser/resources/app_management/permission_view_header.html b/chrome/browser/resources/app_management/permission_view_header.html
index d541c4f1..7a35bad 100644
--- a/chrome/browser/resources/app_management/permission_view_header.html
+++ b/chrome/browser/resources/app_management/permission_view_header.html
@@ -43,8 +43,8 @@
         <paper-ripple class="circle"></paper-ripple>
       </button>
     </paper-icon-button-light>
-    <img id="permission-view-header-icon" src="[[iconUrlFromId_(app)]]">
-    <div class="page-title">[[app.title]]</div>
+    <img id="permission-view-header-icon" src="[[iconUrlFromId_(app_)]]">
+    <div class="page-title">[[app_.title]]</div>
     <slot name="extra-right-buttons"></slot>
     <paper-button id="uninstall-button" on-click="onClickUninstallButton_">
       $i18n{uninstall}
diff --git a/chrome/browser/resources/app_management/permission_view_header.js b/chrome/browser/resources/app_management/permission_view_header.js
index aa20efc..248478a 100644
--- a/chrome/browser/resources/app_management/permission_view_header.js
+++ b/chrome/browser/resources/app_management/permission_view_header.js
@@ -10,19 +10,13 @@
 
   properties: {
     /** @type {App} */
-    app: {
+    app_: {
       type: Object,
     },
   },
 
   attached: function() {
-    this.watch('app', (state) => {
-      const selectedAppId = state.currentPage.selectedAppId;
-      if (selectedAppId) {
-        return state.apps[selectedAppId];
-      }
-    });
-
+    this.watch('app_', state => app_management.util.getSelectedApp(state));
     this.updateFromStore();
   },
 
@@ -50,6 +44,6 @@
    * @private
    */
   onClickUninstallButton_: function() {
-    app_management.BrowserProxy.getInstance().handler.uninstall(this.app.id);
+    app_management.BrowserProxy.getInstance().handler.uninstall(this.app_.id);
   },
 });
diff --git a/chrome/browser/resources/app_management/pwa_permission_view.html b/chrome/browser/resources/app_management/pwa_permission_view.html
index 459cb63..95139ce 100644
--- a/chrome/browser/resources/app_management/pwa_permission_view.html
+++ b/chrome/browser/resources/app_management/pwa_permission_view.html
@@ -59,7 +59,7 @@
       margin-inline-end: 16px;
     }
     </style>
-    <app-management-permission-view-header app="[[app_]]">
+    <app-management-permission-view-header>
       <div slot="extra-right-buttons" id="extra-button">
         <paper-button id="site-settings-button"
             class="secondary-text"
@@ -76,7 +76,6 @@
     <div id="permission-list" class="card-container">
       <app-management-permission-item
         class="permission-card-row separated-row header-text"
-        app="[[app_]]"
         permission-label="$i18n{notifications}"
         permission-type="CONTENT_SETTINGS_TYPE_NOTIFICATIONS">
       </app-management-permission-item>
@@ -100,21 +99,18 @@
           <iron-collapse opened="[[listExpanded_]]">
             <app-management-permission-item
                 class="subpermission-row"
-                app="[[app_]]"
                 icon="cr:location-on"
                 permission-label="$i18n{location}"
                 permission-type="CONTENT_SETTINGS_TYPE_GEOLOCATION">
             </app-management-permission-item>
             <app-management-permission-item
                 class="subpermission-row"
-                app="[[app_]]"
                 icon="cr:videocam"
                 permission-label="$i18n{camera}"
                 permission-type="CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA">
             </app-management-permission-item>
             <app-management-permission-item
                 class="subpermission-row"
-                app="[[app_]]"
                 icon="cr:mic"
                 permission-label="$i18n{microphone}"
                 permission-type="CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC">
diff --git a/chrome/browser/resources/app_management/pwa_permission_view.js b/chrome/browser/resources/app_management/pwa_permission_view.js
index 425f9176..ab676853 100644
--- a/chrome/browser/resources/app_management/pwa_permission_view.js
+++ b/chrome/browser/resources/app_management/pwa_permission_view.js
@@ -24,13 +24,7 @@
   },
 
   attached: function() {
-    this.watch('app_', (state) => {
-      const selectedAppId = state.currentPage.selectedAppId;
-      if (selectedAppId) {
-        return state.apps[selectedAppId];
-      }
-    });
-
+    this.watch('app_', state => app_management.util.getSelectedApp(state));
     this.updateFromStore();
   },
 
diff --git a/chrome/browser/resources/app_management/reducers.js b/chrome/browser/resources/app_management/reducers.js
index 15c5b54..2263e85 100644
--- a/chrome/browser/resources/app_management/reducers.js
+++ b/chrome/browser/resources/app_management/reducers.js
@@ -18,6 +18,8 @@
    * @return {AppMap}
    */
   AppState.addApp = function(apps, action) {
+    assert(!apps[action.app.id]);
+
     const newAppEntry = {};
     newAppEntry[action.app.id] = action.app;
     return Object.assign({}, apps, newAppEntry);
diff --git a/chrome/browser/resources/app_management/router.html b/chrome/browser/resources/app_management/router.html
index d7c67f0..0e404f2 100644
--- a/chrome/browser/resources/app_management/router.html
+++ b/chrome/browser/resources/app_management/router.html
@@ -13,5 +13,5 @@
     <iron-query-params params-string="{{query_}}"
         params-object="{{queryParams_}}"></iron-query-params>
   </template>
-  <script src="chrome://apps/router.js"></script>
+  <script src="router.js"></script>
 </dom-module>
diff --git a/chrome/browser/resources/app_management/util.js b/chrome/browser/resources/app_management/util.js
index 445da94..6e381888 100644
--- a/chrome/browser/resources/app_management/util.js
+++ b/chrome/browser/resources/app_management/util.js
@@ -56,10 +56,24 @@
     return `chrome://extension-icon/${app.id}/128/1`;
   }
 
+  /**
+   * If there is no selected app, returns undefined so that Polymer bindings
+   * won't be updated.
+   * @param {AppManagementPageState} state
+   * @return {App|undefined}
+   */
+  function getSelectedApp(state) {
+    const selectedAppId = state.currentPage.selectedAppId;
+    if (selectedAppId) {
+      return state.apps[selectedAppId];
+    }
+  }
+
   return {
     createEmptyState: createEmptyState,
     createInitialState: createInitialState,
     createPermission: createPermission,
     getAppIcon: getAppIcon,
+    getSelectedApp: getSelectedApp,
   };
 });
diff --git a/chrome/browser/resources/chromeos/chromevox/braille/bluetooth_braille_display_manager.js b/chrome/browser/resources/chromeos/chromevox/braille/bluetooth_braille_display_manager.js
index 953913e..3cab81d 100644
--- a/chrome/browser/resources/chromeos/chromevox/braille/bluetooth_braille_display_manager.js
+++ b/chrome/browser/resources/chromeos/chromevox/braille/bluetooth_braille_display_manager.js
@@ -176,12 +176,7 @@
    */
   finishPairing: function(display, pincode) {
     chrome.bluetoothPrivate.setPairingResponse(
-        {
-          response: chrome.bluetoothPrivate.PairingResponse.CONFIRM,
-          device: display,
-          pincode: pincode
-        },
-        () => {});
+        {response: 'confirm', device: display, pincode: pincode}, () => {});
   },
 
   /**
diff --git a/chrome/browser/resources/chromeos/chromevox/tools/check_chromevox.py b/chrome/browser/resources/chromeos/chromevox/tools/check_chromevox.py
index c7d2d80..0dab9310 100755
--- a/chrome/browser/resources/chromeos/chromevox/tools/check_chromevox.py
+++ b/chrome/browser/resources/chromeos/chromevox/tools/check_chromevox.py
@@ -54,10 +54,6 @@
 _AUTOMATION_EXTERNS = (
     ChromeRootPath('third_party/closure_compiler/externs/automation.js'))
 
-# BluetoothPrivate externs file.
-_BLUETOOTH_PRIVATE_EXTERNS = (
-    ChromeRootPath('third_party/closure_compiler/externs/bluetooth_private.js'))
-
 # MetricsPrivate externs file.
 _METRICS_PRIVATE_EXTERNS = (
     ChromeRootPath('third_party/closure_compiler/externs/metrics_private.js'))
@@ -89,7 +85,6 @@
     _ACCESSIBILITY_PRIVATE_EXTERNS,
     _AUDIO_EXTERNS,
     _AUTOMATION_EXTERNS,
-    _BLUETOOTH_PRIVATE_EXTERNS,
     _CHROME_EXTERNS,
     _CHROME_EXTENSIONS_EXTERNS,
     _COMMANDLINE_PRIVATE_EXTERNS,
diff --git a/chrome/browser/resources/chromeos/select_to_speak/BUILD.gn b/chrome/browser/resources/chromeos/select_to_speak/BUILD.gn
index 6370746..0abf4a5 100644
--- a/chrome/browser/resources/chromeos/select_to_speak/BUILD.gn
+++ b/chrome/browser/resources/chromeos/select_to_speak/BUILD.gn
@@ -3,10 +3,10 @@
 # found in the LICENSE file.
 
 import("//build/config/features.gni")
-import("//chrome/common/features.gni")
-import("//testing/test.gni")
-import("//chrome/test/base/js2gtest.gni")
 import("//chrome/browser/resources/chromeos/chromevox/run_jsbundler.gni")
+import("//chrome/common/features.gni")
+import("//chrome/test/base/js2gtest.gni")
+import("//testing/test.gni")
 import("//third_party/closure_compiler/compile_js.gni")
 
 assert(is_chromeos)
@@ -194,6 +194,7 @@
     "$externs_path/chrome_extensions.js",
     "$externs_path/clipboard.js",
     "$externs_path/command_line_private.js",
+    "$externs_path/pending.js",
   ]
 }
 
diff --git a/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js b/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
index 38ad21d..1ad5afd 100644
--- a/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
+++ b/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
@@ -75,7 +75,11 @@
   /** @private {number} */
   this.currentNodeGroupIndex_ = -1;
 
-  /** @private {?Object} */
+  /**
+   * The indexes within the current node representing the word currently being
+   * spoken. Only updated if word highlighting is enabled.
+   * @private {?Object}
+   */
   this.currentNodeWord_ = null;
 
   /** @private {?AutomationNode} */
@@ -621,42 +625,8 @@
           if (isLast)
             this.onStateChanged_(SelectToSpeakState.INACTIVE);
         } else if (event.type == 'word') {
-          console.debug(nodeGroup.text + ' (index ' + event.charIndex + ')');
-          console.debug('-'.repeat(event.charIndex) + '^');
-          if (this.currentNodeGroupIndex_ + 1 < nodeGroup.nodes.length) {
-            let next = nodeGroup.nodes[this.currentNodeGroupIndex_ + 1];
-            let nodeUpdated = false;
-            // Check if we've reached this next node yet using the
-            // character index of the event. Add 1 for the space character
-            // between node names, and another to make it to the start of the
-            // next node name.
-            while (event.charIndex + 2 >= next.startChar &&
-                   this.currentNodeGroupIndex_ + 1 < nodeGroup.nodes.length) {
-              // Move to the next node.
-              this.currentNodeGroupIndex_ += 1;
-              this.currentNode_ = next;
-              this.currentNodeWord_ = null;
-              nodeUpdated = true;
-              if (this.currentNodeGroupIndex_ + 1 >= nodeGroup.nodes.length)
-                break;
-              next = nodeGroup.nodes[this.currentNodeGroupIndex_ + 1];
-            }
-            if (nodeUpdated) {
-              if (!this.prefsManager_.wordHighlightingEnabled()) {
-                // If we are doing a per-word highlight, we will test the
-                // node after figuring out what the currently highlighted
-                // word is.
-                this.testCurrentNode_();
-              }
-            }
-          }
-          if (this.prefsManager_.wordHighlightingEnabled()) {
-            this.updateNodeHighlight_(
-                nodeGroup.text, event.charIndex, undefined,
-                isLast ? opt_endIndex : undefined);
-          } else {
-            this.currentNodeWord_ = null;
-          }
+          this.onTtsWordEvent_(
+              event, nodeGroup, isLast ? opt_endIndex : undefined);
         }
       };
       chrome.tts.speak(nodeGroup.text || '', options);
@@ -678,6 +648,109 @@
   },
 
   /**
+   * Uses the 'word' speech event to determine which node is currently beings
+   * spoken, and prepares for highlight if enabled.
+   * @param {!TtsEvent} event The event to use for updates.
+   * @param {ParagraphUtils.NodeGroup} nodeGroup The node group for this
+   *     utterance.
+   * @param {number=} opt_endIndex The last index for speech, if applicable.
+   * @private
+   */
+  onTtsWordEvent_: function(event, nodeGroup, opt_endIndex) {
+    // Not all speech engines include length in the ttsEvent object. If the
+    // engine does have it, it makes word highlighting easier and more
+    // accurate.
+    let hasLength = event.length !== undefined && event.length >= 0;
+    console.debug(nodeGroup.text + ' (index ' + event.charIndex + ')');
+    let debug = '-'.repeat(event.charIndex);
+    if (hasLength)
+      debug += '^'.repeat(event.length);
+    else
+      debug += '^';
+    console.debug(debug);
+
+    // First determine which node contains the word currently being spoken,
+    // and update this.currentNode_, this.currentNodeWord_, and
+    // this.currentNodeGroupIndex_ to match.
+    if (this.currentNodeGroupIndex_ + 1 < nodeGroup.nodes.length) {
+      let next = nodeGroup.nodes[this.currentNodeGroupIndex_ + 1];
+      let nodeUpdated = false;
+      // TODO(katie): For something like a date, the start and end
+      // node group nodes can actually be different. Example:
+      // "<span>Tuesday,</span> December 18, 2018".
+      if (hasLength) {
+        while (next && event.charIndex >= next.startChar &&
+               this.currentNodeGroupIndex_ + 1 < nodeGroup.nodes.length) {
+          next = this.incrementCurrentNodeAndGetNext_(nodeGroup);
+          nodeUpdated = true;
+        }
+
+        // Check if we've reached this next node yet using the
+        // character index of the event. Add 1 for the space character
+        // between node names, and another to make it to the start of the
+        // next node name.
+        // TODO: Do not use next.name.length instead use the next-next startChar
+        while (next &&
+               event.charIndex + event.length + 2 >=
+                   next.startChar + next.node.name.length &&
+               this.currentNodeGroupIndex_ + 1 < nodeGroup.nodes.length) {
+          next = this.incrementCurrentNodeAndGetNext_(nodeGroup);
+          nodeUpdated = true;
+        }
+      } else {
+        while (next && event.charIndex + 2 >= next.startChar &&
+               this.currentNodeGroupIndex_ + 1 < nodeGroup.nodes.length) {
+          next = this.incrementCurrentNodeAndGetNext_(nodeGroup);
+          nodeUpdated = true;
+        }
+      }
+      if (nodeUpdated) {
+        if (!this.prefsManager_.wordHighlightingEnabled()) {
+          // If we are doing a per-word highlight, we will test the
+          // node after figuring out what the currently highlighted
+          // word is. Otherwise, test it now.
+          this.testCurrentNode_();
+        }
+      }
+    }
+
+    // Finally update the word highlight if it is enabled.
+    if (this.prefsManager_.wordHighlightingEnabled()) {
+      if (hasLength) {
+        this.currentNodeWord_ = {
+          'start': event.charIndex - this.currentNode_.startChar,
+          'end': event.charIndex + event.length - this.currentNode_.startChar
+        };
+        this.testCurrentNode_();
+      } else {
+        this.updateNodeHighlight_(
+            nodeGroup.text, event.charIndex, undefined, opt_endIndex);
+      }
+    } else {
+      this.currentNodeWord_ = null;
+    }
+  },
+
+  /**
+   * Updates the current node and relevant points to be the next node in the
+   * group, then returns the next node in the group after that.
+   * @param {!ParagraphUtils.NodeGroup} nodeGroup
+   * @return {ParagraphUtils.NodeGroupItem}
+   * @private
+   */
+  incrementCurrentNodeAndGetNext_: function(nodeGroup) {
+    // Move to the next node.
+    this.currentNodeGroupIndex_ += 1;
+    this.currentNode_ = nodeGroup.nodes[this.currentNodeGroupIndex_];
+    // Setting this.currentNodeWord_ to null signals it should be recalculated
+    // later.
+    this.currentNodeWord_ = null;
+    if (this.currentNodeGroupIndex_ + 1 >= nodeGroup.nodes.length)
+      return null;
+    return nodeGroup.nodes[this.currentNodeGroupIndex_ + 1];
+  },
+
+  /**
    * Updates the state.
    * @param {!chrome.accessibilityPrivate.SelectToSpeakState} state
    * @private
diff --git a/chrome/browser/resources/component_extension_resources.grd b/chrome/browser/resources/component_extension_resources.grd
index 67606ba..708038d 100644
--- a/chrome/browser/resources/component_extension_resources.grd
+++ b/chrome/browser/resources/component_extension_resources.grd
@@ -102,6 +102,7 @@
         <include name="IDR_PDF_TOOLBAR_MANAGER_JS" file="pdf/toolbar_manager.js" type="BINDATA" />
         <include name="IDR_PDF_PDF_FITTING_TYPE_JS" file="pdf/pdf_fitting_type.js" type="BINDATA" />
         <include name="IDR_PDF_VIEWPORT_JS" file="pdf/viewport.js" type="BINDATA" />
+        <include name="IDR_PDF_VIEWPORT_INTERFACE_JS" file="pdf/viewport_interface.js" type="BINDATA" />
         <include name="IDR_PDF_OPEN_PDF_PARAMS_PARSER_JS" file="pdf/open_pdf_params_parser.js" type="BINDATA" />
         <include name="IDR_PDF_NAVIGATOR_JS" file="pdf/navigator.js" type="BINDATA" />
         <include name="IDR_PDF_VIEWPORT_SCROLLER_JS" file="pdf/viewport_scroller.js" type="BINDATA" />
diff --git a/chrome/browser/resources/downloads/item.js b/chrome/browser/resources/downloads/item.js
index 0775982..3c87033 100644
--- a/chrome/browser/resources/downloads/item.js
+++ b/chrome/browser/resources/downloads/item.js
@@ -10,6 +10,9 @@
       cr.ui.FocusRowBehavior,
     ],
 
+    /** Used by FocusRowBehavior. */
+    overrideCustomEquivalent: true,
+
     properties: {
       /** @type {!downloads.Data} */
       data: Object,
@@ -104,6 +107,17 @@
       this.content = this.$.content;
     },
 
+    /** Overrides FocusRowBehavior. */
+    getCustomEquivalent: function(sampleElement) {
+      if (sampleElement.getAttribute('focus-type') == 'cancel') {
+        return this.$$('[focus-type="retry"]');
+      }
+      if (sampleElement.getAttribute('focus-type') == 'retry') {
+        return this.$$('[focus-type="pauseOrResume"]');
+      }
+      return null;
+    },
+
     /** @return {!HTMLElement} */
     getFileIcon: function() {
       return assert(this.$['file-icon']);
diff --git a/chrome/browser/resources/downloads/toolbar.js b/chrome/browser/resources/downloads/toolbar.js
index 829d48f..db9f9b8 100644
--- a/chrome/browser/resources/downloads/toolbar.js
+++ b/chrome/browser/resources/downloads/toolbar.js
@@ -69,12 +69,12 @@
     },
 
     /**
-     * @param {!CustomEvent} event
+     * @param {!CustomEvent<string>} event
      * @private
      */
     onSearchChanged_: function(event) {
       const searchService = downloads.SearchService.getInstance();
-      if (searchService.search(/** @type {string} */ (event.detail))) {
+      if (searchService.search(event.detail)) {
         this.spinnerActive = searchService.isSearching();
       }
       this.updateClearAll_();
diff --git a/chrome/browser/resources/history/history_toolbar.js b/chrome/browser/resources/history/history_toolbar.js
index 23eae2f0..5ead9094 100644
--- a/chrome/browser/resources/history/history_toolbar.js
+++ b/chrome/browser/resources/history/history_toolbar.js
@@ -96,7 +96,7 @@
   },
 
   /**
-   * @param {!CustomEvent} event
+   * @param {!CustomEvent<string>} event
    * @private
    */
   onSearchChanged_: function(event) {
diff --git a/chrome/browser/resources/history/side_bar.js b/chrome/browser/resources/history/side_bar.js
index 647d931b..f6a5723c 100644
--- a/chrome/browser/resources/history/side_bar.js
+++ b/chrome/browser/resources/history/side_bar.js
@@ -38,7 +38,7 @@
   },
 
   /**
-   * @param {CustomEvent} e
+   * @param {!CustomEvent<{keyboardEvent: !KeyboardEvent}>} e
    * @private
    */
   onSpacePressed_: function(e) {
diff --git a/chrome/browser/resources/history/synced_device_card.js b/chrome/browser/resources/history/synced_device_card.js
index 5f95836..a6ce0c7 100644
--- a/chrome/browser/resources/history/synced_device_card.js
+++ b/chrome/browser/resources/history/synced_device_card.js
@@ -145,7 +145,7 @@
   },
 
   /**
-   * @param {CustomEvent} e
+   * @param {!Event} e
    * @private
    */
   onMenuButtonTap_: function(e) {
diff --git a/chrome/browser/resources/local_ntp/local_ntp.js b/chrome/browser/resources/local_ntp/local_ntp.js
index e0ac8f4..d585c33 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.js
+++ b/chrome/browser/resources/local_ntp/local_ntp.js
@@ -256,6 +256,13 @@
 
 
 /**
+ * True if dark mode is enabled.
+ * @type {boolean}
+ */
+let isDarkModeEnabled = false;
+
+
+/**
  * Returns a timeout that can be executed early.
  * @param {!Function} timeout The timeout function.
  * @param {number} delay The timeout delay.
@@ -317,7 +324,7 @@
   // custom background set).
   if (!info || info.usingDefaultTheme && !info.customBackgroundConfigured) {
     // Dark mode is always considered a dark theme.
-    return configData.isDarkModeEnabled;
+    return isDarkModeEnabled;
   }
 
   // Heuristic: light text implies dark theme.
@@ -338,10 +345,15 @@
     return;
   }
 
+  const useDarkMode = !!info.usingDarkMode;
+  if (isDarkModeEnabled != useDarkMode) {
+    document.documentElement.setAttribute('darkmode', useDarkMode);
+    isDarkModeEnabled = useDarkMode;
+  }
+
   var background = [
-    (configData.isDarkModeEnabled ?
-         DARK_MODE_BACKGROUND_COLOR :
-         convertToRGBAColor(info.backgroundColorRgba)),
+    (isDarkModeEnabled ? DARK_MODE_BACKGROUND_COLOR :
+                         convertToRGBAColor(info.backgroundColorRgba)),
     info.imageUrl, info.imageTiling, info.imageHorizontalAlignment,
     info.imageVerticalAlignment
   ].join(' ').trim();
@@ -353,8 +365,8 @@
   }
 
   // Dark mode uses a white Google logo.
-  const useWhiteLogo = info.alternateLogo ||
-      (info.usingDefaultTheme && configData.isDarkModeEnabled);
+  const useWhiteLogo =
+      info.alternateLogo || (info.usingDefaultTheme && isDarkModeEnabled);
   document.body.classList.toggle(CLASSES.ALTERNATE_LOGO, useWhiteLogo);
   const isNonWhiteBackground = !WHITE_BACKGROUND_COLORS.includes(background);
   document.body.classList.toggle(CLASSES.NON_WHITE_BG, isNonWhiteBackground);
@@ -424,6 +436,7 @@
   var message = {cmd: 'updateTheme'};
   message.isThemeDark = isThemeDark;
   message.isUsingTheme = !info.usingDefaultTheme;
+  message.isDarkMode = !!info.usingDarkMode;
 
   var titleColor = NTP_DESIGN.titleColor;
   if (!info.usingDefaultTheme && info.textColorRgba) {
@@ -465,9 +478,18 @@
  * @private
  */
 function onThemeChange() {
+  // Save the current dark mode state to check if dark mode has changed.
+  const usingDarkMode = isDarkModeEnabled;
+
   renderTheme();
   renderOneGoogleBarTheme();
   sendThemeInfoToMostVisitedIframe();
+
+  // If dark mode has been changed, refresh the MV tiles to render the
+  // appropriate icon.
+  if (usingDarkMode != isDarkModeEnabled) {
+    reloadTiles();
+  }
 }
 
 
@@ -577,7 +599,7 @@
   let maxNumTiles = configData.isGooglePage ? MAX_NUM_TILES_CUSTOM_LINKS :
                                               MAX_NUM_TILES_MOST_VISITED;
   for (var i = 0; i < Math.min(maxNumTiles, pages.length); ++i) {
-    cmds.push({cmd: 'tile', rid: pages[i].rid});
+    cmds.push({cmd: 'tile', rid: pages[i].rid, darkMode: isDarkModeEnabled});
   }
   cmds.push({cmd: 'show'});
 
@@ -1020,9 +1042,6 @@
   ntpApiHandle.onthemechange = onThemeChange;
   ntpApiHandle.onmostvisitedchange = onMostVisitedChange;
 
-  if (configData.isDarkModeEnabled) {
-    document.documentElement.setAttribute('darkmode', true);
-  }
   renderTheme();
 
   var searchboxApiHandle = embeddedSearchApiHandle.searchBox;
diff --git a/chrome/browser/resources/local_ntp/most_visited_single.js b/chrome/browser/resources/local_ntp/most_visited_single.js
index 9271b92..d4752fc 100644
--- a/chrome/browser/resources/local_ntp/most_visited_single.js
+++ b/chrome/browser/resources/local_ntp/most_visited_single.js
@@ -343,6 +343,7 @@
   document.body.style.setProperty('--tile-title-color', info.tileTitleColor);
   document.body.classList.toggle('dark-theme', info.isThemeDark);
   document.body.classList.toggle('using-theme', info.isUsingTheme);
+  document.documentElement.setAttribute('darkmode', info.isDarkMode);
 
   // Reduce font weight on the default(white) background for Mac and CrOS.
   document.body.classList.toggle('mac-chromeos',
@@ -482,6 +483,9 @@
     }
 
     data.tid = data.rid;
+    // Use a dark icon if dark mode is enabled. Keep value in sync with
+    // NtpIconSource.
+    data.dark = args.darkMode ? 'dark/' : '';
     if (!data.faviconUrl) {
       data.faviconUrl = 'chrome-search://favicon/size/16@' +
           window.devicePixelRatio + 'x/' + data.renderViewId + '/' + data.tid;
@@ -729,7 +733,7 @@
     fi.title = '';
     fi.alt = '';
     fi.src = 'chrome-search://ntpicon/size/24@' + window.devicePixelRatio +
-        'x/' + data.url;
+        'x/' + data.dark + data.url;
     loadedCounter += 1;
     fi.addEventListener('load', function(ev) {
       // Store the type for a potential later navigation.
diff --git a/chrome/browser/resources/md_extensions/manager.js b/chrome/browser/resources/md_extensions/manager.js
index 77ce634..2125fc2 100644
--- a/chrome/browser/resources/md_extensions/manager.js
+++ b/chrome/browser/resources/md_extensions/manager.js
@@ -284,14 +284,14 @@
     },
 
     /**
-     * @param {!CustomEvent} event
+     * @param {!CustomEvent<string>} event
      * @private
      */
     onFilterChanged_: function(event) {
       if (this.currentPage_.page !== Page.LIST) {
         extensions.navigation.navigateTo({page: Page.LIST});
       }
-      this.filter = /** @type {string} */ (event.detail);
+      this.filter = event.detail;
     },
 
     /** @private */
@@ -543,7 +543,7 @@
     },
 
     /**
-     * @param {!CustomEvent} e
+     * @param {!Event} e
      * @private
      */
     onViewExitStart_: function(e) {
@@ -552,7 +552,7 @@
     },
 
     /**
-     * @param {!CustomEvent} e
+     * @param {!Event} e
      * @private
      */
     onViewExitFinish_: function(e) {
@@ -577,14 +577,14 @@
     },
 
     /**
-     * @param {!CustomEvent} e
+     * @param {!CustomEvent<!Array<string>>} e
      * @private
      */
     onShowInstallWarnings_: function(e) {
       // Leverage Polymer data bindings instead of just assigning the
       // installWarnings on the dialog since the dialog hasn't been stamped
       // in the DOM yet.
-      this.installWarnings_ = /** @type{!Array<string>} */ (e.detail);
+      this.installWarnings_ = e.detail;
       this.showInstallWarningsDialog_ = true;
     },
 
diff --git a/chrome/browser/resources/md_extensions/toggle_row.js b/chrome/browser/resources/md_extensions/toggle_row.js
index d1786a77..c4f67ba 100644
--- a/chrome/browser/resources/md_extensions/toggle_row.js
+++ b/chrome/browser/resources/md_extensions/toggle_row.js
@@ -57,8 +57,7 @@
     },
 
     /**
-     * Fires
-     * @param {!CustomEvent} e
+     * @param {!CustomEvent<boolean>} e
      * @private
      */
     onCrToggleChange_: function(e) {
diff --git a/chrome/browser/resources/pdf/BUILD.gn b/chrome/browser/resources/pdf/BUILD.gn
index f79f345..ac96b75 100644
--- a/chrome/browser/resources/pdf/BUILD.gn
+++ b/chrome/browser/resources/pdf/BUILD.gn
@@ -53,6 +53,29 @@
 js_library("viewport_scroller") {
 }
 
+js_library("viewport_interface") {
+  deps = [
+    ":pdf_fitting_type",
+  ]
+}
+
+js_library("viewport") {
+  deps = [
+    ":gesture_detector",
+    ":viewport_interface",
+    ":zoom_manager",
+    "//ui/webui/resources/js:util",
+  ]
+  externs_list = [ "$externs_path/pending.js" ]
+}
+
+js_library("zoom_manager") {
+  deps = [
+    ":browser_api",
+    ":viewport_interface",
+  ]
+}
+
 js_type_check("pdf_resources") {
   deps = [
     ":browser_api",
@@ -60,6 +83,9 @@
     ":open_pdf_params_parser",
     ":pdf_fitting_type",
     ":pdf_scripting_api",
+    ":viewport",
+    ":viewport_interface",
     ":viewport_scroller",
+    ":zoom_manager",
   ]
 }
diff --git a/chrome/browser/resources/pdf/elements/viewer-ink-host/BUILD.gn b/chrome/browser/resources/pdf/elements/viewer-ink-host/BUILD.gn
index 420a227..a2cb5af 100644
--- a/chrome/browser/resources/pdf/elements/viewer-ink-host/BUILD.gn
+++ b/chrome/browser/resources/pdf/elements/viewer-ink-host/BUILD.gn
@@ -12,11 +12,7 @@
 
 js_library("viewer-ink-host") {
   deps = [
+    "//chrome/browser/resources/pdf:viewport",
     "//chrome/browser/resources/pdf/ink:ink_api",
   ]
-  externs_list = [
-    # TODO(dstockwell): Once viewport can be typechecked this can be replaced
-    # by a dep on viewport.
-    "externs.js",
-  ]
 }
diff --git a/chrome/browser/resources/pdf/elements/viewer-ink-host/externs.js b/chrome/browser/resources/pdf/elements/viewer-ink-host/externs.js
deleted file mode 100644
index f3769e2..0000000
--- a/chrome/browser/resources/pdf/elements/viewer-ink-host/externs.js
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @typedef {{
- *   x: number,
- *   y: number
- * }}
- */
-let Point;
-
-/**
- * @typedef {{
- *   width: number,
- *   height: number
- * }}
- */
-let Size;
-
-class Viewport {
-  /**
-   * @param {number} zoom
-   * @return {{width: number, height: number}}
-   */
-  getDocumentDimensions(zoom) {}
-
-  /**
-   * @param {!Point} point
-   * @return {boolean}
-   */
-  isPointInsidePage(point) {}
-
-  /** @return {!Point} */
-  get position() {}
-
-  /** @return {!Size} */
-  get size() {}
-
-  /** @return {number} */
-  get zoom() {}
-}
-
-/** @type {Object} */
-Viewport.PAGE_SHADOW;
-
-/** @type {number} */
-Viewport.PAGE_SHADOW.top;
-
-/** @type {number} */
-Viewport.PAGE_SHADOW.left;
diff --git a/chrome/browser/resources/pdf/elements/viewer-ink-host/viewer-ink-host.js b/chrome/browser/resources/pdf/elements/viewer-ink-host/viewer-ink-host.js
index 1895a2347..d4e26340 100644
--- a/chrome/browser/resources/pdf/elements/viewer-ink-host/viewer-ink-host.js
+++ b/chrome/browser/resources/pdf/elements/viewer-ink-host/viewer-ink-host.js
@@ -222,7 +222,7 @@
     const pos = viewport.position;
     const size = viewport.size;
     const zoom = viewport.zoom;
-    const documentWidth = viewport.getDocumentDimensions(zoom).width * zoom;
+    const documentWidth = viewport.getDocumentDimensions().width * zoom;
     // Adjust for page shadows.
     const y = pos.y - Viewport.PAGE_SHADOW.top * zoom;
     let x = pos.x - Viewport.PAGE_SHADOW.left * zoom;
diff --git a/chrome/browser/resources/pdf/index.html b/chrome/browser/resources/pdf/index.html
index 3450bd5..9d79f99 100644
--- a/chrome/browser/resources/pdf/index.html
+++ b/chrome/browser/resources/pdf/index.html
@@ -35,6 +35,7 @@
 </body>
 <script src="pdf_fitting_type.js"></script>
 <script src="toolbar_manager.js"></script>
+<script src="viewport_interface.js"></script>
 <script src="viewport.js"></script>
 <script src="open_pdf_params_parser.js"></script>
 <script src="navigator.js"></script>
diff --git a/chrome/browser/resources/pdf/pdf_viewer.js b/chrome/browser/resources/pdf/pdf_viewer.js
index f98dfc0a..cf5b485eb 100644
--- a/chrome/browser/resources/pdf/pdf_viewer.js
+++ b/chrome/browser/resources/pdf/pdf_viewer.js
@@ -175,7 +175,7 @@
       this.browserApi_.getZoomBehavior() == BrowserApi.ZoomBehavior.MANAGE ?
       this.browserApi_.getDefaultZoom() :
       1.0;
-  this.viewport_ = new Viewport(
+  this.viewport_ = new ViewportImpl(
       window, this.sizer_, this.viewportChanged_.bind(this),
       () => this.currentController_.beforeZoom(),
       () => {
diff --git a/chrome/browser/resources/pdf/viewport.js b/chrome/browser/resources/pdf/viewport.js
index dce8d53..02664b3f 100644
--- a/chrome/browser/resources/pdf/viewport.js
+++ b/chrome/browser/resources/pdf/viewport.js
@@ -2,37 +2,24 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-/**
- * @typedef {{
- *   x: number,
- *   y: number
- * }}
- */
-let Point;
 
 /**
- * @typedef {{
- *   x: (number|undefined),
- *   y: (number|undefined),
- * }}
+ * Clamps the zoom factor (or page scale factor) to be within the limits.
+ *
+ * @param {number} factor The zoom/scale factor.
+ * @return {number} The factor clamped within the limits.
  */
-let PartialPoint;
-
-/**
- * @typedef {{
- *   x: number,
- *   y: number,
- *   width: number,
- *   heigh: number,
- * }}
- */
-let Rect;
+function clampZoom(factor) {
+  return Math.max(
+      Viewport.ZOOM_FACTOR_RANGE.min,
+      Math.min(factor, Viewport.ZOOM_FACTOR_RANGE.max));
+}
 
 /**
  * Returns the height of the intersection of two rectangles.
  *
- * @param {Rect} rect1 the first rect
- * @param {Rect} rect2 the second rect
+ * @param {!ViewportRect} rect1 the first rect
+ * @param {!ViewportRect} rect2 the second rect
  * @return {number} the height of the intersection of the rects
  */
 function getIntersectionHeight(rect1, rect2) {
@@ -45,9 +32,9 @@
 /**
  * Computes vector between two points.
  *
- * @param {!Object} p1 The first point.
- * @param {!Object} p2 The second point.
- * @return {!Object} The vector.
+ * @param {!Point} p1 The first point.
+ * @param {!Point} p2 The second point.
+ * @return {!Point} The vector.
  */
 function vectorDelta(p1, p2) {
   return {x: p2.x - p1.x, y: p2.y - p1.y};
@@ -61,133 +48,75 @@
   };
 }
 
-// TODO: convert Viewport to ES6 class syntax
-/**
- * Create a new viewport.
- *
- * @param {Window} window the window
- * @param {Object} sizer is the element which represents the size of the
- *     document in the viewport
- * @param {Function} viewportChangedCallback is run when the viewport changes
- * @param {Function} beforeZoomCallback is run before a change in zoom
- * @param {Function} afterZoomCallback is run after a change in zoom
- * @param {Function} setUserInitiatedCallback is run to indicate whether a zoom
- *     event is user initiated.
- * @param {number} scrollbarWidth the width of scrollbars on the page
- * @param {number} defaultZoom The default zoom level.
- * @param {number} topToolbarHeight The number of pixels that should initially
- *     be left blank above the document for the toolbar.
- * @constructor
- */
-function Viewport(
-    window, sizer, viewportChangedCallback, beforeZoomCallback,
-    afterZoomCallback, setUserInitiatedCallback, scrollbarWidth, defaultZoom,
-    topToolbarHeight) {
-  this.window_ = window;
-  this.sizer_ = sizer;
-  this.viewportChangedCallback_ = viewportChangedCallback;
-  this.beforeZoomCallback_ = beforeZoomCallback;
-  this.afterZoomCallback_ = afterZoomCallback;
-  this.setUserInitiatedCallback_ = setUserInitiatedCallback;
-  this.allowedToChangeZoom_ = false;
-  this.internalZoom_ = 1;
-  this.zoomManager_ = new InactiveZoomManager(this, 1);
-  this.documentDimensions_ = null;
-  this.pageDimensions_ = [];
-  this.scrollbarWidth_ = scrollbarWidth;
-  this.fittingType_ = FittingType.NONE;
-  this.defaultZoom_ = defaultZoom;
-  this.topToolbarHeight_ = topToolbarHeight;
-  this.prevScale_ = 1;
-  this.pinchPhase_ = Viewport.PinchPhase.PINCH_NONE;
-  this.pinchPanVector_ = null;
-  this.pinchCenter_ = null;
-  this.firstPinchCenterInFrame_ = null;
-  this.rotations_ = 0;
+/** @implements {Viewport} */
+class ViewportImpl {
+  /**
+   * Create a new viewport.
+   *
+   * @param {Window} window the window
+   * @param {Object} sizer is the element which represents the size of the
+   *     document in the viewport
+   * @param {Function} viewportChangedCallback is run when the viewport changes
+   * @param {Function} beforeZoomCallback is run before a change in zoom
+   * @param {Function} afterZoomCallback is run after a change in zoom
+   * @param {Function} setUserInitiatedCallback is run to indicate whether a
+   *     zoom event is user initiated.
+   * @param {number} scrollbarWidth the width of scrollbars on the page
+   * @param {number} defaultZoom The default zoom level.
+   * @param {number} topToolbarHeight The number of pixels that should initially
+   *     be left blank above the document for the toolbar.
+   */
+  constructor(
+      window, sizer, viewportChangedCallback, beforeZoomCallback,
+      afterZoomCallback, setUserInitiatedCallback, scrollbarWidth, defaultZoom,
+      topToolbarHeight) {
+    this.window_ = window;
+    this.sizer_ = sizer;
+    this.viewportChangedCallback_ = viewportChangedCallback;
+    this.beforeZoomCallback_ = beforeZoomCallback;
+    this.afterZoomCallback_ = afterZoomCallback;
+    this.setUserInitiatedCallback_ = setUserInitiatedCallback;
+    this.allowedToChangeZoom_ = false;
+    this.internalZoom_ = 1;
+    this.zoomManager_ = new InactiveZoomManager(this, 1);
+    /** @private {?DocumentDimensions} */
+    this.documentDimensions_ = null;
+    /** @private {Array<ViewportRect>} */
+    this.pageDimensions_ = [];
+    this.scrollbarWidth_ = scrollbarWidth;
+    this.fittingType_ = FittingType.NONE;
+    this.defaultZoom_ = defaultZoom;
+    this.topToolbarHeight_ = topToolbarHeight;
+    this.prevScale_ = 1;
+    this.pinchPhase_ = Viewport.PinchPhase.PINCH_NONE;
+    this.pinchPanVector_ = null;
+    this.pinchCenter_ = null;
+    /** @private {?Point} */
+    this.firstPinchCenterInFrame_ = null;
+    this.rotations_ = 0;
+    // TODO(dstockwell): why isn't this private?
+    this.oldCenterInContent = null;
+    this.keepContentCentered_ = null;
 
-  window.addEventListener('scroll', this.updateViewport_.bind(this));
-  window.addEventListener('resize', this.resizeWrapper_.bind(this));
-}
-
-/**
- * Enumeration of pinch states.
- * This should match PinchPhase enum in pdf/out_of_process_instance.h
- * @enum {number}
- */
-Viewport.PinchPhase = {
-  PINCH_NONE: 0,
-  PINCH_START: 1,
-  PINCH_UPDATE_ZOOM_OUT: 2,
-  PINCH_UPDATE_ZOOM_IN: 3,
-  PINCH_END: 4
-};
-
-/**
- * The increment to scroll a page by in pixels when up/down/left/right arrow
- * keys are pressed. Usually we just let the browser handle scrolling on the
- * window when these keys are pressed but in certain cases we need to simulate
- * these events.
- */
-Viewport.SCROLL_INCREMENT = 40;
-
-/**
- * Predefined zoom factors to be used when zooming in/out. These are in
- * ascending order. This should match the lists in
- * components/ui/zoom/page_zoom_constants.h and
- * chrome/browser/resources/settings/appearance_page/appearance_page.js
- */
-Viewport.ZOOM_FACTORS = [
-  0.25, 1 / 3, 0.5, 2 / 3, 0.75, 0.8, 0.9, 1, 1.1, 1.25, 1.5, 1.75, 2, 2.5, 3,
-  4, 5
-];
-
-/**
- * The minimum and maximum range to be used to clip zoom factor.
- */
-Viewport.ZOOM_FACTOR_RANGE = {
-  min: Viewport.ZOOM_FACTORS[0],
-  max: Viewport.ZOOM_FACTORS[Viewport.ZOOM_FACTORS.length - 1]
-};
-
-/**
- * Clamps the zoom factor (or page scale factor) to be within the limits.
- *
- * @param {number} factor The zoom/scale factor.
- * @return {number} The factor clamped within the limits.
- */
-Viewport.clampZoom = function(factor) {
-  return Math.max(
-      Viewport.ZOOM_FACTOR_RANGE.min,
-      Math.min(factor, Viewport.ZOOM_FACTOR_RANGE.max));
-};
-
-/**
- * The width of the page shadow around pages in pixels.
- */
-Viewport.PAGE_SHADOW = {
-  top: 3,
-  bottom: 7,
-  left: 5,
-  right: 5
-};
-
-Viewport.prototype = {
+    window.addEventListener('scroll', this.updateViewport_.bind(this));
+    window.addEventListener('resize', this.resizeWrapper_.bind(this));
+  }
 
   /**
    * @param {number} n the number of clockwise 90-degree rotations to
    *     increment by.
    */
-  rotateClockwise: function(n) {
+  rotateClockwise(n) {
     this.rotations_ = (this.rotations_ + n) % 4;
-  },
+  }
 
   /**
    * @return {number} the number of clockwise 90-degree rotations that have been
    *     applied.
    */
-  getClockwiseRotations: function() {
+  getClockwiseRotations() {
     return this.rotations_;
-  },
+  }
 
   /**
    * Converts a page position (e.g. the location of a bookmark) to a screen
@@ -197,7 +126,7 @@
    * @param {Point} point The position on `page`.
    * @return The screen position.
    */
-  convertPageToScreen: function(page, point) {
+  convertPageToScreen(page, point) {
     const dimensions = this.getPageInsetDimensions(page);
 
     // width & height are already rotated.
@@ -231,7 +160,7 @@
       x: result.x + Viewport.PAGE_SHADOW.left,
       y: result.y + Viewport.PAGE_SHADOW.top,
     };
-  },
+  }
 
 
   /**
@@ -244,7 +173,7 @@
    * @return {Object} A dictionary with scaled 'width'/'height' of the document.
    * @private
    */
-  getZoomedDocumentDimensions_: function(zoom) {
+  getZoomedDocumentDimensions_(zoom) {
     if (!this.documentDimensions_) {
       return null;
     }
@@ -252,30 +181,23 @@
       width: Math.round(this.documentDimensions_.width * zoom),
       height: Math.round(this.documentDimensions_.height * zoom)
     };
-  },
+  }
 
-  /**
-   * Returns the document dimensions.
-   *
-   * @return {Point} A dictionary with the 'width'/'height' of the document.
-   */
-  getDocumentDimensions: function() {
+  /** @override */
+  getDocumentDimensions() {
     return {
       width: this.documentDimensions_.width,
       height: this.documentDimensions_.height
     };
-  },
+  }
 
   /**
-   * Returns true if the document needs scrollbars at the given zoom level.
-   *
    * @param {number} zoom compute whether scrollbars are needed at this zoom
-   * @return {Object} with 'horizontal' and 'vertical' keys which map to bool
-   *     values indicating if the horizontal and vertical scrollbars are needed
-   *     respectively.
+   * @return {{horizontal: boolean, vertical: boolean}} whether horizontal or
+   *     vertical scrollbars are needed.
    * @private
    */
-  documentNeedsScrollbars_: function(zoom) {
+  documentNeedsScrollbars_(zoom) {
     const zoomedDimensions = this.getZoomedDocumentDimensions_(zoom);
     if (!zoomedDimensions) {
       return {horizontal: false, vertical: false};
@@ -294,7 +216,7 @@
       vertical: zoomedDimensions.height + this.topToolbarHeight_ >
           this.window_.innerHeight
     };
-  },
+  }
 
   /**
    * Returns true if the document needs scrollbars at the current zoom level.
@@ -303,50 +225,50 @@
    *     indicating if the horizontal and vertical scrollbars are needed
    *     respectively.
    */
-  documentHasScrollbars: function() {
+  documentHasScrollbars() {
     return this.documentNeedsScrollbars_(this.zoom);
-  },
+  }
 
   /**
    * Helper function called when the zoomed document size changes.
    *
    * @private
    */
-  contentSizeChanged_: function() {
+  contentSizeChanged_() {
     const zoomedDimensions = this.getZoomedDocumentDimensions_(this.zoom);
     if (zoomedDimensions) {
       this.sizer_.style.width = zoomedDimensions.width + 'px';
       this.sizer_.style.height =
           zoomedDimensions.height + this.topToolbarHeight_ + 'px';
     }
-  },
+  }
 
   /**
    * Called when the viewport should be updated.
    *
    * @private
    */
-  updateViewport_: function() {
+  updateViewport_() {
     this.viewportChangedCallback_();
-  },
+  }
 
   /**
    * Called when the browser window size changes.
    *
    * @private
    */
-  resizeWrapper_: function() {
+  resizeWrapper_() {
     this.setUserInitiatedCallback_(false);
     this.resize_();
     this.setUserInitiatedCallback_(true);
-  },
+  }
 
   /**
    * Called when the viewport size changes.
    *
    * @private
    */
-  resize_: function() {
+  resize_() {
     if (this.fittingType_ == FittingType.FIT_TO_PAGE) {
       this.fitToPageInternal_(false);
     } else if (this.fittingType_ == FittingType.FIT_TO_WIDTH) {
@@ -358,30 +280,26 @@
     } else {
       this.updateViewport_();
     }
-  },
+  }
 
-  /**
-   * @type {Point} the scroll position of the viewport.
-   */
+  /** @override */
   get position() {
     return {
       x: this.window_.pageXOffset,
       y: this.window_.pageYOffset - this.topToolbarHeight_
     };
-  },
+  }
 
   /**
    * Scroll the viewport to the specified position.
    *
-   * @type {Point} position The position to scroll to.
+   * @param {Point} position The position to scroll to.
    */
   set position(position) {
     this.window_.scrollTo(position.x, position.y + this.topToolbarHeight_);
-  },
+  }
 
-  /**
-   * @type {Object} the size of the viewport excluding scrollbars.
-   */
+  /** @override */
   get size() {
     const needsScrollbars = this.documentNeedsScrollbars_(this.zoom);
     const scrollbarWidth = needsScrollbars.vertical ? this.scrollbarWidth_ : 0;
@@ -391,14 +309,12 @@
       width: this.window_.innerWidth - scrollbarWidth,
       height: this.window_.innerHeight - scrollbarHeight
     };
-  },
+  }
 
-  /**
-   * @type {number} the zoom level of the viewport.
-   */
+  /** @override */
   get zoom() {
     return this.zoomManager_.applyBrowserZoom(this.internalZoom_);
-  },
+  }
 
   /**
    * Set the zoom manager.
@@ -407,30 +323,31 @@
    */
   set zoomManager(manager) {
     this.zoomManager_ = manager;
-  },
+  }
 
   /**
-   * @type {Viewport.PinchPhase} The phase of the current pinch gesture for
+   * @return {Viewport.PinchPhase} The phase of the current pinch gesture for
    *    the viewport.
    */
   get pinchPhase() {
     return this.pinchPhase_;
-  },
+  }
 
   /**
-   * @type {Object} The panning caused by the current pinch gesture (as
+   * @return {Object} The panning caused by the current pinch gesture (as
    *    the deltas of the x and y coordinates).
    */
   get pinchPanVector() {
     return this.pinchPanVector_;
-  },
+  }
 
   /**
-   * @type {Object} The coordinates of the center of the current pinch gesture.
+   * @return {Object} The coordinates of the center of the current pinch
+   *     gesture.
    */
   get pinchCenter() {
     return this.pinchCenter_;
-  },
+  }
 
   /**
    * Used to wrap a function that might perform zooming on the viewport. This is
@@ -441,13 +358,13 @@
    * @param {Function} f Function to wrap
    * @private
    */
-  mightZoom_: function(f) {
+  mightZoom_(f) {
     this.beforeZoomCallback_();
     this.allowedToChangeZoom_ = true;
     f();
     this.allowedToChangeZoom_ = false;
     this.afterZoomCallback_();
-  },
+  }
 
   /**
    * Sets the zoom of the viewport.
@@ -455,7 +372,7 @@
    * @param {number} newZoom the zoom level to zoom to.
    * @private
    */
-  setZoomInternal_: function(newZoom) {
+  setZoomInternal_(newZoom) {
     assert(
         this.allowedToChangeZoom_,
         'Called Viewport.setZoomInternal_ without calling ' +
@@ -473,7 +390,7 @@
       x: currentScrollPos.x * this.zoom,
       y: currentScrollPos.y * this.zoom
     };
-  },
+  }
 
   /**
    * Sets the zoom of the viewport.
@@ -483,12 +400,12 @@
    * @param {!Object} center The pinch center in content coordinates.
    * @private
    */
-  setPinchZoomInternal_: function(scaleDelta, center) {
+  setPinchZoomInternal_(scaleDelta, center) {
     assert(
         this.allowedToChangeZoom_,
         'Called Viewport.setPinchZoomInternal_ without calling ' +
             'Viewport.mightZoom_.');
-    this.internalZoom_ = Viewport.clampZoom(this.internalZoom_ * scaleDelta);
+    this.internalZoom_ = clampZoom(this.internalZoom_ * scaleDelta);
 
     const newCenterInContent = this.frameToContent(center);
     const delta = {
@@ -505,7 +422,7 @@
     this.contentSizeChanged_();
     // Scroll to the scaled scroll position.
     this.position = {x: currentScrollPos.x, y: currentScrollPos.y};
-  },
+  }
 
   /**
    *  Converts a point from frame to content coordinates.
@@ -514,35 +431,30 @@
    *  @return {!Object} The content coordinates.
    *  @private
    */
-  frameToContent: function(framePoint) {
+  frameToContent(framePoint) {
     // TODO(mcnee) Add a helper Point class to avoid duplicating operations
     // on plain {x,y} objects.
     return {
       x: (framePoint.x + this.position.x) / this.zoom,
       y: (framePoint.y + this.position.y) / this.zoom
     };
-  },
+  }
 
   /**
    * Sets the zoom to the given zoom level.
    *
    * @param {number} newZoom the zoom level to zoom to.
    */
-  setZoom: function(newZoom) {
+  setZoom(newZoom) {
     this.fittingType_ = FittingType.NONE;
     this.mightZoom_(() => {
-      this.setZoomInternal_(Viewport.clampZoom(newZoom));
+      this.setZoomInternal_(clampZoom(newZoom));
       this.updateViewport_();
     });
-  },
+  }
 
-  /**
-   * Gets notified of the browser zoom changing seperately from the
-   * internal zoom.
-   *
-   * @param {number} oldBrowserZoom the previous value of the browser zoom.
-   */
-  updateZoomFromBrowserChange: function(oldBrowserZoom) {
+  /** @override */
+  updateZoomFromBrowserChange(oldBrowserZoom) {
     this.mightZoom_(() => {
       // Record the scroll position (relative to the top-left of the window).
       const oldZoom = oldBrowserZoom * this.internalZoom_;
@@ -558,21 +470,21 @@
       };
       this.updateViewport_();
     });
-  },
+  }
 
   /**
-   * @type {number} the width of scrollbars in the viewport in pixels.
+   * @return {number} the width of scrollbars in the viewport in pixels.
    */
   get scrollbarWidth() {
     return this.scrollbarWidth_;
-  },
+  }
 
   /**
-   * @type {FittingType} the fitting type the viewport is currently in.
+   * @return {FittingType} the fitting type the viewport is currently in.
    */
   get fittingType() {
     return this.fittingType_;
-  },
+  }
 
   /**
    * Get the which page is at a given y position.
@@ -581,7 +493,7 @@
    * @return {number} the index of a page overlapping the given y-coordinate.
    * @private
    */
-  getPageAtY_: function(y) {
+  getPageAtY_(y) {
     let min = 0;
     let max = this.pageDimensions_.length - 1;
     while (max >= min) {
@@ -607,12 +519,9 @@
       }
     }
     return 0;
-  },
+  }
 
-  /**
-   * @param {Point} point
-   * @return {boolean} Whether |point| (in screen coordinates) is inside a page
-   */
+  /** @override */
   isPointInsidePage(point) {
     const zoom = this.zoom;
     const size = this.size;
@@ -632,7 +541,7 @@
     const minX = (outerWidth - pageWidth) / 2;
     const maxX = outerWidth - minX;
     return x >= minX && x <= maxX;
-  },
+  }
 
   /**
    * Returns the page with the greatest proportion of its height in the current
@@ -640,7 +549,7 @@
    *
    * @return {number} the index of the most visible page.
    */
-  getMostVisiblePage: function() {
+  getMostVisiblePage() {
     const firstVisiblePage = this.getPageAtY_(this.position.y / this.zoom);
     if (firstVisiblePage == this.pageDimensions_.length - 1) {
       return firstVisiblePage;
@@ -664,7 +573,7 @@
       return firstVisiblePage + 1;
     }
     return firstVisiblePage;
-  },
+  }
 
   /**
    * Compute the zoom level for fit-to-page, fit-to-width or fit-to-height.
@@ -679,7 +588,7 @@
    * @return {number} the internal zoom to set
    * @private
    */
-  computeFittingZoom_: function(pageDimensions, fitWidth, fitHeight) {
+  computeFittingZoom_(pageDimensions, fitWidth, fitHeight) {
     assert(
         fitWidth || fitHeight,
         'Invalid parameters. At least one of fitWidth and fitHeight must be ' +
@@ -730,7 +639,7 @@
         pageDimensions.height);
 
     return this.zoomManager_.internalZoomComponent(zoom);
-  },
+  }
 
   /**
    * Compute a zoom level given the dimensions to fit and the actual numbers
@@ -747,7 +656,7 @@
    * @return {number} the internal zoom to set
    * @private
    */
-  computeFittingZoomGivenDimensions_: function(
+  computeFittingZoomGivenDimensions_(
       fitWidth, fitHeight, windowWidth, windowHeight, pageWidth, pageHeight) {
     // Assumes at least one of {fitWidth, fitHeight} is set.
     let zoomWidth;
@@ -772,12 +681,12 @@
     }
 
     return Math.max(zoom, 0);
-  },
+  }
 
   /**
    * Zoom the viewport so that the page width consumes the entire viewport.
    */
-  fitToWidth: function() {
+  fitToWidth() {
     this.mightZoom_(() => {
       this.fittingType_ = FittingType.FIT_TO_WIDTH;
       if (!this.documentDimensions_) {
@@ -789,7 +698,7 @@
           this.computeFittingZoom_(this.documentDimensions_, true, false));
       this.updateViewport_();
     });
-  },
+  }
 
   /**
    * Zoom the viewport so that the page height consumes the entire viewport.
@@ -799,7 +708,7 @@
    *     should remain at the current scroll position.
    * @private
    */
-  fitToHeightInternal_: function(scrollToTopOfPage) {
+  fitToHeightInternal_(scrollToTopOfPage) {
     this.mightZoom_(() => {
       this.fittingType_ = FittingType.FIT_TO_HEIGHT;
       if (!this.documentDimensions_) {
@@ -818,14 +727,14 @@
       }
       this.updateViewport_();
     });
-  },
+  }
 
   /**
    * Zoom the viewport so that the page height consumes the entire viewport.
    */
-  fitToHeight: function() {
+  fitToHeight() {
     this.fitToHeightInternal_(true);
-  },
+  }
 
   /**
    * Zoom the viewport so that a page consumes as much as possible of the it.
@@ -835,7 +744,7 @@
    *     should remain at the current scroll position.
    * @private
    */
-  fitToPageInternal_: function(scrollToTopOfPage) {
+  fitToPageInternal_(scrollToTopOfPage) {
     this.mightZoom_(() => {
       this.fittingType_ = FittingType.FIT_TO_PAGE;
       if (!this.documentDimensions_) {
@@ -853,20 +762,20 @@
       }
       this.updateViewport_();
     });
-  },
+  }
 
   /**
    * Zoom the viewport so that a page consumes the entire viewport. Also scrolls
    * the viewport to the top of the current page.
    */
-  fitToPage: function() {
+  fitToPage() {
     this.fitToPageInternal_(true);
-  },
+  }
 
   /**
    * Zoom the viewport to the default zoom policy.
    */
-  fitToNone: function() {
+  fitToNone() {
     this.mightZoom_(() => {
       this.fittingType_ = FittingType.NONE;
       if (!this.documentDimensions_) {
@@ -877,12 +786,12 @@
           this.computeFittingZoom_(this.documentDimensions_, true, false)));
       this.updateViewport_();
     });
-  },
+  }
 
   /**
    * Zoom out to the next predefined zoom level.
    */
-  zoomOut: function() {
+  zoomOut() {
     this.mightZoom_(() => {
       this.fittingType_ = FittingType.NONE;
       let nextZoom = Viewport.ZOOM_FACTORS[0];
@@ -894,12 +803,12 @@
       this.setZoomInternal_(nextZoom);
       this.updateViewport_();
     });
-  },
+  }
 
   /**
    * Zoom in to the next predefined zoom level.
    */
-  zoomIn: function() {
+  zoomIn() {
     this.mightZoom_(() => {
       this.fittingType_ = FittingType.NONE;
       let nextZoom = Viewport.ZOOM_FACTORS[Viewport.ZOOM_FACTORS.length - 1];
@@ -911,26 +820,28 @@
       this.setZoomInternal_(nextZoom);
       this.updateViewport_();
     });
-  },
+  }
 
   /**
    * Pinch zoom event handler.
    *
    * @param {!Object} e The pinch event.
    */
-  pinchZoom: function(e) {
+  pinchZoom(e) {
     this.mightZoom_(() => {
       this.pinchPhase_ = e.direction == 'out' ?
           Viewport.PinchPhase.PINCH_UPDATE_ZOOM_OUT :
           Viewport.PinchPhase.PINCH_UPDATE_ZOOM_IN;
 
       const scaleDelta = e.startScaleRatio / this.prevScale_;
-      this.pinchPanVector_ =
-          vectorDelta(e.center, this.firstPinchCenterInFrame_);
+      if (this.firstPinchCenterInFrame_ != null) {
+        this.pinchPanVector_ =
+            vectorDelta(e.center, this.firstPinchCenterInFrame_);
+      }
 
       const needsScrollbars =
           this.documentNeedsScrollbars_(this.zoomManager_.applyBrowserZoom(
-              Viewport.clampZoom(this.internalZoom_ * scaleDelta)));
+              clampZoom(this.internalZoom_ * scaleDelta)));
 
       this.pinchCenter_ = e.center;
 
@@ -955,9 +866,10 @@
       this.updateViewport_();
       this.prevScale_ = e.startScaleRatio;
     });
-  },
+  }
 
-  pinchZoomStart: function(e) {
+  /** @param {!Object} e The pinch event. */
+  pinchZoomStart(e) {
     this.pinchPhase_ = Viewport.PinchPhase.PINCH_START;
     this.prevScale_ = 1;
     this.oldCenterInContent =
@@ -968,9 +880,10 @@
     // We keep track of begining of the pinch.
     // By doing so we will be able to compute the pan distance.
     this.firstPinchCenterInFrame_ = e.center;
-  },
+  }
 
-  pinchZoomEnd: function(e) {
+  /** @param {!Object} e The pinch event. */
+  pinchZoomEnd(e) {
     this.mightZoom_(() => {
       this.pinchPhase_ = Viewport.PinchPhase.PINCH_END;
       const scaleDelta = e.startScaleRatio / this.prevScale_;
@@ -984,16 +897,16 @@
     this.pinchPanVector_ = null;
     this.pinchCenter_ = null;
     this.firstPinchCenterInFrame_ = null;
-  },
+  }
 
   /**
    * Go to the given page index.
    *
    * @param {number} page the index of the page to go to. zero-based.
    */
-  goToPage: function(page) {
+  goToPage(page) {
     this.goToPageAndXY(page, 0, 0);
-  },
+  }
 
   /**
    * Go to the given y position in the given page index.
@@ -1002,7 +915,7 @@
    * @param {number} x the x position in the page to go to.
    * @param {number} y the y position in the page to go to.
    */
-  goToPageAndXY: function(page, x, y) {
+  goToPageAndXY(page, x, y) {
     this.mightZoom_(() => {
       if (this.pageDimensions_.length === 0) {
         return;
@@ -1027,14 +940,15 @@
       };
       this.updateViewport_();
     });
-  },
+  }
 
   /**
    * Set the dimensions of the document.
    *
-   * @param {Object} documentDimensions the dimensions of the document
+   * @param {DocumentDimensions} documentDimensions the dimensions of the
+   *     document
    */
-  setDocumentDimensions: function(documentDimensions) {
+  setDocumentDimensions(documentDimensions) {
     this.mightZoom_(() => {
       const initialDimensions = !this.documentDimensions_;
       this.documentDimensions_ = documentDimensions;
@@ -1048,13 +962,13 @@
       this.contentSizeChanged_();
       this.resize_();
     });
-  },
+  }
 
   /**
    * @param {number} page
-   * @return {Rect} The bounds for page `page` minus the shadows.
+   * @return {ViewportRect} The bounds for page `page` minus the shadows.
    */
-  getPageInsetDimensions: function(page) {
+  getPageInsetDimensions(page) {
     const pageDimensions = this.pageDimensions_[page];
     const shadow = Viewport.PAGE_SHADOW;
     return {
@@ -1063,7 +977,7 @@
       width: pageDimensions.width - shadow.left - shadow.right,
       height: pageDimensions.height - shadow.top - shadow.bottom,
     };
-  },
+  }
 
   /**
    * Get the coordinates of the page contents (excluding the page shadow)
@@ -1072,7 +986,7 @@
    * @param {number} page the index of the page to get the rect for.
    * @return {Object} a rect representing the page in screen coordinates.
    */
-  getPageScreenRect: function(page) {
+  getPageScreenRect(page) {
     if (!this.documentDimensions_) {
       return {x: 0, y: 0, width: 0, height: 0};
     }
@@ -1102,7 +1016,7 @@
       width: insetDimensions.width * this.zoom,
       height: insetDimensions.height * this.zoom
     };
-  },
+  }
 
   /**
    * Check if the current fitting type is a paged mode.
@@ -1112,18 +1026,18 @@
    *
    * @return {boolean} Whether the current fitting type is a paged mode.
    */
-  isPagedMode: function(page) {
+  isPagedMode() {
     return (
         this.fittingType_ == FittingType.FIT_TO_PAGE ||
         this.fittingType_ == FittingType.FIT_TO_HEIGHT);
-  },
+  }
 
   /**
    * Scroll the viewport to the specified position.
    *
-   * @param {!PartialPoint} point The position to which to move the viewport.
+   * @param {!Point} point The position to which to move the viewport.
    */
-  scrollTo: function(point) {
+  scrollTo(point) {
     let changed = false;
     const newPosition = this.position;
     if (point.x !== undefined && point.x != newPosition.x) {
@@ -1138,17 +1052,17 @@
     if (changed) {
       this.position = newPosition;
     }
-  },
+  }
 
   /**
    * Scroll the viewport by the specified delta.
    *
    * @param {!Point} delta The delta by which to move the viewport.
    */
-  scrollBy: function(delta) {
+  scrollBy(delta) {
     const newPosition = this.position;
     newPosition.x += delta.x;
     newPosition.y += delta.y;
     this.scrollTo(newPosition);
   }
-};
+}
diff --git a/chrome/browser/resources/pdf/viewport_interface.js b/chrome/browser/resources/pdf/viewport_interface.js
new file mode 100644
index 0000000..c9f99ac
--- /dev/null
+++ b/chrome/browser/resources/pdf/viewport_interface.js
@@ -0,0 +1,136 @@
+// 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.
+
+/**
+ * @typedef {{
+ *   width: number,
+ *   height: number,
+ *   pageDimensions: Array<ViewportRect>,
+ * }}
+ */
+let DocumentDimensions;
+
+/**
+ * @typedef {{
+ *   x: number,
+ *   y: number
+ * }}
+ */
+let Point;
+
+/**
+ * @typedef {{
+ *   width: number,
+ *   height: number,
+ * }}
+ */
+let Size;
+
+/**
+ * @typedef {{
+ *   x: number,
+ *   y: number,
+ *   width: number,
+ *   height: number,
+ * }}
+ */
+let ViewportRect;
+
+/**
+ * @interface
+ */
+class Viewport {
+  /**
+   * Returns the document dimensions.
+   *
+   * @return {!Size} A dictionary with the 'width'/'height' of the document.
+   */
+  getDocumentDimensions() {}
+
+  /**
+   * @return {!Point} the scroll position of the viewport.
+   */
+  get position() {}
+
+  /**
+   * @return {!Size} the size of the viewport excluding scrollbars.
+   */
+  get size() {}
+
+  /**
+   * @return {number} the zoom level of the viewport.
+   */
+  get zoom() {}
+
+  /**
+   * Sets the zoom to the given zoom level.
+   *
+   * @param {number} newZoom the zoom level to zoom to.
+   */
+  setZoom(newZoom) {}
+
+  /**
+   * Gets notified of the browser zoom changing separately from the
+   * internal zoom.
+   *
+   * @param {number} oldBrowserZoom the previous value of the browser zoom.
+   */
+  updateZoomFromBrowserChange(oldBrowserZoom) {}
+
+  /**
+   * @param {!Point} point
+   * @return {boolean} Whether |point| (in screen coordinates) is inside a page
+   */
+  isPointInsidePage(point) {}
+}
+
+/**
+ * Enumeration of pinch states.
+ * This should match PinchPhase enum in pdf/out_of_process_instance.h
+ * @enum {number}
+ */
+Viewport.PinchPhase = {
+  PINCH_NONE: 0,
+  PINCH_START: 1,
+  PINCH_UPDATE_ZOOM_OUT: 2,
+  PINCH_UPDATE_ZOOM_IN: 3,
+  PINCH_END: 4
+};
+
+/**
+ * The increment to scroll a page by in pixels when up/down/left/right arrow
+ * keys are pressed. Usually we just let the browser handle scrolling on the
+ * window when these keys are pressed but in certain cases we need to simulate
+ * these events.
+ */
+Viewport.SCROLL_INCREMENT = 40;
+
+/**
+ * Predefined zoom factors to be used when zooming in/out. These are in
+ * ascending order. This should match the lists in
+ * components/ui/zoom/page_zoom_constants.h and
+ * chrome/browser/resources/settings/appearance_page/appearance_page.js
+ */
+Viewport.ZOOM_FACTORS = [
+  0.25, 1 / 3, 0.5, 2 / 3, 0.75, 0.8, 0.9, 1, 1.1, 1.25, 1.5, 1.75, 2, 2.5, 3,
+  4, 5
+];
+
+/**
+ * The minimum and maximum range to be used to clip zoom factor.
+ */
+Viewport.ZOOM_FACTOR_RANGE = {
+  min: Viewport.ZOOM_FACTORS[0],
+  max: Viewport.ZOOM_FACTORS[Viewport.ZOOM_FACTORS.length - 1]
+};
+
+/**
+ * The width of the page shadow around pages in pixels.
+ */
+Viewport.PAGE_SHADOW = {
+  top: 3,
+  bottom: 7,
+  left: 5,
+  right: 5
+};
diff --git a/chrome/browser/resources/print_preview/metrics.js b/chrome/browser/resources/print_preview/metrics.js
index 34af759..83d0cf7 100644
--- a/chrome/browser/resources/print_preview/metrics.js
+++ b/chrome/browser/resources/print_preview/metrics.js
@@ -49,8 +49,10 @@
     INVITATION_ACCEPTED: 12,
     // User rejected printer sharing invitation.
     INVITATION_REJECTED: 13,
+    // User clicked on Manage button
+    MANAGE_BUTTON_CLICKED: 14,
     // Max value.
-    DESTINATION_SEARCH_MAX_BUCKET: 14
+    DESTINATION_SEARCH_MAX_BUCKET: 15
   };
 
   /**
diff --git a/chrome/browser/resources/print_preview/new/destination_dialog.js b/chrome/browser/resources/print_preview/new/destination_dialog.js
index b5d3299..7229839c 100644
--- a/chrome/browser/resources/print_preview/new/destination_dialog.js
+++ b/chrome/browser/resources/print_preview/new/destination_dialog.js
@@ -405,6 +405,8 @@
 
   /** @private */
   onOpenSettingsPrintPage_: function() {
+    this.metrics_.record(
+        print_preview.Metrics.DestinationSearchBucket.MANAGE_BUTTON_CLICKED);
     print_preview.NativeLayer.getInstance().openSettingsPrintPage();
   },
 });
diff --git a/chrome/browser/resources/quota_internals/event_handler.js b/chrome/browser/resources/quota_internals/event_handler.js
index 98c89c5..9fd9c8a 100644
--- a/chrome/browser/resources/quota_internals/event_handler.js
+++ b/chrome/browser/resources/quota_internals/event_handler.js
@@ -260,12 +260,9 @@
  * Event Handler for |cr.quota.onAvailableSpaceUpdated|.
  * |event.detail| contains |availableSpace|.
  * |availableSpace| represents total available disk space.
- * @param {CustomEvent} event AvailableSpaceUpdated event.
+ * @param {!CustomEvent<string>} event AvailableSpaceUpdated event.
  */
 function handleAvailableSpace(event) {
-  /**
-   * @type {string}
-   */
   availableSpace = event.detail;
   $('diskspace-entry').innerHTML = numBytesToText_(availableSpace);
 }
@@ -284,17 +281,14 @@
  *
  *  |usage|, |unlimitedUsage| and |quota| can be missing,
  *  and some additional fields can be included.
- * @param {CustomEvent} event GlobalInfoUpdated event.
+ * @param {!CustomEvent<!{
+ *     type: string,
+ *     usage: ?number,
+ *     unlimitedUsage: ?number,
+ *     quota: ?string
+ * }>} event GlobalInfoUpdated event.
  */
 function handleGlobalInfo(event) {
-  /**
-   * @type {{
-   *         type: {!string},
-   *         usage: {?number},
-   *         unlimitedUsage: {?number}
-   *         quota: {?string}
-   *       }}
-   */
   const data = event.detail;
   const storageObject = getStorageObject(data.type);
   copyAttributes_(data, storageObject.detail.payload);
@@ -318,17 +312,14 @@
  *
  * |usage| and |quota| can be missing,
  * and some additional fields can be included.
- * @param {CustomEvent} event PerHostInfoUpdated event.
+ * @param {!CustomEvent<!Array<{
+ *     host: string,
+ *     type: string,
+ *     usage: ?number,
+ *     quota: ?number
+ * }>>} event PerHostInfoUpdated event.
  */
 function handlePerHostInfo(event) {
-  /**
-   * @type {Array<{
-   *         host: {!string},
-   *         type: {!string},
-   *         usage: {?number},
-   *         quota: {?number}
-   *       }}
-   */
   const dataArray = event.detail;
 
   for (let i = 0; i < dataArray.length; ++i) {
@@ -364,20 +355,17 @@
  *
  * |inUse|, |usedCount|, |lastAccessTime| and |lastModifiedTime| can be missing,
  * and some additional fields can be included.
- * @param {CustomEvent} event PerOriginInfoUpdated event.
+ * @param {!CustomEvent<!Array<!{
+ *     origin: string,
+ *     type: string,
+ *     host: string,
+ *     inUse: ?boolean,
+ *     usedCount: ?number,
+ *     lastAccessTime: ?number,
+ *     lastModifiedTime: ?number
+ * }>>} event PerOriginInfoUpdated event.
  */
 function handlePerOriginInfo(event) {
-  /**
-   * @type {Array<{
-   *         origin: {!string},
-   *         type: {!string},
-   *         host: {!string},
-   *         inUse: {?boolean},
-   *         usedCount: {?number},
-   *         lastAccessTime: {?number}
-   *         lastModifiedTime: {?number}
-   *       }>}
-   */
   const dataArray = event.detail;
 
   for (let i = 0; i < dataArray.length; ++i) {
@@ -394,12 +382,9 @@
 /**
  * Event Handler for |cr.quota.onStatisticsUpdated|.
  * |event.detail| contains misc statistics data as dictionary.
- * @param {CustomEvent} event StatisticsUpdated event.
+ * @param {!CustomEvent<!Object>} event StatisticsUpdated event.
  */
 function handleStatistics(event) {
-  /**
-   * @type {Object<string>}
-   */
   const data = event.detail;
   for (const key in data) {
     let entry = statistics[key];
diff --git a/chrome/browser/resources/safe_browsing/download_file_types.asciipb b/chrome/browser/resources/safe_browsing/download_file_types.asciipb
index e82e6166..0d36080 100644
--- a/chrome/browser/resources/safe_browsing/download_file_types.asciipb
+++ b/chrome/browser/resources/safe_browsing/download_file_types.asciipb
@@ -8,7 +8,7 @@
 ##
 ## Top level settings
 ##
-version_id: 27
+version_id: 28
 sampled_ping_probability: 0.01
 max_archived_binaries_to_report: 10
 default_file_type {
@@ -24,7 +24,8 @@
 ## General cross-platform file types
 ##
 
-# Files that are not dangerous but for which we want to track via UMA.
+# Files that are not dangerous, and are popular enough that we'd like to
+# exclude them from download pings.
 file_types {
   extension: "bin"
   uma_value: 159
@@ -41,6 +42,106 @@
   uma_value: 244
   ping_setting: SAMPLED_PING
 }
+file_types {
+  extension: "jpg"
+  uma_value: 322
+  ping_setting: SAMPLED_PING
+}
+file_types {
+  extension: "jpeg"
+  uma_value: 323
+  ping_setting: SAMPLED_PING
+}
+file_types {
+  extension: "mp3"
+  uma_value: 324
+  ping_setting: SAMPLED_PING
+}
+file_types {
+  extension: "mp4"
+  uma_value: 325
+  ping_setting: SAMPLED_PING
+}
+file_types {
+  extension: "png"
+  uma_value: 326
+  ping_setting: SAMPLED_PING
+}
+file_types {
+  extension: "xls"
+  uma_value: 327
+  ping_setting: SAMPLED_PING
+}
+file_types {
+  extension: "doc"
+  uma_value: 328
+  ping_setting: SAMPLED_PING
+}
+file_types {
+  extension: "pptx"
+  uma_value: 329
+  ping_setting: SAMPLED_PING
+}
+file_types {
+  extension: "csv"
+  uma_value: 330
+  ping_setting: SAMPLED_PING
+}
+file_types {
+  extension: "ica"
+  uma_value: 331
+  ping_setting: SAMPLED_PING
+}
+file_types {
+  extension: "ppt"
+  uma_value: 332
+  ping_setting: SAMPLED_PING
+}
+file_types {
+  extension: "gif"
+  uma_value: 333
+  ping_setting: SAMPLED_PING
+}
+file_types {
+  extension: "txt"
+  uma_value: 334
+  ping_setting: SAMPLED_PING
+}
+file_types {
+  extension: "package"
+  uma_value: 335
+  ping_setting: SAMPLED_PING
+}
+file_types {
+  extension: "tif"
+  uma_value: 336
+  ping_setting: SAMPLED_PING
+}
+file_types {
+  extension: "rtf"
+  uma_value: 337
+  ping_setting: SAMPLED_PING
+}
+file_types {
+  extension: "webp"
+  uma_value: 338
+  ping_setting: SAMPLED_PING
+}
+file_types {
+  extension: "mkv"
+  uma_value: 339
+  ping_setting: SAMPLED_PING
+}
+file_types {
+  extension: "wav"
+  uma_value: 340
+  ping_setting: SAMPLED_PING
+}
+file_types {
+  extension: "mov"
+  uma_value: 341
+  ping_setting: SAMPLED_PING
+}
 
 # Flash files downloaded locally can sometimes access the local filesystem
 file_types {
diff --git a/chrome/browser/resources/settings/device_page/display.js b/chrome/browser/resources/settings/device_page/display.js
index c1881cb5..7528b6f7 100644
--- a/chrome/browser/resources/settings/device_page/display.js
+++ b/chrome/browser/resources/settings/device_page/display.js
@@ -136,14 +136,6 @@
     },
 
     /** @private */
-    multiMirroringAvailable_: {
-      type: Boolean,
-      value: function() {
-        return loadTimeData.getBoolean('multiMirroringAvailable');
-      }
-    },
-
-    /** @private */
     unifiedDesktopMode_: {
       type: Boolean,
       value: false,
@@ -576,9 +568,7 @@
     }
 
     return this.isMirrored_(displays) ||
-        (!unifiedDesktopMode &&
-         ((this.multiMirroringAvailable_ && displays.length > 1) ||
-          displays.length == 2));
+        (!unifiedDesktopMode && displays.length > 1);
   },
 
   /**
diff --git a/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.html b/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.html
index c40e950..fda8a287d 100644
--- a/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.html
+++ b/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.html
@@ -39,6 +39,10 @@
       #messageDiv[invisible] {
         visibility: hidden;
       }
+
+      #closeButton {
+        margin-inline-start: 5px;
+      }
     </style>
 
     <cr-dialog id="dialog" on-close="close"
diff --git a/chrome/browser/resources/settings/people_page/sync_page.html b/chrome/browser/resources/settings/people_page/sync_page.html
index af69a2e..d4cd336 100644
--- a/chrome/browser/resources/settings/people_page/sync_page.html
+++ b/chrome/browser/resources/settings/people_page/sync_page.html
@@ -3,6 +3,7 @@
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
 <link rel="import" href="chrome://resources/html/util.html">
 <link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
+<link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_input/cr_input.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_radio_button/cr_radio_button.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_radio_group/cr_radio_group.html">
@@ -334,6 +335,24 @@
     </template>
 
 <if expr="not chromeos">
+    <template is="dom-if" if="[[showSetupCancelDialog_]]" restamp>
+      <cr-dialog id="setupCancelDialog" on-close="onSetupCancelDialogClose_"
+          ignore-popstate>
+        <div slot="title">$i18n{syncSetupCancelDialogTitle}</div>
+        <div slot="body">$i18n{syncSetupCancelDialogBody}</div>
+        <div slot="button-container">
+          <paper-button class="cancel-button"
+              on-click="onSetupCancelDialogBack_">
+            $i18n{back}
+          </paper-button>
+          <paper-button class="action-button"
+              on-click="onSetupCancelDialogConfirm_">
+            $i18n{cancelSync}
+          </paper-button>
+        </div>
+      </cr-dialog>
+    </template>
+
     <template is="dom-if" if="[[!unifiedConsentEnabled]]">
       <cr-toast id="toast" open="[[syncStatus.setupInProgress]]">
         <div>$i18n{syncWillStart}</div>
diff --git a/chrome/browser/resources/settings/people_page/sync_page.js b/chrome/browser/resources/settings/people_page/sync_page.js
index e7377df..b102f70 100644
--- a/chrome/browser/resources/settings/people_page/sync_page.js
+++ b/chrome/browser/resources/settings/people_page/sync_page.js
@@ -141,6 +141,12 @@
       type: Boolean,
       observer: 'initializeDidAbort_',
     },
+
+    /** @private */
+    showSetupCancelDialog_: {
+      type: Boolean,
+      value: false,
+    },
   },
 
   /** @private {?settings.SyncBrowserProxy} */
@@ -172,6 +178,12 @@
    */
   didAbort_: false,
 
+  /**
+   * Whether the user clicked the confirm button on the "Cancel sync?" dialog.
+   * @private {boolean}
+   */
+  setupCancelDialogConfirmed_: false,
+
   /** @override */
   created: function() {
     this.browserProxy_ = settings.SyncBrowserProxyImpl.getInstance();
@@ -229,12 +241,42 @@
     return this.syncStatus != undefined && !!this.syncStatus.managed;
   },
 
+  /** @private */
+  onSetupCancelDialogBack_: function() {
+    this.$$('#setupCancelDialog').cancel();
+  },
+
+  /** @private */
+  onSetupCancelDialogConfirm_: function() {
+    this.setupCancelDialogConfirmed_ = true;
+    this.$$('#setupCancelDialog').close();
+    settings.navigateTo(settings.routes.BASIC);
+  },
+
+  /** @private */
+  onSetupCancelDialogClose_: function() {
+    this.showSetupCancelDialog_ = false;
+  },
+
   /** @protected */
   currentRouteChanged: function() {
     if (settings.getCurrentRoute() == settings.routes.SYNC) {
       this.onNavigateToPage_();
     } else if (!settings.routes.SYNC.contains(settings.getCurrentRoute())) {
-      this.onNavigateAwayFromPage_();
+      // When the user wants to cancel the sync setup, but hasn't confirmed
+      // the cancel dialog, navigate back and show the dialog.
+      if (this.unifiedConsentEnabled && this.syncStatus &&
+          !!this.syncStatus.setupInProgress && this.didAbort_ &&
+          !this.setupCancelDialogConfirmed_) {
+        settings.navigateTo(settings.routes.SYNC);
+        this.showSetupCancelDialog_ = true;
+        // Flush to make sure that the setup cancel dialog is attached.
+        Polymer.dom.flush();
+        this.$$('#setupCancelDialog').showModal();
+      } else {
+        this.setupCancelDialogConfirmed_ = false;
+        this.onNavigateAwayFromPage_();
+      }
     }
   },
 
@@ -540,7 +582,10 @@
         !this.unifiedConsentEnabled;
   },
 
-  /** @private */
+  /**
+   * Used when unified consent is disabled.
+   * @private
+   */
   onSyncSetupCancel_: function() {
     this.didAbort_ = true;
     settings.navigateTo(settings.routes.BASIC);
diff --git a/chrome/browser/resources/settings/search_engines_page/search_engines_page.js b/chrome/browser/resources/settings/search_engines_page/search_engines_page.js
index b840321..84e5d33b 100644
--- a/chrome/browser/resources/settings/search_engines_page/search_engines_page.js
+++ b/chrome/browser/resources/settings/search_engines_page/search_engines_page.js
@@ -128,14 +128,14 @@
   },
 
   /**
-   * @param {!CustomEvent} e
+   * @param {!CustomEvent<!{
+   *     engine: !SearchEngine,
+   *     anchorElement: !HTMLElement
+   * }>} e
    * @private
    */
   onEditSearchEngine_: function(e) {
-    const params =
-        /** @type {!{engine: !SearchEngine, anchorElement: !HTMLElement}} */ (
-            e.detail);
-    this.openDialog_(params.engine, params.anchorElement);
+    this.openDialog_(e.detail.engine, e.detail.anchorElement);
   },
 
   /** @private */
diff --git a/chrome/browser/resources/settings/settings_main/settings_main.js b/chrome/browser/resources/settings/settings_main/settings_main.js
index a3d68c29..da38cb80 100644
--- a/chrome/browser/resources/settings/settings_main/settings_main.js
+++ b/chrome/browser/resources/settings/settings_main/settings_main.js
@@ -153,7 +153,7 @@
    * A handler for the 'showing-section' event fired from settings-basic-page,
    * indicating that a section should be scrolled into view as a result of a
    * navigation.
-   * @param {!CustomEvent} e
+   * @param {!CustomEvent<!HTMLElement>} e
    * @private
    */
   onShowingSection_: function(e) {
diff --git a/chrome/browser/safe_browsing/BUILD.gn b/chrome/browser/safe_browsing/BUILD.gn
index 4126eaa..a0264f5 100644
--- a/chrome/browser/safe_browsing/BUILD.gn
+++ b/chrome/browser/safe_browsing/BUILD.gn
@@ -281,11 +281,13 @@
   ]
 
   deps = [
+    "//components/keyed_service/content",
     "//components/prefs:prefs",
     "//components/safe_browsing/common:common",
     "//components/safe_browsing/common:safe_browsing_prefs",
     "//components/signin/core/browser:browser",
     "//content/public/browser:browser",
+    "//services/identity/public/cpp",
   ]
 }
 
@@ -299,6 +301,10 @@
     deps = [
       ":safe_browsing",
       "//chrome/common/safe_browsing:proto",
+      "//components/safe_browsing/db:database_manager",
+      "//components/safe_browsing/db:test_database_manager",
+      "//components/safe_browsing/db:v4_feature_list",
+      "//components/safe_browsing/db:v4_protocol_manager_util",
       "//content/public/browser:browser",
     ]
   }
diff --git a/chrome/browser/search/instant_service.cc b/chrome/browser/search/instant_service.cc
index 85da0e38..c99f1ede 100644
--- a/chrome/browser/search/instant_service.cc
+++ b/chrome/browser/search/instant_service.cc
@@ -9,6 +9,7 @@
 #include "base/bind.h"
 #include "base/files/file_util.h"
 #include "base/path_service.h"
+#include "base/scoped_observer.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/post_task.h"
@@ -49,6 +50,7 @@
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/url_data_source.h"
 #include "ui/gfx/color_utils.h"
+#include "ui/native_theme/native_theme_observer.h"
 
 namespace {
 
@@ -145,6 +147,48 @@
   base::RepeatingCallback<void(bool)> callback_;
 };
 
+// Keeps track of any changes to system dark mode and notifies InstantService if
+// dark mode has been changed. Use this to check if dark mode is enabled.
+class InstantService::DarkModeHandler : public ui::NativeThemeObserver {
+ public:
+  explicit DarkModeHandler(ui::NativeTheme* theme,
+                           base::RepeatingCallback<void(bool)> callback)
+      : theme_(theme), callback_(std::move(callback)), observer_(this) {
+    using_dark_mode_ = IsDarkModeEnabled();
+    observer_.Add(theme_);
+  }
+
+  bool IsDarkModeEnabled() { return theme_->SystemDarkModeEnabled(); }
+
+  void SetThemeForTesting(ui::NativeTheme* theme) {
+    observer_.RemoveAll();
+
+    theme_ = theme;
+    using_dark_mode_ = IsDarkModeEnabled();
+    observer_.Add(theme_);
+  }
+
+ private:
+  // ui::NativeThemeObserver:
+  void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override {
+    DCHECK_EQ(observed_theme, theme_);
+
+    bool using_dark_mode = IsDarkModeEnabled();
+    if (using_dark_mode == using_dark_mode_)
+      return;
+
+    using_dark_mode_ = using_dark_mode;
+    callback_.Run(using_dark_mode_);
+  }
+
+  // The theme to query/watch for changes.
+  ui::NativeTheme* theme_;
+  // Whether or not the theme is using dark mode.
+  bool using_dark_mode_;
+  base::RepeatingCallback<void(bool)> callback_;
+  ScopedObserver<ui::NativeTheme, DarkModeHandler> observer_;
+};
+
 InstantService::InstantService(Profile* profile)
     : profile_(profile),
       pref_service_(profile_->GetPrefs()),
@@ -198,6 +242,11 @@
     }
   }
 
+  dark_mode_handler_ = std::make_unique<DarkModeHandler>(
+      ui::NativeTheme::GetInstanceForNativeUi(),
+      base::BindRepeating(&InstantService::OnDarkModeChanged,
+                          weak_ptr_factory_.GetWeakPtr()));
+
   background_service_ = NtpBackgroundServiceFactory::GetForProfile(profile_);
 
   // Listen for theme installation.
@@ -400,6 +449,11 @@
                      weak_ptr_factory_.GetWeakPtr()));
 }
 
+void InstantService::SetDarkModeThemeForTesting(ui::NativeTheme* theme) {
+  if (dark_mode_handler_)
+    dark_mode_handler_->SetThemeForTesting(theme);
+}
+
 void InstantService::Shutdown() {
   process_ids_.clear();
 
@@ -464,6 +518,12 @@
   most_visited_sites_->EnableCustomLinks(is_google);
 }
 
+void InstantService::OnDarkModeChanged(bool dark_mode) {
+  if (theme_info_)
+    theme_info_->using_dark_mode = dark_mode;
+  UpdateThemeInfo();
+}
+
 void InstantService::OnURLsAvailable(
     const std::map<ntp_tiles::SectionType, ntp_tiles::NTPTilesVector>&
         sections) {
@@ -525,6 +585,8 @@
   theme_info_->using_default_theme =
       theme_service->UsingDefaultTheme() || theme_service->UsingSystemTheme();
 
+  theme_info_->using_dark_mode = dark_mode_handler_->IsDarkModeEnabled();
+
   // Get theme colors.
   const ui::ThemeProvider& theme_provider =
       ThemeService::GetThemeProviderForProfile(profile_);
diff --git a/chrome/browser/search/instant_service.h b/chrome/browser/search/instant_service.h
index 4c5b1226..0db2b10 100644
--- a/chrome/browser/search/instant_service.h
+++ b/chrome/browser/search/instant_service.h
@@ -25,6 +25,7 @@
 #include "components/prefs/pref_registry_simple.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
+#include "ui/native_theme/native_theme.h"
 #include "url/gurl.h"
 
 #if defined(OS_ANDROID)
@@ -135,6 +136,10 @@
   // Used for testing.
   ThemeBackgroundInfo* GetThemeInfoForTesting() { return theme_info_.get(); }
 
+  // Used for testing.
+  void SetDarkModeThemeForTesting(ui::NativeTheme* theme);
+
+  // Used for testing.
   void AddValidBackdropUrlForTesting(const GURL& url) const;
 
   // Check if a custom background has been set by the user.
@@ -143,6 +148,8 @@
  private:
   class SearchProviderObserver;
 
+  class DarkModeHandler;
+
   friend class InstantExtendedTest;
   friend class InstantUnitTestBase;
 
@@ -165,6 +172,10 @@
   // search provider is not Google.
   void OnSearchProviderChanged(bool is_google);
 
+  // Called when dark mode changes. Updates current theme info as necessary and
+  // notifies that the theme has changed.
+  void OnDarkModeChanged(bool dark_mode);
+
   // ntp_tiles::MostVisitedSites::Observer implementation.
   void OnURLsAvailable(
       const std::map<ntp_tiles::SectionType, ntp_tiles::NTPTilesVector>&
@@ -225,6 +236,9 @@
   // Keeps track of any changes in search engine provider. May be null.
   std::unique_ptr<SearchProviderObserver> search_provider_observer_;
 
+  // Keeps track of any changes to system dark mode.
+  std::unique_ptr<DarkModeHandler> dark_mode_handler_;
+
   PrefChangeRegistrar pref_change_registrar_;
 
   PrefService* pref_service_;
diff --git a/chrome/browser/search/instant_service_unittest.cc b/chrome/browser/search/instant_service_unittest.cc
index a6435c2..a867712 100644
--- a/chrome/browser/search/instant_service_unittest.cc
+++ b/chrome/browser/search/instant_service_unittest.cc
@@ -22,6 +22,7 @@
 #include "components/ntp_tiles/section_type.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/native_theme/test_native_theme.h"
 #include "url/gurl.h"
 
 namespace {
@@ -416,3 +417,38 @@
   EXPECT_EQ(GURL(), theme_info->custom_background_url);
   EXPECT_FALSE(instant_service_->IsCustomBackgroundSet());
 }
+
+class InstantServiceThemeTest : public InstantServiceTest {
+ public:
+  InstantServiceThemeTest() {}
+  ~InstantServiceThemeTest() override {}
+
+  ui::TestNativeTheme* theme() { return &theme_; }
+
+ private:
+  ui::TestNativeTheme theme_;
+
+  DISALLOW_COPY_AND_ASSIGN(InstantServiceThemeTest);
+};
+
+TEST_F(InstantServiceThemeTest, DarkModeHandler) {
+  theme()->SetDarkMode(false);
+  instant_service_->SetDarkModeThemeForTesting(theme());
+  thread_bundle()->RunUntilIdle();
+
+  // Enable dark mode.
+  theme()->SetDarkMode(true);
+  theme()->NotifyObservers();
+  thread_bundle()->RunUntilIdle();
+
+  ThemeBackgroundInfo* theme_info = instant_service_->GetThemeInfoForTesting();
+  EXPECT_TRUE(theme_info->using_dark_mode);
+
+  // Disable dark mode.
+  theme()->SetDarkMode(false);
+  theme()->NotifyObservers();
+  thread_bundle()->RunUntilIdle();
+
+  theme_info = instant_service_->GetThemeInfoForTesting();
+  EXPECT_FALSE(theme_info->using_dark_mode);
+}
diff --git a/chrome/browser/search/local_ntp_source.cc b/chrome/browser/search/local_ntp_source.cc
index 2a102fe..b06a19c 100644
--- a/chrome/browser/search/local_ntp_source.cc
+++ b/chrome/browser/search/local_ntp_source.cc
@@ -73,10 +73,7 @@
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
-#include "ui/base/ui_base_features.h"
-#include "ui/base/ui_base_switches.h"
 #include "ui/base/webui/web_ui_util.h"
-#include "ui/native_theme/native_theme.h"
 #include "ui/resources/grit/ui_resources.h"
 #include "url/gurl.h"
 
@@ -591,9 +588,6 @@
     config_data.SetBoolean("isAccessibleBrowser",
                            content::BrowserAccessibilityState::GetInstance()
                                ->IsAccessibleBrowser());
-    config_data.SetBoolean(
-        "isDarkModeEnabled",
-        ui::NativeTheme::GetInstanceForNativeUi()->SystemDarkModeEnabled());
 
     // Serialize the dictionary.
     std::string js_text;
diff --git a/chrome/browser/search/ntp_icon_source.cc b/chrome/browser/search/ntp_icon_source.cc
index 4e07db92..d0db7807 100644
--- a/chrome/browser/search/ntp_icon_source.cc
+++ b/chrome/browser/search/ntp_icon_source.cc
@@ -61,6 +61,9 @@
 // Delimiter in the url that looks for the size specification.
 const char kSizeParameter[] = "size/";
 
+// Delimiter in the url for dark mode specification.
+const char kDarkModeParameter[] = "dark/";
+
 // Size of the icon background (gray circle), in dp.
 const int kIconSizeDip = 48;
 
@@ -128,6 +131,15 @@
 
   parsed_index = slash + 1;
 
+  // Parse the dark mode spec (e.g. "dark"), if available. The value is not
+  // used, but is required to generate a new icon for dark mode.
+  if (HasSubstringAt(path, parsed_index, kDarkModeParameter)) {
+    slash = path.find("/", parsed_index);
+    if (slash == std::string::npos)
+      return parsed;
+    parsed_index = slash + 1;
+  }
+
   parsed.url = GURL(path.substr(parsed_index));
   return parsed;
 }
diff --git a/chrome/browser/search/search_suggest/search_suggest_loader.h b/chrome/browser/search/search_suggest/search_suggest_loader.h
index 7a041ee..8209726c 100644
--- a/chrome/browser/search/search_suggest/search_suggest_loader.h
+++ b/chrome/browser/search/search_suggest/search_suggest_loader.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_SEARCH_SEARCH_SUGGEST_SEARCH_SUGGEST_LOADER_H_
 #define CHROME_BROWSER_SEARCH_SEARCH_SUGGEST_SEARCH_SUGGEST_LOADER_H_
 
+#include <string>
+
 #include "base/callback_forward.h"
 #include "base/optional.h"
 
@@ -41,8 +43,8 @@
 
   // Initiates a load from the network. On completion (successful or not), the
   // callback will be called with the result, which will be nullopt on failure.
-  // |blacklist| will be appended to the request as the url param 'vtgb'.
-  virtual void Load(const std::string& blacklist,
+  // |blocklist| will be appended to the request as the url param 'vtgb'.
+  virtual void Load(const std::string& blocklist,
                     SearchSuggestionsCallback callback) = 0;
 
   // Retrieves the URL from which SearchSuggestData will be loaded.
diff --git a/chrome/browser/search/search_suggest/search_suggest_loader_impl.cc b/chrome/browser/search/search_suggest/search_suggest_loader_impl.cc
index f0d04a9..8067939 100644
--- a/chrome/browser/search/search_suggest/search_suggest_loader_impl.cc
+++ b/chrome/browser/search/search_suggest/search_suggest_loader_impl.cc
@@ -212,7 +212,7 @@
 
 SearchSuggestLoaderImpl::~SearchSuggestLoaderImpl() = default;
 
-void SearchSuggestLoaderImpl::Load(const std::string& blacklist,
+void SearchSuggestLoaderImpl::Load(const std::string& blocklist,
                                    SearchSuggestionsCallback callback) {
   callbacks_.push_back(std::move(callback));
 
@@ -220,18 +220,18 @@
   // something has changed in the meantime (e.g. signin state) that would make
   // the result obsolete.
   pending_request_ = std::make_unique<AuthenticatedURLLoader>(
-      url_loader_factory_, GetApiUrl(blacklist),
+      url_loader_factory_, GetApiUrl(blocklist),
       base::BindOnce(&SearchSuggestLoaderImpl::LoadDone,
                      base::Unretained(this)));
   pending_request_->Start();
 }
 
 GURL SearchSuggestLoaderImpl::GetLoadURLForTesting() const {
-  std::string blacklist;
-  return GetApiUrl(blacklist);
+  std::string blocklist;
+  return GetApiUrl(blocklist);
 }
 
-GURL SearchSuggestLoaderImpl::GetApiUrl(const std::string& blacklist) const {
+GURL SearchSuggestLoaderImpl::GetApiUrl(const std::string& blocklist) const {
   GURL google_base_url = google_util::CommandLineGoogleBaseURL();
   if (!google_base_url.is_valid()) {
     google_base_url = google_url_tracker_->google_url();
@@ -239,7 +239,7 @@
 
   GURL api_url = google_base_url.Resolve(kNewTabSearchSuggestionsApiPath);
 
-  api_url = net::AppendQueryParameter(api_url, "vtgb", blacklist);
+  api_url = net::AppendQueryParameter(api_url, "vtgb", blocklist);
 
   return api_url;
 }
diff --git a/chrome/browser/search/search_suggest/search_suggest_loader_impl.h b/chrome/browser/search/search_suggest/search_suggest_loader_impl.h
index b529650..cd9d5d0d 100644
--- a/chrome/browser/search/search_suggest/search_suggest_loader_impl.h
+++ b/chrome/browser/search/search_suggest/search_suggest_loader_impl.h
@@ -35,7 +35,7 @@
       const std::string& application_locale);
   ~SearchSuggestLoaderImpl() override;
 
-  void Load(const std::string& blacklist,
+  void Load(const std::string& blocklist,
             SearchSuggestionsCallback callback) override;
 
   GURL GetLoadURLForTesting() const override;
@@ -43,7 +43,7 @@
  private:
   class AuthenticatedURLLoader;
 
-  GURL GetApiUrl(const std::string& blacklist) const;
+  GURL GetApiUrl(const std::string& blocklist) const;
 
   void LoadDone(const network::SimpleURLLoader* simple_loader,
                 std::unique_ptr<std::string> response_body);
diff --git a/chrome/browser/search/search_suggest/search_suggest_loader_impl_unittest.cc b/chrome/browser/search/search_suggest/search_suggest_loader_impl_unittest.cc
index 50541cc..f5b5b20 100644
--- a/chrome/browser/search/search_suggest/search_suggest_loader_impl_unittest.cc
+++ b/chrome/browser/search/search_suggest/search_suggest_loader_impl_unittest.cc
@@ -141,8 +141,8 @@
   SetUpResponseWithData(kMinimalValidResponse);
 
   base::MockCallback<SearchSuggestLoader::SearchSuggestionsCallback> callback;
-  std::string blacklist;
-  search_suggest_loader()->Load(blacklist, callback.Get());
+  std::string blocklist;
+  search_suggest_loader()->Load(blocklist, callback.Get());
 
   base::Optional<SearchSuggestData> data;
   base::RunLoop loop;
@@ -159,8 +159,8 @@
   SetUpResponseWithData(std::string(")]}'") + kMinimalValidResponse);
 
   base::MockCallback<SearchSuggestLoader::SearchSuggestionsCallback> callback;
-  std::string blacklist;
-  search_suggest_loader()->Load(blacklist, callback.Get());
+  std::string blocklist;
+  search_suggest_loader()->Load(blocklist, callback.Get());
 
   base::Optional<SearchSuggestData> data;
   base::RunLoop loop;
@@ -179,8 +179,8 @@
             }}})json");
 
   base::MockCallback<SearchSuggestLoader::SearchSuggestionsCallback> callback;
-  std::string blacklist;
-  search_suggest_loader()->Load(blacklist, callback.Get());
+  std::string blocklist;
+  search_suggest_loader()->Load(blocklist, callback.Get());
 
   base::Optional<SearchSuggestData> data;
   base::RunLoop loop;
@@ -202,11 +202,11 @@
   // Trigger two requests.
   base::MockCallback<SearchSuggestLoader::SearchSuggestionsCallback>
       first_callback;
-  std::string blacklist;
-  search_suggest_loader()->Load(blacklist, first_callback.Get());
+  std::string blocklist;
+  search_suggest_loader()->Load(blocklist, first_callback.Get());
   base::MockCallback<SearchSuggestLoader::SearchSuggestionsCallback>
       second_callback;
-  search_suggest_loader()->Load(blacklist, second_callback.Get());
+  search_suggest_loader()->Load(blocklist, second_callback.Get());
 
   // Make sure that a single response causes both callbacks to be called.
   base::Optional<SearchSuggestData> first_data;
@@ -228,8 +228,8 @@
   SetUpResponseWithNetworkError();
 
   base::MockCallback<SearchSuggestLoader::SearchSuggestionsCallback> callback;
-  std::string blacklist;
-  search_suggest_loader()->Load(blacklist, callback.Get());
+  std::string blocklist;
+  search_suggest_loader()->Load(blocklist, callback.Get());
 
   base::RunLoop loop;
   EXPECT_CALL(callback, Run(SearchSuggestLoader::Status::TRANSIENT_ERROR,
@@ -243,8 +243,8 @@
   SetUpResponseWithData(kMinimalValidResponse + std::string(")"));
 
   base::MockCallback<SearchSuggestLoader::SearchSuggestionsCallback> callback;
-  std::string blacklist;
-  search_suggest_loader()->Load(blacklist, callback.Get());
+  std::string blocklist;
+  search_suggest_loader()->Load(blocklist, callback.Get());
 
   base::RunLoop loop;
   EXPECT_CALL(callback,
@@ -257,8 +257,8 @@
   SetUpResponseWithData(R"json({"update": {}})json");
 
   base::MockCallback<SearchSuggestLoader::SearchSuggestionsCallback> callback;
-  std::string blacklist;
-  search_suggest_loader()->Load(blacklist, callback.Get());
+  std::string blocklist;
+  search_suggest_loader()->Load(blocklist, callback.Get());
 
   base::RunLoop loop;
   EXPECT_CALL(callback,
diff --git a/chrome/browser/search/search_suggest/search_suggest_service.cc b/chrome/browser/search/search_suggest/search_suggest_service.cc
index 731cf981..c2e2dae 100644
--- a/chrome/browser/search/search_suggest/search_suggest_service.cc
+++ b/chrome/browser/search/search_suggest/search_suggest_service.cc
@@ -8,13 +8,31 @@
 
 #include "base/bind.h"
 #include "base/callback.h"
+#include "base/strings/string_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/search/search.h"
 #include "chrome/browser/search/search_suggest/search_suggest_loader.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/scoped_user_pref_update.h"
 #include "services/identity/public/cpp/identity_manager.h"
+#include "third_party/re2/src/re2/re2.h"
 
 namespace {
 
+constexpr char kSuggestionHashRegex[] = "[a-z0-9]{1,4}";
+
+std::string* ValidateHash(const uint8_t hash[4]) {
+  const std::string hash_string = reinterpret_cast<const char*>(hash);
+
+  std::string* trimmed_string = new std::string("");
+  // The uint8_t array received via IPC ends in an EOT byte (\4), remove it.
+  base::TrimString(hash_string, "\4", trimmed_string);
+
+  if (!re2::RE2::FullMatch(*trimmed_string, kSuggestionHashRegex))
+    return nullptr;
+  return trimmed_string;
+}
+
 const char kFirstShownTimeMs[] = "first_shown_time_ms";
 const char kImpressionCapExpireTimeMs[] = "impression_cap_expire_time_ms";
 const char kImpressionsCount[] = "impressions_count";
@@ -72,7 +90,7 @@
 };
 
 SearchSuggestService::SearchSuggestService(
-    PrefService* pref_service,
+    Profile* profile,
     identity::IdentityManager* identity_manager,
     std::unique_ptr<SearchSuggestLoader> loader)
     : loader_(std::move(loader)),
@@ -80,7 +98,7 @@
           identity_manager,
           base::BindRepeating(&SearchSuggestService::SigninStatusChanged,
                               base::Unretained(this)))),
-      pref_service_(pref_service) {}
+      profile_(profile) {}
 
 SearchSuggestService::~SearchSuggestService() = default;
 
@@ -94,10 +112,17 @@
 }
 
 void SearchSuggestService::Refresh() {
+  const std::string blocklist = GetBlocklistAsString();
+  MaybeLoadWithBlocklist(blocklist);
+}
+
+void SearchSuggestService::MaybeLoadWithBlocklist(
+    const std::string& blocklist) {
   if (!signin_observer_->SignedIn()) {
     SearchSuggestDataLoaded(SearchSuggestLoader::Status::SIGNED_OUT,
                             base::nullopt);
-  } else if (pref_service_->GetBoolean(prefs::kNtpSearchSuggestionsOptOut)) {
+  } else if (profile_->GetPrefs()->GetBoolean(
+                 prefs::kNtpSearchSuggestionsOptOut)) {
     SearchSuggestDataLoaded(SearchSuggestLoader::Status::OPTED_OUT,
                             base::nullopt);
   } else if (RequestsFrozen()) {
@@ -107,8 +132,7 @@
     SearchSuggestDataLoaded(SearchSuggestLoader::Status::IMPRESSION_CAP,
                             base::nullopt);
   } else {
-    const std::string blacklist = GetBlacklistAsString();
-    loader_->Load(blacklist,
+    loader_->Load(blocklist,
                   base::BindOnce(&SearchSuggestService::SearchSuggestDataLoaded,
                                  base::Unretained(this)));
   }
@@ -139,7 +163,7 @@
     search_suggest_data_ = data;
     search_suggest_status_ = status;
 
-    DictionaryPrefUpdate update(pref_service_,
+    DictionaryPrefUpdate update(profile_->GetPrefs(),
                                 prefs::kNtpSearchSuggestionsImpressions);
 
     if (data.has_value()) {
@@ -164,8 +188,8 @@
 }
 
 bool SearchSuggestService::ImpressionCapReached() {
-  const base::DictionaryValue* dict =
-      pref_service_->GetDictionary(prefs::kNtpSearchSuggestionsImpressions);
+  const base::DictionaryValue* dict = profile_->GetPrefs()->GetDictionary(
+      prefs::kNtpSearchSuggestionsImpressions);
 
   int first_shown_time_ms = 0;
   int impression_cap_expire_time_ms = 0;
@@ -182,7 +206,7 @@
           .InMilliseconds();
   if (time_delta > impression_cap_expire_time_ms) {
     impression_count = 0;
-    DictionaryPrefUpdate update(pref_service_,
+    DictionaryPrefUpdate update(profile_->GetPrefs(),
                                 prefs::kNtpSearchSuggestionsImpressions);
     update.Get()->SetInteger(kImpressionsCount, impression_count);
   }
@@ -191,8 +215,8 @@
 }
 
 bool SearchSuggestService::RequestsFrozen() {
-  const base::DictionaryValue* dict =
-      pref_service_->GetDictionary(prefs::kNtpSearchSuggestionsImpressions);
+  const base::DictionaryValue* dict = profile_->GetPrefs()->GetDictionary(
+      prefs::kNtpSearchSuggestionsImpressions);
 
   bool is_request_frozen = false;
   int request_freeze_time_ms = 0;
@@ -209,7 +233,7 @@
     if (time_delta < request_freeze_time_ms) {
       return true;
     } else {
-      DictionaryPrefUpdate update(pref_service_,
+      DictionaryPrefUpdate update(profile_->GetPrefs(),
                                   prefs::kNtpSearchSuggestionsImpressions);
       update.Get()->SetBoolean(kIsRequestFrozen, false);
     }
@@ -218,46 +242,79 @@
   return false;
 }
 
-void SearchSuggestService::BlacklistSearchSuggestion(int task_version,
+void SearchSuggestService::BlocklistSearchSuggestion(int task_version,
                                                      long task_id) {
+  if (!search::DefaultSearchProviderIsGoogle(profile_))
+    return;
+
   std::string task_version_id =
       std::to_string(task_version) + "_" + std::to_string(task_id);
-  DictionaryPrefUpdate update(pref_service_,
-                              prefs::kNtpSearchSuggestionsBlacklist);
-  base::DictionaryValue* blacklist = update.Get();
-  blacklist->SetKey(task_version_id, base::ListValue());
+  DictionaryPrefUpdate update(profile_->GetPrefs(),
+                              prefs::kNtpSearchSuggestionsBlocklist);
+  base::DictionaryValue* blocklist = update.Get();
+  blocklist->SetKey(task_version_id, base::ListValue());
 
   search_suggest_data_ = base::nullopt;
   Refresh();
 }
 
-void SearchSuggestService::BlacklistSearchSuggestionWithHash(
+void SearchSuggestService::BlocklistSearchSuggestionWithHash(
     int task_version,
     long task_id,
-    const std::vector<uint8_t>& hash) {
+    const uint8_t hash[4]) {
+  if (!search::DefaultSearchProviderIsGoogle(profile_))
+    return;
+
+  std::string* hash_string = ValidateHash(hash);
+
+  if (!hash_string)
+    return;
+
   std::string task_version_id =
       std::to_string(task_version) + "_" + std::to_string(task_id);
-  std::string hash_string;
-  hash_string.assign(hash.begin(), hash.end());
-  DictionaryPrefUpdate update(pref_service_,
-                              prefs::kNtpSearchSuggestionsBlacklist);
-  base::DictionaryValue* blacklist = update.Get();
-  base::Value* value = blacklist->FindKey(task_version_id);
+
+  DictionaryPrefUpdate update(profile_->GetPrefs(),
+                              prefs::kNtpSearchSuggestionsBlocklist);
+  base::DictionaryValue* blocklist = update.Get();
+  base::Value* value = blocklist->FindKey(task_version_id);
   if (!value)
-    value = blacklist->SetKey(task_version_id, base::ListValue());
-  value->GetList().emplace_back(base::Value(hash_string));
+    value = blocklist->SetKey(task_version_id, base::ListValue());
+  value->GetList().emplace_back(base::Value(*hash_string));
 
   search_suggest_data_ = base::nullopt;
   Refresh();
 }
 
-std::string SearchSuggestService::GetBlacklistAsString() {
-  const base::DictionaryValue* blacklist =
-      pref_service_->GetDictionary(prefs::kNtpSearchSuggestionsBlacklist);
+void SearchSuggestService::SearchSuggestionSelected(int task_version,
+                                                    long task_id,
+                                                    const uint8_t hash[4]) {
+  if (!search::DefaultSearchProviderIsGoogle(profile_))
+    return;
 
-  std::string blacklist_as_string;
-  for (const auto& dict : blacklist->DictItems()) {
-    blacklist_as_string += dict.first;
+  std::string* hash_string = ValidateHash(hash);
+
+  if (!hash_string)
+    return;
+
+  std::string blocklist_item = std::to_string(task_version) + "_" +
+                               std::to_string(task_id) + ":" + *hash_string;
+
+  std::string blocklist = GetBlocklistAsString();
+  if (!blocklist.empty())
+    blocklist += ";";
+  blocklist += blocklist_item;
+
+  search_suggest_data_ = base::nullopt;
+  MaybeLoadWithBlocklist(blocklist);
+}
+
+std::string SearchSuggestService::GetBlocklistAsString() {
+  const base::DictionaryValue* blocklist = profile_->GetPrefs()->GetDictionary(
+      prefs::kNtpSearchSuggestionsBlocklist);
+
+  std::string blocklist_as_string;
+  for (const auto& dict : blocklist->DictItems()) {
+    blocklist_as_string += dict.first;
 
     if (!dict.second.GetList().empty()) {
       std::string list = ":";
@@ -268,22 +325,22 @@
 
       // Remove trailing comma.
       list.pop_back();
-      blacklist_as_string += list;
+      blocklist_as_string += list;
     }
 
-    blacklist_as_string += ";";
+    blocklist_as_string += ";";
   }
 
   // Remove trailing semi-colon.
-  if (!blacklist_as_string.empty())
-    blacklist_as_string.pop_back();
-  return blacklist_as_string;
+  if (!blocklist_as_string.empty())
+    blocklist_as_string.pop_back();
+  return blocklist_as_string;
 }
 
 void SearchSuggestService::SuggestionsDisplayed() {
   search_suggest_data_ = base::nullopt;
 
-  DictionaryPrefUpdate update(pref_service_,
+  DictionaryPrefUpdate update(profile_->GetPrefs(),
                               prefs::kNtpSearchSuggestionsImpressions);
   base::DictionaryValue* dict = update.Get();
 
@@ -298,14 +355,17 @@
 }
 
 void SearchSuggestService::OptOutOfSearchSuggestions() {
-  pref_service_->SetBoolean(prefs::kNtpSearchSuggestionsOptOut, true);
+  if (!search::DefaultSearchProviderIsGoogle(profile_))
+    return;
+
+  profile_->GetPrefs()->SetBoolean(prefs::kNtpSearchSuggestionsOptOut, true);
 
   search_suggest_data_ = base::nullopt;
 }
 
 // static
 void SearchSuggestService::RegisterProfilePrefs(PrefRegistrySimple* registry) {
-  registry->RegisterDictionaryPref(prefs::kNtpSearchSuggestionsBlacklist);
+  registry->RegisterDictionaryPref(prefs::kNtpSearchSuggestionsBlocklist);
   registry->RegisterDictionaryPref(prefs::kNtpSearchSuggestionsImpressions,
                                    ImpressionDictDefaults());
   registry->RegisterBooleanPref(prefs::kNtpSearchSuggestionsOptOut, false);
diff --git a/chrome/browser/search/search_suggest/search_suggest_service.h b/chrome/browser/search/search_suggest/search_suggest_service.h
index ebb8c5b..513eb9d 100644
--- a/chrome/browser/search/search_suggest/search_suggest_service.h
+++ b/chrome/browser/search/search_suggest/search_suggest_service.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_SEARCH_SEARCH_SUGGEST_SEARCH_SUGGEST_SERVICE_H_
 
 #include <memory>
+#include <string>
 
 #include "base/observer_list.h"
 #include "base/optional.h"
@@ -16,6 +17,8 @@
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 
+class Profile;
+
 namespace identity {
 class IdentityManager;
 }  // namespace identity
@@ -25,7 +28,7 @@
 // user signs in or out, the cached value is cleared.
 class SearchSuggestService : public KeyedService {
  public:
-  SearchSuggestService(PrefService* pref_service,
+  SearchSuggestService(Profile* profile,
                        identity::IdentityManager* identity_manager,
                        std::unique_ptr<SearchSuggestLoader> loader);
   ~SearchSuggestService() override;
@@ -57,13 +60,13 @@
   // Register prefs associated with the NTP.
   static void RegisterProfilePrefs(PrefRegistrySimple* registry);
 
-  // Add the task_id to the blacklist stored in user prefs. Overrides any
+  // Add the task_id to the blocklist stored in user prefs. Overrides any
   // existing entry for the given task_id.
   //
   // A task_id represents a category of searches such as "Camping", a
   // task_version represents the selection criteria used to generate the
   // suggestion.
-  void BlacklistSearchSuggestion(int task_version, long task_id);
+  void BlocklistSearchSuggestion(int task_version, long task_id);
 
   // Add the hash to the list of hashes for the task_id. Stored as a
   // dict of task_ids to lists of hashes in user prefs.
@@ -72,9 +75,17 @@
   // is a specific search within the category such as "Camping equipment", and
   // a task_version represents the selection criteria used ti generate the
   // suggestion.
-  void BlacklistSearchSuggestionWithHash(int task_version,
+  void BlocklistSearchSuggestionWithHash(int task_version,
                                          long task_id,
-                                         const std::vector<uint8_t>& hash);
+                                         const uint8_t hash[4]);
+
+  // Issue a new request with the selected suggestion appended to the blocklist
+  // but NOT stored in user prefs.  This prevents a race condition where the
+  // request completes before the data server side is updated to reflect the
+  // selection, resulting in the same suggestion appearing in the next set.
+  void SearchSuggestionSelected(int task_version,
+                                long task_id,
+                                const uint8_t hash[4]);
 
   // Opt the current profile out of seeing search suggestions. Requests will
   // no longer be made.
@@ -82,9 +93,9 @@
 
   SearchSuggestLoader* loader_for_testing() { return loader_.get(); }
 
-  // Returns the string representation of the suggestions blacklist in the form:
+  // Returns the string representation of the suggestions blocklist in the form:
   // "task_id1:hash1,hash2,hash3;task_id2;task_id3:hash1,hash2".
-  std::string GetBlacklistAsString();
+  std::string GetBlocklistAsString();
 
   // Called when suggestions are displayed on the NTP, clears the cached data
   // and updates timestamps and impression counts.
@@ -95,6 +106,10 @@
 
   void SigninStatusChanged();
 
+  // Either calls SearchSuggestLoader::Load with |blocklist| or immediately
+  // calls SearchSuggestDataLoaded with the reason a request was not made.
+  void MaybeLoadWithBlocklist(const std::string& blocklist);
+
   // Called when a Refresh() is requested. If |status|==OK, |data| will contain
   // the fetched data. Otherwise |data| will be nullopt and |status| will
   // indicate if the request failed or the reason it was not sent.
@@ -121,7 +136,7 @@
 
   std::unique_ptr<SigninObserver> signin_observer_;
 
-  PrefService* pref_service_;
+  Profile* profile_;
 
   base::ObserverList<SearchSuggestServiceObserver, true>::Unchecked observers_;
 
diff --git a/chrome/browser/search/search_suggest/search_suggest_service_factory.cc b/chrome/browser/search/search_suggest/search_suggest_service_factory.cc
index 0d854ba..131d0a8 100644
--- a/chrome/browser/search/search_suggest/search_suggest_service_factory.cc
+++ b/chrome/browser/search/search_suggest/search_suggest_service_factory.cc
@@ -54,7 +54,6 @@
     return nullptr;
   }
   Profile* profile = Profile::FromBrowserContext(context);
-  PrefService* pref_service = profile->GetPrefs();
   identity::IdentityManager* identity_manager =
       IdentityManagerFactory::GetForProfile(profile);
   GoogleURLTracker* google_url_tracker =
@@ -63,7 +62,7 @@
       content::BrowserContext::GetDefaultStoragePartition(context)
           ->GetURLLoaderFactoryForBrowserProcess();
   return new SearchSuggestService(
-      pref_service, identity_manager,
+      profile, identity_manager,
       std::make_unique<SearchSuggestLoaderImpl>(
           url_loader_factory, google_url_tracker,
           g_browser_process->GetApplicationLocale()));
diff --git a/chrome/browser/search/search_suggest/search_suggest_service_unittest.cc b/chrome/browser/search/search_suggest/search_suggest_service_unittest.cc
index 22a1fc2..5e0f55c 100644
--- a/chrome/browser/search/search_suggest/search_suggest_service_unittest.cc
+++ b/chrome/browser/search/search_suggest/search_suggest_service_unittest.cc
@@ -9,10 +9,15 @@
 #include <vector>
 
 #include "base/optional.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_task_environment.h"
 #include "chrome/browser/search/search_suggest/search_suggest_data.h"
 #include "chrome/browser/search/search_suggest/search_suggest_loader.h"
+#include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/common/pref_names.h"
+#include "chrome/test/base/browser_with_test_window_test.h"
+#include "chrome/test/base/search_test_utils.h"
+#include "components/search_engines/template_url_service.h"
 #include "components/signin/core/browser/account_tracker_service.h"
 #include "components/signin/core/browser/test_signin_client.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
@@ -21,7 +26,6 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using testing::Eq;
 using testing::InSequence;
 using testing::StrictMock;
 
@@ -47,35 +51,63 @@
   std::vector<SearchSuggestionsCallback> callbacks_;
 };
 
-class SearchSuggestServiceTest : public testing::Test {
+class SearchSuggestServiceTest : public BrowserWithTestWindowTest {
  public:
-  SearchSuggestServiceTest()
-      : identity_env_(&test_url_loader_factory_, &pref_service_) {
-    SearchSuggestService::RegisterProfilePrefs(pref_service_.registry());
+  SearchSuggestServiceTest() {}
+  ~SearchSuggestServiceTest() override {}
 
+  void SetUp() override {
+    BrowserWithTestWindowTest::SetUp();
+
+    TemplateURLServiceFactory::GetInstance()->SetTestingFactoryAndUse(
+        profile(),
+        base::BindRepeating(&TemplateURLServiceFactory::BuildInstanceFor));
+    template_url_service_ = TemplateURLServiceFactory::GetForProfile(profile());
+    search_test_utils::WaitForTemplateURLServiceToLoad(template_url_service_);
+
+    identity_env_ = std::make_unique<identity::IdentityTestEnvironment>(
+        &test_url_loader_factory_);
     auto loader = std::make_unique<FakeSearchSuggestLoader>();
     loader_ = loader.get();
     service_ = std::make_unique<SearchSuggestService>(
-        &pref_service_, identity_env_.identity_manager(), std::move(loader));
+        profile(), identity_env_->identity_manager(), std::move(loader));
 
-    identity_env_.MakePrimaryAccountAvailable("example@gmail.com");
-    identity_env_.SetAutomaticIssueOfAccessTokens(true);
+    identity_env_->MakePrimaryAccountAvailable("example@gmail.com");
+    identity_env_->SetAutomaticIssueOfAccessTokens(true);
+  }
+
+  void TearDown() override { BrowserWithTestWindowTest::TearDown(); }
+
+  TestingProfile* CreateProfile() override {
+    TestingProfile* profile = BrowserWithTestWindowTest::CreateProfile();
+    return profile;
   }
 
   FakeSearchSuggestLoader* loader() { return loader_; }
   SearchSuggestService* service() { return service_.get(); }
   sync_preferences::TestingPrefServiceSyncable* pref_service() {
-    return &pref_service_;
+    return profile()->GetTestingPrefService();
   }
 
   void SignIn() {
     AccountInfo account_info =
-        identity_env_.MakeAccountAvailable("test@email.com");
-    identity_env_.SetCookieAccounts({{account_info.email, account_info.gaia}});
+        identity_env_->MakeAccountAvailable("test@email.com");
+    identity_env_->SetCookieAccounts({{account_info.email, account_info.gaia}});
   }
 
-  void SignOut() {
-    identity_env_.SetCookieAccounts({});
+  void SignOut() { identity_env_->SetCookieAccounts({}); }
+
+  void SetUserSelectedDefaultSearchProvider(const std::string& base_url) {
+    TemplateURLData data;
+    data.SetShortName(base::UTF8ToUTF16(base_url));
+    data.SetKeyword(base::UTF8ToUTF16(base_url));
+    data.SetURL(base_url + "url?bar={searchTerms}");
+    data.new_tab_url = base_url + "newtab";
+    data.alternate_urls.push_back(base_url + "alt#quux={searchTerms}");
+
+    TemplateURL* template_url =
+        template_url_service_->Add(std::make_unique<TemplateURL>(data));
+    template_url_service_->SetUserSelectedDefaultSearchProvider(template_url);
   }
 
   // Returns a default data object for testing, initializes the impression
@@ -99,11 +131,9 @@
   }
 
  private:
-  base::test::ScopedTaskEnvironment task_environment_;
-
-  sync_preferences::TestingPrefServiceSyncable pref_service_;
+  TemplateURLService* template_url_service_;
   network::TestURLLoaderFactory test_url_loader_factory_;
-  identity::IdentityTestEnvironment identity_env_;
+  std::unique_ptr<identity::IdentityTestEnvironment> identity_env_;
 
   // Owned by the service.
   FakeSearchSuggestLoader* loader_;
@@ -112,158 +142,241 @@
 };
 
 TEST_F(SearchSuggestServiceTest, NoRefreshOnSignedOutRequest) {
-  ASSERT_THAT(service()->search_suggest_data(), Eq(base::nullopt));
+  ASSERT_EQ(base::nullopt, service()->search_suggest_data());
 
   // Request a refresh. That should do nothing as no user is signed-in.
   service()->Refresh();
-  EXPECT_THAT(loader()->GetCallbackCount(), Eq(0u));
-  EXPECT_THAT(service()->search_suggest_data(), Eq(base::nullopt));
+  EXPECT_EQ(0u, loader()->GetCallbackCount());
+  EXPECT_EQ(base::nullopt, service()->search_suggest_data());
 }
 
 TEST_F(SearchSuggestServiceTest, RefreshesOnSignedInRequest) {
-  ASSERT_THAT(service()->search_suggest_data(), Eq(base::nullopt));
+  ASSERT_EQ(base::nullopt, service()->search_suggest_data());
   SignIn();
 
   // Request a refresh. That should arrive at the loader.
   service()->Refresh();
-  EXPECT_THAT(loader()->GetCallbackCount(), Eq(1u));
+  EXPECT_EQ(1u, loader()->GetCallbackCount());
 
   // Fulfill it.
   SearchSuggestData data = TestSearchSuggestData();
   loader()->RespondToAllCallbacks(SearchSuggestLoader::Status::OK, data);
-  EXPECT_THAT(service()->search_suggest_data(), Eq(data));
+  EXPECT_EQ(data, service()->search_suggest_data());
 
   // Request another refresh.
   service()->Refresh();
-  EXPECT_THAT(loader()->GetCallbackCount(), Eq(1u));
+  EXPECT_EQ(1u, loader()->GetCallbackCount());
 
   // For now, the old data should still be there.
-  EXPECT_THAT(service()->search_suggest_data(), Eq(data));
+  EXPECT_EQ(data, service()->search_suggest_data());
 
   // Fulfill the second request.
   SearchSuggestData other_data = TestSearchSuggestData();
   loader()->RespondToAllCallbacks(SearchSuggestLoader::Status::OK, other_data);
-  EXPECT_THAT(service()->search_suggest_data(), Eq(other_data));
+  EXPECT_EQ(other_data, service()->search_suggest_data());
 }
 
 TEST_F(SearchSuggestServiceTest, KeepsCacheOnTransientError) {
-  ASSERT_THAT(service()->search_suggest_data(), Eq(base::nullopt));
+  ASSERT_EQ(base::nullopt, service()->search_suggest_data());
   SignIn();
 
   // Load some data.
   service()->Refresh();
   SearchSuggestData data = TestSearchSuggestData();
   loader()->RespondToAllCallbacks(SearchSuggestLoader::Status::OK, data);
-  ASSERT_THAT(service()->search_suggest_data(), Eq(data));
+  ASSERT_EQ(data, service()->search_suggest_data());
 
   // Request a refresh and respond with a transient error.
   service()->Refresh();
   loader()->RespondToAllCallbacks(SearchSuggestLoader::Status::TRANSIENT_ERROR,
                                   base::nullopt);
   // Cached data should still be there.
-  EXPECT_THAT(service()->search_suggest_data(), Eq(data));
+  EXPECT_EQ(data, service()->search_suggest_data());
 }
 
 TEST_F(SearchSuggestServiceTest, ClearsCacheOnFatalError) {
-  ASSERT_THAT(service()->search_suggest_data(), Eq(base::nullopt));
+  ASSERT_EQ(base::nullopt, service()->search_suggest_data());
   SignIn();
 
   // Load some data.
   service()->Refresh();
   SearchSuggestData data = TestSearchSuggestData();
   loader()->RespondToAllCallbacks(SearchSuggestLoader::Status::OK, data);
-  ASSERT_THAT(service()->search_suggest_data(), Eq(data));
+  ASSERT_EQ(data, service()->search_suggest_data());
 
   // Request a refresh and respond with a fatal error.
   service()->Refresh();
   loader()->RespondToAllCallbacks(SearchSuggestLoader::Status::FATAL_ERROR,
                                   base::nullopt);
   // Cached data should be gone now.
-  EXPECT_THAT(service()->search_suggest_data(), Eq(base::nullopt));
+  EXPECT_EQ(base::nullopt, service()->search_suggest_data());
 }
 
 TEST_F(SearchSuggestServiceTest, ResetsOnSignOut) {
-  ASSERT_THAT(service()->search_suggest_data(), Eq(base::nullopt));
+  ASSERT_EQ(base::nullopt, service()->search_suggest_data());
   SignIn();
 
   // Load some data.
   service()->Refresh();
   SearchSuggestData data = TestSearchSuggestData();
   loader()->RespondToAllCallbacks(SearchSuggestLoader::Status::OK, data);
-  ASSERT_THAT(service()->search_suggest_data(), Eq(data));
+  ASSERT_EQ(data, service()->search_suggest_data());
 
   // Sign out. This should clear the cached data and notify the observer.
   SignOut();
-  EXPECT_THAT(service()->search_suggest_data(), Eq(base::nullopt));
+  EXPECT_EQ(base::nullopt, service()->search_suggest_data());
 }
 
-TEST_F(SearchSuggestServiceTest, BlacklistSuggestionUpdatesBlacklistString) {
-  ASSERT_THAT(service()->GetBlacklistAsString(), Eq(std::string()));
+TEST_F(SearchSuggestServiceTest, BlocklistSuggestionUpdatesBlocklistString) {
+  SetUserSelectedDefaultSearchProvider("{google:baseURL}");
+  ASSERT_EQ(std::string(), service()->GetBlocklistAsString());
 
-  std::vector<uint8_t> hash1 = {'a', 'b', 'c', 'd'};
-  std::vector<uint8_t> hash2 = {'e', 'f', 'g', 'h'};
-  service()->BlacklistSearchSuggestionWithHash(0, 1234, hash1);
-  service()->BlacklistSearchSuggestion(2, 5678);
-  service()->BlacklistSearchSuggestionWithHash(1, 1234, hash2);
-  service()->BlacklistSearchSuggestionWithHash(2, 1234, hash1);
-  service()->BlacklistSearchSuggestion(4, 1234);
-  service()->BlacklistSearchSuggestionWithHash(2, 1234, hash2);
-  service()->BlacklistSearchSuggestionWithHash(0, 1234, hash2);
+  uint8_t hash1[5] = {'a', 'b', 'c', 'd', '\0'};
+  uint8_t hash2[5] = {'e', 'f', 'g', 'h', '\0'};
+  service()->BlocklistSearchSuggestionWithHash(0, 1234, hash1);
+  service()->BlocklistSearchSuggestion(2, 5678);
+  service()->BlocklistSearchSuggestionWithHash(1, 1234, hash2);
+  service()->BlocklistSearchSuggestionWithHash(2, 1234, hash1);
+  service()->BlocklistSearchSuggestion(4, 1234);
+  service()->BlocklistSearchSuggestionWithHash(2, 1234, hash2);
+  service()->BlocklistSearchSuggestionWithHash(0, 1234, hash2);
 
   std::string expected =
       "0_1234:abcd,efgh;1_1234:efgh;2_1234:abcd,efgh;2_5678;4_1234";
 
-  ASSERT_THAT(service()->GetBlacklistAsString(), Eq(expected));
+  ASSERT_EQ(expected, service()->GetBlocklistAsString());
+}
+
+TEST_F(SearchSuggestServiceTest, BlocklistUnchangedOnInvalidHash) {
+  SetUserSelectedDefaultSearchProvider("{google:baseURL}");
+  ASSERT_EQ(std::string(), service()->GetBlocklistAsString());
+
+  uint8_t hash1[5] = {'a', 'b', '?', 'd', '\0'};
+  uint8_t hash2[5] = {'a', '_', 'b', 'm', '\0'};
+  uint8_t hash3[5] = {'A', 'B', 'C', 'D', '\0'};
+  uint8_t hash4[6] = {'a', 'b', 'c', 'd', 'e', '\0'};
+  std::string expected = std::string();
+
+  service()->BlocklistSearchSuggestionWithHash(0, 1234, hash1);
+  service()->BlocklistSearchSuggestionWithHash(0, 1234, hash2);
+  service()->BlocklistSearchSuggestionWithHash(0, 1234, hash3);
+  service()->BlocklistSearchSuggestionWithHash(0, 1234, hash4);
+  ASSERT_EQ(expected, service()->GetBlocklistAsString());
+}
+
+TEST_F(SearchSuggestServiceTest, ShortHashUpdatesBlackist) {
+  SetUserSelectedDefaultSearchProvider("{google:baseURL}");
+  ASSERT_EQ(std::string(), service()->GetBlocklistAsString());
+
+  uint8_t hash1[4] = {'a', 'b', 'c', '\0'};
+  uint8_t hash2[5] = {'d', 'e', '\0', 'f', '\0'};
+  std::string expected = "0_1234:abc;1_5678:de";
+
+  service()->BlocklistSearchSuggestionWithHash(0, 1234, hash1);
+  service()->BlocklistSearchSuggestionWithHash(1, 5678, hash2);
+  ASSERT_EQ(expected, service()->GetBlocklistAsString());
 }
 
 TEST_F(SearchSuggestServiceTest,
-       BlacklistSuggestionOverridesBlackistSuggestionWithHash) {
-  ASSERT_THAT(service()->GetBlacklistAsString(), Eq(std::string()));
+       BlocklistSuggestionOverridesBlackistSuggestionWithHash) {
+  SetUserSelectedDefaultSearchProvider("{google:baseURL}");
+  ASSERT_EQ(std::string(), service()->GetBlocklistAsString());
 
-  std::vector<uint8_t> hash = {'a', 'b', 'c', 'd'};
-  service()->BlacklistSearchSuggestionWithHash(0, 1234, hash);
-  ASSERT_THAT(service()->GetBlacklistAsString(), Eq("0_1234:abcd"));
+  uint8_t hash[5] = {'a', 'b', 'c', 'd', '\0'};
+  service()->BlocklistSearchSuggestionWithHash(0, 1234, hash);
+  ASSERT_EQ("0_1234:abcd", service()->GetBlocklistAsString());
 
-  service()->BlacklistSearchSuggestion(0, 1234);
-  ASSERT_THAT(service()->GetBlacklistAsString(), Eq("0_1234"));
+  service()->BlocklistSearchSuggestion(0, 1234);
+  ASSERT_EQ("0_1234", service()->GetBlocklistAsString());
 }
 
-TEST_F(SearchSuggestServiceTest, BlacklistClearsCachedDataAndIssuesRequest) {
-  ASSERT_THAT(service()->search_suggest_data(), Eq(base::nullopt));
+TEST_F(SearchSuggestServiceTest, BlocklistClearsCachedDataAndIssuesRequest) {
+  SetUserSelectedDefaultSearchProvider("{google:baseURL}");
+  ASSERT_EQ(base::nullopt, service()->search_suggest_data());
   SignIn();
 
   // Request a refresh. That should arrive at the loader.
   service()->Refresh();
-  EXPECT_THAT(loader()->GetCallbackCount(), Eq(1u));
+  EXPECT_EQ(1u, loader()->GetCallbackCount());
 
   // Fulfill it.
   SearchSuggestData data = TestSearchSuggestData();
   loader()->RespondToAllCallbacks(SearchSuggestLoader::Status::OK, data);
-  EXPECT_THAT(service()->search_suggest_data(), Eq(data));
+  EXPECT_EQ(data, service()->search_suggest_data());
 
-  // Blacklist something.
-  std::vector<uint8_t> hash = {'a', 'b', 'c', 'd'};
-  service()->BlacklistSearchSuggestionWithHash(0, 1234, hash);
-  ASSERT_THAT(service()->GetBlacklistAsString(), Eq("0_1234:abcd"));
-  EXPECT_THAT(loader()->GetCallbackCount(), Eq(1u));
+  // Select a suggestion to blocklist.
+  uint8_t hash[5] = {'a', 'b', 'c', 'd', '\0'};
+  service()->BlocklistSearchSuggestionWithHash(0, 1234, hash);
+  ASSERT_EQ("0_1234:abcd", service()->GetBlocklistAsString());
+  EXPECT_EQ(1u, loader()->GetCallbackCount());
 
   // Fulfill the second request.
   SearchSuggestData other_data;
   other_data.suggestions_html = "<div>Different!</div>";
   loader()->RespondToAllCallbacks(SearchSuggestLoader::Status::OK, other_data);
-  EXPECT_THAT(service()->search_suggest_data(), Eq(other_data));
+  EXPECT_EQ(other_data, service()->search_suggest_data());
+}
+
+TEST_F(SearchSuggestServiceTest,
+       SuggestionSelectedClearsCachedDataAndIssuesRequest) {
+  SetUserSelectedDefaultSearchProvider("{google:baseURL}");
+  ASSERT_EQ(base::nullopt, service()->search_suggest_data());
+  SignIn();
+
+  // Request a refresh. That should arrive at the loader.
+  service()->Refresh();
+  EXPECT_EQ(1u, loader()->GetCallbackCount());
+
+  // Fulfill it.
+  SearchSuggestData data = TestSearchSuggestData();
+  loader()->RespondToAllCallbacks(SearchSuggestLoader::Status::OK, data);
+  EXPECT_EQ(data, service()->search_suggest_data());
+
+  // Select a suggestion to blocklist.
+  uint8_t hash[5] = {'a', 'b', 'c', 'd', '\0'};
+  service()->SearchSuggestionSelected(0, 1234, hash);
+
+  // The local blocklist should not be updated.
+  ASSERT_EQ(std::string(), service()->GetBlocklistAsString());
+  EXPECT_EQ(1u, loader()->GetCallbackCount());
+
+  // Fulfill the second request.
+  SearchSuggestData other_data;
+  other_data.suggestions_html = "<div>Different!</div>";
+  loader()->RespondToAllCallbacks(SearchSuggestLoader::Status::OK, other_data);
+  EXPECT_EQ(other_data, service()->search_suggest_data());
 }
 
 TEST_F(SearchSuggestServiceTest, OptOutPreventsRequests) {
-  ASSERT_THAT(service()->search_suggest_data(), Eq(base::nullopt));
+  SetUserSelectedDefaultSearchProvider("{google:baseURL}");
+  ASSERT_EQ(base::nullopt, service()->search_suggest_data());
   SignIn();
 
   service()->OptOutOfSearchSuggestions();
 
   // Request a refresh. That should do nothing as the user opted-out.
   service()->Refresh();
-  EXPECT_THAT(loader()->GetCallbackCount(), Eq(0u));
-  EXPECT_THAT(service()->search_suggest_data(), Eq(base::nullopt));
+  EXPECT_EQ(0u, loader()->GetCallbackCount());
+  EXPECT_EQ(base::nullopt, service()->search_suggest_data());
+}
+
+TEST_F(SearchSuggestServiceTest, SuggestionAPIsDoNothingWithNonGoogleDSP) {
+  SetUserSelectedDefaultSearchProvider("https://search.com/");
+  ASSERT_EQ(std::string(), service()->GetBlocklistAsString());
+
+  uint8_t hash[5] = {'a', 'b', 'c', 'd', '\0'};
+  service()->BlocklistSearchSuggestionWithHash(0, 1234, hash);
+  EXPECT_EQ(std::string(), service()->GetBlocklistAsString());
+
+  service()->BlocklistSearchSuggestion(1, 2345);
+  EXPECT_EQ(std::string(), service()->GetBlocklistAsString());
+
+  service()->OptOutOfSearchSuggestions();
+  EXPECT_FALSE(profile()->GetTestingPrefService()->GetBoolean(
+      prefs::kNtpSearchSuggestionsOptOut));
+
+  service()->SearchSuggestionSelected(0, 1234, hash);
+  EXPECT_EQ(0u, loader()->GetCallbackCount());
 }
 
 TEST_F(SearchSuggestServiceTest, UpdateImpressionCapParameters) {
diff --git a/chrome/browser/serial/OWNERS b/chrome/browser/serial/OWNERS
new file mode 100644
index 0000000..4ae83ca
--- /dev/null
+++ b/chrome/browser/serial/OWNERS
@@ -0,0 +1,3 @@
+file://content/browser/serial/OWNERS
+
+# COMPONENT: Blink>Serial
diff --git a/chrome/browser/serial/chrome_serial_delegate.cc b/chrome/browser/serial/chrome_serial_delegate.cc
new file mode 100644
index 0000000..b6dbbab
--- /dev/null
+++ b/chrome/browser/serial/chrome_serial_delegate.cc
@@ -0,0 +1,62 @@
+// 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/serial/chrome_serial_delegate.h"
+
+#include <utility>
+
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/serial/serial_chooser_context.h"
+#include "chrome/browser/serial/serial_chooser_context_factory.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/chrome_bubble_manager.h"
+#include "chrome/browser/ui/permission_bubble/chooser_bubble_delegate.h"
+#include "chrome/browser/ui/serial/serial_chooser.h"
+#include "chrome/browser/ui/serial/serial_chooser_controller.h"
+#include "content/public/browser/web_contents.h"
+
+ChromeSerialDelegate::ChromeSerialDelegate() = default;
+
+ChromeSerialDelegate::~ChromeSerialDelegate() = default;
+
+std::unique_ptr<content::SerialChooser> ChromeSerialDelegate::RunChooser(
+    content::RenderFrameHost* frame,
+    std::vector<blink::mojom::SerialPortFilterPtr> filters,
+    content::SerialChooser::Callback callback) {
+  Browser* browser = chrome::FindBrowserWithWebContents(
+      content::WebContents::FromRenderFrameHost(frame));
+  if (!browser) {
+    std::move(callback).Run(nullptr);
+    return nullptr;
+  }
+
+  auto chooser_controller = std::make_unique<SerialChooserController>(
+      frame, std::move(filters), std::move(callback));
+  auto chooser_bubble_delegate = std::make_unique<ChooserBubbleDelegate>(
+      frame, std::move(chooser_controller));
+  BubbleReference bubble_reference = browser->GetBubbleManager()->ShowBubble(
+      std::move(chooser_bubble_delegate));
+  return std::make_unique<SerialChooser>(std::move(bubble_reference));
+}
+
+bool ChromeSerialDelegate::HasPortPermission(
+    content::RenderFrameHost* frame,
+    const device::mojom::SerialPortInfo& port) {
+  auto* web_contents = content::WebContents::FromRenderFrameHost(frame);
+  auto* profile =
+      Profile::FromBrowserContext(web_contents->GetBrowserContext());
+  auto* chooser_context = SerialChooserContextFactory::GetForProfile(profile);
+  return chooser_context->HasPortPermission(
+      frame->GetLastCommittedOrigin(),
+      web_contents->GetMainFrame()->GetLastCommittedOrigin(), port);
+}
+
+device::mojom::SerialPortManager* ChromeSerialDelegate::GetPortManager(
+    content::RenderFrameHost* frame) {
+  auto* web_contents = content::WebContents::FromRenderFrameHost(frame);
+  auto* profile =
+      Profile::FromBrowserContext(web_contents->GetBrowserContext());
+  auto* chooser_context = SerialChooserContextFactory::GetForProfile(profile);
+  return chooser_context->GetPortManager();
+}
diff --git a/chrome/browser/serial/chrome_serial_delegate.h b/chrome/browser/serial/chrome_serial_delegate.h
new file mode 100644
index 0000000..2ca3730
--- /dev/null
+++ b/chrome/browser/serial/chrome_serial_delegate.h
@@ -0,0 +1,31 @@
+// 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_SERIAL_CHROME_SERIAL_DELEGATE_H_
+#define CHROME_BROWSER_SERIAL_CHROME_SERIAL_DELEGATE_H_
+
+#include <memory>
+#include <vector>
+
+#include "content/public/browser/serial_delegate.h"
+
+class ChromeSerialDelegate : public content::SerialDelegate {
+ public:
+  ChromeSerialDelegate();
+  ~ChromeSerialDelegate() override;
+
+  std::unique_ptr<content::SerialChooser> RunChooser(
+      content::RenderFrameHost* frame,
+      std::vector<blink::mojom::SerialPortFilterPtr> filters,
+      content::SerialChooser::Callback callback) override;
+  bool HasPortPermission(content::RenderFrameHost* frame,
+                         const device::mojom::SerialPortInfo& port) override;
+  device::mojom::SerialPortManager* GetPortManager(
+      content::RenderFrameHost* frame) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ChromeSerialDelegate);
+};
+
+#endif  // CHROME_BROWSER_SERIAL_CHROME_SERIAL_DELEGATE_H_
diff --git a/chrome/browser/serial/serial_chooser_context.cc b/chrome/browser/serial/serial_chooser_context.cc
new file mode 100644
index 0000000..8c0611bce
--- /dev/null
+++ b/chrome/browser/serial/serial_chooser_context.cc
@@ -0,0 +1,215 @@
+// 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/serial/serial_chooser_context.h"
+
+#include <utility>
+
+#include "base/base64.h"
+#include "base/values.h"
+#include "chrome/browser/profiles/profile.h"
+#include "content/public/common/service_manager_connection.h"
+#include "services/device/public/mojom/constants.mojom.h"
+#include "services/service_manager/public/cpp/connector.h"
+
+namespace {
+
+constexpr char kPortNameKey[] = "name";
+constexpr char kTokenKey[] = "token";
+
+std::string EncodeToken(const base::UnguessableToken& token) {
+  const uint64_t data[2] = {token.GetHighForSerialization(),
+                            token.GetLowForSerialization()};
+  std::string buffer;
+  base::Base64Encode(
+      base::StringPiece(reinterpret_cast<const char*>(&data[0]), sizeof(data)),
+      &buffer);
+  return buffer;
+}
+
+base::UnguessableToken DecodeToken(base::StringPiece input) {
+  std::string buffer;
+  if (!base::Base64Decode(input, &buffer) ||
+      buffer.length() != sizeof(uint64_t) * 2) {
+    return base::UnguessableToken();
+  }
+
+  const uint64_t* data = reinterpret_cast<const uint64_t*>(buffer.data());
+  return base::UnguessableToken::Deserialize(data[0], data[1]);
+}
+
+base::Value PortInfoToValue(const device::mojom::SerialPortInfo& port) {
+  base::Value value(base::Value::Type::DICTIONARY);
+  if (port.display_name)
+    value.SetKey(kPortNameKey, base::Value(*port.display_name));
+  else
+    value.SetKey(kPortNameKey, base::Value(port.path.LossyDisplayName()));
+  value.SetKey(kTokenKey, base::Value(EncodeToken(port.token)));
+  return value;
+}
+
+}  // namespace
+
+SerialChooserContext::SerialChooserContext(Profile* profile)
+    : ChooserContextBase(profile,
+                         CONTENT_SETTINGS_TYPE_SERIAL_GUARD,
+                         CONTENT_SETTINGS_TYPE_SERIAL_CHOOSER_DATA),
+      is_incognito_(profile->IsOffTheRecord()) {}
+
+SerialChooserContext::~SerialChooserContext() = default;
+
+bool SerialChooserContext::IsValidObject(const base::DictionaryValue& object) {
+  const std::string* token = object.FindStringKey(kTokenKey);
+  return object.size() == 2 && object.FindStringKey(kPortNameKey) && token &&
+         DecodeToken(*token);
+}
+
+std::string SerialChooserContext::GetObjectName(
+    const base::DictionaryValue& object) {
+  DCHECK(IsValidObject(object));
+  return *object.FindStringKey(kPortNameKey);
+}
+
+std::vector<std::unique_ptr<ChooserContextBase::Object>>
+SerialChooserContext::GetGrantedObjects(const GURL& requesting_origin,
+                                        const GURL& embedding_origin) {
+  std::vector<std::unique_ptr<Object>> objects;
+  auto origin_it = ephemeral_ports_.find(
+      std::make_pair(url::Origin::Create(requesting_origin),
+                     url::Origin::Create(embedding_origin)));
+  if (origin_it == ephemeral_ports_.end())
+    return objects;
+  const std::set<base::UnguessableToken> ports = origin_it->second;
+
+  for (const auto& token : ports) {
+    auto it = port_info_.find(token);
+    if (it == port_info_.end())
+      continue;
+
+    // Object's constructor should take a base::Value directly.
+    base::Value clone = it->second.Clone();
+    base::DictionaryValue* object;
+    clone.GetAsDictionary(&object);
+
+    objects.push_back(std::make_unique<Object>(
+        requesting_origin, embedding_origin, object,
+        content_settings::SettingSource::SETTING_SOURCE_USER, is_incognito_));
+  }
+
+  return objects;
+}
+
+std::vector<std::unique_ptr<ChooserContextBase::Object>>
+SerialChooserContext::GetAllGrantedObjects() {
+  std::vector<std::unique_ptr<Object>> objects;
+  for (const auto& map_entry : ephemeral_ports_) {
+    GURL requesting_origin = map_entry.first.first.GetURL();
+    GURL embedding_origin = map_entry.first.second.GetURL();
+
+    if (!CanRequestObjectPermission(requesting_origin, embedding_origin))
+      continue;
+
+    for (const auto& token : map_entry.second) {
+      auto it = port_info_.find(token);
+      if (it == port_info_.end())
+        continue;
+
+      // Object's constructor should take a base::Value directly.
+      base::Value clone = it->second.Clone();
+      base::DictionaryValue* object;
+      clone.GetAsDictionary(&object);
+
+      objects.push_back(std::make_unique<Object>(
+          requesting_origin, embedding_origin, object,
+          content_settings::SettingSource::SETTING_SOURCE_USER, is_incognito_));
+    }
+  }
+
+  return objects;
+}
+
+void SerialChooserContext::RevokeObjectPermission(
+    const GURL& requesting_origin,
+    const GURL& embedding_origin,
+    const base::DictionaryValue& object) {
+  auto origin_it = ephemeral_ports_.find(
+      std::make_pair(url::Origin::Create(requesting_origin),
+                     url::Origin::Create(embedding_origin)));
+  if (origin_it == ephemeral_ports_.end())
+    return;
+  std::set<base::UnguessableToken>& ports = origin_it->second;
+
+  DCHECK(IsValidObject(object));
+  ports.erase(DecodeToken(*object.FindStringKey(kTokenKey)));
+}
+
+void SerialChooserContext::GrantPortPermission(
+    const url::Origin& requesting_origin,
+    const url::Origin& embedding_origin,
+    const device::mojom::SerialPortInfo& port) {
+  // TODO(crbug.com/908836): If |port| can be remembered persistently call into
+  // ChooserContextBase to store it in user preferences.
+  ephemeral_ports_[std::make_pair(requesting_origin, embedding_origin)].insert(
+      port.token);
+  port_info_[port.token] = PortInfoToValue(port);
+}
+
+bool SerialChooserContext::HasPortPermission(
+    const url::Origin& requesting_origin,
+    const url::Origin& embedding_origin,
+    const device::mojom::SerialPortInfo& port) {
+  if (!CanRequestObjectPermission(requesting_origin.GetURL(),
+                                  embedding_origin.GetURL())) {
+    return false;
+  }
+
+  auto origin_it = ephemeral_ports_.find(
+      std::make_pair(requesting_origin, embedding_origin));
+  if (origin_it == ephemeral_ports_.end())
+    return false;
+  const std::set<base::UnguessableToken> ports = origin_it->second;
+
+  // TODO(crbug.com/908836): Call into ChooserContextBase to check persistent
+  // permissions.
+  auto port_it = ports.find(port.token);
+  return port_it != ports.end();
+}
+
+device::mojom::SerialPortManager* SerialChooserContext::GetPortManager() {
+  EnsurePortManagerConnection();
+  return port_manager_.get();
+}
+
+void SerialChooserContext::SetPortManagerForTesting(
+    device::mojom::SerialPortManagerPtr manager) {
+  SetUpPortManagerConnection(std::move(manager));
+}
+
+base::WeakPtr<SerialChooserContext> SerialChooserContext::AsWeakPtr() {
+  return weak_factory_.GetWeakPtr();
+}
+
+void SerialChooserContext::EnsurePortManagerConnection() {
+  if (port_manager_)
+    return;
+
+  device::mojom::SerialPortManagerPtr manager;
+  content::ServiceManagerConnection::GetForProcess()
+      ->GetConnector()
+      ->BindInterface(device::mojom::kServiceName, mojo::MakeRequest(&manager));
+  SetUpPortManagerConnection(std::move(manager));
+}
+
+void SerialChooserContext::SetUpPortManagerConnection(
+    device::mojom::SerialPortManagerPtr manager) {
+  port_manager_ = std::move(manager);
+  port_manager_.set_connection_error_handler(
+      base::BindOnce(&SerialChooserContext::OnPortManagerConnectionError,
+                     base::Unretained(this)));
+}
+
+void SerialChooserContext::OnPortManagerConnectionError() {
+  port_info_.clear();
+  ephemeral_ports_.clear();
+}
diff --git a/chrome/browser/serial/serial_chooser_context.h b/chrome/browser/serial/serial_chooser_context.h
new file mode 100644
index 0000000..7079a73c
--- /dev/null
+++ b/chrome/browser/serial/serial_chooser_context.h
@@ -0,0 +1,86 @@
+// 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_SERIAL_SERIAL_CHOOSER_CONTEXT_H_
+#define CHROME_BROWSER_SERIAL_SERIAL_CHOOSER_CONTEXT_H_
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "base/unguessable_token.h"
+#include "chrome/browser/permissions/chooser_context_base.h"
+#include "services/device/public/mojom/serial.mojom.h"
+#include "third_party/blink/public/mojom/serial/serial.mojom.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+class SerialChooserContext : public ChooserContextBase {
+ public:
+  explicit SerialChooserContext(Profile* profile);
+  ~SerialChooserContext() override;
+
+  // ChooserContextBase implementation.
+  bool IsValidObject(const base::DictionaryValue& object) override;
+  std::string GetObjectName(const base::DictionaryValue& object) override;
+
+  // In addition these methods from ChooserContextBase are overridden in order
+  // to expose ephemeral devices through the public interface.
+  std::vector<std::unique_ptr<Object>> GetGrantedObjects(
+      const GURL& requesting_origin,
+      const GURL& embedding_origin) override;
+  std::vector<std::unique_ptr<Object>> GetAllGrantedObjects() override;
+  void RevokeObjectPermission(const GURL& requesting_origin,
+                              const GURL& embedding_origin,
+                              const base::DictionaryValue& object) override;
+
+  // Serial-specific interface for granting and checking permissions.
+  void GrantPortPermission(const url::Origin& requesting_origin,
+                           const url::Origin& embedding_origin,
+                           const device::mojom::SerialPortInfo& port);
+  bool HasPortPermission(const url::Origin& requesting_origin,
+                         const url::Origin& embedding_origin,
+                         const device::mojom::SerialPortInfo& port);
+
+  device::mojom::SerialPortManager* GetPortManager();
+
+  void SetPortManagerForTesting(device::mojom::SerialPortManagerPtr manager);
+  base::WeakPtr<SerialChooserContext> AsWeakPtr();
+
+ private:
+  void EnsurePortManagerConnection();
+  void SetUpPortManagerConnection(device::mojom::SerialPortManagerPtr manager);
+  void OnPortManagerConnectionError();
+  void OnGetPorts(const url::Origin& requesting_origin,
+                  const url::Origin& embedding_origin,
+                  blink::mojom::SerialService::GetPortsCallback callback,
+                  std::vector<device::mojom::SerialPortInfoPtr> ports);
+
+  const bool is_incognito_;
+
+  // Tracks the set of ports to which an origin (potentially embedded in another
+  // origin) has access to. Key is (requesting_origin, embedding_origin).
+  std::map<std::pair<url::Origin, url::Origin>,
+           std::set<base::UnguessableToken>>
+      ephemeral_ports_;
+
+  // Holds information about ports in |ephemeral_ports_|.
+  std::map<base::UnguessableToken, base::Value> port_info_;
+
+  device::mojom::SerialPortManagerPtr port_manager_;
+
+  base::WeakPtrFactory<SerialChooserContext> weak_factory_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(SerialChooserContext);
+};
+
+#endif  // CHROME_BROWSER_SERIAL_SERIAL_CHOOSER_CONTEXT_H_
diff --git a/chrome/browser/serial/serial_chooser_context_factory.cc b/chrome/browser/serial/serial_chooser_context_factory.cc
new file mode 100644
index 0000000..89c7e4c
--- /dev/null
+++ b/chrome/browser/serial/serial_chooser_context_factory.cc
@@ -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.
+
+#include "chrome/browser/serial/serial_chooser_context_factory.h"
+
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/profiles/incognito_helpers.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/serial/serial_chooser_context.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+
+SerialChooserContextFactory::SerialChooserContextFactory()
+    : BrowserContextKeyedServiceFactory(
+          "SerialChooserContext",
+          BrowserContextDependencyManager::GetInstance()) {
+  DependsOn(HostContentSettingsMapFactory::GetInstance());
+}
+
+SerialChooserContextFactory::~SerialChooserContextFactory() {}
+
+KeyedService* SerialChooserContextFactory::BuildServiceInstanceFor(
+    content::BrowserContext* context) const {
+  return new SerialChooserContext(Profile::FromBrowserContext(context));
+}
+
+// static
+SerialChooserContextFactory* SerialChooserContextFactory::GetInstance() {
+  return base::Singleton<SerialChooserContextFactory>::get();
+}
+
+// static
+SerialChooserContext* SerialChooserContextFactory::GetForProfile(
+    Profile* profile) {
+  return static_cast<SerialChooserContext*>(
+      GetInstance()->GetServiceForBrowserContext(profile, true));
+}
+
+content::BrowserContext* SerialChooserContextFactory::GetBrowserContextToUse(
+    content::BrowserContext* context) const {
+  return chrome::GetBrowserContextOwnInstanceInIncognito(context);
+}
diff --git a/chrome/browser/serial/serial_chooser_context_factory.h b/chrome/browser/serial/serial_chooser_context_factory.h
new file mode 100644
index 0000000..d19b033
--- /dev/null
+++ b/chrome/browser/serial/serial_chooser_context_factory.h
@@ -0,0 +1,35 @@
+// 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_SERIAL_SERIAL_CHOOSER_CONTEXT_FACTORY_H_
+#define CHROME_BROWSER_SERIAL_SERIAL_CHOOSER_CONTEXT_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/memory/singleton.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+class SerialChooserContext;
+class Profile;
+
+class SerialChooserContextFactory : public BrowserContextKeyedServiceFactory {
+ public:
+  static SerialChooserContext* GetForProfile(Profile* profile);
+  static SerialChooserContextFactory* GetInstance();
+
+ private:
+  friend struct base::DefaultSingletonTraits<SerialChooserContextFactory>;
+
+  SerialChooserContextFactory();
+  ~SerialChooserContextFactory() override;
+
+  // BrowserContextKeyedBaseFactory methods:
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* profile) const override;
+  content::BrowserContext* GetBrowserContextToUse(
+      content::BrowserContext* context) const override;
+
+  DISALLOW_COPY_AND_ASSIGN(SerialChooserContextFactory);
+};
+
+#endif  // CHROME_BROWSER_SERIAL_SERIAL_CHOOSER_CONTEXT_FACTORY_H_
diff --git a/chrome/browser/serial/serial_chooser_context_unittest.cc b/chrome/browser/serial/serial_chooser_context_unittest.cc
new file mode 100644
index 0000000..8a95111e
--- /dev/null
+++ b/chrome/browser/serial/serial_chooser_context_unittest.cc
@@ -0,0 +1,66 @@
+// 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/serial/serial_chooser_context.h"
+
+#include "base/run_loop.h"
+#include "chrome/browser/serial/serial_chooser_context_factory.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "services/device/public/mojom/serial.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class SerialChooserContextTest : public testing::Test {
+ public:
+  SerialChooserContextTest() = default;
+  ~SerialChooserContextTest() override = default;
+
+  Profile* profile() { return &profile_; }
+
+  SerialChooserContext* GetContext(Profile* profile) {
+    return SerialChooserContextFactory::GetForProfile(profile);
+  }
+
+ private:
+  content::TestBrowserThreadBundle thread_bundle_;
+  TestingProfile profile_;
+};
+
+}  // namespace
+
+TEST_F(SerialChooserContextTest, GrantAndRevokeEphemeralPermission) {
+  const auto origin = url::Origin::Create(GURL("https://google.com"));
+
+  auto port = device::mojom::SerialPortInfo::New();
+  port->token = base::UnguessableToken::Create();
+
+  SerialChooserContext* context = GetContext(profile());
+  EXPECT_FALSE(context->HasPortPermission(origin, origin, *port));
+  context->GrantPortPermission(origin, origin, *port);
+  EXPECT_TRUE(context->HasPortPermission(origin, origin, *port));
+
+  std::vector<std::unique_ptr<ChooserContextBase::Object>> origin_objects =
+      context->GetGrantedObjects(origin.GetURL(), origin.GetURL());
+  ASSERT_EQ(1u, origin_objects.size());
+
+  std::vector<std::unique_ptr<ChooserContextBase::Object>> objects =
+      context->GetAllGrantedObjects();
+  ASSERT_EQ(1u, objects.size());
+  EXPECT_EQ(origin.GetURL(), objects[0]->requesting_origin);
+  EXPECT_EQ(origin.GetURL(), objects[0]->embedding_origin);
+  EXPECT_EQ(origin_objects[0]->value, objects[0]->value);
+  EXPECT_EQ(content_settings::SettingSource::SETTING_SOURCE_USER,
+            objects[0]->source);
+  EXPECT_FALSE(objects[0]->incognito);
+
+  context->RevokeObjectPermission(origin.GetURL(), origin.GetURL(),
+                                  objects[0]->value);
+  EXPECT_FALSE(context->HasPortPermission(origin, origin, *port));
+  origin_objects = context->GetGrantedObjects(origin.GetURL(), origin.GetURL());
+  EXPECT_EQ(0u, origin_objects.size());
+  objects = context->GetAllGrantedObjects();
+  EXPECT_EQ(0u, objects.size());
+}
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index daf6d6ad..0d70af2 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -3719,6 +3719,8 @@
     sources += [
       "ash/fake_tablet_mode_controller.cc",
       "ash/fake_tablet_mode_controller.h",
+      "ash/test_login_screen.cc",
+      "ash/test_login_screen.h",
       "ash/test_session_controller.cc",
       "ash/test_session_controller.h",
       "ash/test_wallpaper_controller.cc",
diff --git a/chrome/browser/ui/ash/keyboard/keyboard_controller_browsertest.cc b/chrome/browser/ui/ash/keyboard/keyboard_controller_browsertest.cc
index 5743d7d..53f5407e 100644
--- a/chrome/browser/ui/ash/keyboard/keyboard_controller_browsertest.cc
+++ b/chrome/browser/ui/ash/keyboard/keyboard_controller_browsertest.cc
@@ -423,7 +423,6 @@
 
   auto* controller = keyboard::KeyboardController::Get();
 
-  controller->LoadKeyboardWindowInBackground();
   EXPECT_EQ(controller->GetStateForTest(),
             keyboard::KeyboardControllerState::LOADING_EXTENSION);
 
diff --git a/chrome/browser/ui/ash/tab_scrubber.cc b/chrome/browser/ui/ash/tab_scrubber.cc
index 5c82cbf..a97836762 100644
--- a/chrome/browser/ui/ash/tab_scrubber.cc
+++ b/chrome/browser/ui/ash/tab_scrubber.cc
@@ -17,7 +17,6 @@
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/immersive_mode_controller.h"
-#include "chrome/browser/ui/views/tabs/glow_hover_controller.h"
 #include "chrome/browser/ui/views/tabs/tab.h"
 #include "chrome/browser/ui/views/tabs/tab_strip.h"
 #include "chrome/browser/ui/views/tabs/tab_style.h"
@@ -153,7 +152,7 @@
   if (highlighted_tab_ != -1) {
     gfx::Point hover_point(swipe_x_, swipe_y_);
     views::View::ConvertPointToTarget(tab_strip_, new_tab, &hover_point);
-    new_tab->hover_controller()->SetLocation(hover_point);
+    new_tab->tab_style()->SetHoverLocation(hover_point);
   }
 }
 
@@ -240,7 +239,7 @@
     TabStrip* tab_strip = browser_view->tabstrip();
     if (activate && highlighted_tab_ != -1) {
       Tab* tab = tab_strip->tab_at(highlighted_tab_);
-      tab->hover_controller()->HideImmediately();
+      tab->tab_style()->HideHover(GlowHoverController::HideStyle::kImmediate);
       int distance = std::abs(highlighted_tab_ -
                               browser_->tab_strip_model()->active_index());
       UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.ScrubDistance", distance, 1, 20, 21);
@@ -318,12 +317,13 @@
 
   if (highlighted_tab_ != -1) {
     Tab* tab = tab_strip_->tab_at(highlighted_tab_);
-    tab->hover_controller()->HideImmediately();
+    tab->tab_style()->HideHover(GlowHoverController::HideStyle::kImmediate);
   }
 
   if (new_index != browser_->tab_strip_model()->active_index()) {
     highlighted_tab_ = new_index;
-    new_tab->hover_controller()->Show(GlowHoverController::PRONOUNCED);
+    new_tab->tab_style()->ShowHover(
+        GlowHoverController::ShowStyle::kPronounced);
   } else {
     highlighted_tab_ = -1;
   }
diff --git a/chrome/browser/ui/ash/test_login_screen.cc b/chrome/browser/ui/ash/test_login_screen.cc
new file mode 100644
index 0000000..fbcad54
--- /dev/null
+++ b/chrome/browser/ui/ash/test_login_screen.cc
@@ -0,0 +1,137 @@
+// 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/test_login_screen.h"
+
+#include <utility>
+
+#include "ash/public/interfaces/constants.mojom.h"
+#include "content/public/common/service_manager_connection.h"
+#include "services/service_manager/public/cpp/connector.h"
+#include "services/service_manager/public/cpp/service_filter.h"
+
+TestLoginScreen::TestLoginScreen() {
+  CHECK(content::ServiceManagerConnection::GetForProcess())
+      << "ServiceManager is uninitialized. Did you forget to create a "
+         "content::TestServiceManagerContext?";
+  content::ServiceManagerConnection::GetForProcess()
+      ->GetConnector()
+      ->OverrideBinderForTesting(
+          service_manager::ServiceFilter::ByName(ash::mojom::kServiceName),
+          ash::mojom::LoginScreen::Name_,
+          base::BindRepeating(&TestLoginScreen::Bind, base::Unretained(this)));
+}
+
+TestLoginScreen::~TestLoginScreen() {
+  content::ServiceManagerConnection::GetForProcess()
+      ->GetConnector()
+      ->ClearBinderOverrideForTesting(
+          service_manager::ServiceFilter::ByName(ash::mojom::kServiceName),
+          ash::mojom::LoginScreen::Name_);
+}
+
+void TestLoginScreen::SetClient(ash::mojom::LoginScreenClientPtr client) {}
+
+void TestLoginScreen::ShowLockScreen(ShowLockScreenCallback callback) {
+  std::move(callback).Run(true);
+}
+
+void TestLoginScreen::ShowLoginScreen(ShowLoginScreenCallback callback) {
+  std::move(callback).Run(true);
+}
+
+void TestLoginScreen::ShowErrorMessage(int32_t login_attempts,
+                                       const std::string& error_text,
+                                       const std::string& help_link_text,
+                                       int32_t help_topic_id) {}
+
+void TestLoginScreen::ShowWarningBanner(const base::string16& message) {}
+
+void TestLoginScreen::HideWarningBanner() {}
+
+void TestLoginScreen::ClearErrors() {}
+
+void TestLoginScreen::ShowUserPodCustomIcon(
+    const AccountId& account_id,
+    ::ash::mojom::EasyUnlockIconOptionsPtr icon) {}
+
+void TestLoginScreen::HideUserPodCustomIcon(const AccountId& account_id) {}
+
+void TestLoginScreen::SetAuthType(const AccountId& account_id,
+                                  ::proximity_auth::mojom::AuthType auth_type,
+                                  const base::string16& initial_value) {}
+
+void TestLoginScreen::SetUserList(
+    std::vector<::ash::mojom::LoginUserInfoPtr> users) {}
+
+void TestLoginScreen::SetPinEnabledForUser(const AccountId& account_id,
+                                           bool is_enabled) {}
+
+void TestLoginScreen::SetFingerprintState(
+    const AccountId& account_id,
+    ::ash::mojom::FingerprintState state) {}
+
+void TestLoginScreen::NotifyFingerprintAuthResult(const AccountId& account_id,
+                                                  bool successful) {}
+
+void TestLoginScreen::SetAvatarForUser(const AccountId& account_id,
+                                       ::ash::mojom::UserAvatarPtr avatar) {}
+
+void TestLoginScreen::SetAuthEnabledForUser(
+    const AccountId& account_id,
+    bool is_enabled,
+    base::Optional<base::Time> auth_reenabled_time) {}
+
+void TestLoginScreen::HandleFocusLeavingLockScreenApps(bool reverse) {}
+
+void TestLoginScreen::SetSystemInfo(bool show_if_hidden,
+                                    const std::string& os_version_label_text,
+                                    const std::string& enterprise_info_text,
+                                    const std::string& bluetooth_name) {}
+
+void TestLoginScreen::IsReadyForPassword(IsReadyForPasswordCallback callback) {
+  std::move(callback).Run(true);
+}
+
+void TestLoginScreen::SetPublicSessionDisplayName(
+    const AccountId& account_id,
+    const std::string& display_name) {}
+
+void TestLoginScreen::SetPublicSessionLocales(
+    const AccountId& account_id,
+    std::vector<::ash::mojom::LocaleItemPtr> locales,
+    const std::string& default_locale,
+    bool show_advanced_view) {}
+
+void TestLoginScreen::SetPublicSessionKeyboardLayouts(
+    const AccountId& account_id,
+    const std::string& locale,
+    std::vector<::ash::mojom::InputMethodItemPtr> keyboard_layouts) {}
+
+void TestLoginScreen::SetPublicSessionShowFullManagementDisclosure(
+    bool show_full_management_disclosure) {}
+
+void TestLoginScreen::SetKioskApps(
+    std::vector<::ash::mojom::KioskAppInfoPtr> kiosk_apps) {}
+
+void TestLoginScreen::ShowKioskAppError(const std::string& message) {}
+
+void TestLoginScreen::NotifyOobeDialogState(ash::mojom::OobeDialogState state) {
+}
+
+void TestLoginScreen::SetAddUserButtonEnabled(bool enable) {}
+
+void TestLoginScreen::SetShutdownButtonEnabled(bool enable) {}
+
+void TestLoginScreen::SetAllowLoginAsGuest(bool allow_guest) {}
+
+void TestLoginScreen::SetShowGuestButtonInOobe(bool show) {}
+
+void TestLoginScreen::SetShowParentAccess(bool show) {}
+
+void TestLoginScreen::FocusLoginShelf(bool reverse) {}
+
+void TestLoginScreen::Bind(mojo::ScopedMessagePipeHandle handle) {
+  binding_.Bind(ash::mojom::LoginScreenRequest(std::move(handle)));
+}
diff --git a/chrome/browser/ui/ash/test_login_screen.h b/chrome/browser/ui/ash/test_login_screen.h
new file mode 100644
index 0000000..cde5266
--- /dev/null
+++ b/chrome/browser/ui/ash/test_login_screen.h
@@ -0,0 +1,95 @@
+// 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_UI_ASH_TEST_LOGIN_SCREEN_H_
+#define CHROME_BROWSER_UI_ASH_TEST_LOGIN_SCREEN_H_
+
+#include <string>
+#include <vector>
+
+#include "ash/public/interfaces/login_screen.mojom.h"
+#include "base/macros.h"
+#include "mojo/public/cpp/bindings/binding.h"
+
+// Test implementation of ash's mojo LoginScreen interface.
+//
+// Registers itself to ServiceManager on construction and deregisters
+// on destruction.
+//
+// Note: A ServiceManagerConnection must be initialized before constructing this
+// object. Consider using content::TestServiceManagerContext on your tests.
+class TestLoginScreen : public ash::mojom::LoginScreen {
+ public:
+  TestLoginScreen();
+  ~TestLoginScreen() override;
+
+  // ash:mojom::LoginScreen:
+  void SetClient(ash::mojom::LoginScreenClientPtr client) override;
+  void ShowLockScreen(ShowLockScreenCallback callback) override;
+  void ShowLoginScreen(ShowLoginScreenCallback callback) override;
+  void ShowErrorMessage(int32_t login_attempts,
+                        const std::string& error_text,
+                        const std::string& help_link_text,
+                        int32_t help_topic_id) override;
+  void ShowWarningBanner(const base::string16& message) override;
+  void HideWarningBanner() override;
+  void ClearErrors() override;
+  void ShowUserPodCustomIcon(
+      const AccountId& account_id,
+      ::ash::mojom::EasyUnlockIconOptionsPtr icon) override;
+  void HideUserPodCustomIcon(const AccountId& account_id) override;
+  void SetAuthType(const AccountId& account_id,
+                   ::proximity_auth::mojom::AuthType auth_type,
+                   const base::string16& initial_value) override;
+  void SetUserList(std::vector<::ash::mojom::LoginUserInfoPtr> users) override;
+
+  void SetPinEnabledForUser(const AccountId& account_id,
+                            bool is_enabled) override;
+  void SetFingerprintState(const AccountId& account_id,
+                           ::ash::mojom::FingerprintState state) override;
+  void NotifyFingerprintAuthResult(const AccountId& account_id,
+                                   bool successful) override;
+  void SetAvatarForUser(const AccountId& account_id,
+                        ::ash::mojom::UserAvatarPtr avatar) override;
+  void SetAuthEnabledForUser(
+      const AccountId& account_id,
+      bool is_enabled,
+      base::Optional<base::Time> auth_reenabled_time) override;
+  void HandleFocusLeavingLockScreenApps(bool reverse) override;
+  void SetSystemInfo(bool show_if_hidden,
+                     const std::string& os_version_label_text,
+                     const std::string& enterprise_info_text,
+                     const std::string& bluetooth_name) override;
+  void IsReadyForPassword(IsReadyForPasswordCallback callback) override;
+  void SetPublicSessionDisplayName(const AccountId& account_id,
+                                   const std::string& display_name) override;
+  void SetPublicSessionLocales(const AccountId& account_id,
+                               std::vector<::ash::mojom::LocaleItemPtr> locales,
+                               const std::string& default_locale,
+                               bool show_advanced_view) override;
+  void SetPublicSessionKeyboardLayouts(
+      const AccountId& account_id,
+      const std::string& locale,
+      std::vector<::ash::mojom::InputMethodItemPtr> keyboard_layouts) override;
+  void SetPublicSessionShowFullManagementDisclosure(
+      bool show_full_management_disclosure) override;
+  void SetKioskApps(
+      std::vector<::ash::mojom::KioskAppInfoPtr> kiosk_apps) override;
+  void ShowKioskAppError(const std::string& message) override;
+  void NotifyOobeDialogState(ash::mojom::OobeDialogState state) override;
+  void SetAddUserButtonEnabled(bool enable) override;
+  void SetShutdownButtonEnabled(bool enable) override;
+  void SetAllowLoginAsGuest(bool allow_guest) override;
+  void SetShowGuestButtonInOobe(bool show) override;
+  void SetShowParentAccess(bool show) override;
+  void FocusLoginShelf(bool reverse) override;
+
+ private:
+  void Bind(mojo::ScopedMessagePipeHandle handle);
+  mojo::Binding<ash::mojom::LoginScreen> binding_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(TestLoginScreen);
+};
+
+#endif  // CHROME_BROWSER_UI_ASH_TEST_LOGIN_SCREEN_H_
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index de5c795e7..49ea4a9 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -135,8 +135,6 @@
 #include "chrome/browser/ui/location_bar/location_bar.h"
 #include "chrome/browser/ui/permission_bubble/chooser_bubble_delegate.h"
 #include "chrome/browser/ui/search/search_tab_helper.h"
-#include "chrome/browser/ui/serial/serial_chooser.h"
-#include "chrome/browser/ui/serial/serial_chooser_controller.h"
 #include "chrome/browser/ui/singleton_tabs.h"
 #include "chrome/browser/ui/status_bubble.h"
 #include "chrome/browser/ui/sync/browser_synced_window_delegate.h"
@@ -1188,19 +1186,6 @@
   return std::move(bluetooth_chooser_desktop);
 }
 
-std::unique_ptr<content::SerialChooser> Browser::RunSerialChooser(
-    content::RenderFrameHost* frame,
-    std::vector<blink::mojom::SerialPortFilterPtr> filters,
-    content::SerialChooser::Callback callback) {
-  auto chooser_controller = std::make_unique<SerialChooserController>(
-      frame, std::move(filters), std::move(callback));
-  auto chooser_bubble_delegate = std::make_unique<ChooserBubbleDelegate>(
-      frame, std::move(chooser_controller));
-  BubbleReference bubble_reference =
-      GetBubbleManager()->ShowBubble(std::move(chooser_bubble_delegate));
-  return std::make_unique<SerialChooser>(std::move(bubble_reference));
-}
-
 void Browser::PassiveInsecureContentFound(const GURL& resource_url) {
   // Note: this implementation is a mirror of
   // ContentSettingsObserver::passiveInsecureContentFound
diff --git a/chrome/browser/ui/browser.h b/chrome/browser/ui/browser.h
index 8a3d545e5..0307e6f 100644
--- a/chrome/browser/ui/browser.h
+++ b/chrome/browser/ui/browser.h
@@ -520,10 +520,6 @@
   std::unique_ptr<content::BluetoothChooser> RunBluetoothChooser(
       content::RenderFrameHost* frame,
       const content::BluetoothChooser::EventHandler& event_handler) override;
-  std::unique_ptr<content::SerialChooser> RunSerialChooser(
-      content::RenderFrameHost* frame,
-      std::vector<blink::mojom::SerialPortFilterPtr> filters,
-      content::SerialChooser::Callback callback) override;
   void PassiveInsecureContentFound(const GURL& resource_url) override;
   bool ShouldAllowRunningInsecureContent(content::WebContents* web_contents,
                                          bool allowed_per_prefs,
diff --git a/chrome/browser/ui/search/search_ipc_router.cc b/chrome/browser/ui/search/search_ipc_router.cc
index db319ab..1f85b6b6 100644
--- a/chrome/browser/ui/search/search_ipc_router.cc
+++ b/chrome/browser/ui/search/search_ipc_router.cc
@@ -358,22 +358,39 @@
   delegate_->OnSelectLocalBackgroundImage();
 }
 
-void SearchIPCRouter::BlacklistSearchSuggestion(int32_t task_version,
+void SearchIPCRouter::BlocklistSearchSuggestion(int32_t task_version,
                                                 int64_t task_id) {
-  if (!policy_->ShouldProcessBlacklistSearchSuggestion())
+  if (!policy_->ShouldProcessBlocklistSearchSuggestion())
     return;
 
-  delegate_->OnBlacklistSearchSuggestion(task_version, task_id);
+  delegate_->OnBlocklistSearchSuggestion(task_version, task_id);
 }
 
-void SearchIPCRouter::BlacklistSearchSuggestionWithHash(
+void SearchIPCRouter::BlocklistSearchSuggestionWithHash(
     int32_t task_version,
     int64_t task_id,
     const std::vector<uint8_t>& hash) {
-  if (!policy_->ShouldProcessBlacklistSearchSuggestionWithHash())
+  if (!policy_->ShouldProcessBlocklistSearchSuggestionWithHash())
     return;
 
-  delegate_->OnBlacklistSearchSuggestionWithHash(task_version, task_id, hash);
+  if (hash.size() > 4) {
+    return;
+  }
+  delegate_->OnBlocklistSearchSuggestionWithHash(task_version, task_id,
+                                                 hash.data());
+}
+
+void SearchIPCRouter::SearchSuggestionSelected(
+    int32_t task_version,
+    int64_t task_id,
+    const std::vector<uint8_t>& hash) {
+  if (!policy_->ShouldProcessSearchSuggestionSelected())
+    return;
+
+  if (hash.size() > 4) {
+    return;
+  }
+  delegate_->OnSearchSuggestionSelected(task_version, task_id, hash.data());
 }
 
 void SearchIPCRouter::OptOutOfSearchSuggestions() {
diff --git a/chrome/browser/ui/search/search_ipc_router.h b/chrome/browser/ui/search/search_ipc_router.h
index 7bf8186b..be735dda 100644
--- a/chrome/browser/ui/search/search_ipc_router.h
+++ b/chrome/browser/ui/search/search_ipc_router.h
@@ -119,16 +119,20 @@
     // NTP background image.
     virtual void OnSelectLocalBackgroundImage() = 0;
 
-    // Called when a search suggestion is blacklisted on the local NTP.
-    virtual void OnBlacklistSearchSuggestion(int task_version,
+    // Called when a search suggestion is blocklisted on the local NTP.
+    virtual void OnBlocklistSearchSuggestion(int task_version,
                                              long task_id) = 0;
 
-    // Called when a search suggestion is blacklisted on the local NTP and a
+    // Called when a search suggestion is blocklisted on the local NTP and a
     // hash is provided.
-    virtual void OnBlacklistSearchSuggestionWithHash(
-        int task_version,
-        long task_id,
-        const std::vector<uint8_t>& hash) = 0;
+    virtual void OnBlocklistSearchSuggestionWithHash(int task_version,
+                                                     long task_id,
+                                                     const uint8_t hash[4]) = 0;
+
+    // Called when a search suggestion is selected on the local NTP.
+    virtual void OnSearchSuggestionSelected(int task_version,
+                                            long task_id,
+                                            const uint8_t hash[4]) = 0;
 
     // Called when a user selected to completely opt out of NTP search
     // suggestions.
@@ -165,8 +169,9 @@
     virtual bool ShouldProcessSetCustomBackgroundURL() = 0;
     virtual bool ShouldProcessSetCustomBackgroundURLWithAttributions() = 0;
     virtual bool ShouldProcessSelectLocalBackgroundImage() = 0;
-    virtual bool ShouldProcessBlacklistSearchSuggestion() = 0;
-    virtual bool ShouldProcessBlacklistSearchSuggestionWithHash() = 0;
+    virtual bool ShouldProcessBlocklistSearchSuggestion() = 0;
+    virtual bool ShouldProcessBlocklistSearchSuggestionWithHash() = 0;
+    virtual bool ShouldProcessSearchSuggestionSelected() = 0;
     virtual bool ShouldProcessOptOutOfSearchSuggestions() = 0;
   };
 
@@ -256,12 +261,15 @@
       const std::string& attribution_line_2,
       const GURL& action_url) override;
   void SelectLocalBackgroundImage() override;
-  void BlacklistSearchSuggestion(int32_t task_version,
+  void BlocklistSearchSuggestion(int32_t task_version,
                                  int64_t task_id) override;
-  void BlacklistSearchSuggestionWithHash(
+  void BlocklistSearchSuggestionWithHash(
       int32_t task_version,
       int64_t task_id,
       const std::vector<uint8_t>& hash) override;
+  void SearchSuggestionSelected(int32_t task_version,
+                                int64_t task_id,
+                                const std::vector<uint8_t>& hash) override;
   void OptOutOfSearchSuggestions() override;
   void set_embedded_search_client_factory_for_testing(
       std::unique_ptr<EmbeddedSearchClientFactory> factory) {
diff --git a/chrome/browser/ui/search/search_ipc_router_policy_impl.cc b/chrome/browser/ui/search/search_ipc_router_policy_impl.cc
index 3f7f7731..b3a6a21 100644
--- a/chrome/browser/ui/search/search_ipc_router_policy_impl.cc
+++ b/chrome/browser/ui/search/search_ipc_router_policy_impl.cc
@@ -108,12 +108,16 @@
   return !is_incognito_ && search::IsInstantNTP(web_contents_);
 }
 
-bool SearchIPCRouterPolicyImpl::ShouldProcessBlacklistSearchSuggestion() {
+bool SearchIPCRouterPolicyImpl::ShouldProcessBlocklistSearchSuggestion() {
   return !is_incognito_ && search::IsInstantNTP(web_contents_);
 }
 
 bool SearchIPCRouterPolicyImpl::
-    ShouldProcessBlacklistSearchSuggestionWithHash() {
+    ShouldProcessBlocklistSearchSuggestionWithHash() {
+  return !is_incognito_ && search::IsInstantNTP(web_contents_);
+}
+
+bool SearchIPCRouterPolicyImpl::ShouldProcessSearchSuggestionSelected() {
   return !is_incognito_ && search::IsInstantNTP(web_contents_);
 }
 
diff --git a/chrome/browser/ui/search/search_ipc_router_policy_impl.h b/chrome/browser/ui/search/search_ipc_router_policy_impl.h
index d54f287..f8e0c19 100644
--- a/chrome/browser/ui/search/search_ipc_router_policy_impl.h
+++ b/chrome/browser/ui/search/search_ipc_router_policy_impl.h
@@ -48,8 +48,9 @@
   bool ShouldProcessSetCustomBackgroundURL() override;
   bool ShouldProcessSetCustomBackgroundURLWithAttributions() override;
   bool ShouldProcessSelectLocalBackgroundImage() override;
-  bool ShouldProcessBlacklistSearchSuggestion() override;
-  bool ShouldProcessBlacklistSearchSuggestionWithHash() override;
+  bool ShouldProcessBlocklistSearchSuggestion() override;
+  bool ShouldProcessBlocklistSearchSuggestionWithHash() override;
+  bool ShouldProcessSearchSuggestionSelected() override;
   bool ShouldProcessOptOutOfSearchSuggestions() override;
 
   // Used by unit tests.
diff --git a/chrome/browser/ui/search/search_ipc_router_unittest.cc b/chrome/browser/ui/search/search_ipc_router_unittest.cc
index fb88b93..1bdad86 100644
--- a/chrome/browser/ui/search/search_ipc_router_unittest.cc
+++ b/chrome/browser/ui/search/search_ipc_router_unittest.cc
@@ -87,12 +87,12 @@
                     const std::string& attribution2,
                     const GURL& attributionActionUrl));
   MOCK_METHOD0(OnSelectLocalBackgroundImage, void());
-  MOCK_METHOD2(OnBlacklistSearchSuggestion,
+  MOCK_METHOD2(OnBlocklistSearchSuggestion,
                void(int task_version, long task_id));
-  MOCK_METHOD3(OnBlacklistSearchSuggestionWithHash,
-               void(int task_version,
-                    long task_id,
-                    const std::vector<uint8_t>& hash));
+  MOCK_METHOD3(OnBlocklistSearchSuggestionWithHash,
+               void(int task_version, long task_id, const uint8_t hash[4]));
+  MOCK_METHOD3(OnSearchSuggestionSelected,
+               void(int task_version, long task_id, const uint8_t hash[4]));
   MOCK_METHOD0(OnOptOutOfSearchSuggestions, void());
 };
 
@@ -117,8 +117,9 @@
   MOCK_METHOD0(ShouldProcessSetCustomBackgroundURL, bool());
   MOCK_METHOD0(ShouldProcessSetCustomBackgroundURLWithAttributions, bool());
   MOCK_METHOD0(ShouldProcessSelectLocalBackgroundImage, bool());
-  MOCK_METHOD0(ShouldProcessBlacklistSearchSuggestion, bool());
-  MOCK_METHOD0(ShouldProcessBlacklistSearchSuggestionWithHash, bool());
+  MOCK_METHOD0(ShouldProcessBlocklistSearchSuggestion, bool());
+  MOCK_METHOD0(ShouldProcessBlocklistSearchSuggestionWithHash, bool());
+  MOCK_METHOD0(ShouldProcessSearchSuggestionSelected, bool());
   MOCK_METHOD0(ShouldProcessOptOutOfSearchSuggestions, bool());
   MOCK_METHOD1(ShouldSendSetInputInProgress, bool(bool));
   MOCK_METHOD0(ShouldSendOmniboxFocusChanged, bool());
@@ -904,39 +905,75 @@
   GetSearchIPCRouter().SelectLocalBackgroundImage();
 }
 
-TEST_F(SearchIPCRouterTest, ProcessBlacklistSearchSuggestion) {
+TEST_F(SearchIPCRouterTest, ProcessBlocklistSearchSuggestion) {
   NavigateAndCommitActiveTab(GURL("chrome-search://foo/bar"));
   SetupMockDelegateAndPolicy();
   MockSearchIPCRouterPolicy* policy = GetSearchIPCRouterPolicy();
   int task_version = 0;
   int task_id = 1234;
   EXPECT_CALL(*mock_delegate(),
-              OnBlacklistSearchSuggestion(task_version, task_id))
+              OnBlocklistSearchSuggestion(task_version, task_id))
       .Times(1);
-  EXPECT_CALL(*policy, ShouldProcessBlacklistSearchSuggestion())
+  EXPECT_CALL(*policy, ShouldProcessBlocklistSearchSuggestion())
       .Times(1)
       .WillOnce(Return(true));
 
-  GetSearchIPCRouter().BlacklistSearchSuggestion(task_version, task_id);
+  GetSearchIPCRouter().BlocklistSearchSuggestion(task_version, task_id);
 }
 
-TEST_F(SearchIPCRouterTest, IgnoreBlacklistSearchSuggestion) {
+TEST_F(SearchIPCRouterTest, IgnoreBlocklistSearchSuggestion) {
   NavigateAndCommitActiveTab(GURL("chrome-search://foo/bar"));
   SetupMockDelegateAndPolicy();
   MockSearchIPCRouterPolicy* policy = GetSearchIPCRouterPolicy();
   int task_version = 0;
   int task_id = 1234;
   EXPECT_CALL(*mock_delegate(),
-              OnBlacklistSearchSuggestion(task_version, task_id))
+              OnBlocklistSearchSuggestion(task_version, task_id))
       .Times(0);
-  EXPECT_CALL(*policy, ShouldProcessBlacklistSearchSuggestion())
+  EXPECT_CALL(*policy, ShouldProcessBlocklistSearchSuggestion())
       .Times(1)
       .WillOnce(Return(false));
 
-  GetSearchIPCRouter().BlacklistSearchSuggestion(task_version, task_id);
+  GetSearchIPCRouter().BlocklistSearchSuggestion(task_version, task_id);
 }
 
-TEST_F(SearchIPCRouterTest, ProcessBlacklistSearchSuggestionWithHash) {
+TEST_F(SearchIPCRouterTest, ProcessBlocklistSearchSuggestionWithHash) {
+  NavigateAndCommitActiveTab(GURL("chrome-search://foo/bar"));
+  SetupMockDelegateAndPolicy();
+  MockSearchIPCRouterPolicy* policy = GetSearchIPCRouterPolicy();
+  int task_version = 0;
+  int task_id = 1234;
+  std::vector<uint8_t> hash = {'a', 'b', 'c', 'd'};
+  EXPECT_CALL(*mock_delegate(), OnBlocklistSearchSuggestionWithHash(
+                                    task_version, task_id, hash.data()))
+      .Times(1);
+  EXPECT_CALL(*policy, ShouldProcessBlocklistSearchSuggestionWithHash())
+      .Times(1)
+      .WillOnce(Return(true));
+
+  GetSearchIPCRouter().BlocklistSearchSuggestionWithHash(task_version, task_id,
+                                                         hash);
+}
+
+TEST_F(SearchIPCRouterTest, IgnoreBlocklistSearchSuggestionWithHash) {
+  NavigateAndCommitActiveTab(GURL("chrome-search://foo/bar"));
+  SetupMockDelegateAndPolicy();
+  MockSearchIPCRouterPolicy* policy = GetSearchIPCRouterPolicy();
+  int task_version = 0;
+  int task_id = 1234;
+  std::vector<uint8_t> hash = {'a', 'b', 'c', 'd'};
+  EXPECT_CALL(*mock_delegate(), OnBlocklistSearchSuggestionWithHash(
+                                    task_version, task_id, hash.data()))
+      .Times(0);
+  EXPECT_CALL(*policy, ShouldProcessBlocklistSearchSuggestionWithHash())
+      .Times(1)
+      .WillOnce(Return(false));
+
+  GetSearchIPCRouter().BlocklistSearchSuggestionWithHash(task_version, task_id,
+                                                         hash);
+}
+
+TEST_F(SearchIPCRouterTest, ProcessSearchSuggestionSelected) {
   NavigateAndCommitActiveTab(GURL("chrome-search://foo/bar"));
   SetupMockDelegateAndPolicy();
   MockSearchIPCRouterPolicy* policy = GetSearchIPCRouterPolicy();
@@ -944,17 +981,16 @@
   int task_id = 1234;
   std::vector<uint8_t> hash = {'a', 'b', 'c', 'd'};
   EXPECT_CALL(*mock_delegate(),
-              OnBlacklistSearchSuggestionWithHash(task_version, task_id, hash))
+              OnSearchSuggestionSelected(task_version, task_id, hash.data()))
       .Times(1);
-  EXPECT_CALL(*policy, ShouldProcessBlacklistSearchSuggestionWithHash())
+  EXPECT_CALL(*policy, ShouldProcessSearchSuggestionSelected())
       .Times(1)
       .WillOnce(Return(true));
 
-  GetSearchIPCRouter().BlacklistSearchSuggestionWithHash(task_version, task_id,
-                                                         hash);
+  GetSearchIPCRouter().SearchSuggestionSelected(task_version, task_id, hash);
 }
 
-TEST_F(SearchIPCRouterTest, IgnoreBlacklistSearchSuggestionWithHash) {
+TEST_F(SearchIPCRouterTest, IgnoreSearchSuggestionSelected) {
   NavigateAndCommitActiveTab(GURL("chrome-search://foo/bar"));
   SetupMockDelegateAndPolicy();
   MockSearchIPCRouterPolicy* policy = GetSearchIPCRouterPolicy();
@@ -962,14 +998,13 @@
   int task_id = 1234;
   std::vector<uint8_t> hash = {'a', 'b', 'c', 'd'};
   EXPECT_CALL(*mock_delegate(),
-              OnBlacklistSearchSuggestionWithHash(task_version, task_id, hash))
+              OnSearchSuggestionSelected(task_version, task_id, hash.data()))
       .Times(0);
-  EXPECT_CALL(*policy, ShouldProcessBlacklistSearchSuggestionWithHash())
+  EXPECT_CALL(*policy, ShouldProcessSearchSuggestionSelected())
       .Times(1)
       .WillOnce(Return(false));
 
-  GetSearchIPCRouter().BlacklistSearchSuggestionWithHash(task_version, task_id,
-                                                         hash);
+  GetSearchIPCRouter().SearchSuggestionSelected(task_version, task_id, hash);
 }
 
 TEST_F(SearchIPCRouterTest, ProcessOptOutOfSearchSuggestions) {
diff --git a/chrome/browser/ui/search/search_tab_helper.cc b/chrome/browser/ui/search/search_tab_helper.cc
index 4c297a6..2d6d9fd 100644
--- a/chrome/browser/ui/search/search_tab_helper.cc
+++ b/chrome/browser/ui/search/search_tab_helper.cc
@@ -461,21 +461,29 @@
   return browser->window()->GetLocationBar()->GetOmniboxView();
 }
 
-void SearchTabHelper::OnBlacklistSearchSuggestion(int task_version,
+void SearchTabHelper::OnBlocklistSearchSuggestion(int task_version,
                                                   long task_id) {
   if (search_suggest_service_)
-    search_suggest_service_->BlacklistSearchSuggestion(task_version, task_id);
+    search_suggest_service_->BlocklistSearchSuggestion(task_version, task_id);
 }
 
-void SearchTabHelper::OnBlacklistSearchSuggestionWithHash(
+void SearchTabHelper::OnBlocklistSearchSuggestionWithHash(
     int task_version,
     long task_id,
-    const std::vector<uint8_t>& hash) {
+    const uint8_t hash[4]) {
   if (search_suggest_service_)
-    search_suggest_service_->BlacklistSearchSuggestionWithHash(task_version,
+    search_suggest_service_->BlocklistSearchSuggestionWithHash(task_version,
                                                                task_id, hash);
 }
 
+void SearchTabHelper::OnSearchSuggestionSelected(int task_version,
+                                                 long task_id,
+                                                 const uint8_t hash[4]) {
+  if (search_suggest_service_)
+    search_suggest_service_->SearchSuggestionSelected(task_version, task_id,
+                                                      hash);
+}
+
 void SearchTabHelper::OnOptOutOfSearchSuggestions() {
   if (search_suggest_service_)
     search_suggest_service_->OptOutOfSearchSuggestions();
diff --git a/chrome/browser/ui/search/search_tab_helper.h b/chrome/browser/ui/search/search_tab_helper.h
index 591257b..3d0d1003 100644
--- a/chrome/browser/ui/search/search_tab_helper.h
+++ b/chrome/browser/ui/search/search_tab_helper.h
@@ -124,11 +124,13 @@
       const std::string& attribution_line_2,
       const GURL& action_url) override;
   void OnSelectLocalBackgroundImage() override;
-  void OnBlacklistSearchSuggestion(int task_version, long task_id) override;
-  void OnBlacklistSearchSuggestionWithHash(
-      int task_version,
-      long task_id,
-      const std::vector<uint8_t>& hash) override;
+  void OnBlocklistSearchSuggestion(int task_version, long task_id) override;
+  void OnBlocklistSearchSuggestionWithHash(int task_version,
+                                           long task_id,
+                                           const uint8_t hash[4]) override;
+  void OnSearchSuggestionSelected(int task_version,
+                                  long task_id,
+                                  const uint8_t hash[4]) override;
   void OnOptOutOfSearchSuggestions() override;
 
   // Overridden from InstantServiceObserver:
diff --git a/chrome/browser/ui/serial/serial_chooser_controller.cc b/chrome/browser/ui/serial/serial_chooser_controller.cc
index 0da4f077..3786b91 100644
--- a/chrome/browser/ui/serial/serial_chooser_controller.cc
+++ b/chrome/browser/ui/serial/serial_chooser_controller.cc
@@ -10,30 +10,13 @@
 #include "base/files/file_path.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/unguessable_token.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/serial/serial_chooser_context.h"
+#include "chrome/browser/serial/serial_chooser_context_factory.h"
 #include "chrome/grit/generated_resources.h"
 #include "content/public/browser/web_contents.h"
-#include "content/public/common/service_manager_connection.h"
-#include "services/device/public/mojom/constants.mojom.h"
-#include "services/service_manager/public/cpp/connector.h"
 #include "ui/base/l10n/l10n_util.h"
 
-namespace {
-
-blink::mojom::SerialPortInfoPtr ToBlinkType(
-    const device::mojom::SerialPortInfo& port) {
-  auto info = blink::mojom::SerialPortInfo::New();
-  info->token = port.token;
-  info->has_vendor_id = port.has_vendor_id;
-  if (port.has_vendor_id)
-    info->vendor_id = port.vendor_id;
-  info->has_product_id = port.has_product_id;
-  if (port.has_product_id)
-    info->product_id = port.product_id;
-  return info;
-}
-
-}  // namespace
-
 SerialChooserController::SerialChooserController(
     content::RenderFrameHost* render_frame_host,
     std::vector<blink::mojom::SerialPortFilterPtr> filters,
@@ -43,15 +26,18 @@
                         IDS_SERIAL_PORT_CHOOSER_PROMPT_EXTENSION_NAME),
       filters_(std::move(filters)),
       callback_(std::move(callback)) {
-  DCHECK(content::ServiceManagerConnection::GetForProcess());
-  content::ServiceManagerConnection::GetForProcess()
-      ->GetConnector()
-      ->BindInterface(device::mojom::kServiceName,
-                      mojo::MakeRequest(&port_manager_));
-  port_manager_.set_connection_error_handler(base::BindOnce(
-      &SerialChooserController::OnGetDevices, base::Unretained(this),
-      std::vector<device::mojom::SerialPortInfoPtr>()));
-  port_manager_->GetDevices(base::BindOnce(
+  auto* web_contents =
+      content::WebContents::FromRenderFrameHost(render_frame_host);
+  requesting_origin_ = render_frame_host->GetLastCommittedOrigin();
+  embedding_origin_ = web_contents->GetMainFrame()->GetLastCommittedOrigin();
+
+  auto* profile =
+      Profile::FromBrowserContext(web_contents->GetBrowserContext());
+  chooser_context_ =
+      SerialChooserContextFactory::GetForProfile(profile)->AsWeakPtr();
+  DCHECK(chooser_context_);
+
+  chooser_context_->GetPortManager()->GetDevices(base::BindOnce(
       &SerialChooserController::OnGetDevices, base::Unretained(this)));
 }
 
@@ -92,7 +78,13 @@
 }
 
 bool SerialChooserController::IsPaired(size_t index) const {
-  return false;
+  DCHECK_LE(index, ports_.size());
+
+  if (!chooser_context_)
+    return false;
+
+  return chooser_context_->HasPortPermission(requesting_origin_,
+                                             embedding_origin_, *ports_[index]);
 }
 
 void SerialChooserController::Select(const std::vector<size_t>& indices) {
@@ -100,8 +92,14 @@
   size_t index = indices[0];
   DCHECK_LT(index, ports_.size());
 
-  const device::mojom::SerialPortInfo& port = *ports_[index];
-  std::move(callback_).Run(ToBlinkType(port));
+  if (!chooser_context_) {
+    std::move(callback_).Run(nullptr);
+    return;
+  }
+
+  chooser_context_->GrantPortPermission(requesting_origin_, embedding_origin_,
+                                        *ports_[index]);
+  std::move(callback_).Run(std::move(ports_[index]));
 }
 
 void SerialChooserController::Cancel() {}
diff --git a/chrome/browser/ui/serial/serial_chooser_controller.h b/chrome/browser/ui/serial/serial_chooser_controller.h
index 92c44d7..8e654bb 100644
--- a/chrome/browser/ui/serial/serial_chooser_controller.h
+++ b/chrome/browser/ui/serial/serial_chooser_controller.h
@@ -15,12 +15,14 @@
 #include "content/public/browser/serial_chooser.h"
 #include "services/device/public/mojom/serial.mojom.h"
 #include "third_party/blink/public/mojom/serial/serial.mojom.h"
-#include "url/gurl.h"
+#include "url/origin.h"
 
 namespace content {
 class RenderFrameHost;
 }  // namespace content
 
+class SerialChooserContext;
+
 // SerialChooserController provides data for the Serial API permission prompt.
 // It is owned by ChooserBubbleDelegate.
 class SerialChooserController : public ChooserController {
@@ -48,8 +50,10 @@
 
   std::vector<blink::mojom::SerialPortFilterPtr> filters_;
   content::SerialChooser::Callback callback_;
+  url::Origin requesting_origin_;
+  url::Origin embedding_origin_;
 
-  device::mojom::SerialPortManagerPtr port_manager_;
+  base::WeakPtr<SerialChooserContext> chooser_context_;
   std::vector<device::mojom::SerialPortInfoPtr> ports_;
 
   DISALLOW_COPY_AND_ASSIGN(SerialChooserController);
diff --git a/chrome/browser/ui/views/autofill/dialog_view_ids.h b/chrome/browser/ui/views/autofill/dialog_view_ids.h
index eb7d8ac..3a9b2be 100644
--- a/chrome/browser/ui/views/autofill/dialog_view_ids.h
+++ b/chrome/browser/ui/views/autofill/dialog_view_ids.h
@@ -8,17 +8,21 @@
 #include "components/autofill/core/browser/field_types.h"
 
 // This defines an enumeration of IDs that can uniquely identify a view within
-// the scope of the local and upload credit card save bubbles.
+// the scope of the local and upload credit card save bubbles as well as the
+// local card migration bubble and dialogs.
 
 namespace autofill {
 
 enum DialogViewId : int {
   VIEW_ID_NONE = 0,
 
-  // The following are the important containing views of the bubble.
-  MAIN_CONTENT_VIEW_LOCAL,   // The main content view, for a local save bubble
-  MAIN_CONTENT_VIEW_UPLOAD,  // The main content view, for an upload save bubble
-  FOOTNOTE_VIEW,             // Contains the legal messages for upload save
+  // The following views are contained in SaveCardBubbleViews.
+  MAIN_CONTENT_VIEW_LOCAL,   // The main content view for a local
+                             // save bubble
+  MAIN_CONTENT_VIEW_UPLOAD,  // The main content view for an upload
+                             // save bubble
+  FOOTNOTE_VIEW,             // The footnote view of either an upload
+                             // save bubble or a manage cards view.
   SIGN_IN_PROMO_VIEW,        // Contains the sign-in promo view
   MANAGE_CARDS_VIEW,         // The manage cards view
   EXPIRATION_DATE_VIEW,      // Contains the dropdowns for expiration date
@@ -26,6 +30,12 @@
   // The sub-view that contains the sign-in button in the promo.
   SIGN_IN_VIEW,
 
+  // The main content view for a migration offer bubble.
+  MAIN_CONTENT_VIEW_MIGRATION_BUBBLE,
+
+  // The main content view for the main migration dialog.
+  MAIN_CONTENT_VIEW_MIGRATION_OFFER_DIALOG,
+
   // The following are views::LabelButton objects (clickable).
   OK_BUTTON,            // Can say [Save], [Next], [Confirm],
                         // or [Done] depending on context
diff --git a/chrome/browser/ui/views/autofill/local_card_migration_browsertest.cc b/chrome/browser/ui/views/autofill/local_card_migration_browsertest.cc
new file mode 100644
index 0000000..4f60dea4
--- /dev/null
+++ b/chrome/browser/ui/views/autofill/local_card_migration_browsertest.cc
@@ -0,0 +1,673 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <ctime>
+#include <list>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/callback_list.h"
+#include "base/command_line.h"
+#include "base/macros.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/autofill/autofill_uitest_util.h"
+#include "chrome/browser/signin/account_fetcher_service_factory.h"
+#include "chrome/browser/signin/account_tracker_service_factory.h"
+#include "chrome/browser/signin/fake_account_fetcher_service_builder.h"
+#include "chrome/browser/signin/fake_signin_manager_builder.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
+#include "chrome/browser/signin/signin_manager_factory.h"
+#include "chrome/browser/sync/profile_sync_service_factory.h"
+#include "chrome/browser/sync/test/integration/profile_sync_service_harness.h"
+#include "chrome/browser/sync/test/integration/sync_test.h"
+#include "chrome/browser/ui/autofill/chrome_autofill_client.h"
+#include "chrome/browser/ui/autofill/local_card_migration_bubble_controller_impl.h"
+#include "chrome/browser/ui/autofill/local_card_migration_dialog_controller_impl.h"
+#include "chrome/browser/ui/autofill/save_card_bubble_controller_impl.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/location_bar/location_bar.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/views/autofill/dialog_view_ids.h"
+#include "chrome/browser/ui/views/autofill/local_card_migration_bubble_views.h"
+#include "chrome/browser/ui/views/autofill/local_card_migration_dialog_view.h"
+#include "chrome/browser/ui/views/autofill/local_card_migration_icon_view.h"
+#include "chrome/browser/ui/views/autofill/save_card_bubble_views.h"
+#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/autofill/content/browser/content_autofill_driver.h"
+#include "components/autofill/core/browser/autofill_test_utils.h"
+#include "components/autofill/core/browser/credit_card_save_manager.h"
+#include "components/autofill/core/browser/form_data_importer.h"
+#include "components/autofill/core/browser/local_card_migration_manager.h"
+#include "components/autofill/core/browser/personal_data_manager.h"
+#include "components/autofill/core/browser/test_event_waiter.h"
+#include "components/autofill/core/common/autofill_features.h"
+#include "components/autofill/core/common/autofill_prefs.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/network_session_configurator/common/network_switches.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/signin/core/browser/account_tracker_service.h"
+#include "components/signin/core/browser/fake_account_fetcher_service.h"
+#include "components/sync/test/fake_server/fake_server.h"
+#include "components/sync/test/fake_server/fake_server_network_resources.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "services/device/public/cpp/test/scoped_geolocation_overrider.h"
+#include "services/identity/public/cpp/identity_manager.h"
+#include "services/identity/public/cpp/identity_test_utils.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/base_event_utils.h"
+#include "ui/views/bubble/bubble_frame_view.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/test/widget_test.h"
+#include "ui/views/window/dialog_client_view.h"
+
+using base::Bucket;
+using testing::ElementsAre;
+
+namespace autofill {
+
+namespace {
+
+constexpr char kURLGetUploadDetailsRequest[] =
+    "https://payments.google.com/payments/apis/chromepaymentsservice/"
+    "getdetailsforsavecard";
+constexpr char kResponseGetUploadDetailsSuccess[] =
+    "{\"legal_message\":{\"line\":[{\"template\":\"Legal message template with "
+    "link: "
+    "{0}.\",\"template_parameter\":[{\"display_text\":\"Link\",\"url\":\"https:"
+    "//www.example.com/\"}]}]},\"context_token\":\"dummy_context_token\"}";
+constexpr char kResponseGetUploadDetailsFailure[] =
+    "{\"error\":{\"code\":\"FAILED_PRECONDITION\",\"user_error_message\":\"An "
+    "unexpected error has occurred. Please try again later.\"}}";
+
+constexpr char kCreditCardFormURL[] =
+    "/credit_card_upload_form_address_and_cc.html";
+
+constexpr char kFirstCardNumber[] = "5428424047572420";   // Mastercard
+constexpr char kSecondCardNumber[] = "4782187095085933";  // Visa
+
+constexpr double kFakeGeolocationLatitude = 1.23;
+constexpr double kFakeGeolocationLongitude = 4.56;
+
+}  // namespace
+
+class LocalCardMigrationBrowserTest
+    : public SyncTest,
+      public LocalCardMigrationManager::ObserverForTest {
+ protected:
+  // Various events that can be waited on by the DialogEventWaiter.
+  enum class DialogEvent : int {
+    REQUESTED_LOCAL_CARD_MIGRATION,
+    RECEIVED_GET_UPLOAD_DETAILS_RESPONSE,
+    SENT_MIGRATE_CARDS_REQUEST,
+    RECEIVED_MIGRATE_CARDS_RESPONSE
+  };
+
+  LocalCardMigrationBrowserTest() : SyncTest(SINGLE_CLIENT) {}
+
+  ~LocalCardMigrationBrowserTest() override {}
+
+  void SetUpOnMainThread() override {
+    SyncTest::SetUpOnMainThread();
+
+    // Set up the HTTPS server (uses the embedded_test_server).
+    ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
+    embedded_test_server()->ServeFilesFromSourceDirectory(
+        "components/test/data/autofill");
+    embedded_test_server()->StartAcceptingConnections();
+
+    ProfileSyncServiceFactory::GetForProfile(browser()->profile())
+        ->OverrideNetworkResourcesForTest(
+            std::make_unique<fake_server::FakeServerNetworkResources>(
+                GetFakeServer()->AsWeakPtr()));
+
+    std::string username;
+#if defined(OS_CHROMEOS)
+    // In ChromeOS browser tests, the profile may already by authenticated with
+    // stub account |user_manager::kStubUserEmail|.
+    AccountInfo info =
+        IdentityManagerFactory::GetForProfile(browser()->profile())
+            ->GetPrimaryAccountInfo();
+    username = info.email;
+#endif
+    if (username.empty())
+      username = "user@gmail.com";
+
+    harness_ = ProfileSyncServiceHarness::Create(
+        browser()->profile(), username, "password",
+        ProfileSyncServiceHarness::SigninType::FAKE_SIGNIN);
+
+    // Set up the URL loader factory for the payments client so we can intercept
+    // those network requests too.
+    test_shared_loader_factory_ =
+        base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+            &test_url_loader_factory_);
+    ContentAutofillDriver::GetForRenderFrameHost(
+        GetActiveWebContents()->GetMainFrame())
+        ->autofill_manager()
+        ->client()
+        ->GetPaymentsClient()
+        ->set_url_loader_factory_for_testing(test_shared_loader_factory_);
+
+    // Set up this class as the ObserverForTest implementation.
+    LocalCardMigrationManager* local_card_migration_manager =
+        ContentAutofillDriver::GetForRenderFrameHost(
+            GetActiveWebContents()->GetMainFrame())
+            ->autofill_manager()
+            ->client()
+            ->GetFormDataImporter()
+            ->local_card_migration_manager_.get();
+
+    local_card_migration_manager->SetEventObserverForTesting(this);
+
+    // Set up the fake geolocation data.
+    geolocation_overrider_ =
+        std::make_unique<device::ScopedGeolocationOverrider>(
+            kFakeGeolocationLatitude, kFakeGeolocationLongitude);
+
+    // Set up billing customer ID.
+    ContentAutofillDriver::GetForRenderFrameHost(
+        GetActiveWebContents()->GetMainFrame())
+        ->autofill_manager()
+        ->client()
+        ->GetPrefs()
+        ->SetDouble(prefs::kAutofillBillingCustomerNumber, 1234);
+
+    scoped_feature_list_.InitAndEnableFeature(
+        features::kAutofillCreditCardLocalCardMigration);
+    ASSERT_TRUE(harness_->SetupSync());
+    SetUploadDetailsRpcPaymentsAccepts();
+  }
+
+  void NavigateTo(const std::string& file_path) {
+    ui_test_utils::NavigateToURL(
+        browser(), file_path.find("data:") == 0U
+                       ? GURL(file_path)
+                       : embedded_test_server()->GetURL(file_path));
+  }
+
+  void OnDecideToRequestLocalCardMigration() override {
+    if (event_waiter_)
+      event_waiter_->OnEvent(DialogEvent::REQUESTED_LOCAL_CARD_MIGRATION);
+  }
+
+  void OnReceivedGetUploadDetailsResponse() override {
+    if (event_waiter_)
+      event_waiter_->OnEvent(DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE);
+  }
+
+  void OnSentMigrateCardsRequest() override {
+    if (event_waiter_)
+      event_waiter_->OnEvent(DialogEvent::SENT_MIGRATE_CARDS_REQUEST);
+  }
+
+  void OnReceivedMigrateCardsResponse() override {
+    if (event_waiter_)
+      event_waiter_->OnEvent(DialogEvent::RECEIVED_MIGRATE_CARDS_RESPONSE);
+  }
+
+  void SaveLocalCard(std::string card_number) {
+    CreditCard local_card;
+    test::SetCreditCardInfo(&local_card, "John Smith", card_number.c_str(),
+                            "12", test::NextYear().c_str(), "1");
+    local_card.set_guid("00000000-0000-0000-0000-" + card_number.substr(0, 12));
+    local_card.set_record_type(CreditCard::LOCAL_CARD);
+    AddTestCreditCard(browser(), local_card);
+  }
+
+  void SaveServerCard(std::string card_number) {
+    CreditCard server_card;
+    test::SetCreditCardInfo(&server_card, "John Smith", card_number.c_str(),
+                            "12", test::NextYear().c_str(), "1");
+    server_card.set_guid("00000000-0000-0000-0000-" +
+                         card_number.substr(0, 12));
+    server_card.set_record_type(CreditCard::FULL_SERVER_CARD);
+    server_card.set_server_id("full_id_" + card_number);
+    AddTestServerCreditCard(browser(), server_card);
+  }
+
+  void UseCardAndWaitForMigrationOffer(std::string card_number) {
+    // Reusing a card should show the migration offer bubble.
+    ResetEventWaiterForSequence(
+        {DialogEvent::REQUESTED_LOCAL_CARD_MIGRATION,
+         DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
+    FillAndSubmitFormWithCard(card_number);
+    WaitForObservedEvent();
+  }
+
+  void FillAndSubmitFormWithCard(std::string card_number) {
+    NavigateTo(kCreditCardFormURL);
+    content::WebContents* web_contents = GetActiveWebContents();
+
+    const std::string click_fill_button_js =
+        "(function() { document.getElementById('fill_form').click(); })();";
+    ASSERT_TRUE(content::ExecuteScript(web_contents, click_fill_button_js));
+
+    const std::string fill_cc_number_js =
+        "(function() { document.getElementsByName(\"cc_number\")[0].value = " +
+        card_number + "; })();";
+    ASSERT_TRUE(content::ExecuteScript(web_contents, fill_cc_number_js));
+
+    const std::string click_submit_button_js =
+        "(function() { document.getElementById('submit').click(); })();";
+    content::TestNavigationObserver nav_observer(web_contents);
+    ASSERT_TRUE(content::ExecuteScript(web_contents, click_submit_button_js));
+    nav_observer.Wait();
+  }
+
+  void SetUploadDetailsRpcPaymentsAccepts() {
+    test_url_loader_factory()->AddResponse(kURLGetUploadDetailsRequest,
+                                           kResponseGetUploadDetailsSuccess);
+  }
+
+  void SetUploadDetailsRpcPaymentsDeclines() {
+    test_url_loader_factory()->AddResponse(kURLGetUploadDetailsRequest,
+                                           kResponseGetUploadDetailsFailure);
+  }
+
+  void ClickOnView(views::View* view) {
+    DCHECK(view);
+    ui::MouseEvent pressed(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
+                           ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
+                           ui::EF_LEFT_MOUSE_BUTTON);
+    view->OnMousePressed(pressed);
+    ui::MouseEvent released_event =
+        ui::MouseEvent(ui::ET_MOUSE_RELEASED, gfx::Point(), gfx::Point(),
+                       ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON,
+                       ui::EF_LEFT_MOUSE_BUTTON);
+    view->OnMouseReleased(released_event);
+  }
+
+  void ClickOnDialogViewAndWait(
+      views::View* view,
+      views::DialogDelegateView* local_card_migration_view) {
+    DCHECK(local_card_migration_view);
+    views::test::WidgetDestroyedWaiter destroyed_waiter(
+        local_card_migration_view->GetWidget());
+    local_card_migration_view->GetDialogClientView()
+        ->ResetViewShownTimeStampForTesting();
+    views::BubbleFrameView* bubble_frame_view =
+        static_cast<views::BubbleFrameView*>(
+            local_card_migration_view->GetWidget()
+                ->non_client_view()
+                ->frame_view());
+    bubble_frame_view->ResetViewShownTimeStampForTesting();
+    ClickOnView(view);
+    destroyed_waiter.Wait();
+  }
+
+  views::View* FindViewInDialogById(
+      DialogViewId view_id,
+      views::DialogDelegateView* local_card_migration_view) {
+    DCHECK(local_card_migration_view);
+
+    views::View* specified_view =
+        local_card_migration_view->GetViewByID(static_cast<int>(view_id));
+
+    if (!specified_view) {
+      specified_view =
+          local_card_migration_view->GetDialogClientView()->GetViewByID(
+              static_cast<int>(view_id));
+    }
+
+    return specified_view;
+  }
+
+  void ClickOnOkButton(views::DialogDelegateView* local_card_migration_view) {
+    views::View* ok_button =
+        local_card_migration_view->GetDialogClientView()->ok_button();
+
+    ClickOnDialogViewAndWait(ok_button, local_card_migration_view);
+  }
+
+  void ClickOnCancelButton(
+      views::DialogDelegateView* local_card_migration_view) {
+    views::View* cancel_button =
+        local_card_migration_view->GetDialogClientView()->cancel_button();
+    ClickOnDialogViewAndWait(cancel_button, local_card_migration_view);
+  }
+
+  views::DialogDelegateView* GetLocalCardMigrationOfferBubbleViews() {
+    LocalCardMigrationBubbleControllerImpl*
+        local_card_migration_bubble_controller_impl =
+            LocalCardMigrationBubbleControllerImpl::FromWebContents(
+                GetActiveWebContents());
+    if (!local_card_migration_bubble_controller_impl)
+      return nullptr;
+    return static_cast<LocalCardMigrationBubbleViews*>(
+        local_card_migration_bubble_controller_impl
+            ->local_card_migration_bubble_view());
+  }
+
+  views::DialogDelegateView* GetLocalCardMigrationMainDialogView() {
+    LocalCardMigrationDialogControllerImpl*
+        local_card_migration_dialog_controller_impl =
+            LocalCardMigrationDialogControllerImpl::FromWebContents(
+                GetActiveWebContents());
+    if (!local_card_migration_dialog_controller_impl)
+      return nullptr;
+    return static_cast<LocalCardMigrationDialogView*>(
+        local_card_migration_dialog_controller_impl
+            ->local_card_migration_dialog_view());
+  }
+
+  LocalCardMigrationIconView* GetLocalCardMigrationIconView() {
+    if (!browser())
+      return nullptr;
+    LocationBarView* location_bar_view =
+        static_cast<LocationBarView*>(browser()->window()->GetLocationBar());
+    DCHECK(location_bar_view->local_card_migration_icon_view());
+    return location_bar_view->local_card_migration_icon_view();
+  }
+
+  views::View* GetCloseButton() {
+    LocalCardMigrationBubbleViews* local_card_migration_bubble_views =
+        static_cast<LocalCardMigrationBubbleViews*>(
+            GetLocalCardMigrationOfferBubbleViews());
+    DCHECK(local_card_migration_bubble_views);
+    return local_card_migration_bubble_views->GetBubbleFrameView()
+        ->GetCloseButtonForTest();
+  }
+
+  views::View* GetCardListView() {
+    return static_cast<LocalCardMigrationDialogView*>(
+               GetLocalCardMigrationMainDialogView())
+        ->card_list_view_;
+  }
+
+  content::WebContents* GetActiveWebContents() {
+    return browser()->tab_strip_model()->GetActiveWebContents();
+  }
+
+  void ResetEventWaiterForSequence(std::list<DialogEvent> event_sequence) {
+    event_waiter_ =
+        std::make_unique<EventWaiter<DialogEvent>>(std::move(event_sequence));
+  }
+
+  void WaitForObservedEvent() { event_waiter_->Wait(); }
+
+  network::TestURLLoaderFactory* test_url_loader_factory() {
+    return &test_url_loader_factory_;
+  }
+
+  std::unique_ptr<
+      base::CallbackList<void(content::BrowserContext*)>::Subscription>
+      will_create_browser_context_services_subscription_;
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+
+  std::unique_ptr<ProfileSyncServiceHarness> harness_;
+
+ private:
+  std::unique_ptr<autofill::EventWaiter<DialogEvent>> event_waiter_;
+  std::unique_ptr<net::FakeURLFetcherFactory> url_fetcher_factory_;
+  network::TestURLLoaderFactory test_url_loader_factory_;
+  scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
+  std::unique_ptr<device::ScopedGeolocationOverrider> geolocation_overrider_;
+
+  DISALLOW_COPY_AND_ASSIGN(LocalCardMigrationBrowserTest);
+};
+
+// Ensures that migration is not offered when user saves a new card.
+IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTest,
+                       UsingNewCardDoesNotShowIntermediateMigrationOffer) {
+  base::HistogramTester histogram_tester;
+
+  SaveLocalCard(kFirstCardNumber);
+  FillAndSubmitFormWithCard(kSecondCardNumber);
+
+  // No migration bubble should be showing, because the single card upload
+  // bubble should be displayed instead.
+  EXPECT_EQ(nullptr, GetLocalCardMigrationOfferBubbleViews());
+  // No metrics are recorded when migration is not offered.
+  histogram_tester.ExpectTotalCount(
+      "Autofill.LocalCardMigrationBubbleOffer.FirstShow", 0);
+}
+
+// Ensures that migration is not offered when payments declines the cards.
+IN_PROC_BROWSER_TEST_F(
+    LocalCardMigrationBrowserTest,
+    IntermediateMigrationOfferDoesNotShowWhenPaymentsDeclines) {
+  base::HistogramTester histogram_tester;
+  SetUploadDetailsRpcPaymentsDeclines();
+
+  SaveLocalCard(kFirstCardNumber);
+  SaveLocalCard(kSecondCardNumber);
+  FillAndSubmitFormWithCard(kFirstCardNumber);
+
+  // No bubble should be showing.
+  EXPECT_EQ(nullptr, GetLocalCardMigrationOfferBubbleViews());
+  // No metrics are recorded when migration is not offered.
+  histogram_tester.ExpectTotalCount(
+      "Autofill.LocalCardMigrationBubbleOffer.FirstShow", 0);
+}
+
+// Ensures that the intermediate migration bubble is not shown after reusing
+// a saved server card, if there are no other cards to migrate.
+IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTest,
+                       ReusingServerCardDoesNotShowIntermediateMigrationOffer) {
+  base::HistogramTester histogram_tester;
+
+  SaveServerCard(kFirstCardNumber);
+  FillAndSubmitFormWithCard(kFirstCardNumber);
+
+  // No bubble should be showing.
+  EXPECT_EQ(nullptr, GetLocalCardMigrationOfferBubbleViews());
+  // No metrics are recorded when migration is not offered.
+  histogram_tester.ExpectTotalCount(
+      "Autofill.LocalCardMigrationBubbleOffer.FirstShow", 0);
+}
+
+// Ensures that the intermediate migration bubble is not shown after reusing
+// a previously saved local card, if there are no other cards to migrate.
+IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTest,
+                       ReusingLocalCardDoesNotShowIntermediateMigrationOffer) {
+  base::HistogramTester histogram_tester;
+
+  SaveLocalCard(kFirstCardNumber);
+  FillAndSubmitFormWithCard(kFirstCardNumber);
+
+  // No migration bubble should be showing, because the single card upload
+  // bubble should be displayed instead.
+  EXPECT_EQ(nullptr, GetLocalCardMigrationOfferBubbleViews());
+  // No metrics are recorded when migration is not offered.
+  histogram_tester.ExpectTotalCount(
+      "Autofill.LocalCardMigrationBubbleOffer.FirstShow", 0);
+}
+
+// Ensures that the intermediate migration bubble is triggered after reusing
+// a saved local card, if there are multiple local cards available to migrate.
+IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTest,
+                       ReusingLocalCardShowsIntermediateMigrationOffer) {
+  base::HistogramTester histogram_tester;
+
+  SaveLocalCard(kFirstCardNumber);
+  SaveLocalCard(kSecondCardNumber);
+  UseCardAndWaitForMigrationOffer(kFirstCardNumber);
+
+  // The intermediate migration bubble should show.
+  EXPECT_TRUE(
+      FindViewInDialogById(DialogViewId::MAIN_CONTENT_VIEW_MIGRATION_BUBBLE,
+                           GetLocalCardMigrationOfferBubbleViews())
+          ->visible());
+  // Metrics
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples(
+          "Autofill.LocalCardMigrationBubbleOffer.FirstShow"),
+      ElementsAre(
+          Bucket(AutofillMetrics::LOCAL_CARD_MIGRATION_BUBBLE_REQUESTED, 1),
+          Bucket(AutofillMetrics::LOCAL_CARD_MIGRATION_BUBBLE_SHOWN, 1)));
+  histogram_tester.ExpectUniqueSample(
+      "Autofill.LocalCardMigrationOrigin.UseOfLocalCard",
+      AutofillMetrics::INTERMEDIATE_BUBBLE_SHOWN, 1);
+}
+
+// Ensures that clicking [X] on the offer bubble makes the bubble disappear.
+IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTest,
+                       ClickingCloseClosesBubble) {
+  base::HistogramTester histogram_tester;
+
+  SaveLocalCard(kFirstCardNumber);
+  SaveLocalCard(kSecondCardNumber);
+  UseCardAndWaitForMigrationOffer(kFirstCardNumber);
+  ClickOnDialogViewAndWait(GetCloseButton(),
+                           GetLocalCardMigrationOfferBubbleViews());
+
+  // No bubble should be showing.
+  EXPECT_EQ(nullptr, GetLocalCardMigrationOfferBubbleViews());
+  // Metrics
+  histogram_tester.ExpectUniqueSample(
+      "Autofill.LocalCardMigrationOrigin.UseOfLocalCard",
+      AutofillMetrics::INTERMEDIATE_BUBBLE_SHOWN, 1);
+}
+
+// Ensures that clicking on the credit card icon in the omnibox reopens the
+// offer bubble after closing it.
+IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTest,
+                       ClickingOmniboxIconReshowsBubble) {
+  base::HistogramTester histogram_tester;
+
+  SaveLocalCard(kFirstCardNumber);
+  SaveLocalCard(kSecondCardNumber);
+  UseCardAndWaitForMigrationOffer(kFirstCardNumber);
+  ClickOnDialogViewAndWait(GetCloseButton(),
+                           GetLocalCardMigrationOfferBubbleViews());
+  ClickOnView(GetLocalCardMigrationIconView());
+
+  // Clicking the icon should reshow the bubble.
+  EXPECT_TRUE(
+      FindViewInDialogById(DialogViewId::MAIN_CONTENT_VIEW_MIGRATION_BUBBLE,
+                           GetLocalCardMigrationOfferBubbleViews())
+          ->visible());
+  // Metrics
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples(
+          "Autofill.LocalCardMigrationOrigin.UseOfLocalCard"),
+      ElementsAre(Bucket(AutofillMetrics::INTERMEDIATE_BUBBLE_SHOWN, 1)));
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples(
+          "Autofill.LocalCardMigrationBubbleOffer.Reshows"),
+      ElementsAre(
+          Bucket(AutofillMetrics::LOCAL_CARD_MIGRATION_BUBBLE_REQUESTED, 1),
+          Bucket(AutofillMetrics::LOCAL_CARD_MIGRATION_BUBBLE_SHOWN, 1)));
+}
+
+// Ensures that accepting the intermediate migration offer opens up the main
+// migration dialog.
+IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTest,
+                       ClickingContinueOpensDialog) {
+  base::HistogramTester histogram_tester;
+
+  SaveLocalCard(kFirstCardNumber);
+  SaveLocalCard(kSecondCardNumber);
+  UseCardAndWaitForMigrationOffer(kFirstCardNumber);
+  // Click the [Continue] button.
+  ClickOnOkButton(GetLocalCardMigrationOfferBubbleViews());
+
+  // Dialog should be visible.
+  EXPECT_TRUE(FindViewInDialogById(
+                  DialogViewId::MAIN_CONTENT_VIEW_MIGRATION_OFFER_DIALOG,
+                  GetLocalCardMigrationMainDialogView())
+                  ->visible());
+  // Intermediate bubble should be gone.
+  EXPECT_EQ(nullptr, GetLocalCardMigrationOfferBubbleViews());
+  // Metrics
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples(
+          "Autofill.LocalCardMigrationOrigin.UseOfLocalCard"),
+      ElementsAre(Bucket(AutofillMetrics::INTERMEDIATE_BUBBLE_SHOWN, 1),
+                  Bucket(AutofillMetrics::INTERMEDIATE_BUBBLE_ACCEPTED, 1),
+                  Bucket(AutofillMetrics::MAIN_DIALOG_SHOWN, 1)));
+  histogram_tester.ExpectUniqueSample(
+      "Autofill.LocalCardMigrationBubbleUserInteraction.FirstShow",
+      AutofillMetrics::LOCAL_CARD_MIGRATION_BUBBLE_CLOSED_ACCEPTED, 1);
+  histogram_tester.ExpectUniqueSample(
+      "Autofill.LocalCardMigrationDialogOffer",
+      AutofillMetrics::LOCAL_CARD_MIGRATION_DIALOG_SHOWN, 1);
+}
+
+// Ensures that rejecting the main migration dialog closes the dialog.
+IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTest,
+                       ClickingCancelClosesDialog) {
+  base::HistogramTester histogram_tester;
+
+  SaveLocalCard(kFirstCardNumber);
+  SaveLocalCard(kSecondCardNumber);
+  UseCardAndWaitForMigrationOffer(kFirstCardNumber);
+  // Click the [Continue] button.
+  ClickOnOkButton(GetLocalCardMigrationOfferBubbleViews());
+  // Click the [Cancel] button.
+  ClickOnCancelButton(GetLocalCardMigrationMainDialogView());
+
+  // No dialog should be showing.
+  EXPECT_EQ(nullptr, GetLocalCardMigrationMainDialogView());
+  // Metrics
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples(
+          "Autofill.LocalCardMigrationOrigin.UseOfLocalCard"),
+      ElementsAre(Bucket(AutofillMetrics::INTERMEDIATE_BUBBLE_SHOWN, 1),
+                  Bucket(AutofillMetrics::INTERMEDIATE_BUBBLE_ACCEPTED, 1),
+                  Bucket(AutofillMetrics::MAIN_DIALOG_SHOWN, 1)));
+  histogram_tester.ExpectUniqueSample(
+      "Autofill.LocalCardMigrationDialogUserInteraction",
+      AutofillMetrics::LOCAL_CARD_MIGRATION_DIALOG_CLOSED_CANCEL_BUTTON_CLICKED,
+      1);
+}
+
+// Ensures that accepting the main migration dialog closes the dialog.
+IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTest,
+                       ClickingSaveClosesDialog) {
+  base::HistogramTester histogram_tester;
+
+  SaveLocalCard(kFirstCardNumber);
+  SaveLocalCard(kSecondCardNumber);
+  UseCardAndWaitForMigrationOffer(kFirstCardNumber);
+  // Click the [Continue] button in the bubble.
+  ClickOnOkButton(GetLocalCardMigrationOfferBubbleViews());
+  // Click the [Save] button in the dialog.
+  // TODO(crbug.com/897998): Add Payments Rpc setup and event waiter.
+  ClickOnOkButton(GetLocalCardMigrationMainDialogView());
+
+  // No dialog should be showing.
+  EXPECT_EQ(nullptr, GetLocalCardMigrationMainDialogView());
+  // Metrics
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples(
+          "Autofill.LocalCardMigrationOrigin.UseOfLocalCard"),
+      ElementsAre(Bucket(AutofillMetrics::INTERMEDIATE_BUBBLE_SHOWN, 1),
+                  Bucket(AutofillMetrics::INTERMEDIATE_BUBBLE_ACCEPTED, 1),
+                  Bucket(AutofillMetrics::MAIN_DIALOG_SHOWN, 1),
+                  Bucket(AutofillMetrics::MAIN_DIALOG_ACCEPTED, 1)));
+  histogram_tester.ExpectUniqueSample(
+      "Autofill.LocalCardMigrationDialogUserInteraction",
+      AutofillMetrics::LOCAL_CARD_MIGRATION_DIALOG_CLOSED_SAVE_BUTTON_CLICKED,
+      1);
+}
+
+// TODO(crbug.com/897998): Add these following tests.
+// 1) Reusing a server card shows the intermediate bubble.
+// 2) All valid cards are visible in the migration offer view.
+// 3) Local cards should get deleted after migration.
+// 4) Expired and invalid cards should not be shown.
+// 5) When user navigates away after five seconds, the bubble disappears.
+// 6) When user navigates away after five seconds, the dialog should stay.
+// 7) When user clicks legal message links, browser should show a pop-up window.
+// 8) Simulate checkboxes to ensure
+//    LocalCardMigrationDialogUserSelectionPercentage is logged correctly.
+// 9) Ensure LocalCardMigrationDialogActiveDuration is logged correctly.
+
+}  // namespace autofill
diff --git a/chrome/browser/ui/views/autofill/local_card_migration_bubble_views.cc b/chrome/browser/ui/views/autofill/local_card_migration_bubble_views.cc
index b2cfc02..472062fe 100644
--- a/chrome/browser/ui/views/autofill/local_card_migration_bubble_views.cc
+++ b/chrome/browser/ui/views/autofill/local_card_migration_bubble_views.cc
@@ -6,6 +6,7 @@
 
 #include <stddef.h>
 #include <memory>
+#include <utility>
 
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
@@ -29,6 +30,7 @@
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/fill_layout.h"
 #include "ui/views/style/typography.h"
+#include "ui/views/window/dialog_client_view.h"
 
 namespace autofill {
 
@@ -154,6 +156,7 @@
   explanatory_message->SetHorizontalAlignment(gfx::ALIGN_LEFT);
   explanatory_message->SetMultiLine(true);
   AddChildView(explanatory_message);
+  set_id(DialogViewId::MAIN_CONTENT_VIEW_MIGRATION_BUBBLE);
 }
 
 }  // namespace autofill
diff --git a/chrome/browser/ui/views/autofill/local_card_migration_bubble_views.h b/chrome/browser/ui/views/autofill/local_card_migration_bubble_views.h
index 80b3503d..aa0f787 100644
--- a/chrome/browser/ui/views/autofill/local_card_migration_bubble_views.h
+++ b/chrome/browser/ui/views/autofill/local_card_migration_bubble_views.h
@@ -47,6 +47,8 @@
   void WindowClosing() override;
 
  private:
+  friend class LocalCardMigrationBrowserTest;
+
   ~LocalCardMigrationBubbleViews() override;
 
   // views::BubbleDialogDelegateView:
diff --git a/chrome/browser/ui/views/autofill/local_card_migration_dialog_view.cc b/chrome/browser/ui/views/autofill/local_card_migration_dialog_view.cc
index 84b0331..3ce85b2 100644
--- a/chrome/browser/ui/views/autofill/local_card_migration_dialog_view.cc
+++ b/chrome/browser/ui/views/autofill/local_card_migration_dialog_view.cc
@@ -4,6 +4,10 @@
 
 #include "chrome/browser/ui/views/autofill/local_card_migration_dialog_view.h"
 
+#include <memory>
+#include <string>
+#include <vector>
+
 #include "base/i18n/message_formatter.h"
 #include "base/location.h"
 #include "base/macros.h"
@@ -311,6 +315,8 @@
   }
 
  private:
+  friend class LocalCardMigrationDialogView;
+
   LocalCardMigrationDialogController* controller_;
 
   views::View* card_list_view_ = nullptr;
@@ -467,6 +473,8 @@
 
   if (view_state == LocalCardMigrationDialogState::kOffered) {
     offer_view_ = new LocalCardMigrationOfferView(controller_, this);
+    offer_view_->set_id(DialogViewId::MAIN_CONTENT_VIEW_MIGRATION_OFFER_DIALOG);
+    card_list_view_ = offer_view_->card_list_view_;
     AddChildView(offer_view_);
   } else {
     AddChildView(CreateFeedbackContentView(controller_, this).release());
diff --git a/chrome/browser/ui/views/autofill/local_card_migration_dialog_view.h b/chrome/browser/ui/views/autofill/local_card_migration_dialog_view.h
index 4b2759b..5f559cd 100644
--- a/chrome/browser/ui/views/autofill/local_card_migration_dialog_view.h
+++ b/chrome/browser/ui/views/autofill/local_card_migration_dialog_view.h
@@ -51,7 +51,10 @@
   void UpdateLayout();
 
  private:
+  friend class LocalCardMigrationBrowserTest;
+
   void ConstructView();
+
   base::string16 GetOkButtonLabel() const;
   base::string16 GetCancelButtonLabel() const;
 
@@ -63,6 +66,10 @@
   // dialog is not in the 'offer' state.
   LocalCardMigrationOfferView* offer_view_ = nullptr;
 
+  // The view containing a list of cards. It is the content of the scroll bar.
+  // Owned by the LocalCardMigrationOfferView.
+  views::View* card_list_view_;
+
   DISALLOW_COPY_AND_ASSIGN(LocalCardMigrationDialogView);
 };
 
diff --git a/chrome/browser/ui/views/tabs/glow_hover_controller.cc b/chrome/browser/ui/views/tabs/glow_hover_controller.cc
index 2dcf1d4..681db8d 100644
--- a/chrome/browser/ui/views/tabs/glow_hover_controller.cc
+++ b/chrome/browser/ui/views/tabs/glow_hover_controller.cc
@@ -40,15 +40,15 @@
   subtle_opacity_scale_ = opacity_scale;
 }
 
-void GlowHoverController::Show(Style style) {
+void GlowHoverController::Show(ShowStyle style) {
   switch (style) {
-    case SUBTLE:
+    case ShowStyle::kSubtle:
       opacity_scale_ = subtle_opacity_scale_;
       animation_.SetSlideDuration(kTrackHoverDurationMs);
       animation_.SetTweenType(gfx::Tween::EASE_OUT);
       animation_.Show();
       break;
-    case PRONOUNCED:
+    case ShowStyle::kPronounced:
       opacity_scale_ = kPronouncedOpacityScale;
       // Force the end state to show immediately.
       animation_.Show();
@@ -57,15 +57,18 @@
   }
 }
 
-void GlowHoverController::Hide() {
-  animation_.SetTweenType(gfx::Tween::EASE_IN);
-  animation_.Hide();
-}
-
-void GlowHoverController::HideImmediately() {
-  if (ShouldDraw())
-    view_->SchedulePaint();
-  animation_.Reset();
+void GlowHoverController::Hide(HideStyle style) {
+  switch (style) {
+    case HideStyle::kGradual:
+      animation_.SetTweenType(gfx::Tween::EASE_IN);
+      animation_.Hide();
+      break;
+    case HideStyle::kImmediate:
+      if (ShouldDraw())
+        view_->SchedulePaint();
+      animation_.Reset();
+      break;
+  }
 }
 
 double GlowHoverController::GetAnimationValue() const {
diff --git a/chrome/browser/ui/views/tabs/glow_hover_controller.h b/chrome/browser/ui/views/tabs/glow_hover_controller.h
index 7fcc29d..d95b558 100644
--- a/chrome/browser/ui/views/tabs/glow_hover_controller.h
+++ b/chrome/browser/ui/views/tabs/glow_hover_controller.h
@@ -27,7 +27,12 @@
 // invokes SchedulePaint() back on the View as necessary.
 class GlowHoverController : public gfx::AnimationDelegate {
  public:
-  enum Style { SUBTLE, PRONOUNCED };
+  enum class ShowStyle { kSubtle, kPronounced };
+
+  enum class HideStyle {
+    kGradual,    // The hover should fade out.
+    kImmediate,  // The hover should cut off, with no fade out.
+  };
 
   explicit GlowHoverController(views::View* view);
   ~GlowHoverController() override;
@@ -45,13 +50,10 @@
   const gfx::Point& location() const { return location_; }
 
   // Initiates showing the hover.
-  void Show(Style style);
+  void Show(ShowStyle style);
 
   // Hides the hover.
-  void Hide();
-
-  // Hides the hover immediately.
-  void HideImmediately();
+  void Hide(HideStyle);
 
   // Returns the value of the animation.
   double GetAnimationValue() const;
diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc
index d06bc67b..ae914a7 100644
--- a/chrome/browser/ui/views/tabs/tab.cc
+++ b/chrome/browser/ui/views/tabs/tab.cc
@@ -91,9 +91,6 @@
 // transitioning a tab from normal to pinned tab.
 constexpr int kPinnedTabExtraWidthToRenderAsNormal = 30;
 
-// Opacity of the active tab background painted over inactive selected tabs.
-constexpr float kSelectedTabOpacity = 0.75f;
-
 // Helper functions ------------------------------------------------------------
 
 // Returns the coordinate for an object of size |item_size| centered in a region
@@ -121,10 +118,7 @@
 Tab::Tab(TabController* controller)
     : controller_(controller),
       title_(new views::Label()),
-      title_animation_(this),
-      hover_controller_(gfx::Animation::ShouldRenderRichAnimation()
-                            ? new GlowHoverController(this)
-                            : nullptr) {
+      title_animation_(this) {
   DCHECK(controller);
 
   tab_style_ = TabStyle::CreateForTab(this);
@@ -173,26 +167,16 @@
 Tab::~Tab() = default;
 
 void Tab::AnimationEnded(const gfx::Animation* animation) {
-  if (animation == &title_animation_)
-    title_->SetBoundsRect(target_title_bounds_);
-  else
-    SchedulePaint();
+  DCHECK_EQ(animation, &title_animation_);
+  title_->SetBoundsRect(target_title_bounds_);
 }
 
 void Tab::AnimationProgressed(const gfx::Animation* animation) {
-  if (animation == &title_animation_) {
-    title_->SetBoundsRect(gfx::Tween::RectValueBetween(
-        gfx::Tween::CalculateValue(gfx::Tween::FAST_OUT_SLOW_IN,
-                                   animation->GetCurrentValue()),
-        start_title_bounds_, target_title_bounds_));
-    return;
-  }
-
-  SchedulePaint();
-}
-
-void Tab::AnimationCanceled(const gfx::Animation* animation) {
-  SchedulePaint();
+  DCHECK_EQ(animation, &title_animation_);
+  title_->SetBoundsRect(gfx::Tween::RectValueBetween(
+      gfx::Tween::CalculateValue(gfx::Tween::FAST_OUT_SLOW_IN,
+                                 animation->GetCurrentValue()),
+      start_title_bounds_, target_title_bounds_));
 }
 
 void Tab::ButtonPressed(views::Button* sender, const ui::Event& event) {
@@ -493,18 +477,13 @@
 }
 
 void Tab::OnMouseMoved(const ui::MouseEvent& event) {
-  if (hover_controller_)
-    hover_controller_->SetLocation(event.location());
+  tab_style_->SetHoverLocation(event.location());
   controller_->OnMouseEventInTab(this, event);
 }
 
 void Tab::OnMouseEntered(const ui::MouseEvent& event) {
   mouse_hovered_ = true;
-  if (hover_controller_) {
-    hover_controller_->SetSubtleOpacityScale(
-        controller_->GetHoverOpacityForRadialHighlight());
-    hover_controller_->Show(GlowHoverController::SUBTLE);
-  }
+  tab_style_->ShowHover(GlowHoverController::ShowStyle::kSubtle);
   UpdateForegroundColors();
   Layout();
   controller_->UpdateHoverCard(this, true);
@@ -512,8 +491,7 @@
 
 void Tab::OnMouseExited(const ui::MouseEvent& event) {
   mouse_hovered_ = false;
-  if (hover_controller_)
-    hover_controller_->Hide();
+  tab_style_->HideHover(GlowHoverController::HideStyle::kGradual);
   UpdateForegroundColors();
   Layout();
 }
@@ -820,23 +798,6 @@
           (ideal_x - bounds->x())));
 }
 
-float Tab::GetThrobValue() const {
-  const bool is_selected = IsSelected();
-  double val = is_selected ? kSelectedTabOpacity : 0;
-
-  // Wrapping in closure to only compute offset when needed (animate or hover).
-  const auto offset = [=] {
-    constexpr float kSelectedTabThrobScale = 0.95f - kSelectedTabOpacity;
-    const float opacity = GetHoverOpacity();
-    return is_selected ? (kSelectedTabThrobScale * opacity) : opacity;
-  };
-
-  if (hover_controller_ && hover_controller_->ShouldDraw())
-    val += hover_controller_->GetAnimationValue() * offset();
-
-  return val;
-}
-
 void Tab::UpdateIconVisibility() {
   // TODO(pkasting): This whole function should go away, and we should simply
   // compute child visibility state in Layout().
@@ -946,17 +907,6 @@
                                         kPinnedTabExtraWidthToRenderAsNormal));
 }
 
-float Tab::GetHoverOpacity() const {
-  // Opacity boost varies on tab width.  The interpolation is nonlinear so
-  // that most tabs will fall on the low end of the opacity range, but very
-  // narrow tabs will still stand out on the high end.
-  const float range_start = float{TabStyle::GetStandardWidth()};
-  const float range_end = float{TabStyle::GetMinimumInactiveWidth()};
-  const float value_in_range = float{width()};
-  const float t = (value_in_range - range_start) / (range_end - range_start);
-  return controller_->GetHoverOpacityForTab(t * t);
-}
-
 void Tab::UpdateTabIconNeedsAttentionBlocked() {
   // Only show the blocked attention indicator on non-active tabs. For active
   // tabs, the user sees the dialog blocking the tab, so there's no point to it
@@ -970,80 +920,18 @@
 }
 
 void Tab::UpdateForegroundColors() {
-  // The theme provider may be null if we're not currently in a widget
-  // hierarchy.
-  const ui::ThemeProvider* theme_provider = GetThemeProvider();
-  if (!theme_provider)
-    return;
+  TabStyle::TabColors colors = tab_style_->CalculateColors();
 
-  // These ratios are calculated from the default Chrome theme colors.
-  // Active/inactive are the contrast ratios of the close X against the tab
-  // background. Hovered/pressed are the contrast ratios of the highlight circle
-  // against the tab background.
-  constexpr float kMinimumActiveContrastRatio = 6.05f;
-  constexpr float kMinimumInactiveContrastRatio = 4.61f;
-  constexpr float kMinimumHoveredContrastRatio = 5.02f;
-  constexpr float kMinimumPressedContrastRatio = 4.41f;
+  icon_->SetBackgroundColor(colors.background_color);
+  title_->SetEnabledColor(colors.title_color);
 
-  // In some cases, inactive tabs may have background more like active tabs than
-  // inactive tabs, so colors should be adapted to ensure appropriate contrast.
-  // In particular, text should have plenty of contrast in all cases, so switch
-  // to using foreground color designed for active tabs if the tab looks more
-  // like an active tab than an inactive tab.
-  float expected_opacity = 0.0f;
-  if (IsActive()) {
-    expected_opacity = 1.0f;
-  } else if (IsSelected()) {
-    expected_opacity = kSelectedTabOpacity;
-  } else if (mouse_hovered_) {
-    expected_opacity = GetHoverOpacity();
-  }
-  const SkColor tab_bg_color = color_utils::AlphaBlend(
-      controller_->GetTabBackgroundColor(TAB_ACTIVE),
-      controller_->GetTabBackgroundColor(TAB_INACTIVE), expected_opacity);
-  SkColor tab_title_color = controller_->GetTabForegroundColor(
-      expected_opacity > 0.5f ? TAB_ACTIVE : TAB_INACTIVE, tab_bg_color);
-  tab_title_color =
-      color_utils::GetColorWithMinimumContrast(tab_title_color, tab_bg_color);
-
-  icon_->SetBackgroundColor(tab_bg_color);
-
-  title_->SetEnabledColor(tab_title_color);
-
-  const SkColor base_hovered_color = theme_provider->GetColor(
-      ThemeProperties::COLOR_TAB_CLOSE_BUTTON_BACKGROUND_HOVER);
-  const SkColor base_pressed_color = theme_provider->GetColor(
-      ThemeProperties::COLOR_TAB_CLOSE_BUTTON_BACKGROUND_PRESSED);
-
-  const auto get_color_for_contrast_ratio = [](SkColor fg_color,
-                                               SkColor bg_color,
-                                               float contrast_ratio) {
-    const SkAlpha blend_alpha = color_utils::GetBlendValueWithMinimumContrast(
-        bg_color, fg_color, bg_color, contrast_ratio);
-    return color_utils::AlphaBlend(fg_color, bg_color, blend_alpha);
-  };
-
-  const SkColor generated_icon_color = get_color_for_contrast_ratio(
-      tab_title_color, tab_bg_color,
-      IsActive() ? kMinimumActiveContrastRatio : kMinimumInactiveContrastRatio);
-  const SkColor generated_hovered_color = get_color_for_contrast_ratio(
-      base_hovered_color, tab_bg_color, kMinimumHoveredContrastRatio);
-  const SkColor generated_pressed_color = get_color_for_contrast_ratio(
-      base_pressed_color, tab_bg_color, kMinimumPressedContrastRatio);
-
-  const SkColor generated_hovered_icon_color =
-      color_utils::GetColorWithMinimumContrast(tab_title_color,
-                                               generated_hovered_color);
-  const SkColor generated_pressed_icon_color =
-      color_utils::GetColorWithMinimumContrast(tab_title_color,
-                                               generated_pressed_color);
   close_button_->SetIconColors(
-      generated_icon_color, generated_hovered_icon_color,
-      generated_pressed_icon_color, generated_hovered_color,
-      generated_pressed_color);
+      colors.button_icon_idle_color, colors.button_icon_hovered_color,
+      colors.button_icon_hovered_color, colors.button_background_hovered_color,
+      colors.button_background_pressed_color);
 
-  if (button_color_ != generated_icon_color) {
-    button_color_ = generated_icon_color;
+  if (button_color_ != colors.button_icon_idle_color) {
+    button_color_ = colors.button_icon_idle_color;
     alert_indicator_->OnParentTabButtonColorChanged();
   }
 
diff --git a/chrome/browser/ui/views/tabs/tab.h b/chrome/browser/ui/views/tabs/tab.h
index 4d0cfa4e..019c1aec 100644
--- a/chrome/browser/ui/views/tabs/tab.h
+++ b/chrome/browser/ui/views/tabs/tab.h
@@ -12,7 +12,6 @@
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
-#include "chrome/browser/ui/views/tabs/glow_hover_controller.h"
 #include "chrome/browser/ui/views/tabs/tab_renderer_data.h"
 #include "ui/base/layout.h"
 #include "ui/gfx/animation/animation_delegate.h"
@@ -65,7 +64,6 @@
   // gfx::AnimationDelegate:
   void AnimationEnded(const gfx::Animation* animation) override;
   void AnimationProgressed(const gfx::Animation* animation) override;
-  void AnimationCanceled(const gfx::Animation* animation) override;
 
   // views::ButtonListener:
   void ButtonPressed(views::Button* sender, const ui::Event& event) override;
@@ -168,19 +166,10 @@
     return tab_activated_with_last_tap_down_;
   }
 
-  GlowHoverController* hover_controller() { return hover_controller_.get(); }
-  const GlowHoverController* hover_controller() const {
-    return hover_controller_.get();
-  }
-
   bool mouse_hovered() const { return mouse_hovered_; }
 
-  // Gets the throb value for the tab. When a tab is not selected the active
-  // background is drawn at GetThrobValue() * 100%. This is used for hover, mini
-  // tab title change and pulsing.
-  float GetThrobValue() const;
-
   // Returns the TabStyle associated with this tab.
+  TabStyle* tab_style() { return tab_style_.get(); }
   const TabStyle* tab_style() const { return tab_style_.get(); }
 
   // Returns the text to show in a tab's tooltip: The contents |title|, followed
@@ -211,9 +200,6 @@
   // pinned tab.
   bool ShouldRenderAsNormalTab() const;
 
-  // Returns the final hover opacity for this tab (considers tab width).
-  float GetHoverOpacity() const;
-
   // Updates the blocked attention state of the |icon_|. This only updates
   // state; it is the responsibility of the caller to request a paint.
   void UpdateTabIconNeedsAttentionBlocked();
@@ -223,7 +209,7 @@
   // and alert icon.
   void UpdateForegroundColors();
 
-  // The controller, never NULL.
+  // The controller, never nullptr.
   TabController* const controller_;
 
   TabRendererData data_;
@@ -252,8 +238,6 @@
 
   bool tab_activated_with_last_tap_down_ = false;
 
-  std::unique_ptr<GlowHoverController> hover_controller_;
-
   // The offset used to paint the inactive background image.
   int background_offset_;
 
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 4f852c3..f0696ba 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -1366,9 +1366,7 @@
   // this code defers drawing such tabs until later.
   const auto paint_or_add_to_tabs = [&paint_info,
                                      &selected_and_hovered_tabs](Tab* tab) {
-    if (tab->IsSelected() ||
-        (tab->mouse_hovered() ||
-         (tab->hover_controller() && tab->hover_controller()->ShouldDraw()))) {
+    if (tab->tab_style()->GetZValue() > 0.0) {
       selected_and_hovered_tabs.push_back(tab);
     } else {
       tab->Paint(paint_info);
@@ -1417,39 +1415,10 @@
     }
   }
 
-  // This will sort the inactive tabs so that they paint in the following order:
-  //
-  // o Unselected and hover-animating tabs in ascending animation value order.
-  // o The single unselected mouse hovered tab, if present.
-  // o Selected tabs in trailing-to-leading order.
-  // o Selected and hover animating tabs in ascending animation value order.
-  // o The single selected and mouse_hovered tab, if present.
-  //
-  // This is accomplished by adding a "weight" to the current hover animation
-  // value which represents the above groupings.
-  //
-  // 0.0 == sort_value         Unselected/non hover animating (already painted).
-  // 0.0 <  sort_value <= 1.0  Unselected/hover animating.
-  // 2.0 <= sort_value <= 3.0  Unselected/mouse hovered tab.
-  // 4.0 == sort_value         Selected/non hover animating.
-  // 4.0 <  sort_value <= 5.0  Selected/hover animating.
-  // 6.0 <= sort_value <= 7.0  Selected/mouse hovered tab.
-  //
-  auto tab_sort_value = [](Tab* tab) {
-    float sort_value = tab->hover_controller()
-                           ? tab->hover_controller()->GetAnimationValue()
-                           : 0;
-    if (tab->IsSelected())
-      sort_value += 4.f;
-    if (tab->mouse_hovered())
-      sort_value += 2.f;
-    return sort_value;
-  };
-
   std::stable_sort(selected_and_hovered_tabs.begin(),
-                   selected_and_hovered_tabs.end(),
-                   [&tab_sort_value](Tab* tab1, Tab* tab2) {
-                     return tab_sort_value(tab1) < tab_sort_value(tab2);
+                   selected_and_hovered_tabs.end(), [](Tab* tab1, Tab* tab2) {
+                     return tab1->tab_style()->GetZValue() <
+                            tab2->tab_style()->GetZValue();
                    });
   for (Tab* tab : selected_and_hovered_tabs)
     tab->Paint(paint_info);
diff --git a/chrome/browser/ui/views/tabs/tab_style.cc b/chrome/browser/ui/views/tabs/tab_style.cc
index d1caa9f..e3f91b1 100644
--- a/chrome/browser/ui/views/tabs/tab_style.cc
+++ b/chrome/browser/ui/views/tabs/tab_style.cc
@@ -28,6 +28,9 @@
 
 namespace {
 
+// Opacity of the active tab background painted over inactive selected tabs.
+constexpr float kSelectedTabOpacity = 0.75f;
+
 // Cache of pre-painted backgrounds for tabs.
 class BackgroundCache {
  public:
@@ -71,7 +74,7 @@
 // Tab style implementation for the GM2 refresh (Chrome 69).
 class GM2TabStyle : public TabStyle {
  public:
-  explicit GM2TabStyle(const Tab* tab);
+  explicit GM2TabStyle(Tab* tab);
 
  protected:
   // TabStyle:
@@ -81,8 +84,12 @@
       bool force_active = false,
       RenderUnits render_units = RenderUnits::kPixels) const override;
   gfx::Insets GetContentsInsets() const override;
-  int GetStrokeThickness(bool should_paint_as_active = false) const override;
+  float GetZValue() const override;
+  TabStyle::TabColors CalculateColors() const override;
   void PaintTab(gfx::Canvas* canvas, const SkPath& clip) const override;
+  void SetHoverLocation(const gfx::Point& location) override;
+  void ShowHover(GlowHoverController::ShowStyle style) override;
+  void HideHover(GlowHoverController::HideStyle style) override;
 
  private:
   // Gets the bounds for the leading and trailing separators for a tab.
@@ -96,6 +103,25 @@
   // Returns whether we shoould extend the hit test region for Fitts' Law.
   bool ShouldExtendHitTest() const;
 
+  // Returns whether the hover animation is being shown.
+  bool IsHoverActive() const;
+
+  // Returns the progress (0 to 1) of the hover animation.
+  double GetHoverAnimationValue() const;
+
+  // Returns the opacity of the hover effect that should be drawn, which may not
+  // be the same as GetHoverAnimationValue.
+  float GetHoverOpacity() const;
+
+  // Gets the throb value. A value of 0 indicates no throbbing.
+  float GetThrobValue() const;
+
+  // Returns the thickness of the stroke drawn around the top and sides of the
+  // tab. Only active tabs may have a stroke, and not in all cases. If there
+  // is no stroke, returns 0. If |should_paint_as_active| is true, the tab is
+  // treated as an active tab regardless of its true current state.
+  int GetStrokeThickness(bool should_paint_as_active = false) const;
+
   // Painting helper functions:
   void PaintInactiveTabBackground(gfx::Canvas* canvas,
                                   const SkPath& clip) const;
@@ -127,9 +153,13 @@
 
   const Tab* const tab_;
 
+  std::unique_ptr<GlowHoverController> hover_controller_;
+
   // Cache of the paint output for tab backgrounds.
   mutable BackgroundCache background_active_cache_;
   mutable BackgroundCache background_inactive_cache_;
+
+  DISALLOW_COPY_AND_ASSIGN(GM2TabStyle);
 };
 
 // Thickness in DIPs of the separator painted on the left and right edges of
@@ -194,7 +224,11 @@
 
 // GM2TabStyle -----------------------------------------------------------------
 
-GM2TabStyle::GM2TabStyle(const Tab* tab) : tab_(tab) {}
+GM2TabStyle::GM2TabStyle(Tab* tab)
+    : tab_(tab),
+      hover_controller_(gfx::Animation::ShouldRenderRichAnimation()
+                            ? new GlowHoverController(tab)
+                            : nullptr) {}
 
 SkPath GM2TabStyle::GetPath(PathType path_type,
                             float scale,
@@ -386,10 +420,105 @@
       horizontal_inset);
 }
 
-int GM2TabStyle::GetStrokeThickness(bool should_paint_as_active) const {
-  return (tab_->IsActive() || should_paint_as_active)
-             ? tab_->controller()->GetStrokeThickness()
-             : 0;
+float GM2TabStyle::GetZValue() const {
+  // This will return values so that inactive tabs can be sorted in the
+  // following order:
+  //
+  // o Unselected tabs, in ascending hover animation value order.
+  // o The single unselected tab being hovered by the mouse, if present.
+  // o Selected tabs, in ascending hover animation value order.
+  // o The single selected tab being hovered by the mouse, if present.
+  //
+  // Representing the above groupings is accomplished by adding a "weight" to
+  // the current hover animation value.
+  //
+  // 0.0 == z-value         Unselected/non hover animating.
+  // 0.0 <  z-value <= 1.0  Unselected/hover animating.
+  // 2.0 <= z-value <= 3.0  Unselected/mouse hovered tab.
+  // 4.0 == z-value         Selected/non hover animating.
+  // 4.0 <  z-value <= 5.0  Selected/hover animating.
+  // 6.0 <= z-value <= 7.0  Selected/mouse hovered tab.
+  //
+  // This function doesn't handle active tabs, as they are normally painted by a
+  // different code path (with z-value infinity).
+  float sort_value = GetHoverAnimationValue();
+  if (tab_->IsSelected())
+    sort_value += 4.f;
+  if (tab_->mouse_hovered())
+    sort_value += 2.f;
+  return sort_value;
+}
+
+TabStyle::TabColors GM2TabStyle::CalculateColors() const {
+  const ui::ThemeProvider* theme_provider = tab_->GetThemeProvider();
+
+  // These ratios are calculated from the default Chrome theme colors.
+  // Active/inactive are the contrast ratios of the close X against the tab
+  // background. Hovered/pressed are the contrast ratios of the highlight circle
+  // against the tab background.
+  constexpr float kMinimumActiveContrastRatio = 6.05f;
+  constexpr float kMinimumInactiveContrastRatio = 4.61f;
+  constexpr float kMinimumHoveredContrastRatio = 5.02f;
+  constexpr float kMinimumPressedContrastRatio = 4.41f;
+
+  // In some cases, inactive tabs may have background more like active tabs than
+  // inactive tabs, so colors should be adapted to ensure appropriate contrast.
+  // In particular, text should have plenty of contrast in all cases, so switch
+  // to using foreground color designed for active tabs if the tab looks more
+  // like an active tab than an inactive tab.
+  float expected_opacity = 0.0f;
+  if (tab_->IsActive()) {
+    expected_opacity = 1.0f;
+  } else if (tab_->IsSelected()) {
+    expected_opacity = kSelectedTabOpacity;
+  } else if (tab_->mouse_hovered()) {
+    expected_opacity = GetHoverOpacity();
+  }
+  const SkColor bg_color = color_utils::AlphaBlend(
+      tab_->controller()->GetTabBackgroundColor(TAB_ACTIVE),
+      tab_->controller()->GetTabBackgroundColor(TAB_INACTIVE),
+      expected_opacity);
+
+  SkColor title_color = tab_->controller()->GetTabForegroundColor(
+      expected_opacity > 0.5f ? TAB_ACTIVE : TAB_INACTIVE, bg_color);
+  title_color = color_utils::GetColorWithMinimumContrast(title_color, bg_color);
+
+  const SkColor base_hovered_color = theme_provider->GetColor(
+      ThemeProperties::COLOR_TAB_CLOSE_BUTTON_BACKGROUND_HOVER);
+  const SkColor base_pressed_color = theme_provider->GetColor(
+      ThemeProperties::COLOR_TAB_CLOSE_BUTTON_BACKGROUND_PRESSED);
+
+  const auto get_color_for_contrast_ratio = [](SkColor fg_color,
+                                               SkColor bg_color,
+                                               float contrast_ratio) {
+    const SkAlpha blend_alpha = color_utils::GetBlendValueWithMinimumContrast(
+        bg_color, fg_color, bg_color, contrast_ratio);
+    return color_utils::AlphaBlend(fg_color, bg_color, blend_alpha);
+  };
+
+  const SkColor generated_icon_color = get_color_for_contrast_ratio(
+      title_color, bg_color,
+      tab_->IsActive() ? kMinimumActiveContrastRatio
+                       : kMinimumInactiveContrastRatio);
+  const SkColor generated_hovered_color = get_color_for_contrast_ratio(
+      base_hovered_color, bg_color, kMinimumHoveredContrastRatio);
+  const SkColor generated_pressed_color = get_color_for_contrast_ratio(
+      base_pressed_color, bg_color, kMinimumPressedContrastRatio);
+
+  const SkColor generated_hovered_icon_color =
+      color_utils::GetColorWithMinimumContrast(title_color,
+                                               generated_hovered_color);
+  const SkColor generated_pressed_icon_color =
+      color_utils::GetColorWithMinimumContrast(title_color,
+                                               generated_pressed_color);
+
+  return {bg_color,
+          title_color,
+          generated_icon_color,
+          generated_hovered_icon_color,
+          generated_pressed_icon_color,
+          generated_hovered_color,
+          generated_pressed_color};
 }
 
 void GM2TabStyle::PaintTab(gfx::Canvas* canvas, const SkPath& clip) const {
@@ -406,7 +535,7 @@
   } else {
     PaintInactiveTabBackground(canvas, clip);
 
-    const float throb_value = tab_->GetThrobValue();
+    const float throb_value = GetThrobValue();
     if (throb_value > 0) {
       canvas->SaveLayerAlpha(gfx::ToRoundedInt(throb_value * 0xff),
                              tab_->GetLocalBounds());
@@ -417,6 +546,27 @@
   }
 }
 
+void GM2TabStyle::SetHoverLocation(const gfx::Point& location) {
+  if (hover_controller_)
+    hover_controller_->SetLocation(location);
+}
+
+void GM2TabStyle::ShowHover(GlowHoverController::ShowStyle style) {
+  if (!hover_controller_)
+    return;
+
+  if (style == GlowHoverController::ShowStyle::kSubtle) {
+    hover_controller_->SetSubtleOpacityScale(
+        tab_->controller()->GetHoverOpacityForRadialHighlight());
+  }
+  hover_controller_->Show(style);
+}
+
+void GM2TabStyle::HideHover(GlowHoverController::HideStyle style) {
+  if (hover_controller_)
+    hover_controller_->Hide(style);
+}
+
 TabStyle::SeparatorBounds GM2TabStyle::GetSeparatorBounds(float scale) const {
   const gfx::RectF aligned_bounds =
       ScaleAndAlignBounds(tab_->bounds(), scale, GetStrokeThickness());
@@ -462,15 +612,14 @@
     // hovered.  If the subsequent tab is active, don't consider its hover
     // animation value, lest the trailing separator on this tab disappear while
     // the subsequent tab is being dragged.
-    const float hover_value =
-        tab_->hover_controller() ? tab_->hover_controller()->GetAnimationValue()
-                                 : 0;
+    const float hover_value = GetHoverAnimationValue();
     const Tab* subsequent_tab = tab_->controller()->GetAdjacentTab(tab_, 1);
-    const float subsequent_hover =
-        !for_layout && subsequent_tab && subsequent_tab->hover_controller() &&
-                !subsequent_tab->IsActive()
-            ? float{subsequent_tab->hover_controller()->GetAnimationValue()}
-            : 0;
+    float subsequent_hover = 0;
+    if (!for_layout && subsequent_tab && !subsequent_tab->IsActive()) {
+      auto* subsequent_tab_style =
+          static_cast<const GM2TabStyle*>(subsequent_tab->tab_style());
+      subsequent_hover = float{subsequent_tab_style->GetHoverAnimationValue()};
+    }
     trailing_opacity = 1.f - std::max(hover_value, subsequent_hover);
 
     // The leading separator need not consider the previous tab's hover value,
@@ -536,6 +685,50 @@
   return widget->IsMaximized() || widget->IsFullscreen();
 }
 
+bool GM2TabStyle::IsHoverActive() const {
+  if (!hover_controller_)
+    return false;
+  return hover_controller_->ShouldDraw();
+}
+
+double GM2TabStyle::GetHoverAnimationValue() const {
+  if (!hover_controller_)
+    return 0.0;
+  return hover_controller_->GetAnimationValue();
+}
+
+float GM2TabStyle::GetHoverOpacity() const {
+  // Opacity boost varies on tab width.  The interpolation is nonlinear so
+  // that most tabs will fall on the low end of the opacity range, but very
+  // narrow tabs will still stand out on the high end.
+  const float range_start = float{GetStandardWidth()};
+  const float range_end = float{GetMinimumInactiveWidth()};
+  const float value_in_range = float{tab_->width()};
+  const float t = (value_in_range - range_start) / (range_end - range_start);
+  return tab_->controller()->GetHoverOpacityForTab(t * t);
+}
+
+float GM2TabStyle::GetThrobValue() const {
+  const bool is_selected = tab_->IsSelected();
+  double val = is_selected ? kSelectedTabOpacity : 0;
+
+  if (IsHoverActive()) {
+    constexpr float kSelectedTabThrobScale = 0.95f - kSelectedTabOpacity;
+    const float opacity = GetHoverOpacity();
+    const float offset =
+        is_selected ? (kSelectedTabThrobScale * opacity) : opacity;
+    val += GetHoverAnimationValue() * offset;
+  }
+
+  return val;
+}
+
+int GM2TabStyle::GetStrokeThickness(bool should_paint_as_active) const {
+  return (tab_->IsActive() || should_paint_as_active)
+             ? tab_->controller()->GetStrokeThickness()
+             : 0;
+}
+
 void GM2TabStyle::PaintInactiveTabBackground(gfx::Canvas* canvas,
                                              const SkPath& clip) const {
   bool has_custom_image;
@@ -564,8 +757,7 @@
           : SK_ColorTRANSPARENT;
   const SkColor stroke_color =
       tab_->controller()->GetToolbarTopSeparatorColor();
-  const bool paint_hover_effect = !active && tab_->hover_controller() &&
-                                  tab_->hover_controller()->ShouldDraw();
+  const bool paint_hover_effect = !active && IsHoverActive();
   const float scale = canvas->image_scale();
   const float stroke_thickness = GetStrokeThickness(active);
 
@@ -660,15 +852,13 @@
   }
 
   if (paint_hover_effect) {
-    SkPoint hover_location(
-        gfx::PointToSkPoint(tab_->hover_controller()->location()));
+    SkPoint hover_location(gfx::PointToSkPoint(hover_controller_->location()));
     hover_location.scale(SkFloatToScalar(scale));
     const SkScalar kMinHoverRadius = 16;
     const SkScalar radius =
         std::max(SkFloatToScalar(tab_->width() / 4.f), kMinHoverRadius);
-    DrawHighlight(
-        canvas, hover_location, radius * scale,
-        SkColorSetA(active_color, tab_->hover_controller()->GetAlpha()));
+    DrawHighlight(canvas, hover_location, radius * scale,
+                  SkColorSetA(active_color, hover_controller_->GetAlpha()));
   }
 }
 
@@ -767,7 +957,7 @@
 TabStyle::~TabStyle() = default;
 
 // static
-std::unique_ptr<TabStyle> TabStyle::CreateForTab(const Tab* tab) {
+std::unique_ptr<TabStyle> TabStyle::CreateForTab(Tab* tab) {
   return std::make_unique<GM2TabStyle>(tab);
 }
 
diff --git a/chrome/browser/ui/views/tabs/tab_style.h b/chrome/browser/ui/views/tabs/tab_style.h
index 1a22ca9..7393cfd 100644
--- a/chrome/browser/ui/views/tabs/tab_style.h
+++ b/chrome/browser/ui/views/tabs/tab_style.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/macros.h"
+#include "chrome/browser/ui/views/tabs/glow_hover_controller.h"
 #include "ui/gfx/geometry/rect_f.h"
 
 namespace gfx {
@@ -67,6 +68,17 @@
     float left = 0, right = 0;
   };
 
+  // Colors for various parts of the tab derived by TabStyle.
+  struct TabColors {
+    SkColor background_color;
+    SkColor title_color;
+    SkColor button_icon_idle_color;
+    SkColor button_icon_hovered_color;
+    SkColor button_icon_pressed_color;
+    SkColor button_background_hovered_color;
+    SkColor button_background_pressed_color;
+  };
+
   // Creates an appropriate TabStyle instance for a particular tab.
   // Caller is responsibly for the TabStyle object's lifespan and should delete
   // it when finished.
@@ -74,7 +86,7 @@
   // We've implemented this as a factory function so that when we're playing
   // with new variatons on tab shapes we can have a few possible implementations
   // and switch them in one place.
-  static std::unique_ptr<TabStyle> CreateForTab(const Tab* tab);
+  static std::unique_ptr<TabStyle> CreateForTab(Tab* tab);
 
   virtual ~TabStyle();
 
@@ -88,18 +100,28 @@
       bool force_active = false,
       RenderUnits render_units = RenderUnits::kPixels) const = 0;
 
-  // Returns the thickness of the stroke drawn around the top and sides of the
-  // tab.  Only active tabs may have a stroke, and not in all cases.  If there
-  // is no stroke, returns 0.  If |should_paint_as_active| is true, the tab is
-  // treated as an active tab regardless of its true current state.
-  virtual int GetStrokeThickness(bool should_paint_as_active = false) const = 0;
-
-  // Paint the tab.
-  virtual void PaintTab(gfx::Canvas* canvas, const SkPath& clip) const = 0;
-
   // Returns the insets to use for laying out tab contents.
   virtual gfx::Insets GetContentsInsets() const = 0;
 
+  // Returns the z-value of the tab, which should be used to paint them in
+  // ascending order.
+  virtual float GetZValue() const = 0;
+
+  // Derives and returns colors for the tab. See TabColors, above.
+  virtual TabColors CalculateColors() const = 0;
+
+  // Paints the tab.
+  virtual void PaintTab(gfx::Canvas* canvas, const SkPath& clip) const = 0;
+
+  // Sets the center of the radial highlight in the hover animation.
+  virtual void SetHoverLocation(const gfx::Point& location) = 0;
+
+  // Shows the hover animation.
+  virtual void ShowHover(GlowHoverController::ShowStyle style) = 0;
+
+  // Hides the hover animation.
+  virtual void HideHover(GlowHoverController::HideStyle style) = 0;
+
   // Returns the minimum possible width of a selected Tab. Selected tabs must
   // always show a close button, and thus have a larger minimum size than
   // unselected tabs.
diff --git a/chrome/browser/ui/web_applications/web_app_metrics_factory.cc b/chrome/browser/ui/web_applications/web_app_metrics_factory.cc
index 79ad76bd..bd1fe48 100644
--- a/chrome/browser/ui/web_applications/web_app_metrics_factory.cc
+++ b/chrome/browser/ui/web_applications/web_app_metrics_factory.cc
@@ -47,8 +47,7 @@
 
 content::BrowserContext* WebAppMetricsFactory::GetBrowserContextToUse(
     content::BrowserContext* context) const {
-  Profile* profile = Profile::FromBrowserContext(context);
-  return AllowWebAppInstallation(profile) ? context : nullptr;
+  return GetBrowserContextForWebAppMetrics(context);
 }
 
 }  //  namespace web_app
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
index b42f338..cc2994c 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc
@@ -394,6 +394,7 @@
     {"pdf/pdf_scripting_api.js", IDR_PDF_PDF_SCRIPTING_API_JS},
     {"pdf/pdf_viewer.js", IDR_PDF_PDF_VIEWER_JS},
     {"pdf/toolbar_manager.js", IDR_PDF_TOOLBAR_MANAGER_JS},
+    {"pdf/viewport_interface.js", IDR_PDF_VIEWPORT_INTERFACE_JS},
     {"pdf/viewport.js", IDR_PDF_VIEWPORT_JS},
     {"pdf/viewport_scroller.js", IDR_PDF_VIEWPORT_SCROLLER_JS},
     {"pdf/zoom_manager.js", IDR_PDF_ZOOM_MANAGER_JS},
diff --git a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
index 982e64ac..4fac866 100644
--- a/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/md_settings_localized_strings_provider.cc
@@ -158,7 +158,7 @@
       "isGuest",
 #if defined(OS_CHROMEOS)
       user_manager::UserManager::Get()->IsLoggedInAsGuest() ||
-      user_manager::UserManager::Get()->IsLoggedInAsPublicAccount());
+          user_manager::UserManager::Get()->IsLoggedInAsPublicAccount());
 #else
       profile->IsOffTheRecord());
 #endif
@@ -822,8 +822,6 @@
   base::CommandLine& cmd = *base::CommandLine::ForCurrentProcess();
   html_source->AddBoolean("unifiedDesktopAvailable",
                           cmd.HasSwitch(::switches::kEnableUnifiedDesktop));
-  html_source->AddBoolean("multiMirroringAvailable",
-                          !cmd.HasSwitch(::switches::kDisableMultiMirroring));
 
   html_source->AddBoolean(
       "enableTouchCalibrationSetting",
@@ -1551,9 +1549,9 @@
                             sync_service->IsUsingSecondaryPassphrase());
     html_source->AddBoolean(
         "uploadToGoogleActive",
-            syncer::GetUploadToGoogleState(
-                sync_service, syncer::ModelType::AUTOFILL_WALLET_DATA) ==
-                syncer::UploadState::ACTIVE);
+        syncer::GetUploadToGoogleState(
+            sync_service, syncer::ModelType::AUTOFILL_WALLET_DATA) ==
+            syncer::UploadState::ACTIVE);
   } else {
     html_source->AddBoolean("isUsingSecondaryPassphrase", false);
     html_source->AddBoolean("uploadToGoogleActive", false);
@@ -1714,6 +1712,8 @@
                           : IDS_SETTINGS_SYNC_WILL_START},
     {"syncSettingsSavedToast", IDS_SETTINGS_SYNC_SETTINGS_SAVED_TOAST_LABEL},
     {"cancelSync", IDS_SETTINGS_SYNC_SETTINGS_CANCEL_SYNC},
+    {"syncSetupCancelDialogTitle", IDS_SETTINGS_SYNC_SETUP_CANCEL_DIALOG_TITLE},
+    {"syncSetupCancelDialogBody", IDS_SETTINGS_SYNC_SETUP_CANCEL_DIALOG_BODY},
 #endif  // defined(OS_CHROMEOS)
 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
     {"peopleSignIn", IDS_PROFILES_DICE_SIGNIN_BUTTON},
@@ -1854,8 +1854,7 @@
       "syncDisconnectManagedProfileExplanation",
       l10n_util::GetStringFUTF8(
           IDS_SETTINGS_SYNC_DISCONNECT_MANAGED_PROFILE_EXPLANATION,
-          base::ASCIIToUTF16("$1"),
-          base::ASCIIToUTF16(sync_dashboard_url)));
+          base::ASCIIToUTF16("$1"), base::ASCIIToUTF16(sync_dashboard_url)));
 
   // The syncDisconnect text differs depending on Dice-enabledness.
   if (AccountConsistencyModeManager::IsDiceEnabledForProfile(profile)) {
diff --git a/chrome/browser/ui/webui/site_settings_helper.cc b/chrome/browser/ui/webui/site_settings_helper.cc
index f7ae9d36..89778049 100644
--- a/chrome/browser/ui/webui/site_settings_helper.cc
+++ b/chrome/browser/ui/webui/site_settings_helper.cc
@@ -104,6 +104,9 @@
     {CONTENT_SETTINGS_TYPE_PLUGINS_DATA, nullptr},
     {CONTENT_SETTINGS_TYPE_BACKGROUND_FETCH, nullptr},
     {CONTENT_SETTINGS_TYPE_INTENT_PICKER_DISPLAY, nullptr},
+    // TODO(crbug.com/908836): Add UI for setting this permission.
+    {CONTENT_SETTINGS_TYPE_SERIAL_GUARD, nullptr},
+    {CONTENT_SETTINGS_TYPE_SERIAL_CHOOSER_DATA, nullptr},
 };
 static_assert(base::size(kContentSettingsTypeGroupNames) ==
                   // ContentSettingsType starts at -1, so add 1 here.
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn
index 3b9fe79..c7a8750 100644
--- a/chrome/browser/web_applications/BUILD.gn
+++ b/chrome/browser/web_applications/BUILD.gn
@@ -92,6 +92,7 @@
     "web_app_icon_manager_unittest.cc",
     "web_app_install_manager_unittest.cc",
     "web_app_registrar_unittest.cc",
+    "web_app_utils_unittest.cc",
   ]
 
   deps = [
@@ -107,6 +108,10 @@
     "//content/test:test_support",
     "//skia",
   ]
+
+  if (is_chromeos) {
+    deps += [ "//chrome/browser/chromeos" ]
+  }
 }
 
 # TODO(loyso): Erase this and move WebAppProvider into web_applications set.
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager.cc b/chrome/browser/web_applications/policy/web_app_policy_manager.cc
index ee6872b..bf7979971 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager.cc
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager.cc
@@ -10,7 +10,6 @@
 #include "base/bind.h"
 #include "base/task/post_task.h"
 #include "base/values.h"
-#include "build/build_config.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
 #include "chrome/browser/web_applications/policy/web_app_policy_constants.h"
@@ -20,10 +19,6 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 
-#if defined(OS_CHROMEOS)
-#include "chrome/browser/chromeos/profiles/profile_helper.h"
-#endif  // OS_CHROMEOS
-
 namespace web_app {
 
 WebAppPolicyManager::WebAppPolicyManager(Profile* profile,
@@ -48,22 +43,6 @@
   registry->RegisterListPref(prefs::kWebAppInstallForceList);
 }
 
-// static
-bool WebAppPolicyManager::ShouldEnableForProfile(Profile* profile) {
-// PolicyBrowserTests applies test policies to all profiles, including the
-// sign-in profile. This causes tests to become flaky since the tests could
-// finish before, during, or after the policy apps fail to install in the
-// sign-in profile. So we temporarily add a guard to ignore the policy for the
-// sign-in profile.
-// TODO(crbug.com/876705): Remove once the policy no longer applies to the
-// sign-in profile during tests.
-#if defined(OS_CHROMEOS)
-  return !chromeos::ProfileHelper::IsSigninProfile(profile);
-#else  // !OS_CHROMEOS
-  return true;
-#endif
-}
-
 void WebAppPolicyManager::InitChangeRegistrarAndRefreshPolicyInstalledApps() {
   pref_change_registrar_.Init(pref_service_);
   pref_change_registrar_.Add(
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager.h b/chrome/browser/web_applications/policy/web_app_policy_manager.h
index 4f81393..cbde4cb6 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager.h
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager.h
@@ -35,8 +35,6 @@
 
   static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
 
-  static bool ShouldEnableForProfile(Profile* profile);
-
  private:
   void InitChangeRegistrarAndRefreshPolicyInstalledApps();
 
diff --git a/chrome/browser/web_applications/system_web_app_manager.cc b/chrome/browser/web_applications/system_web_app_manager.cc
index 1d96397..13b00c86 100644
--- a/chrome/browser/web_applications/system_web_app_manager.cc
+++ b/chrome/browser/web_applications/system_web_app_manager.cc
@@ -12,15 +12,12 @@
 #include "base/task/post_task.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
+#include "chrome/browser/web_applications/web_app_utils.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/webui_url_constants.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 
-#if defined(OS_CHROMEOS)
-#include "chrome/browser/chromeos/profiles/profile_helper.h"
-#endif  // OS_CHROMEOS
-
 namespace web_app {
 
 namespace {
@@ -55,12 +52,8 @@
 
 // static
 bool SystemWebAppManager::ShouldEnableForProfile(Profile* profile) {
-  bool is_enabled = base::FeatureList::IsEnabled(features::kSystemWebApps);
-#if defined(OS_CHROMEOS)
-  // System Apps should not be installed to the signin profile.
-  is_enabled = is_enabled && !chromeos::ProfileHelper::IsSigninProfile(profile);
-#endif
-  return is_enabled;
+  return AreWebAppsEnabled(profile) &&
+         base::FeatureList::IsEnabled(features::kSystemWebApps);
 }
 
 void SystemWebAppManager::StartAppInstallation() {
diff --git a/chrome/browser/web_applications/web_app_install_manager.cc b/chrome/browser/web_applications/web_app_install_manager.cc
index 83a0189..3438c76 100644
--- a/chrome/browser/web_applications/web_app_install_manager.cc
+++ b/chrome/browser/web_applications/web_app_install_manager.cc
@@ -25,21 +25,22 @@
     Profile* profile,
     std::unique_ptr<InstallFinalizer> install_finalizer)
     : data_retriever_(std::make_unique<WebAppDataRetriever>()),
-      install_finalizer_(std::move(install_finalizer)) {
-  DCHECK(AllowWebAppInstallation(profile));
-}
+      install_finalizer_(std::move(install_finalizer)),
+      profile_(profile) {}
 
 WebAppInstallManager::~WebAppInstallManager() = default;
 
 bool WebAppInstallManager::CanInstallWebApp(
     content::WebContents* web_contents) {
-  return IsValidWebAppUrl(web_contents->GetURL());
+  return AreWebAppsUserInstallable(profile_) &&
+         IsValidWebAppUrl(web_contents->GetLastCommittedURL());
 }
 
 void WebAppInstallManager::InstallWebApp(content::WebContents* contents,
                                          bool force_shortcut_app,
                                          OnceInstallCallback install_callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  DCHECK(AreWebAppsUserInstallable(profile_));
 
   // Concurrent calls are not allowed.
   DCHECK(!web_contents());
diff --git a/chrome/browser/web_applications/web_app_install_manager.h b/chrome/browser/web_applications/web_app_install_manager.h
index 8137030..d50b796 100644
--- a/chrome/browser/web_applications/web_app_install_manager.h
+++ b/chrome/browser/web_applications/web_app_install_manager.h
@@ -74,6 +74,7 @@
 
   std::unique_ptr<WebAppDataRetriever> data_retriever_;
   std::unique_ptr<InstallFinalizer> install_finalizer_;
+  Profile* profile_;
 
   base::WeakPtrFactory<WebAppInstallManager> weak_ptr_factory_{this};
 
diff --git a/chrome/browser/web_applications/web_app_install_manager_unittest.cc b/chrome/browser/web_applications/web_app_install_manager_unittest.cc
index 4cf1064e..f9d87bc0 100644
--- a/chrome/browser/web_applications/web_app_install_manager_unittest.cc
+++ b/chrome/browser/web_applications/web_app_install_manager_unittest.cc
@@ -173,7 +173,7 @@
 };
 
 TEST_F(WebAppInstallManagerTest, InstallFromWebContents) {
-  EXPECT_EQ(true, AllowWebAppInstallation(profile()));
+  EXPECT_TRUE(AreWebAppsUserInstallable(profile()));
 
   const GURL url = GURL("https://example.com/path");
   const std::string name = "Name";
diff --git a/chrome/browser/web_applications/web_app_provider.cc b/chrome/browser/web_applications/web_app_provider.cc
index 023334a..d163b8c 100644
--- a/chrome/browser/web_applications/web_app_provider.cc
+++ b/chrome/browser/web_applications/web_app_provider.cc
@@ -55,7 +55,12 @@
   return WebAppProvider::Get(profile);
 }
 
-WebAppProvider::WebAppProvider(Profile* profile) : profile_(profile) {}
+WebAppProvider::WebAppProvider(Profile* profile) : profile_(profile) {
+  DCHECK(AreWebAppsEnabled(profile_));
+  // WebApp System must have only one instance in original profile.
+  // Exclude secondary off-the-record profiles.
+  DCHECK(!profile_->IsOffTheRecord());
+}
 
 WebAppProvider::~WebAppProvider() = default;
 
@@ -73,10 +78,8 @@
                               content::Source<Profile>(profile_));
 
   if (base::FeatureList::IsEnabled(features::kDesktopPWAsWithoutExtensions)) {
-    if (AllowWebAppInstallation(profile_)) {
-      registrar_->Init(base::BindOnce(&WebAppProvider::OnRegistryReady,
-                                      weak_ptr_factory_.GetWeakPtr()));
-    }
+    registrar_->Init(base::BindOnce(&WebAppProvider::OnRegistryReady,
+                                    weak_ptr_factory_.GetWeakPtr()));
   } else {
     system_web_app_manager_->Init();
 
@@ -91,9 +94,6 @@
 }
 
 void WebAppProvider::CreateWebAppsSubsystems(Profile* profile) {
-  if (!AllowWebAppInstallation(profile))
-    return;
-
   database_factory_ = std::make_unique<WebAppDatabaseFactory>(profile);
   database_ = std::make_unique<WebAppDatabase>(database_factory_.get());
   registrar_ = std::make_unique<WebAppRegistrar>(database_.get());
@@ -112,10 +112,8 @@
   pending_app_manager_ =
       std::make_unique<extensions::PendingBookmarkAppManager>(profile);
 
-  if (WebAppPolicyManager::ShouldEnableForProfile(profile)) {
-    web_app_policy_manager_ = std::make_unique<WebAppPolicyManager>(
-        profile, pending_app_manager_.get());
-  }
+  web_app_policy_manager_ = std::make_unique<WebAppPolicyManager>(
+      profile, pending_app_manager_.get());
 
   system_web_app_manager_ = std::make_unique<SystemWebAppManager>(
       profile, pending_app_manager_.get());
diff --git a/chrome/browser/web_applications/web_app_provider_factory.cc b/chrome/browser/web_applications/web_app_provider_factory.cc
index d17cd91b..1361373 100644
--- a/chrome/browser/web_applications/web_app_provider_factory.cc
+++ b/chrome/browser/web_applications/web_app_provider_factory.cc
@@ -6,6 +6,7 @@
 
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
+#include "chrome/browser/web_applications/web_app_utils.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "extensions/browser/extension_system_provider.h"
 #include "extensions/browser/extensions_browser_client.h"
@@ -49,8 +50,7 @@
 
 content::BrowserContext* WebAppProviderFactory::GetBrowserContextToUse(
     content::BrowserContext* context) const {
-  Profile* profile = Profile::FromBrowserContext(context);
-  return profile ? profile->GetOriginalProfile() : nullptr;
+  return GetBrowserContextForWebApps(context);
 }
 
 }  //  namespace web_app
diff --git a/chrome/browser/web_applications/web_app_utils.cc b/chrome/browser/web_applications/web_app_utils.cc
index cfe1848..14ab85b 100644
--- a/chrome/browser/web_applications/web_app_utils.cc
+++ b/chrome/browser/web_applications/web_app_utils.cc
@@ -7,14 +7,55 @@
 #include "base/files/file_path.h"
 #include "chrome/browser/profiles/profile.h"
 
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#endif  // OS_CHROMEOS
+
 namespace web_app {
 
 constexpr base::FilePath::CharType kWebAppsDirectoryName[] =
     FILE_PATH_LITERAL("WebApps");
 
-bool AllowWebAppInstallation(Profile* profile) {
-  return !profile->IsGuestSession() && !profile->IsOffTheRecord() &&
-         !profile->IsSystemProfile();
+bool AreWebAppsEnabled(Profile* profile) {
+  if (!profile)
+    return false;
+
+  Profile* original_profile = profile->GetOriginalProfile();
+  DCHECK(!original_profile->IsOffTheRecord());
+
+  if (original_profile->IsSystemProfile())
+    return false;
+
+#if defined(OS_CHROMEOS)
+  // Web Apps should not be installed to the ChromeOS system profiles.
+  if (chromeos::ProfileHelper::IsSigninProfile(original_profile) ||
+      chromeos::ProfileHelper::IsLockScreenAppProfile(original_profile)) {
+    return false;
+  }
+#endif
+  return true;
+}
+
+bool AreWebAppsUserInstallable(Profile* profile) {
+  return AreWebAppsEnabled(profile) && !profile->IsGuestSession();
+}
+
+content::BrowserContext* GetBrowserContextForWebApps(
+    content::BrowserContext* context) {
+  // Use original profile to create only one KeyedService instance.
+  Profile* original_profile =
+      Profile::FromBrowserContext(context)->GetOriginalProfile();
+  return AreWebAppsEnabled(original_profile) ? original_profile : nullptr;
+}
+
+content::BrowserContext* GetBrowserContextForWebAppMetrics(
+    content::BrowserContext* context) {
+  // Use original profile to create only one KeyedService instance.
+  Profile* original_profile =
+      Profile::FromBrowserContext(context)->GetOriginalProfile();
+  const bool is_web_app_metrics_enabled = AreWebAppsEnabled(original_profile) &&
+                                          !original_profile->IsGuestSession();
+  return is_web_app_metrics_enabled ? original_profile : nullptr;
 }
 
 base::FilePath GetWebAppsDirectory(Profile* profile) {
diff --git a/chrome/browser/web_applications/web_app_utils.h b/chrome/browser/web_applications/web_app_utils.h
index 3f21e79..2d9b3cb 100644
--- a/chrome/browser/web_applications/web_app_utils.h
+++ b/chrome/browser/web_applications/web_app_utils.h
@@ -11,9 +11,27 @@
 class FilePath;
 }
 
+namespace content {
+class BrowserContext;
+}
+
 namespace web_app {
 
-bool AllowWebAppInstallation(Profile* profile);
+// These functions return true if the WebApp System or its subset is allowed
+// for a given profile.
+// |profile| can be original profile or its secondary off-the-record profile.
+// Returns false if |profile| is nullptr.
+//
+// Is main WebApp System allowed (WebAppProvider exists):
+bool AreWebAppsEnabled(Profile* profile);
+// Is user allowed to install web apps from UI:
+bool AreWebAppsUserInstallable(Profile* profile);
+
+// Get BrowserContext to use for a WebApp KeyedService creation.
+content::BrowserContext* GetBrowserContextForWebApps(
+    content::BrowserContext* context);
+content::BrowserContext* GetBrowserContextForWebAppMetrics(
+    content::BrowserContext* context);
 
 base::FilePath GetWebAppsDirectory(Profile* profile);
 
diff --git a/chrome/browser/web_applications/web_app_utils_unittest.cc b/chrome/browser/web_applications/web_app_utils_unittest.cc
new file mode 100644
index 0000000..4d035e533
--- /dev/null
+++ b/chrome/browser/web_applications/web_app_utils_unittest.cc
@@ -0,0 +1,134 @@
+// 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/web_applications/web_app_utils.h"
+
+#include "base/files/file_path.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/web_applications/test/web_app_test.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#endif  // OS_CHROMEOS
+
+namespace web_app {
+
+class WebAppUtilsTest : public WebAppTest {};
+
+TEST_F(WebAppUtilsTest, AreWebAppsEnabled) {
+  Profile* regular_profile = profile();
+
+  EXPECT_FALSE(AreWebAppsEnabled(nullptr));
+  EXPECT_TRUE(AreWebAppsEnabled(regular_profile));
+  EXPECT_TRUE(AreWebAppsEnabled(regular_profile->GetOffTheRecordProfile()));
+
+  TestingProfileManager profile_manager(TestingBrowserProcess::GetGlobal());
+  ASSERT_TRUE(profile_manager.SetUp());
+
+  Profile* guest_profile = profile_manager.CreateGuestProfile();
+  EXPECT_TRUE(AreWebAppsEnabled(guest_profile));
+  EXPECT_TRUE(AreWebAppsEnabled(guest_profile->GetOffTheRecordProfile()));
+
+  Profile* system_profile = profile_manager.CreateSystemProfile();
+  EXPECT_FALSE(AreWebAppsEnabled(system_profile));
+  EXPECT_FALSE(AreWebAppsEnabled(system_profile->GetOffTheRecordProfile()));
+
+#if defined(OS_CHROMEOS)
+  Profile* signin_profile =
+      profile_manager.CreateTestingProfile(chrome::kInitialProfile);
+  EXPECT_FALSE(AreWebAppsEnabled(signin_profile));
+  EXPECT_FALSE(AreWebAppsEnabled(signin_profile->GetOffTheRecordProfile()));
+
+  Profile* lock_screen_profile = profile_manager.CreateTestingProfile(
+      chromeos::ProfileHelper::GetLockScreenAppProfileName());
+  EXPECT_FALSE(AreWebAppsEnabled(lock_screen_profile));
+  EXPECT_FALSE(
+      AreWebAppsEnabled(lock_screen_profile->GetOffTheRecordProfile()));
+#endif
+}
+
+TEST_F(WebAppUtilsTest, AreWebAppsUserInstallable) {
+  Profile* regular_profile = profile();
+
+  EXPECT_FALSE(AreWebAppsEnabled(nullptr));
+  EXPECT_TRUE(AreWebAppsUserInstallable(regular_profile));
+  EXPECT_TRUE(
+      AreWebAppsUserInstallable(regular_profile->GetOffTheRecordProfile()));
+
+  TestingProfileManager profile_manager(TestingBrowserProcess::GetGlobal());
+  ASSERT_TRUE(profile_manager.SetUp());
+
+  Profile* guest_profile = profile_manager.CreateGuestProfile();
+  EXPECT_FALSE(AreWebAppsUserInstallable(guest_profile));
+  EXPECT_FALSE(
+      AreWebAppsUserInstallable(guest_profile->GetOffTheRecordProfile()));
+
+  Profile* system_profile = profile_manager.CreateSystemProfile();
+  EXPECT_FALSE(AreWebAppsUserInstallable(system_profile));
+  EXPECT_FALSE(
+      AreWebAppsUserInstallable(system_profile->GetOffTheRecordProfile()));
+
+#if defined(OS_CHROMEOS)
+  Profile* signin_profile =
+      profile_manager.CreateTestingProfile(chrome::kInitialProfile);
+  EXPECT_FALSE(AreWebAppsUserInstallable(signin_profile));
+  EXPECT_FALSE(
+      AreWebAppsUserInstallable(signin_profile->GetOffTheRecordProfile()));
+
+  Profile* lock_screen_profile = profile_manager.CreateTestingProfile(
+      chromeos::ProfileHelper::GetLockScreenAppProfileName());
+  EXPECT_FALSE(AreWebAppsUserInstallable(lock_screen_profile));
+  EXPECT_FALSE(
+      AreWebAppsUserInstallable(lock_screen_profile->GetOffTheRecordProfile()));
+#endif
+}
+
+TEST_F(WebAppUtilsTest, GetBrowserContextForWebApps) {
+  Profile* regular_profile = profile();
+
+  EXPECT_EQ(regular_profile, GetBrowserContextForWebApps(regular_profile));
+  EXPECT_EQ(regular_profile, GetBrowserContextForWebApps(
+                                 regular_profile->GetOffTheRecordProfile()));
+
+  TestingProfileManager profile_manager(TestingBrowserProcess::GetGlobal());
+  ASSERT_TRUE(profile_manager.SetUp());
+
+  Profile* guest_profile = profile_manager.CreateGuestProfile();
+  EXPECT_EQ(guest_profile, GetBrowserContextForWebApps(guest_profile));
+  EXPECT_EQ(guest_profile, GetBrowserContextForWebApps(
+                               guest_profile->GetOffTheRecordProfile()));
+
+  Profile* system_profile = profile_manager.CreateSystemProfile();
+  EXPECT_EQ(nullptr, GetBrowserContextForWebApps(system_profile));
+  EXPECT_EQ(nullptr, GetBrowserContextForWebApps(
+                         system_profile->GetOffTheRecordProfile()));
+}
+
+TEST_F(WebAppUtilsTest, GetBrowserContextForWebAppMetrics) {
+  Profile* regular_profile = profile();
+
+  EXPECT_EQ(regular_profile,
+            GetBrowserContextForWebAppMetrics(regular_profile));
+  EXPECT_EQ(regular_profile, GetBrowserContextForWebAppMetrics(
+                                 regular_profile->GetOffTheRecordProfile()));
+
+  TestingProfileManager profile_manager(TestingBrowserProcess::GetGlobal());
+  ASSERT_TRUE(profile_manager.SetUp());
+
+  Profile* guest_profile = profile_manager.CreateGuestProfile();
+  EXPECT_EQ(nullptr, GetBrowserContextForWebAppMetrics(guest_profile));
+  EXPECT_EQ(nullptr, GetBrowserContextForWebAppMetrics(
+                         guest_profile->GetOffTheRecordProfile()));
+
+  Profile* system_profile = profile_manager.CreateSystemProfile();
+  EXPECT_EQ(nullptr, GetBrowserContextForWebAppMetrics(system_profile));
+  EXPECT_EQ(nullptr, GetBrowserContextForWebAppMetrics(
+                         system_profile->GetOffTheRecordProfile()));
+}
+
+}  // namespace web_app
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index cc10e29..594214c 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -495,6 +495,12 @@
 const base::Feature kPushMessagingBackgroundMode{
     "PushMessagingBackgroundMode", base::FEATURE_DISABLED_BY_DEFAULT};
 
+#if defined(OS_CHROMEOS)
+// Enables permanent removal of Legacy Supervised Users on startup.
+const base::Feature kRemoveSupervisedUsersOnStartup{
+    "RemoveSupervisedUsersOnStartup", base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
 const base::Feature kSafeSearchUrlReporting{"SafeSearchUrlReporting",
                                             base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 8919c3b..f617750 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -327,6 +327,11 @@
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kPushMessagingBackgroundMode;
 
+#if defined(OS_CHROMEOS)
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kRemoveSupervisedUsersOnStartup;
+#endif
+
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kSafeSearchUrlReporting;
 
diff --git a/chrome/common/extensions/api/automation.idl b/chrome/common/extensions/api/automation.idl
index d684388..4ac4c913 100644
--- a/chrome/common/extensions/api/automation.idl
+++ b/chrome/common/extensions/api/automation.idl
@@ -844,9 +844,12 @@
     // A data url with the contents of this object's image or thumbnail.
     DOMString? imageDataUrl;
 
-    // The language code for this subtree.
+    // The author-provided language code for this subtree.
     DOMString? language;
 
+    // The detected language code for this subtree.
+    DOMString? detectedLanguage;
+
     // Indicates the availability and type of interactive popup element
     // true - the popup is a menu
     // menu - the popup is a menu
diff --git a/chrome/common/instant_struct_traits.h b/chrome/common/instant_struct_traits.h
index b1c17cc..5fac96a 100644
--- a/chrome/common/instant_struct_traits.h
+++ b/chrome/common/instant_struct_traits.h
@@ -60,6 +60,7 @@
 
 IPC_STRUCT_TRAITS_BEGIN(ThemeBackgroundInfo)
   IPC_STRUCT_TRAITS_MEMBER(using_default_theme)
+  IPC_STRUCT_TRAITS_MEMBER(using_dark_mode)
   IPC_STRUCT_TRAITS_MEMBER(custom_background_url)
   IPC_STRUCT_TRAITS_MEMBER(custom_background_attribution_line_1)
   IPC_STRUCT_TRAITS_MEMBER(custom_background_attribution_line_2)
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 5784af1..e7ef619 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -1542,8 +1542,8 @@
     "ntp.custom_background_local_to_device";
 
 // Data associated with search suggestions that appear on the NTP.
-const char kNtpSearchSuggestionsBlacklist[] =
-    "ntp.search_suggestions_blacklist";
+const char kNtpSearchSuggestionsBlocklist[] =
+    "ntp.search_suggestions_blocklist";
 const char kNtpSearchSuggestionsImpressions[] =
     "ntp.search_suggestions_impressions";
 const char kNtpSearchSuggestionsOptOut[] = "ntp.search_suggestions_opt_out";
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index f2383b9..3ebba0b 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -536,7 +536,7 @@
 #else
 extern const char kNtpCustomBackgroundDict[];
 extern const char kNtpCustomBackgroundLocalToDevice[];
-extern const char kNtpSearchSuggestionsBlacklist[];
+extern const char kNtpSearchSuggestionsBlocklist[];
 extern const char kNtpSearchSuggestionsImpressions[];
 extern const char kNtpSearchSuggestionsOptOut[];
 #endif  // defined(OS_ANDROID)
diff --git a/chrome/common/search.mojom b/chrome/common/search.mojom
index d405d3f..db1579e 100644
--- a/chrome/common/search.mojom
+++ b/chrome/common/search.mojom
@@ -105,12 +105,17 @@
   // Let the user select a local file for the NTP background.
   SelectLocalBackgroundImage();
 
-  // Add a search suggestion task id to the blacklist.
-  BlacklistSearchSuggestion(int32 task_version, int64 task_id);
+  // Add a search suggestion task id to the blocklist.
+  BlocklistSearchSuggestion(int32 task_version, int64 task_id);
 
-  // Add a search suggestion task id and hash to the blacklist.
-  BlacklistSearchSuggestionWithHash(int32 task_version, int64 task_id,
-                                    array<uint8> hash);
+  // Add a search suggestion task id and hash to the blocklist.
+  BlocklistSearchSuggestionWithHash(int32 task_version, int64 task_id,
+                                    array<uint8, 4> hash);
+
+  // A search suggestion was selected, issue a new request with the suggestion
+  // temporarily added to the blocklist.
+  SearchSuggestionSelected(int32 task_version, int64 task_id,
+                                    array<uint8, 4> hash);
 
   // Opts the user out of receiving search suggestions.
   OptOutOfSearchSuggestions();
diff --git a/chrome/common/search/instant_types.cc b/chrome/common/search/instant_types.cc
index d4e907d..aee253b 100644
--- a/chrome/common/search/instant_types.cc
+++ b/chrome/common/search/instant_types.cc
@@ -26,6 +26,7 @@
 
 ThemeBackgroundInfo::ThemeBackgroundInfo()
     : using_default_theme(true),
+      using_dark_mode(false),
       custom_background_url(std::string()),
       custom_background_attribution_line_1(std::string()),
       custom_background_attribution_line_2(std::string()),
@@ -47,6 +48,7 @@
 
 bool ThemeBackgroundInfo::operator==(const ThemeBackgroundInfo& rhs) const {
   return using_default_theme == rhs.using_default_theme &&
+         using_dark_mode == rhs.using_dark_mode &&
          custom_background_url == rhs.custom_background_url &&
          custom_background_attribution_line_1 ==
              rhs.custom_background_attribution_line_1 &&
diff --git a/chrome/common/search/instant_types.h b/chrome/common/search/instant_types.h
index eea2d4ef..a6ea2f1 100644
--- a/chrome/common/search/instant_types.h
+++ b/chrome/common/search/instant_types.h
@@ -67,6 +67,9 @@
   // True if the default theme is selected.
   bool using_default_theme;
 
+  // True if dark mode is enabled.
+  bool using_dark_mode;
+
   // Url of the custom background selected by the user.
   GURL custom_background_url;
 
diff --git a/chrome/renderer/extensions/automation_internal_custom_bindings.cc b/chrome/renderer/extensions/automation_internal_custom_bindings.cc
index 8e251c72..a42637a 100644
--- a/chrome/renderer/extensions/automation_internal_custom_bindings.cc
+++ b/chrome/renderer/extensions/automation_internal_custom_bindings.cc
@@ -969,6 +969,15 @@
         result.Set(v8::Boolean::New(isolate, value));
       });
   RouteNodeIDFunction(
+      "GetDetectedLanguage",
+      [](v8::Isolate* isolate, v8::ReturnValue<v8::Value> result,
+         AutomationAXTreeWrapper* tree_wrapper, ui::AXNode* node) {
+        std::string detectedLanguage = node->GetLanguage();
+        result.Set(v8::String::NewFromUtf8(isolate, detectedLanguage.c_str(),
+                                           v8::NewStringType::kNormal)
+                       .ToLocalChecked());
+      });
+  RouteNodeIDFunction(
       "GetCustomActions",
       [](v8::Isolate* isolate, v8::ReturnValue<v8::Value> result,
          AutomationAXTreeWrapper* tree_wrapper, ui::AXNode* node) {
diff --git a/chrome/renderer/resources/extensions/automation/automation_node.js b/chrome/renderer/resources/extensions/automation/automation_node.js
index ea9f407..52794fb2 100644
--- a/chrome/renderer/resources/extensions/automation/automation_node.js
+++ b/chrome/renderer/resources/extensions/automation/automation_node.js
@@ -380,6 +380,13 @@
  */
 var GetTableCellRowIndex = natives.GetTableCellRowIndex;
 
+/**
+ * @param {string} axTreeId The id of the accessibility tree.
+ * @param {number} nodeID The id of a node.
+ * @return {string} Detected language for this node.
+ */
+var GetDetectedLanguage = natives.GetDetectedLanguage;
+
 var logging = requireNative('logging');
 var utils = require('utils');
 
@@ -564,6 +571,10 @@
     return GetLineThrough(this.treeID, this.id);
   },
 
+  get detectedLanguage() {
+    return GetDetectedLanguage(this.treeID, this.id)
+  },
+
   get customActions() {
     return GetCustomActions(this.treeID, this.id);
   },
@@ -1551,6 +1562,7 @@
         'italic',
         'underline',
         'lineThrough',
+        'detectedLanguage',
         'customActions',
         'standardActions',
         'unclippedLocation',
diff --git a/chrome/renderer/searchbox/searchbox.cc b/chrome/renderer/searchbox/searchbox.cc
index 2a9ea9d6..a4bb4537 100644
--- a/chrome/renderer/searchbox/searchbox.cc
+++ b/chrome/renderer/searchbox/searchbox.cc
@@ -379,18 +379,25 @@
   embedded_search_service_->SelectLocalBackgroundImage();
 }
 
-void SearchBox::BlacklistSearchSuggestion(int task_version, long task_id) {
-  embedded_search_service_->BlacklistSearchSuggestion(task_version, task_id);
+void SearchBox::BlocklistSearchSuggestion(int task_version, long task_id) {
+  embedded_search_service_->BlocklistSearchSuggestion(task_version, task_id);
 }
 
-void SearchBox::BlacklistSearchSuggestionWithHash(
+void SearchBox::BlocklistSearchSuggestionWithHash(
     int task_version,
     long task_id,
     const std::vector<uint8_t>& hash) {
-  embedded_search_service_->BlacklistSearchSuggestionWithHash(task_version,
+  embedded_search_service_->BlocklistSearchSuggestionWithHash(task_version,
                                                               task_id, hash);
 }
 
+void SearchBox::SearchSuggestionSelected(int task_version,
+                                         long task_id,
+                                         const std::vector<uint8_t>& hash) {
+  embedded_search_service_->SearchSuggestionSelected(task_version, task_id,
+                                                     hash);
+}
+
 void SearchBox::OptOutOfSearchSuggestions() {
   embedded_search_service_->OptOutOfSearchSuggestions();
 }
diff --git a/chrome/renderer/searchbox/searchbox.h b/chrome/renderer/searchbox/searchbox.h
index fb75ea48..c7e38fc3 100644
--- a/chrome/renderer/searchbox/searchbox.h
+++ b/chrome/renderer/searchbox/searchbox.h
@@ -149,14 +149,20 @@
   // Let the user select a local file for the NTP background.
   void SelectLocalBackgroundImage();
 
-  // Add a search suggestion task id to the blacklist.
-  void BlacklistSearchSuggestion(int task_version, long task_id);
+  // Add a search suggestion task id to the blocklist.
+  void BlocklistSearchSuggestion(int task_version, long task_id);
 
-  // Add a search suggestion task id and hash to the blacklist.
-  void BlacklistSearchSuggestionWithHash(int task_version,
+  // Add a search suggestion task id and hash to the blocklist.
+  void BlocklistSearchSuggestionWithHash(int task_version,
                                          long task_id,
                                          const std::vector<uint8_t>& hash);
 
+  // A suggestion collected, issue a new request with the suggestion
+  // temporarily added to the blocklist.
+  void SearchSuggestionSelected(int task_version,
+                                long task_id,
+                                const std::vector<uint8_t>& hash);
+
   // Opts the user out of receiving search suggestions.
   void OptOutOfSearchSuggestions();
 
diff --git a/chrome/renderer/searchbox/searchbox_extension.cc b/chrome/renderer/searchbox/searchbox_extension.cc
index 587d338a..ea00543 100644
--- a/chrome/renderer/searchbox/searchbox_extension.cc
+++ b/chrome/renderer/searchbox/searchbox_extension.cc
@@ -196,6 +196,8 @@
 
   builder.Set("usingDefaultTheme", theme_info.using_default_theme);
 
+  builder.Set("usingDarkMode", theme_info.using_dark_mode);
+
   // The theme background color is in RGBA format "rgba(R,G,B,A)" where R, G and
   // B are between 0 and 255 inclusive, and A is a double between 0 and 1
   // inclusive.
@@ -634,10 +636,13 @@
       const std::string& attribution_line_2,
       const std::string& attributionActionUrl);
   static void SelectLocalBackgroundImage();
-  static void BlacklistSearchSuggestion(int task_version, int task_id);
-  static void BlacklistSearchSuggestionWithHash(int task_version,
+  static void BlocklistSearchSuggestion(int task_version, int task_id);
+  static void BlocklistSearchSuggestionWithHash(int task_version,
                                                 int task_id,
                                                 const std::string& hash);
+  static void SearchSuggestionSelected(int task_version,
+                                       int task_id,
+                                       const std::string& hash);
   static void OptOutOfSearchSuggestions();
 
   DISALLOW_COPY_AND_ASSIGN(NewTabPageBindings);
@@ -690,9 +695,11 @@
       .SetMethod("selectLocalBackgroundImage",
                  &NewTabPageBindings::SelectLocalBackgroundImage)
       .SetMethod("blacklistSearchSuggestion",
-                 &NewTabPageBindings::BlacklistSearchSuggestion)
+                 &NewTabPageBindings::BlocklistSearchSuggestion)
       .SetMethod("blacklistSearchSuggestionWithHash",
-                 &NewTabPageBindings::BlacklistSearchSuggestionWithHash)
+                 &NewTabPageBindings::BlocklistSearchSuggestionWithHash)
+      .SetMethod("searchSuggestionSelected",
+                 &NewTabPageBindings::SearchSuggestionSelected)
       .SetMethod("optOutOfSearchSuggestions",
                  &NewTabPageBindings::OptOutOfSearchSuggestions);
 }
@@ -1005,24 +1012,43 @@
 }
 
 // static
-void NewTabPageBindings::BlacklistSearchSuggestion(const int task_version,
+void NewTabPageBindings::BlocklistSearchSuggestion(const int task_version,
                                                    const int task_id) {
   SearchBox* search_box = GetSearchBoxForCurrentContext();
   if (!search_box)
     return;
-  search_box->BlacklistSearchSuggestion(task_version, task_id);
+  search_box->BlocklistSearchSuggestion(task_version, task_id);
 }
 
 // static
-void NewTabPageBindings::BlacklistSearchSuggestionWithHash(
+void NewTabPageBindings::BlocklistSearchSuggestionWithHash(
     int task_version,
     int task_id,
     const std::string& hash) {
+  if (hash.length() > 4) {
+    return;
+  }
+
   std::vector<uint8_t> data(hash.begin(), hash.end());
   SearchBox* search_box = GetSearchBoxForCurrentContext();
   if (!search_box)
     return;
-  search_box->BlacklistSearchSuggestionWithHash(task_version, task_id, data);
+  search_box->BlocklistSearchSuggestionWithHash(task_version, task_id, data);
+}
+
+// static
+void NewTabPageBindings::SearchSuggestionSelected(int task_version,
+                                                  int task_id,
+                                                  const std::string& hash) {
+  if (hash.length() > 4) {
+    return;
+  }
+
+  std::vector<uint8_t> data(hash.begin(), hash.end());
+  SearchBox* search_box = GetSearchBoxForCurrentContext();
+  if (!search_box)
+    return;
+  search_box->SearchSuggestionSelected(task_version, task_id, data);
 }
 
 // static
diff --git a/chrome/service/BUILD.gn b/chrome/service/BUILD.gn
index 6f72490..6b1f867d 100644
--- a/chrome/service/BUILD.gn
+++ b/chrome/service/BUILD.gn
@@ -4,7 +4,6 @@
 
 import("//build/config/features.gni")
 import("//printing/buildflags/buildflags.gni")
-import("//services/catalog/public/tools/catalog.gni")
 
 assert(!is_chromeos)
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index f5d7d19..6e514d4 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -23,8 +23,6 @@
 import("//ppapi/buildflags/buildflags.gni")
 import("//remoting/remoting_enable.gni")
 import("//rlz/buildflags/buildflags.gni")
-import("//services/catalog/public/tools/catalog.gni")
-import("//services/service_manager/public/service_manifest.gni")
 import("//testing/libfuzzer/fuzzer_test.gni")
 import("//testing/test.gni")
 import("//third_party/widevine/cdm/widevine.gni")
@@ -273,6 +271,8 @@
     public_deps += [
       "//ash",
       "//ash:test_support",
+      "//ash/public/cpp:manifest",
+      "//ash/public/cpp:manifest_for_tests",
       "//components/ownership",
       "//components/user_manager:test_support",
       "//ui/aura",
@@ -291,6 +291,8 @@
   if (toolkit_views) {
     public_deps += [ "//ui/views:test_support" ]
     sources += [
+      "../browser/autofill/autofill_uitest_util.cc",
+      "../browser/autofill/autofill_uitest_util.h",
       "../browser/ui/views/media_router/app_menu_test_api.h",
       "../browser/ui/views/media_router/app_menu_test_api_views.cc",
       "views/accessibility_checker.cc",
@@ -1540,6 +1542,7 @@
         "../browser/ui/views/autofill/autofill_popup_base_view_browsertest.cc",
         "../browser/ui/views/autofill/card_unmask_prompt_view_tester_views.cc",
         "../browser/ui/views/autofill/card_unmask_prompt_view_tester_views.h",
+        "../browser/ui/views/autofill/local_card_migration_browsertest.cc",
         "../browser/ui/views/autofill/save_card_bubble_views_browsertest.cc",
         "../browser/ui/views/bookmarks/bookmark_bubble_sign_in_delegate_browsertest.cc",
         "../browser/ui/views/bookmarks/bookmark_bubble_view_browsertest.cc",
@@ -1744,8 +1747,8 @@
         "../browser/chromeos/login/login_screen_policy_browsertest.cc",
         "../browser/chromeos/login/login_ui_keyboard_browsertest.cc",
         "../browser/chromeos/login/login_utils_browsertest.cc",
-        "../browser/chromeos/login/mixin_based_browser_test.cc",
-        "../browser/chromeos/login/mixin_based_browser_test.h",
+        "../browser/chromeos/login/mixin_based_in_process_browser_test.cc",
+        "../browser/chromeos/login/mixin_based_in_process_browser_test.h",
         "../browser/chromeos/login/oobe_localization_browsertest.cc",
         "../browser/chromeos/login/proxy_auth_dialog_browsertest.cc",
         "../browser/chromeos/login/quick_unlock/pin_migration_browsertest.cc",
@@ -1784,6 +1787,7 @@
         "../browser/chromeos/login/users/avatar/user_image_manager_browsertest.cc",
         "../browser/chromeos/login/users/avatar/user_image_manager_test_util.cc",
         "../browser/chromeos/login/users/avatar/user_image_manager_test_util.h",
+        "../browser/chromeos/login/users/remove_supervised_users_browsertest.cc",
         "../browser/chromeos/login/users/user_manager_hide_supervised_users_browsertest.cc",
         "../browser/chromeos/login/users/wallpaper_policy_browsertest.cc",
         "../browser/chromeos/login/webview_login_browsertest.cc",
@@ -3215,6 +3219,7 @@
       "../browser/search/search_suggest/search_suggest_loader_impl_unittest.cc",
       "../browser/search/search_suggest/search_suggest_service_unittest.cc",
       "../browser/search/search_unittest.cc",
+      "../browser/serial/serial_chooser_context_unittest.cc",
       "../browser/sessions/tab_restore_service_unittest.cc",
       "../browser/signin/signin_promo_unittest.cc",
       "../browser/speech/extension_api/extension_manifests_tts_unittest.cc",
@@ -4745,8 +4750,6 @@
       "../browser/autofill/autofill_interactive_uitest.cc",
       "../browser/autofill/autofill_uitest.cc",
       "../browser/autofill/autofill_uitest.h",
-      "../browser/autofill/autofill_uitest_util.cc",
-      "../browser/autofill/autofill_uitest_util.h",
       "../browser/browser_keyevents_browsertest.cc",
       "../browser/devtools/devtools_sanity_interactive_browsertest.cc",
       "../browser/extensions/api/extension_action/browser_action_interactive_test.cc",
@@ -5031,8 +5034,8 @@
         "../browser/chromeos/login/login_manager_test.h",
         "../browser/chromeos/login/login_ui_browsertest.cc",
         "../browser/chromeos/login/login_ui_hide_supervised_users_browsertest.cc",
-        "../browser/chromeos/login/mixin_based_browser_test.cc",
-        "../browser/chromeos/login/mixin_based_browser_test.h",
+        "../browser/chromeos/login/mixin_based_in_process_browser_test.cc",
+        "../browser/chromeos/login/mixin_based_in_process_browser_test.h",
         "../browser/chromeos/login/oobe_browsertest.cc",
         "../browser/chromeos/login/oobe_interactive_ui_test.cc",
         "../browser/chromeos/login/screenshot_testing/SkDiffPixelsMetric.h",
@@ -5720,8 +5723,6 @@
       "../browser/autofill/autofill_captured_sites_interactive_uitest.cc",
       "../browser/autofill/autofill_uitest.cc",
       "../browser/autofill/autofill_uitest.h",
-      "../browser/autofill/autofill_uitest_util.cc",
-      "../browser/autofill/autofill_uitest_util.h",
       "../browser/autofill/captured_sites_test_utils.cc",
       "../browser/autofill/captured_sites_test_utils.h",
       "../browser/password_manager/password_manager_captured_sites_interactive_uitest.cc",
diff --git a/chrome/test/base/chrome_test_launcher.cc b/chrome/test/base/chrome_test_launcher.cc
index f59d1c3..8da3ff5 100644
--- a/chrome/test/base/chrome_test_launcher.cc
+++ b/chrome/test/base/chrome_test_launcher.cc
@@ -49,6 +49,8 @@
 #if defined(OS_CHROMEOS)
 #include "ash/mojo_interface_factory.h"
 #include "ash/mojo_test_interface_factory.h"
+#include "ash/public/cpp/manifest.h"
+#include "ash/public/cpp/test_manifest.h"
 #include "ash/test/ui_controls_factory_ash.h"
 #endif
 
@@ -178,6 +180,7 @@
 #if defined(OS_CHROMEOS)
   // Inject the test interfaces for ash. Use a callback to avoid linking test
   // interface support into production code.
+  ash::AmendManifestForTesting(ash::GetManifestOverlayForTesting());
   ash::mojo_interface_factory::SetRegisterInterfacesCallback(
       base::Bind(&ash::mojo_test_interface_factory::RegisterInterfaces));
 #endif
diff --git a/chrome/test/data/pdf/navigator_test.js b/chrome/test/data/pdf/navigator_test.js
index 55da969..25a77e6 100644
--- a/chrome/test/data/pdf/navigator_test.js
+++ b/chrome/test/data/pdf/navigator_test.js
@@ -75,10 +75,9 @@
   var mockWindow = new MockWindow(100, 100);
   var mockSizer = new MockSizer();
   var mockViewportChangedCallback = new MockViewportChangedCallback();
-  var viewport = new Viewport(mockWindow, mockSizer,
-                              mockViewportChangedCallback.callback,
-                              function() {}, function() {}, function() {},
-                              0, 1, 0);
+  var viewport = new ViewportImpl(
+      mockWindow, mockSizer, mockViewportChangedCallback.callback,
+      function() {}, function() {}, function() {}, 0, 1, 0);
 
   var paramsParser = new OpenPDFParamsParser(function(name) {
     paramsParser.onNamedDestinationReceived(-1);
@@ -108,9 +107,9 @@
     var mockWindow = new MockWindow(100, 100);
     var mockSizer = new MockSizer();
     var mockCallback = new MockViewportChangedCallback();
-    var viewport = new Viewport(mockWindow, mockSizer, mockCallback.callback,
-                                function() {}, function() {}, function() {},
-                                0, 1, 0);
+    var viewport = new ViewportImpl(
+        mockWindow, mockSizer, mockCallback.callback, function() {},
+        function() {}, function() {}, 0, 1, 0);
 
     var paramsParser = new OpenPDFParamsParser(function(message) {
       if (message.namedDestination == 'US')
diff --git a/chrome/test/data/pdf/viewport_test.js b/chrome/test/data/pdf/viewport_test.js
index 876cce14..2e24d9f2 100644
--- a/chrome/test/data/pdf/viewport_test.js
+++ b/chrome/test/data/pdf/viewport_test.js
@@ -4,9 +4,9 @@
 
 var tests = [
   function testDocumentNeedsScrollbars() {
-    var viewport =
-        new Viewport(new MockWindow(100, 100), new MockSizer(), function() {},
-                     function() {}, function() {}, function() {}, 10, 1, 0);
+    var viewport = new ViewportImpl(
+        new MockWindow(100, 100), new MockSizer(), function() {}, function() {},
+        function() {}, function() {}, 10, 1, 0);
     var scrollbars;
 
     viewport.setDocumentDimensions(new MockDocumentDimensions(90, 90));
@@ -62,10 +62,9 @@
 
     // Test the case when there is a toolbar at the top.
     var toolbarHeight = 10;
-    var viewport =
-        new Viewport(new MockWindow(100, 100), new MockSizer(), function() {},
-                     function() {}, function() {}, function() {}, 10, 1,
-                     toolbarHeight);
+    var viewport = new ViewportImpl(
+        new MockWindow(100, 100), new MockSizer(), function() {}, function() {},
+        function() {}, function() {}, 10, 1, toolbarHeight);
     var scrollbars;
 
     viewport.setDocumentDimensions(new MockDocumentDimensions(90, 90));
@@ -103,9 +102,9 @@
     var mockSizer = new MockSizer();
     var mockWindow = new MockWindow(100, 100, mockSizer);
     var mockCallback = new MockViewportChangedCallback();
-    var viewport = new Viewport(mockWindow, mockSizer, mockCallback.callback,
-                                function() {}, function() {}, function() {},
-                                0, 1, 0);
+    var viewport = new ViewportImpl(
+        mockWindow, mockSizer, mockCallback.callback, function() {},
+        function() {}, function() {}, 0, 1, 0);
 
     // Test setting the zoom without the document dimensions set. The sizer
     // shouldn't change size.
@@ -179,9 +178,9 @@
 
   function testGetMostVisiblePage() {
     var mockWindow = new MockWindow(100, 100);
-    var viewport = new Viewport(mockWindow, new MockSizer(), function() {},
-                                function() {}, function() {}, function() {},
-                                0, 1, 0);
+    var viewport = new ViewportImpl(
+        mockWindow, new MockSizer(), function() {}, function() {},
+        function() {}, function() {}, 0, 1, 0);
 
     var documentDimensions = new MockDocumentDimensions(100, 100);
     documentDimensions.addPage(50, 100);
@@ -230,9 +229,9 @@
     var mockWindow = new MockWindow(100, 100);
     var mockSizer = new MockSizer();
     var mockCallback = new MockViewportChangedCallback();
-    var viewport = new Viewport(mockWindow, mockSizer, mockCallback.callback,
-                                function() {}, function() {}, function() {},
-                                0, 1, 0);
+    var viewport = new ViewportImpl(
+        mockWindow, mockSizer, mockCallback.callback, function() {},
+        function() {}, function() {}, 0, 1, 0);
     var documentDimensions = new MockDocumentDimensions();
 
     function assertZoomed(expectedMockWidth, expectedMockHeight, expectedZoom) {
@@ -288,9 +287,9 @@
     // Test fitting works with scrollbars. The page will need to be zoomed to
     // fit to width, which will cause the page height to span outside of the
     // viewport, triggering 15px scrollbars to be shown.
-    viewport = new Viewport(mockWindow, mockSizer, mockCallback.callback,
-                            function() {}, function() {}, function() {},
-                            15, 1, 0);
+    viewport = new ViewportImpl(
+        mockWindow, mockSizer, mockCallback.callback, function() {},
+        function() {}, function() {}, 15, 1, 0);
     documentDimensions.reset();
     documentDimensions.addPage(50, 100);
     viewport.setDocumentDimensions(documentDimensions);
@@ -307,9 +306,9 @@
     var mockWindow = new MockWindow(100, 100);
     var mockSizer = new MockSizer();
     var mockCallback = new MockViewportChangedCallback();
-    var viewport = new Viewport(mockWindow, mockSizer, mockCallback.callback,
-                                function() {}, function() {}, function() {},
-                                0, 1, 0);
+    var viewport = new ViewportImpl(
+        mockWindow, mockSizer, mockCallback.callback, function() {},
+        function() {}, function() {}, 0, 1, 0);
     var documentDimensions = new MockDocumentDimensions();
 
     function assertZoomed(expectedMockWidth, expectedMockHeight, expectedZoom) {
@@ -410,7 +409,7 @@
     var mockWindow = new MockWindow(100, 100);
     var mockSizer = new MockSizer();
     var mockCallback = new MockViewportChangedCallback();
-    var viewport = new Viewport(
+    var viewport = new ViewportImpl(
         mockWindow, mockSizer, mockCallback.callback, function() {},
         function() {}, function() {}, 0, 1, 0);
     var documentDimensions = new MockDocumentDimensions();
@@ -513,9 +512,9 @@
     var mockWindow = new MockWindow(100, 100);
     var mockSizer = new MockSizer();
     var mockCallback = new MockViewportChangedCallback();
-    var viewport = new Viewport(mockWindow, mockSizer, mockCallback.callback,
-                                function() {}, function() {}, function() {},
-                                0, 1, 0);
+    var viewport = new ViewportImpl(
+        mockWindow, mockSizer, mockCallback.callback, function() {},
+        function() {}, function() {}, 0, 1, 0);
     var documentDimensions = new MockDocumentDimensions();
 
     documentDimensions.addPage(100, 100);
@@ -555,9 +554,9 @@
     var mockWindow = new MockWindow(100, 100);
     var mockSizer = new MockSizer();
     var mockCallback = new MockViewportChangedCallback();
-    var viewport = new Viewport(mockWindow, mockSizer, mockCallback.callback,
-                                function() {}, function() {}, function() {},
-                                0, 1, 0);
+    var viewport = new ViewportImpl(
+        mockWindow, mockSizer, mockCallback.callback, function() {},
+        function() {}, function() {}, 0, 1, 0);
     var documentDimensions = new MockDocumentDimensions();
 
     documentDimensions.addPage(100, 100);
@@ -609,9 +608,9 @@
     var mockWindow = new MockWindow(100, 100);
     var mockSizer = new MockSizer();
     var mockCallback = new MockViewportChangedCallback();
-    var viewport = new Viewport(mockWindow, mockSizer, mockCallback.callback,
-                                function() {}, function() {}, function() {},
-                                0, 1, 0);
+    var viewport = new ViewportImpl(
+        mockWindow, mockSizer, mockCallback.callback, function() {},
+        function() {}, function() {}, 0, 1, 0);
     var documentDimensions = new MockDocumentDimensions();
 
     documentDimensions.addPage(200, 200);
@@ -664,9 +663,9 @@
     var mockWindow = new MockWindow(100, 100);
     var mockSizer = new MockSizer();
     var mockCallback = new MockViewportChangedCallback();
-    var viewport = new Viewport(mockWindow, mockSizer, mockCallback.callback,
-                                function() {}, function() {}, function() {},
-                                0, 1, 0);
+    var viewport = new ViewportImpl(
+        mockWindow, mockSizer, mockCallback.callback, function() {},
+        function() {}, function() {}, 0, 1, 0);
     var documentDimensions = new MockDocumentDimensions();
 
     documentDimensions.addPage(200, 200);
@@ -713,9 +712,9 @@
     var mockWindow = new MockWindow(100, 100);
     var mockSizer = new MockSizer();
     var mockCallback = new MockViewportChangedCallback();
-    var viewport = new Viewport(mockWindow, mockSizer, mockCallback.callback,
-                                function() {}, function() {}, function() {},
-                                0, 1, 0);
+    var viewport = new ViewportImpl(
+        mockWindow, mockSizer, mockCallback.callback, function() {},
+        function() {}, function() {}, 0, 1, 0);
     var documentDimensions = new MockDocumentDimensions();
     documentDimensions.addPage(100, 100);
     documentDimensions.addPage(200, 200);
@@ -769,26 +768,26 @@
         chrome.test.assertFalse(afterZoomCalled);
         chrome.test.assertEq(1, viewport.zoom);
     };
-    viewport = new Viewport(mockWindow, mockSizer, function() {},
-                            beforeZoom, afterZoom, function() {}, 0, 1, 0);
+    viewport = new ViewportImpl(
+        mockWindow, mockSizer, function() {}, beforeZoom, afterZoom,
+        function() {}, 0, 1, 0);
     viewport.setZoom(0.5);
     chrome.test.succeed();
   },
 
   function testInitialSetDocumentDimensionsZoomConstrained() {
-    var viewport =
-        new Viewport(new MockWindow(100, 100), new MockSizer(), function() {},
-                     function() {}, function() {}, function() {}, 0, 1.2, 0);
+    var viewport = new ViewportImpl(
+        new MockWindow(100, 100), new MockSizer(), function() {}, function() {},
+        function() {}, function() {}, 0, 1.2, 0);
     viewport.setDocumentDimensions(new MockDocumentDimensions(50, 50));
     chrome.test.assertEq(1.2, viewport.zoom);
     chrome.test.succeed();
   },
 
   function testInitialSetDocumentDimensionsZoomUnconstrained() {
-    var viewport = new Viewport(
-        new MockWindow(100, 100),
-        new MockSizer(), function() {}, function() {}, function() {},
-        function() {}, 0, 3, 0);
+    var viewport = new ViewportImpl(
+        new MockWindow(100, 100), new MockSizer(), function() {}, function() {},
+        function() {}, function() {}, 0, 3, 0);
     viewport.setDocumentDimensions(new MockDocumentDimensions(50, 50));
     chrome.test.assertEq(2, viewport.zoom);
     chrome.test.succeed();
@@ -797,9 +796,9 @@
   function testToolbarHeightOffset() {
     var mockSizer = new MockSizer();
     var mockWindow = new MockWindow(100, 100);
-    var viewport = new Viewport(mockWindow,
-        mockSizer, function() {}, function() {}, function() {}, function() {},
-        0, 1, 50);
+    var viewport = new ViewportImpl(
+        mockWindow, mockSizer, function() {}, function() {}, function() {},
+        function() {}, 0, 1, 50);
     var documentDimensions = new MockDocumentDimensions(0, 0);
     documentDimensions.addPage(50, 500);
     viewport.setDocumentDimensions(documentDimensions);
diff --git a/chrome/test/data/webui/app_management/app_management_browsertest.js b/chrome/test/data/webui/app_management/app_management_browsertest.js
index 1d5bee5..f1371334 100644
--- a/chrome/test/data/webui/app_management/app_management_browsertest.js
+++ b/chrome/test/data/webui/app_management/app_management_browsertest.js
@@ -18,7 +18,9 @@
 
   browsePreload: 'chrome://apps',
 
-  extraLibraries: PolymerTest.getLibraries(ROOT_PATH),
+  extraLibraries: PolymerTest.getLibraries(ROOT_PATH).concat([
+    'test_util.js',
+  ]),
 
   featureList: ['features::kAppManagement', ''],
 
@@ -26,7 +28,6 @@
   runAccessibilityChecks: true,
 };
 
-
 function AppManagementAppTest() {}
 
 AppManagementAppTest.prototype = {
@@ -55,6 +56,20 @@
   mocha.run();
 });
 
+function AppManagementMetadataViewTest() {}
+
+AppManagementMetadataViewTest.prototype = {
+  __proto__: AppManagementBrowserTest.prototype,
+
+  extraLibraries: AppManagementBrowserTest.prototype.extraLibraries.concat([
+    'metadata_view_test.js',
+  ]),
+};
+
+TEST_F('AppManagementMetadataViewTest', 'All', function() {
+  mocha.run();
+});
+
 function AppManagementReducersTest() {}
 
 AppManagementReducersTest.prototype = {
diff --git a/chrome/test/data/webui/app_management/app_test.js b/chrome/test/data/webui/app_management/app_test.js
index c2eddaf2..207c635 100644
--- a/chrome/test/data/webui/app_management/app_test.js
+++ b/chrome/test/data/webui/app_management/app_test.js
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+'use strict';
+
 suite('<app-management-app>', function() {
   test('loads', async function() {
     // Check that the browser responds to the getApps() message.
diff --git a/chrome/test/data/webui/app_management/main_view_test.js b/chrome/test/data/webui/app_management/main_view_test.js
index dc3eba52..379591e 100644
--- a/chrome/test/data/webui/app_management/main_view_test.js
+++ b/chrome/test/data/webui/app_management/main_view_test.js
@@ -2,74 +2,57 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+'use strict';
+
 suite('<app-management-main-view>', function() {
   let mainView;
   let fakeHandler;
-  let callbackRouterProxy;
   let appIdCounter;
 
+  /**
+   * @param {number} numApps
+   */
+  async function addApps(numApps) {
+    for (let i = 0; i < numApps; i++) {
+      await fakeHandler.addApp((appIdCounter++).toString());
+    }
+  }
+
   setup(function() {
     appIdCounter = 0;
 
     mainView = document.createElement('app-management-main-view');
     PolymerTest.clearBody();
 
-    let browserProxy = app_management.BrowserProxy.getInstance();
-    callbackRouterProxy = browserProxy.callbackRouter.createProxy();
-
-    fakeHandler = new app_management.FakePageHandler(callbackRouterProxy);
-    browserProxy.handler = fakeHandler;
-
-    app_management.Store.instance_ = new app_management.Store();
-
-    app_management.Store.getInstance().init(
-        app_management.util.createEmptyState());
+    fakeHandler = setupFakeHandler();
+    replaceStore();
 
     document.body.appendChild(mainView);
   });
 
-  /**
-   * @param {number} numApps
-   * @return {!Array<appManagement.mojom.App>} apps
-   */
-  function createTestApps(numApps) {
-    let apps = [];
-    for (let i = 0; i < numApps; i++) {
-      apps.push(
-          app_management.FakePageHandler.createApp('TestApp' + appIdCounter++));
-    }
-    return apps;
-  }
-
-  async function addApps(apps) {
-    for (const app of apps) {
-      callbackRouterProxy.onAppAdded(app);
-    }
-    await callbackRouterProxy.flushForTesting();
-  }
-
   test('simple app addition', async function() {
     // Ensure there is no apps initially
     expectEquals(
         0, mainView.root.querySelectorAll('app-management-app-item').length);
 
-    let apps = createTestApps(1);
-    await addApps(apps);
+    const appId = '1';
+    await fakeHandler.addApp(appId);
+
     let appItems = mainView.root.querySelectorAll('app-management-app-item');
     expectEquals(1, appItems.length);
 
-    expectEquals(appItems[0].app.id, apps[0].id);
+    expectEquals(appId, appItems[0].app.id);
   });
 
   test('more apps bar visibility', async function() {
     // The more apps bar shouldn't appear when there are 4 apps.
-    await addApps(createTestApps(4));
+    await addApps(4);
     expectEquals(
         4, mainView.root.querySelectorAll('app-management-app-item').length);
     expectTrue(mainView.$['expander-row'].hidden);
 
     // The more apps bar appears when there are 5 apps.
-    await addApps(createTestApps(1));
+    await addApps(1);
     expectEquals(
         5, mainView.root.querySelectorAll('app-management-app-item').length);
     expectFalse(mainView.$['expander-row'].hidden);
@@ -77,14 +60,14 @@
 
   test('notifications sublabel collapsibility', async function() {
     // The three spans contains collapsible attribute.
-    await addApps(createTestApps(4));
+    await addApps(4);
     const pieces = await mainView.getNotificationSublabelPieces_();
     expectTrue(pieces.filter(p => p.arg === '$1')[0].collapsible);
     expectTrue(pieces.filter(p => p.arg === '$2')[0].collapsible);
     expectTrue(pieces.filter(p => p.arg === '$3')[0].collapsible);
 
     // Checking ",and other x apps" is non-collapsible
-    await addApps(createTestApps(6));
+    await addApps(6);
     const pieces2 = await mainView.getNotificationSublabelPieces_();
     expectFalse(pieces2.filter(p => p.arg === '$4')[0].collapsible);
   });
diff --git a/chrome/test/data/webui/app_management/metadata_view_test.js b/chrome/test/data/webui/app_management/metadata_view_test.js
new file mode 100644
index 0000000..c337bc5f
--- /dev/null
+++ b/chrome/test/data/webui/app_management/metadata_view_test.js
@@ -0,0 +1,72 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+suite('<app-management-metadata-view>', function() {
+  let metadataView;
+  let fakeHandler;
+
+  const APP_ID = '1';
+
+  setup(async function() {
+    metadataView = document.createElement('app-management-metadata-view');
+
+    PolymerTest.clearBody();
+    fakeHandler = setupFakeHandler();
+    replaceStore();
+
+    // Add an app, and make it the currently selected app.
+    await fakeHandler.addApp(APP_ID);
+    app_management.Store.getInstance().dispatch(
+        app_management.actions.changePage(PageType.DETAIL, APP_ID));
+
+    document.body.appendChild(metadataView);
+  });
+
+  test(
+      'when app.isPinned is unknown, the pin to shelf toggle is not visible',
+      async function() {
+        await fakeHandler.changeApp(APP_ID, {isPinned: OptionalBool.kUnknown});
+
+        // Check that the toggle is not visible.
+        const toggle = metadataView.root.getElementById('pin-to-shelf-toggle');
+        if (toggle) {
+          expectTrue(isHidden(toggle));
+        }
+      });
+
+  test(
+      'clicking the pin to shelf toggle changes the isPinned field of the app',
+      async function() {
+        // Set app.isPinned to false.
+        await fakeHandler.changeApp(APP_ID, {isPinned: OptionalBool.kFalse});
+
+        const toggle = metadataView.root.getElementById('pin-to-shelf-toggle');
+
+        // Check that the toggle is visible and is not checked.
+        expectTrue(!!toggle && !isHidden(toggle));
+        expectFalse(toggle.checked);
+
+        // Toggle from false to true.
+        toggle.click();
+        await fakeHandler.flushForTesting();
+
+        // Check that the isPinned field of the app has changed.
+        expectEquals(OptionalBool.kTrue, metadataView.app_.isPinned);
+
+        // Check that the toggle is now checked.
+        expectTrue(toggle.checked);
+
+        // Toggle from true to false.
+        toggle.click();
+        await fakeHandler.flushForTesting();
+
+        // Check that the isPinned field of the app has changed.
+        expectEquals(OptionalBool.kFalse, metadataView.app_.isPinned);
+
+        // Check that the toggle is no longer checked.
+        expectFalse(toggle.checked);
+      });
+});
diff --git a/chrome/test/data/webui/app_management/reducers_test.js b/chrome/test/data/webui/app_management/reducers_test.js
index b8a4192..cfac2873 100644
--- a/chrome/test/data/webui/app_management/reducers_test.js
+++ b/chrome/test/data/webui/app_management/reducers_test.js
@@ -2,9 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+'use strict';
+
 suite('app state', function() {
   let apps;
-  let action;
   let state;
 
   function createApp(id, config) {
@@ -27,8 +28,7 @@
 
   test('updates when an app is added', function() {
     const newApp = createApp('3', {type: 1, title: 'a'});
-
-    action = app_management.actions.addApp(newApp);
+    const action = app_management.actions.addApp(newApp);
     apps = app_management.AppState.updateApps(apps, action);
 
     // Check that apps contains a key for each app id.
@@ -45,7 +45,7 @@
 
   test('updates when an app is changed', function() {
     const changedApp = createApp('2', {type: 1, title: 'a'});
-    action = app_management.actions.changeApp(changedApp);
+    const action = app_management.actions.changeApp(changedApp);
     apps = app_management.AppState.updateApps(apps, action);
 
     // Check that app has changed.
@@ -58,7 +58,7 @@
   });
 
   test('updates when an app is removed', function() {
-    action = app_management.actions.removeApp('1');
+    const action = app_management.actions.removeApp('1');
     apps = app_management.AppState.updateApps(apps, action);
 
     // Check that app is removed.
@@ -74,7 +74,7 @@
         state.currentPage.selectedAppId = '1';
         state.currentPage.pageType = PageType.DETAIL;
 
-        action = app_management.actions.removeApp('1');
+        let action = app_management.actions.removeApp('1');
         state = app_management.reduceAction(state, action);
 
         assertEquals(null, state.currentPage.selectedAppId);
@@ -97,7 +97,7 @@
     state.currentPage.selectedAppId = '1';
     state.currentPage.pageType = PageType.DETAIL;
 
-    action = app_management.actions.changePage(PageType.MAIN);
+    let action = app_management.actions.changePage(PageType.MAIN);
     state = app_management.reduceAction(state, action);
 
     assertEquals(null, state.currentPage.selectedAppId);
@@ -113,7 +113,7 @@
 
   test('state updates when changing to app detail page', function() {
     // State updates when a valid app detail page is selected.
-    action = app_management.actions.changePage(PageType.DETAIL, '2');
+    let action = app_management.actions.changePage(PageType.DETAIL, '2');
     state = app_management.reduceAction(state, action);
 
     assertEquals('2', state.currentPage.selectedAppId);
@@ -131,7 +131,7 @@
   });
 
   test('state updates when changing to notifications page', function() {
-    action = app_management.actions.changePage(PageType.NOTIFICATIONS);
+    const action = app_management.actions.changePage(PageType.NOTIFICATIONS);
     state = app_management.reduceAction(state, action);
 
     assertEquals(PageType.NOTIFICATIONS, state.currentPage.pageType);
diff --git a/chrome/test/data/webui/app_management/test_util.js b/chrome/test/data/webui/app_management/test_util.js
new file mode 100644
index 0000000..d11716b
--- /dev/null
+++ b/chrome/test/data/webui/app_management/test_util.js
@@ -0,0 +1,37 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+/**
+ * @return {app_management.FakePageHandler}
+ */
+function setupFakeHandler() {
+  const browserProxy = app_management.BrowserProxy.getInstance();
+  const callbackRouterProxy = browserProxy.callbackRouter.createProxy();
+
+  const fakeHandler = new app_management.FakePageHandler(callbackRouterProxy);
+  browserProxy.handler = fakeHandler;
+
+  return fakeHandler;
+}
+
+/**
+ * Replace the app management store instance with a new, empty store.
+ */
+function replaceStore() {
+  app_management.Store.instance_ = new app_management.Store();
+
+  app_management.Store.getInstance().init(
+      app_management.util.createEmptyState());
+}
+
+/**
+ * @param {Element} element
+ * @return {bool}
+ */
+function isHidden(element) {
+  const rect = element.getBoundingClientRect();
+  return rect.height === 0 && rect.width === 0;
+}
diff --git a/chrome/test/data/webui/settings/people_page_sync_page_test.js b/chrome/test/data/webui/settings/people_page_sync_page_test.js
index b7b2e1e..a444757 100644
--- a/chrome/test/data/webui/settings/people_page_sync_page_test.js
+++ b/chrome/test/data/webui/settings/people_page_sync_page_test.js
@@ -536,13 +536,43 @@
         const cancelButton =
             syncPage.$$('settings-sync-account-control')
                 .shadowRoot.querySelector('#setup-buttons .secondary-button');
-
         assertTrue(!!cancelButton);
-        cancelButton.click();
 
-        return browserProxy.whenCalled('didNavigateAwayFromSyncPage')
-            .then(abort => {
-              assertTrue(abort);
+        // Clicking the setup cancel button opens the 'Cancel sync?' dialog.
+        cancelButton.click();
+        Polymer.dom.flush();
+
+        assertEquals(settings.routes.SYNC, settings.getCurrentRoute());
+        assertTrue(!!syncPage.$$('#setupCancelDialog'));
+        assertTrue(syncPage.$$('#setupCancelDialog').open);
+
+        // Clicking the cancel button on the 'Cancel sync?' dialog closes the
+        // dialog and removes it from the DOM.
+        syncPage.$$('#setupCancelDialog')
+            .querySelector('.cancel-button')
+            .click();
+        return test_util
+            .eventToPromise('close', syncPage.$$('#setupCancelDialog'))
+            .then(() => {
+              Polymer.dom.flush();
+              assertEquals(settings.routes.SYNC, settings.getCurrentRoute());
+              assertFalse(!!syncPage.$$('#setupCancelDialog'));
+
+              // Clicking the setup cancel button shows the 'Cancel sync?'
+              // dialog again.
+              cancelButton.click();
+              Polymer.dom.flush();
+              assertTrue(syncPage.$$('#setupCancelDialog').open);
+
+              // Clicking the confirm button on the dialog aborts sync.
+              syncPage.$$('#setupCancelDialog')
+                  .querySelector('.action-button')
+                  .click();
+
+              return browserProxy.whenCalled('didNavigateAwayFromSyncPage')
+                  .then(abort => {
+                    assertTrue(abort);
+                  });
             });
       });
 
@@ -573,9 +603,26 @@
 
       test('SyncSetupLeavePage UnifiedConsentEnabled', function() {
         syncPage.unifiedConsentEnabled = true;
+        syncPage.syncStatus = {
+          signinAllowed: true,
+          syncSystemEnabled: true,
+          setupInProgress: true,
+          signedIn: true
+        };
         Polymer.dom.flush();
 
+        // Navigating away while setup is in progress opens the 'Cancel sync?'
+        // dialog.
         settings.navigateTo(settings.routes.BASIC);
+        Polymer.dom.flush();
+
+        assertEquals(settings.routes.SYNC, settings.getCurrentRoute());
+        assertTrue(syncPage.$$('#setupCancelDialog').open);
+
+        // Clicking the confirm button on the dialog aborts sync.
+        syncPage.$$('#setupCancelDialog')
+            .querySelector('.action-button')
+            .click();
 
         return browserProxy.whenCalled('didNavigateAwayFromSyncPage')
             .then(abort => {
diff --git a/components/arc/common/app.mojom b/components/arc/common/app.mojom
index 203e4dd..99489cd 100644
--- a/components/arc/common/app.mojom
+++ b/components/arc/common/app.mojom
@@ -191,6 +191,42 @@
   kDynamic = 1,
 };
 
+[Extensible]
+enum AppReinstallState {
+    // Request and response successful.
+    REQUEST_SUCCESS = 0,
+    // Request Timeout
+    REQUEST_TIMEOUT = 1,
+    // No Account.
+    REQUEST_NO_ACCOUNT = 2,
+    // Null data from aidl.
+    REQUEST_NULL_DATA = 3,
+    // Data that can't be converted from aidl bundle to mojom struct.
+    REQUEST_BUNDLE_ERROR = 4,
+    // Request failure other reason.
+    REQUEST_UNKNOWN_FAILURE = 5,
+};
+
+// Describes app reinstall candidates that are shown to users as suggestions.
+struct AppReinstallCandidate {
+  // The package name of this in the Play Store.
+  string package_name;
+
+  // The title of this package in the Play Store.
+  string title;
+
+  // The url of an icon for this package. Optional, a default icon is used if
+  // unset.
+  string? icon_url;
+
+  // Ratings Count from Play Store for this package. e.g. "17103" .
+  // Default of 0 if not set.
+  int64 rating_count;
+
+  // Mean star rating for this package, [1, 5]. 0 indicates missing.
+  float star_rating;
+};
+
 // Describes app shortcut that is published by Android's ShortcutManager.
 struct AppShortcutItem {
   // The ID of this shortcut. Unique within each publisher app and stable across
@@ -432,6 +468,12 @@
   // Sends a request to ARC to start FastAppReinstall flow.
   [MinVersion=33] StartFastAppReinstallFlow@25(array<string> arc_package_names);
 
+  // Sends a request to ARC to retrieve an array of app reinstall
+  // candidates for this user on this device. Order of results is in an
+  // unspecified order of relevance from Play Store.
+  [MinVersion=39] GetAppReinstallCandidates@31() =>
+      (AppReinstallState state, array<AppReinstallCandidate> candidates);
+
   // Sends a request to ARC to uninstall the given package.  Error (if ever
   // happens) is ignored, and uninstall option should appear in the UI.
   [MinVersion=2] UninstallPackage@5(string package_name);
diff --git a/components/arc/test/fake_app_instance.cc b/components/arc/test/fake_app_instance.cc
index d479edd..7011018 100644
--- a/components/arc/test/fake_app_instance.cc
+++ b/components/arc/test/fake_app_instance.cc
@@ -467,6 +467,17 @@
   std::move(callback).Run(pai_state_response_);
 }
 
+void FakeAppInstance::GetAppReinstallCandidates(
+    GetAppReinstallCandidatesCallback callback) {
+  ++get_app_reinstall_callback_count_;
+  std::vector<arc::mojom::AppReinstallCandidatePtr> candidates;
+  for (const auto& candidate : app_reinstall_candidates_)
+    candidates.emplace_back(candidate.Clone());
+
+  std::move(callback).Run(arc::mojom::AppReinstallState::REQUEST_SUCCESS,
+                          std::move(candidates));
+}
+
 void FakeAppInstance::StartFastAppReinstallFlow(
     const std::vector<std::string>& package_names) {
   ++start_fast_app_reinstall_request_count_;
@@ -516,4 +527,11 @@
 
 void FakeAppInstance::RemoveCachedIcon(const std::string& icon_resource_id) {}
 
+void FakeAppInstance::SetAppReinstallCandidates(
+    const std::vector<arc::mojom::AppReinstallCandidatePtr>& candidates) {
+  app_reinstall_candidates_.clear();
+  for (const auto& candidate : candidates)
+    app_reinstall_candidates_.emplace_back(candidate.Clone());
+}
+
 }  // namespace arc
diff --git a/components/arc/test/fake_app_instance.h b/components/arc/test/fake_app_instance.h
index e5eca74..c1b848a 100644
--- a/components/arc/test/fake_app_instance.h
+++ b/components/arc/test/fake_app_instance.h
@@ -156,8 +156,11 @@
       GetAppShortcutGlobalQueryItemsCallback callback) override;
   void GetAppShortcutItems(const std::string& package_name,
                            GetAppShortcutItemsCallback callback) override;
+
   void StartPaiFlowDeprecated() override;
   void StartPaiFlow(StartPaiFlowCallback callback) override;
+  void GetAppReinstallCandidates(
+      GetAppReinstallCandidatesCallback callback) override;
   void StartFastAppReinstallFlow(
       const std::vector<std::string>& package_names) override;
   void RequestAssistStructure(RequestAssistStructureCallback callback) override;
@@ -231,6 +234,10 @@
     return launch_intents_;
   }
 
+  int get_app_reinstall_callback_count() const {
+    return get_app_reinstall_callback_count_;
+  }
+
   const std::vector<std::unique_ptr<IconRequest>>& icon_requests() const {
     return icon_requests_;
   }
@@ -240,6 +247,9 @@
     return shortcut_icon_requests_;
   }
 
+  void SetAppReinstallCandidates(
+      const std::vector<arc::mojom::AppReinstallCandidatePtr>& candidates);
+
  private:
   using TaskIdToInfo = std::map<int32_t, std::unique_ptr<Request>>;
   // Mojo endpoints.
@@ -254,6 +264,11 @@
   int start_fast_app_reinstall_request_count_ = 0;
   // Keeps information about launch app shortcut requests.
   int launch_app_shortcut_item_count_ = 0;
+  // Keeps info about the number of times we got a request for app reinstalls.
+  int get_app_reinstall_callback_count_ = 0;
+
+  // Vector to send as app reinstall candidates.
+  std::vector<arc::mojom::AppReinstallCandidatePtr> app_reinstall_candidates_;
   // Keeps information about launch requests.
   std::vector<std::unique_ptr<Request>> launch_requests_;
   // Keeps information about launch intents.
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index 6f71f1eb..def89f8 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -179,6 +179,8 @@
     "strike_database.h",
     "strike_database_integrator_base.cc",
     "strike_database_integrator_base.h",
+    "strike_database_integrator_test_strike_database.cc",
+    "strike_database_integrator_test_strike_database.h",
     "subkey_requester.cc",
     "subkey_requester.h",
     "suggestion.cc",
@@ -385,8 +387,6 @@
     "test_legacy_strike_database.h",
     "test_local_card_migration_manager.cc",
     "test_local_card_migration_manager.h",
-    "test_local_card_migration_strike_database.cc",
-    "test_local_card_migration_strike_database.h",
     "test_personal_data_manager.cc",
     "test_personal_data_manager.h",
     "test_region_data_loader.cc",
@@ -512,7 +512,6 @@
     "country_names_unittest.cc",
     "credit_card_field_unittest.cc",
     "credit_card_save_manager_unittest.cc",
-    "credit_card_save_strike_database_unittest.cc",
     "credit_card_unittest.cc",
     "field_candidates_unittest.cc",
     "field_filler_unittest.cc",
@@ -522,7 +521,6 @@
     "legacy_strike_database_unittest.cc",
     "legal_message_line_unittest.cc",
     "local_card_migration_manager_unittest.cc",
-    "local_card_migration_strike_database_unittest.cc",
     "name_field_unittest.cc",
     "password_generator_fips181_unittest.cc",
     "password_generator_unittest.cc",
@@ -540,6 +538,7 @@
     "rationalization_util_unittest.cc",
     "region_combobox_model_unittest.cc",
     "search_field_unittest.cc",
+    "strike_database_integrator_test_strike_database_unittest.cc",
     "strike_database_unittest.cc",
     "subkey_requester_unittest.cc",
     "suggestion_selection_unittest.cc",
diff --git a/components/autofill/core/browser/autofill_metrics.cc b/components/autofill/core/browser/autofill_metrics.cc
index 3c637d11..6e40565 100644
--- a/components/autofill/core/browser/autofill_metrics.cc
+++ b/components/autofill/core/browser/autofill_metrics.cc
@@ -1473,6 +1473,7 @@
 void AutofillMetrics::LogAutofillFormSubmittedState(
     AutofillFormSubmittedState state,
     bool is_for_credit_card,
+    bool has_upi_vpa_field,
     const std::set<FormType>& form_types,
     const base::TimeTicks& form_parsed_timestamp,
     FormSignature form_signature,
@@ -1510,9 +1511,9 @@
       NOTREACHED();
       break;
   }
-  form_interactions_ukm_logger->LogFormSubmitted(is_for_credit_card, form_types,
-                                                 state, form_parsed_timestamp,
-                                                 form_signature);
+  form_interactions_ukm_logger->LogFormSubmitted(
+      is_for_credit_card, has_upi_vpa_field, form_types, state,
+      form_parsed_timestamp, form_signature);
 }
 
 // static
@@ -2198,6 +2199,7 @@
 
 void AutofillMetrics::FormInteractionsUkmLogger::LogFormSubmitted(
     bool is_for_credit_card,
+    bool has_upi_vpa_field,
     const std::set<FormType>& form_types,
     AutofillFormSubmittedState state,
     const base::TimeTicks& form_parsed_timestamp,
@@ -2208,6 +2210,7 @@
   ukm::builders::Autofill_FormSubmitted builder(source_id_);
   builder.SetAutofillFormSubmittedState(static_cast<int>(state))
       .SetIsForCreditCard(is_for_credit_card)
+      .SetHasUpiVpaField(has_upi_vpa_field)
       .SetFormTypes(FormTypesToBitVector(form_types))
       .SetFormSignature(HashFormSignature(form_signature));
   if (form_parsed_timestamp.is_null())
diff --git a/components/autofill/core/browser/autofill_metrics.h b/components/autofill/core/browser/autofill_metrics.h
index 0391e0d..2e2f615 100644
--- a/components/autofill/core/browser/autofill_metrics.h
+++ b/components/autofill/core/browser/autofill_metrics.h
@@ -891,6 +891,7 @@
                       ServerFieldType predicted_type,
                       ServerFieldType actual_type);
     void LogFormSubmitted(bool is_for_credit_card,
+                          bool has_upi_vpa_field,
                           const std::set<FormType>& form_types,
                           AutofillFormSubmittedState state,
                           const base::TimeTicks& form_parsed_timestamp,
@@ -1212,6 +1213,7 @@
   static void LogAutofillFormSubmittedState(
       AutofillFormSubmittedState state,
       bool is_for_credit_card,
+      bool has_upi_vpa_field,
       const std::set<FormType>& form_types,
       const base::TimeTicks& form_parsed_timestamp,
       FormSignature form_signature,
diff --git a/components/autofill/core/browser/autofill_metrics_unittest.cc b/components/autofill/core/browser/autofill_metrics_unittest.cc
index d44102b..8dcda40 100644
--- a/components/autofill/core/browser/autofill_metrics_unittest.cc
+++ b/components/autofill/core/browser/autofill_metrics_unittest.cc
@@ -154,12 +154,14 @@
                          const FormData& form,
                          AutofillMetrics::AutofillFormSubmittedState state,
                          bool is_for_credit_card,
+                         bool has_upi_vpa_field,
                          const std::set<FormType>& form_types) {
   VerifyFormInteractionUkm(
       ukm_recorder, form, UkmFormSubmittedType::kEntryName,
       {{{UkmFormSubmittedType::kAutofillFormSubmittedStateName, state},
         {UkmSuggestionFilledType::kMillisecondsSinceFormParsedName, 0},
         {UkmFormSubmittedType::kIsForCreditCardName, is_for_credit_card},
+        {UkmFormSubmittedType::kHasUpiVpaFieldName, has_upi_vpa_field},
         {UkmFormSubmittedType::kFormTypesName,
          AutofillMetrics::FormTypesToBitVector(form_types)},
         {UkmFormSubmittedType::kFormSignatureName,
@@ -3158,9 +3160,37 @@
          Collapse(CalculateFormSignature(form))}}});
   // Expect |NON_FILLABLE_FORM_OR_NEW_DATA| in |AutofillFormSubmittedState|
   // because |field.value| is empty in |DeterminePossibleFieldTypesForUpload|.
-  VerifySubmitFormUkm(
-      test_ukm_recorder_, form, AutofillMetrics::NON_FILLABLE_FORM_OR_NEW_DATA,
-      /*is_for_credit_card=*/true, {FormType::CREDIT_CARD_FORM});
+  VerifySubmitFormUkm(test_ukm_recorder_, form,
+                      AutofillMetrics::NON_FILLABLE_FORM_OR_NEW_DATA,
+                      /*is_for_credit_card=*/true, /* has_upi_vpa_field=*/false,
+                      {FormType::CREDIT_CARD_FORM});
+}
+
+// Test that the UPI Checkout flow form submit is correctly logged
+TEST_F(AutofillMetricsTest, UpiVpaUkmTest) {
+  FormData form;
+  form.name = ASCIIToUTF16("TestForm");
+  form.origin = GURL("http://example.com/form.html");
+  form.action = GURL("http://example.com/submit.html");
+  form.main_frame_origin = url::Origin::Create(autofill_client_.form_origin());
+  FormFieldData field;
+  test::CreateTestFormField("Enter VPA", "upi-vpa", "unique_id@upi", "text",
+                            &field);
+  form.fields.push_back(field);
+
+  std::vector<FormData> forms(1, form);
+
+  {
+    autofill_manager_->OnFormsSeen(forms, TimeTicks::Now());
+
+    VerifySubmitFormUkm(test_ukm_recorder_, forms.back(),
+                        AutofillMetrics::NON_FILLABLE_FORM_OR_NEW_DATA,
+                        /*is_for_credit_card=*/false,
+                        /* has_upi_vpa_field */ true,
+                        /* UPI VPA has Unknown form type.*/
+                        {FormType::ADDRESS_FORM, FormType::UNKNOWN_FORM_TYPE});
+    PurgeUKM();
+  }
 }
 
 // Test that the profile checkout flow user actions are correctly logged.
@@ -3297,7 +3327,8 @@
   // because |field.value| is empty in |DeterminePossibleFieldTypesForUpload|.
   VerifySubmitFormUkm(test_ukm_recorder_, form,
                       AutofillMetrics::NON_FILLABLE_FORM_OR_NEW_DATA,
-                      /*is_for_credit_card=*/false, {FormType::ADDRESS_FORM});
+                      /*is_for_credit_card=*/false,
+                      /* has_upi_vpa_field=*/false, {FormType::ADDRESS_FORM});
 }
 
 // Tests that the Autofill_PolledCreditCardSuggestions user action is only
@@ -4678,6 +4709,7 @@
     VerifySubmitFormUkm(test_ukm_recorder_, form,
                         AutofillMetrics::NON_FILLABLE_FORM_OR_NEW_DATA,
                         /*is_for_credit_card=*/true,
+                        /* has_upi_vpa_field=*/false,
                         {FormType::CREDIT_CARD_FORM});
   }
 
@@ -4721,6 +4753,7 @@
     VerifySubmitFormUkm(test_ukm_recorder_, form,
                         AutofillMetrics::NON_FILLABLE_FORM_OR_NEW_DATA,
                         /*is_for_credit_card=*/true,
+                        /* has_upi_vpa_field=*/false,
                         {FormType::CREDIT_CARD_FORM});
   }
 
@@ -4768,6 +4801,7 @@
     VerifySubmitFormUkm(test_ukm_recorder_, form,
                         AutofillMetrics::NON_FILLABLE_FORM_OR_NEW_DATA,
                         /*is_for_credit_card=*/true,
+                        /* has_upi_vpa_field=*/false,
                         {FormType::CREDIT_CARD_FORM});
   }
 
@@ -4812,6 +4846,7 @@
     VerifySubmitFormUkm(test_ukm_recorder_, form,
                         AutofillMetrics::NON_FILLABLE_FORM_OR_NEW_DATA,
                         /*is_for_credit_card=*/true,
+                        /* has_upi_vpa_field=*/false,
                         {FormType::CREDIT_CARD_FORM});
   }
 
@@ -4858,6 +4893,7 @@
     VerifySubmitFormUkm(test_ukm_recorder_, form,
                         AutofillMetrics::NON_FILLABLE_FORM_OR_NEW_DATA,
                         /*is_for_credit_card=*/true,
+                        /* has_upi_vpa_field=*/false,
                         {FormType::CREDIT_CARD_FORM});
   }
 
@@ -4909,6 +4945,7 @@
     VerifySubmitFormUkm(test_ukm_recorder_, form,
                         AutofillMetrics::NON_FILLABLE_FORM_OR_NEW_DATA,
                         /*is_for_credit_card=*/true,
+                        /* has_upi_vpa_field=*/false,
                         {FormType::CREDIT_CARD_FORM});
   }
 
@@ -4936,6 +4973,7 @@
     VerifySubmitFormUkm(test_ukm_recorder_, form,
                         AutofillMetrics::NON_FILLABLE_FORM_OR_NEW_DATA,
                         /*is_for_credit_card=*/true,
+                        /* has_upi_vpa_field=*/false,
                         {FormType::CREDIT_CARD_FORM});
 
     autofill_manager_->OnFormSubmitted(form, false,
@@ -4947,6 +4985,7 @@
            AutofillMetrics::NON_FILLABLE_FORM_OR_NEW_DATA},
           {UkmSuggestionFilledType::kMillisecondsSinceFormParsedName, 0},
           {UkmFormSubmittedType::kIsForCreditCardName, true},
+          {UkmFormSubmittedType::kHasUpiVpaFieldName, false},
           {UkmFormSubmittedType::kFormTypesName,
            AutofillMetrics::FormTypesToBitVector({FormType::CREDIT_CARD_FORM})},
           {UkmFormSubmittedType::kFormSignatureName,
@@ -4955,6 +4994,7 @@
            AutofillMetrics::NON_FILLABLE_FORM_OR_NEW_DATA},
           {UkmSuggestionFilledType::kMillisecondsSinceFormParsedName, 0},
           {UkmFormSubmittedType::kIsForCreditCardName, true},
+          {UkmFormSubmittedType::kHasUpiVpaFieldName, false},
           {UkmFormSubmittedType::kFormTypesName,
            AutofillMetrics::FormTypesToBitVector({FormType::CREDIT_CARD_FORM})},
           {UkmFormSubmittedType::kFormSignatureName,
@@ -5125,6 +5165,7 @@
     VerifySubmitFormUkm(test_ukm_recorder_, form,
                         AutofillMetrics::NON_FILLABLE_FORM_OR_NEW_DATA,
                         /*is_for_credit_card=*/true,
+                        /* has_upi_vpa_field=*/false,
                         {FormType::CREDIT_CARD_FORM});
   }
 }
@@ -5889,7 +5930,8 @@
 
     VerifySubmitFormUkm(test_ukm_recorder_, form,
                         AutofillMetrics::NON_FILLABLE_FORM_OR_NEW_DATA,
-                        /*is_for_credit_card=*/false, {FormType::ADDRESS_FORM});
+                        /*is_for_credit_card=*/false,
+                        /* has_upi_vpa_field=*/false, {FormType::ADDRESS_FORM});
   }
 
   // Reset the autofill manager state and purge UKM logs.
@@ -5917,7 +5959,8 @@
 
     VerifySubmitFormUkm(test_ukm_recorder_, form,
                         AutofillMetrics::NON_FILLABLE_FORM_OR_NEW_DATA,
-                        /*is_for_credit_card=*/false, {FormType::ADDRESS_FORM});
+                        /*is_for_credit_card=*/false,
+                        /* has_upi_vpa_field=*/false, {FormType::ADDRESS_FORM});
   }
 
   // Reset the autofill manager state and purge UKM logs.
@@ -6482,6 +6525,7 @@
           AutofillMetrics::NON_FILLABLE_FORM_OR_NEW_DATA},
          {UkmSuggestionFilledType::kMillisecondsSinceFormParsedName, 0},
          {UkmFormSubmittedType::kIsForCreditCardName, false},
+         {UkmFormSubmittedType::kHasUpiVpaFieldName, false},
          {UkmFormSubmittedType::kFormTypesName,
           AutofillMetrics::FormTypesToBitVector(
               {FormType::ADDRESS_FORM, FormType::UNKNOWN_FORM_TYPE})},
@@ -6518,6 +6562,7 @@
           AutofillMetrics::NON_FILLABLE_FORM_OR_NEW_DATA},
          {UkmSuggestionFilledType::kMillisecondsSinceFormParsedName, 0},
          {UkmFormSubmittedType::kIsForCreditCardName, false},
+         {UkmFormSubmittedType::kHasUpiVpaFieldName, false},
          {UkmFormSubmittedType::kFormTypesName,
           AutofillMetrics::FormTypesToBitVector(
               {FormType::ADDRESS_FORM, FormType::UNKNOWN_FORM_TYPE})},
@@ -6558,6 +6603,7 @@
               FILLABLE_FORM_AUTOFILLED_NONE_DID_NOT_SHOW_SUGGESTIONS},
          {UkmSuggestionFilledType::kMillisecondsSinceFormParsedName, 0},
          {UkmFormSubmittedType::kIsForCreditCardName, false},
+         {UkmFormSubmittedType::kHasUpiVpaFieldName, false},
          {UkmFormSubmittedType::kFormTypesName,
           AutofillMetrics::FormTypesToBitVector(
               {FormType::ADDRESS_FORM, FormType::UNKNOWN_FORM_TYPE})},
@@ -6605,6 +6651,7 @@
           AutofillMetrics::FILLABLE_FORM_AUTOFILLED_NONE_DID_SHOW_SUGGESTIONS},
          {UkmSuggestionFilledType::kMillisecondsSinceFormParsedName, 0},
          {UkmFormSubmittedType::kIsForCreditCardName, false},
+         {UkmFormSubmittedType::kHasUpiVpaFieldName, false},
          {UkmFormSubmittedType::kFormTypesName,
           AutofillMetrics::FormTypesToBitVector(
               {FormType::ADDRESS_FORM, FormType::UNKNOWN_FORM_TYPE})},
@@ -6641,6 +6688,7 @@
           AutofillMetrics::FILLABLE_FORM_AUTOFILLED_SOME},
          {UkmSuggestionFilledType::kMillisecondsSinceFormParsedName, 0},
          {UkmFormSubmittedType::kIsForCreditCardName, false},
+         {UkmFormSubmittedType::kHasUpiVpaFieldName, false},
          {UkmFormSubmittedType::kFormTypesName,
           AutofillMetrics::FormTypesToBitVector(
               {FormType::ADDRESS_FORM, FormType::UNKNOWN_FORM_TYPE})},
@@ -6678,6 +6726,7 @@
           AutofillMetrics::FILLABLE_FORM_AUTOFILLED_ALL},
          {UkmSuggestionFilledType::kMillisecondsSinceFormParsedName, 0},
          {UkmFormSubmittedType::kIsForCreditCardName, false},
+         {UkmFormSubmittedType::kHasUpiVpaFieldName, false},
          {UkmFormSubmittedType::kFormTypesName,
           AutofillMetrics::FormTypesToBitVector(
               {FormType::ADDRESS_FORM, FormType::UNKNOWN_FORM_TYPE})},
@@ -6722,6 +6771,7 @@
           AutofillMetrics::NON_FILLABLE_FORM_OR_NEW_DATA},
          {UkmSuggestionFilledType::kMillisecondsSinceFormParsedName, 0},
          {UkmFormSubmittedType::kIsForCreditCardName, false},
+         {UkmFormSubmittedType::kHasUpiVpaFieldName, false},
          {UkmFormSubmittedType::kFormTypesName,
           AutofillMetrics::FormTypesToBitVector(
               {FormType::ADDRESS_FORM, FormType::UNKNOWN_FORM_TYPE})},
@@ -6797,6 +6847,7 @@
           AutofillMetrics::FILLABLE_FORM_AUTOFILLED_ALL},
          {UkmSuggestionFilledType::kMillisecondsSinceFormParsedName, 0},
          {UkmFormSubmittedType::kIsForCreditCardName, false},
+         {UkmFormSubmittedType::kHasUpiVpaFieldName, false},
          {UkmFormSubmittedType::kFormTypesName,
           AutofillMetrics::FormTypesToBitVector({FormType::ADDRESS_FORM})},
          {UkmFormSubmittedType::kFormSignatureName,
diff --git a/components/autofill/core/browser/credit_card_save_manager.h b/components/autofill/core/browser/credit_card_save_manager.h
index a1ae70e..1489879 100644
--- a/components/autofill/core/browser/credit_card_save_manager.h
+++ b/components/autofill/core/browser/credit_card_save_manager.h
@@ -124,6 +124,7 @@
  private:
   friend class CreditCardSaveManagerTest;
   friend class CreditCardSaveManagerTestObserverBridge;
+  friend class LocalCardMigrationBrowserTest;
   friend class TestCreditCardSaveManager;
   friend class SaveCardBubbleViewsFullFormBrowserTest;
 
diff --git a/components/autofill/core/browser/credit_card_save_strike_database_unittest.cc b/components/autofill/core/browser/credit_card_save_strike_database_unittest.cc
deleted file mode 100644
index 6628663..0000000
--- a/components/autofill/core/browser/credit_card_save_strike_database_unittest.cc
+++ /dev/null
@@ -1,138 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/autofill/core/browser/credit_card_save_strike_database.h"
-
-#include <utility>
-#include <vector>
-
-#include "base/files/scoped_temp_dir.h"
-#include "base/run_loop.h"
-#include "base/test/metrics/histogram_tester.h"
-#include "base/test/scoped_task_environment.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "components/autofill/core/browser/proto/strike_data.pb.h"
-#include "components/autofill/core/browser/test_autofill_clock.h"
-#include "components/autofill/core/browser/test_credit_card_save_strike_database.h"
-#include "components/autofill/core/common/autofill_clock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace autofill {
-
-class CreditCardSaveStrikeDatabaseTest : public ::testing::Test {
- public:
-  CreditCardSaveStrikeDatabaseTest()
-      : strike_database_(new StrikeDatabase(InitFilePath())) {}
-
- protected:
-  base::HistogramTester* GetHistogramTester() { return &histogram_tester_; }
-  base::test::ScopedTaskEnvironment scoped_task_environment_;
-  TestCreditCardSaveStrikeDatabase strike_database_;
-
- private:
-  static const base::FilePath InitFilePath() {
-    base::ScopedTempDir temp_dir_;
-    EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
-    const base::FilePath file_path =
-        temp_dir_.GetPath().AppendASCII("StrikeDatabaseTest");
-    return file_path;
-  }
-
-  base::HistogramTester histogram_tester_;
-};
-
-TEST_F(CreditCardSaveStrikeDatabaseTest, GetKeyForCreditCardSaveTest) {
-  const std::string last_four = "1234";
-  EXPECT_EQ("CreditCardSave__1234", strike_database_.GetKey(last_four));
-}
-
-TEST_F(CreditCardSaveStrikeDatabaseTest, MaxStrikesLimitReachedTest) {
-  const std::string last_four = "1234";
-  EXPECT_EQ(false, strike_database_.IsMaxStrikesLimitReached(last_four));
-  // 1st strike added for |last_four|.
-  strike_database_.AddStrike(last_four);
-  EXPECT_EQ(false, strike_database_.IsMaxStrikesLimitReached(last_four));
-  // 2nd strike added for |last_four|.
-  strike_database_.AddStrike(last_four);
-  EXPECT_EQ(false, strike_database_.IsMaxStrikesLimitReached(last_four));
-  // 3rd strike added for |last_four|.
-  strike_database_.AddStrike(last_four);
-  EXPECT_EQ(true, strike_database_.IsMaxStrikesLimitReached(last_four));
-}
-
-TEST_F(CreditCardSaveStrikeDatabaseTest,
-       CreditCardSaveNthStrikeAddedHistogram) {
-  const std::string last_four1 = "1234";
-  const std::string last_four2 = "9876";
-  // 1st strike added for |last_four1|.
-  strike_database_.AddStrike(last_four1);
-  // 2nd strike added for |last_four1|.
-  strike_database_.AddStrike(last_four1);
-  // 1st strike added for |last_four2|.
-  strike_database_.AddStrike(last_four2);
-  std::vector<base::Bucket> buckets = GetHistogramTester()->GetAllSamples(
-      "Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave");
-  // There should be two buckets, one for 1st strike, one for 2nd strike count.
-  ASSERT_EQ(2U, buckets.size());
-  // Both |last_four1| and |last_four2| have 1st strikes recorded.
-  EXPECT_EQ(2, buckets[0].count);
-  // Only |last_four1| has 2nd strike recorded.
-  EXPECT_EQ(1, buckets[1].count);
-}
-
-TEST_F(CreditCardSaveStrikeDatabaseTest,
-       AddStrikeForZeroAndNonZeroStrikesTest) {
-  const std::string last_four = "1234";
-  EXPECT_EQ(0, strike_database_.GetStrikes(last_four));
-  strike_database_.AddStrike(last_four);
-  EXPECT_EQ(1, strike_database_.GetStrikes(last_four));
-  strike_database_.AddStrike(last_four);
-  EXPECT_EQ(2, strike_database_.GetStrikes(last_four));
-}
-
-TEST_F(CreditCardSaveStrikeDatabaseTest, ClearStrikesForNonZeroStrikesTest) {
-  const std::string last_four = "1234";
-  strike_database_.AddStrike(last_four);
-  EXPECT_EQ(1, strike_database_.GetStrikes(last_four));
-  strike_database_.ClearStrikes(last_four);
-  EXPECT_EQ(0, strike_database_.GetStrikes(last_four));
-}
-
-TEST_F(CreditCardSaveStrikeDatabaseTest, ClearStrikesForZeroStrikesTest) {
-  const std::string last_four = "1234";
-  strike_database_.ClearStrikes(last_four);
-  EXPECT_EQ(0, strike_database_.GetStrikes(last_four));
-}
-
-TEST_F(CreditCardSaveStrikeDatabaseTest, RemoveExpiredStrikesTest) {
-  autofill::TestAutofillClock test_clock;
-  test_clock.SetNow(AutofillClock::Now());
-  const std::string last_four1 = "1234";
-  const std::string last_four2 = "9876";
-  strike_database_.AddStrike(last_four1);
-
-  // Advance clock to past the entry for |last_four1|'s expiry time.
-  test_clock.Advance(base::TimeDelta::FromMicroseconds(
-      strike_database_.GetExpiryTimeMicros() + 1));
-
-  strike_database_.AddStrike(last_four2);
-  strike_database_.RemoveExpiredStrikes();
-
-  // |last_four1|'s entry should have its most recent strike expire, but
-  // |last_four2|'s should not.
-  EXPECT_EQ(0, strike_database_.GetStrikes(last_four1));
-  EXPECT_EQ(1, strike_database_.GetStrikes(last_four2));
-
-  // Advance clock to past |last_four2|'s expiry time.
-  test_clock.Advance(base::TimeDelta::FromMicroseconds(
-      strike_database_.GetExpiryTimeMicros() + 1));
-
-  strike_database_.RemoveExpiredStrikes();
-
-  // |last_four1| and |last_four2| should have no more unexpired strikes.
-  EXPECT_EQ(0, strike_database_.GetStrikes(last_four1));
-  EXPECT_EQ(0, strike_database_.GetStrikes(last_four2));
-}
-
-}  // namespace autofill
diff --git a/components/autofill/core/browser/form_data_importer.h b/components/autofill/core/browser/form_data_importer.h
index 7fe9efed..56e32ab1 100644
--- a/components/autofill/core/browser/form_data_importer.h
+++ b/components/autofill/core/browser/form_data_importer.h
@@ -145,6 +145,7 @@
   friend class AutofillMergeTest;
   friend class FormDataImporterTest;
   friend class FormDataImporterTestBase;
+  friend class LocalCardMigrationBrowserTest;
   friend class SaveCardBubbleViewsFullFormBrowserTest;
   friend class SaveCardInfobarEGTestHelper;
   FRIEND_TEST_ALL_PREFIXES(AutofillMergeTest, MergeProfiles);
diff --git a/components/autofill/core/browser/form_structure.cc b/components/autofill/core/browser/form_structure.cc
index 006b157..c715abb 100644
--- a/components/autofill/core/browser/form_structure.cc
+++ b/components/autofill/core/browser/form_structure.cc
@@ -966,6 +966,7 @@
   bool did_autofill_all_possible_fields = true;
   bool did_autofill_some_possible_fields = false;
   bool is_for_credit_card = IsCompleteCreditCardForm();
+  bool has_upi_vpa_field = false;
 
   // Determine the correct suffix for the metric, depending on whether or
   // not a submission was observed.
@@ -976,6 +977,7 @@
   for (size_t i = 0; i < field_count(); ++i) {
     auto* const field = this->field(i);
     if (IsUPIVirtualPaymentAddress(field->value)) {
+      has_upi_vpa_field = true;
       AutofillMetrics::LogUserHappinessMetric(
           AutofillMetrics::USER_DID_ENTER_UPI_VPA, field->Type().group(),
           security_state::SecurityLevel::SECURITY_LEVEL_COUNT);
@@ -1060,8 +1062,8 @@
     }
 
     AutofillMetrics::LogAutofillFormSubmittedState(
-        state, is_for_credit_card, GetFormTypes(), form_parsed_timestamp_,
-        form_signature(), form_interactions_ukm_logger);
+        state, is_for_credit_card, has_upi_vpa_field, GetFormTypes(),
+        form_parsed_timestamp_, form_signature(), form_interactions_ukm_logger);
   }
 }
 
diff --git a/components/autofill/core/browser/local_card_migration_manager.cc b/components/autofill/core/browser/local_card_migration_manager.cc
index 764f915..bc9634e 100644
--- a/components/autofill/core/browser/local_card_migration_manager.cc
+++ b/components/autofill/core/browser/local_card_migration_manager.cc
@@ -88,6 +88,9 @@
     return;
   migration_request_ = payments::PaymentsClient::MigrationRequestDetails();
 
+  if (observer_for_testing_)
+    observer_for_testing_->OnDecideToRequestLocalCardMigration();
+
   payments_client_->GetUploadDetails(
       std::vector<AutofillProfile>(), GetDetectedValues(),
       /*active_experiments=*/std::vector<const char*>(), app_locale_,
@@ -145,15 +148,25 @@
   bool migration_experiment_enabled =
       features::GetLocalCardMigrationExperimentalFlag() !=
       features::LocalCardMigrationExperimentalFlag::kMigrationDisabled;
-  bool credit_card_upload_enabled = ::autofill::IsCreditCardUploadEnabled(
-      client_->GetPrefs(), client_->GetSyncService(),
-      client_->GetIdentityManager()->GetPrimaryAccountInfo().email);
+
+  // If |observer_for_testing_| is set, assume we are in a browsertest and
+  // credit card upload should be enabled by default. Cannot get around this as
+  // Chrome OS testing requires an unsupported email domain (i.e.
+  // stub-user@example.com).
+  bool credit_card_upload_enabled =
+      observer_for_testing_ ||
+      ::autofill::IsCreditCardUploadEnabled(
+          client_->GetPrefs(), client_->GetSyncService(),
+          client_->GetIdentityManager()->GetPrimaryAccountInfo().email);
+
   bool has_google_payments_account =
       (payments::GetBillingCustomerId(personal_data_manager_,
                                       payments_client_->GetPrefService()) != 0);
+
   bool sync_feature_enabled =
       (personal_data_manager_->GetSyncSigninState() ==
        AutofillSyncSigninState::kSignedInAndSyncFeature);
+
   return migration_experiment_enabled && credit_card_upload_enabled &&
          has_google_payments_account && sync_feature_enabled;
 }
@@ -163,6 +176,9 @@
     AutofillClient::PaymentsRpcResult result,
     const base::string16& context_token,
     std::unique_ptr<base::Value> legal_message) {
+  if (observer_for_testing_)
+    observer_for_testing_->OnReceivedGetUploadDetailsResponse();
+
   if (result == AutofillClient::SUCCESS) {
     migration_request_.context_token = context_token;
     legal_message_ = base::DictionaryValue::From(std::move(legal_message));
@@ -196,6 +212,9 @@
     AutofillClient::PaymentsRpcResult result,
     std::unique_ptr<std::unordered_map<std::string, std::string>> save_result,
     const std::string& display_text) {
+  if (observer_for_testing_)
+    observer_for_testing_->OnReceivedMigrateCardsResponse();
+
   if (!save_result)
     return;
 
@@ -254,6 +273,9 @@
 // Send the migration request. Will call payments_client to create a new
 // PaymentsRequest. Also create a new callback function OnDidMigrateLocalCards.
 void LocalCardMigrationManager::SendMigrateLocalCardsRequest() {
+  if (observer_for_testing_)
+    observer_for_testing_->OnSentMigrateCardsRequest();
+
   migration_request_.app_locale = app_locale_;
   migration_request_.billing_customer_number = payments::GetBillingCustomerId(
       personal_data_manager_, payments_client_->GetPrefService());
diff --git a/components/autofill/core/browser/local_card_migration_manager.h b/components/autofill/core/browser/local_card_migration_manager.h
index 06e5ce5..dc2fa329 100644
--- a/components/autofill/core/browser/local_card_migration_manager.h
+++ b/components/autofill/core/browser/local_card_migration_manager.h
@@ -43,7 +43,7 @@
     FAILURE_ON_UPLOAD,
   };
 
-  MigratableCreditCard(const CreditCard& credit_card);
+  explicit MigratableCreditCard(const CreditCard& credit_card);
   ~MigratableCreditCard();
 
   CreditCard credit_card() const { return credit_card_; }
@@ -66,6 +66,16 @@
 // Owned by FormDataImporter.
 class LocalCardMigrationManager {
  public:
+  // An observer class used by browsertests that gets notified whenever
+  // particular actions occur.
+  class ObserverForTest {
+   public:
+    virtual void OnDecideToRequestLocalCardMigration() = 0;
+    virtual void OnReceivedGetUploadDetailsResponse() = 0;
+    virtual void OnSentMigrateCardsRequest() = 0;
+    virtual void OnReceivedMigrateCardsResponse() = 0;
+  };
+
   // The parameters should outlive the LocalCardMigrationManager.
   LocalCardMigrationManager(AutofillClient* client,
                             payments::PaymentsClient* payments_client,
@@ -141,6 +151,7 @@
   payments::PaymentsClient* payments_client_;
 
  private:
+  friend class LocalCardMigrationBrowserTest;
   FRIEND_TEST_ALL_PREFIXES(LocalCardMigrationManagerTest,
                            MigrateCreditCard_MigrationPermanentFailure);
   FRIEND_TEST_ALL_PREFIXES(LocalCardMigrationManagerTest,
@@ -161,6 +172,11 @@
   // Finalizes the migration request and calls PaymentsClient.
   void SendMigrateLocalCardsRequest();
 
+  // For testing.
+  void SetEventObserverForTesting(ObserverForTest* observer) {
+    observer_for_testing_ = observer;
+  }
+
   std::unique_ptr<base::DictionaryValue> legal_message_;
 
   std::string app_locale_;
@@ -187,6 +203,9 @@
   // Record the triggering source of the local card migration.
   AutofillMetrics::LocalCardMigrationOrigin local_card_migration_origin_;
 
+  // Initialized only during tests.
+  ObserverForTest* observer_for_testing_ = nullptr;
+
   base::WeakPtrFactory<LocalCardMigrationManager> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(LocalCardMigrationManager);
diff --git a/components/autofill/core/browser/local_card_migration_strike_database_unittest.cc b/components/autofill/core/browser/local_card_migration_strike_database_unittest.cc
deleted file mode 100644
index c7d3ad0..0000000
--- a/components/autofill/core/browser/local_card_migration_strike_database_unittest.cc
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/autofill/core/browser/local_card_migration_strike_database.h"
-
-#include <utility>
-#include <vector>
-
-#include "base/files/scoped_temp_dir.h"
-#include "base/run_loop.h"
-#include "base/test/metrics/histogram_tester.h"
-#include "base/test/scoped_task_environment.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "components/autofill/core/browser/proto/strike_data.pb.h"
-#include "components/autofill/core/browser/test_autofill_clock.h"
-#include "components/autofill/core/browser/test_local_card_migration_strike_database.h"
-#include "components/autofill/core/common/autofill_clock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace autofill {
-
-class LocalCardMigrationStrikeDatabaseTest : public ::testing::Test {
- public:
-  LocalCardMigrationStrikeDatabaseTest()
-      : strike_database_(new StrikeDatabase(InitFilePath())) {}
-
- protected:
-  base::HistogramTester* GetHistogramTester() { return &histogram_tester_; }
-  base::test::ScopedTaskEnvironment scoped_task_environment_;
-  TestLocalCardMigrationStrikeDatabase strike_database_;
-
- private:
-  static const base::FilePath InitFilePath() {
-    base::ScopedTempDir temp_dir_;
-    EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
-    const base::FilePath file_path =
-        temp_dir_.GetPath().AppendASCII("StrikeDatabaseTest");
-    return file_path;
-  }
-
-  base::HistogramTester histogram_tester_;
-};
-
-TEST_F(LocalCardMigrationStrikeDatabaseTest, MaxStrikesLimitReachedTest) {
-  EXPECT_EQ(false, strike_database_.IsMaxStrikesLimitReached());
-  // 3 strikes added.
-  strike_database_.AddStrikes(3);
-  EXPECT_EQ(false, strike_database_.IsMaxStrikesLimitReached());
-  // 4 strike added, total strike count is 7.
-  strike_database_.AddStrikes(4);
-  EXPECT_EQ(true, strike_database_.IsMaxStrikesLimitReached());
-}
-
-TEST_F(LocalCardMigrationStrikeDatabaseTest,
-       LocalCardMigrationNthStrikeAddedHistogram) {
-  // 2 strikes logged.
-  strike_database_.AddStrikes(2);
-  strike_database_.RemoveStrikes(2);
-  // 1 strike logged.
-  strike_database_.AddStrike();
-  // 2 strikes logged.
-  strike_database_.AddStrike();
-  std::vector<base::Bucket> buckets = GetHistogramTester()->GetAllSamples(
-      "Autofill.StrikeDatabase.NthStrikeAdded.LocalCardMigration");
-  // There should be two buckets, for strike counts of 1 and 2.
-  ASSERT_EQ(2U, buckets.size());
-  // Bucket for 1 strike should have count of 1.
-  EXPECT_EQ(1, buckets[0].count);
-  // Bucket for 2 strikes should have count of 2.
-  EXPECT_EQ(2, buckets[1].count);
-}
-
-TEST_F(LocalCardMigrationStrikeDatabaseTest,
-       AddStrikeForZeroAndNonZeroStrikesTest) {
-  EXPECT_EQ(0, strike_database_.GetStrikes());
-  strike_database_.AddStrike();
-  EXPECT_EQ(1, strike_database_.GetStrikes());
-  strike_database_.AddStrikes(2);
-  EXPECT_EQ(3, strike_database_.GetStrikes());
-}
-
-TEST_F(LocalCardMigrationStrikeDatabaseTest,
-       ClearStrikesForNonZeroStrikesTest) {
-  strike_database_.AddStrikes(3);
-  EXPECT_EQ(3, strike_database_.GetStrikes());
-  strike_database_.ClearStrikes();
-  EXPECT_EQ(0, strike_database_.GetStrikes());
-}
-
-TEST_F(LocalCardMigrationStrikeDatabaseTest, ClearStrikesForZeroStrikesTest) {
-  strike_database_.ClearStrikes();
-  EXPECT_EQ(0, strike_database_.GetStrikes());
-}
-
-TEST_F(LocalCardMigrationStrikeDatabaseTest, RemoveExpiredStrikesTest) {
-  autofill::TestAutofillClock test_clock;
-  test_clock.SetNow(AutofillClock::Now());
-  strike_database_.AddStrikes(2);
-  EXPECT_EQ(2, strike_database_.GetStrikes());
-
-  // Advance clock to past expiry time.
-  test_clock.Advance(base::TimeDelta::FromMicroseconds(
-      strike_database_.GetExpiryTimeMicros() + 1));
-
-  // One strike should be removed.
-  strike_database_.RemoveExpiredStrikes();
-  EXPECT_EQ(1, strike_database_.GetStrikes());
-}
-
-}  // namespace autofill
diff --git a/components/autofill/core/browser/strike_database_integrator_base.cc b/components/autofill/core/browser/strike_database_integrator_base.cc
index f643819..b4f544e 100644
--- a/components/autofill/core/browser/strike_database_integrator_base.cc
+++ b/components/autofill/core/browser/strike_database_integrator_base.cc
@@ -4,6 +4,7 @@
 
 #include "components/autofill/core/browser/strike_database_integrator_base.h"
 
+#include <algorithm>
 #include <string>
 #include <utility>
 #include <vector>
@@ -81,8 +82,15 @@
         expired_keys.push_back(entry.first);
     }
   }
-  for (std::string key : expired_keys)
-    strike_database_->RemoveStrikes(1, key);
+  for (std::string key : expired_keys) {
+    int strikes_to_remove = 1;
+    // If the key is already over the limit, remove additional strikes to
+    // emulate setting it back to the limit. These are done together to avoid
+    // multiple calls to the file system ProtoDatabase.
+    strikes_to_remove +=
+        std::max(0, strike_database_->GetStrikes(key) - GetMaxStrikesLimit());
+    strike_database_->RemoveStrikes(strikes_to_remove, key);
+  }
 }
 
 std::string StrikeDatabaseIntegratorBase::GetKey(const std::string id) {
diff --git a/components/autofill/core/browser/strike_database_integrator_base.h b/components/autofill/core/browser/strike_database_integrator_base.h
index 9f207da..7996986 100644
--- a/components/autofill/core/browser/strike_database_integrator_base.h
+++ b/components/autofill/core/browser/strike_database_integrator_base.h
@@ -56,14 +56,12 @@
  private:
   FRIEND_TEST_ALL_PREFIXES(ChromeBrowsingDataRemoverDelegateTest,
                            StrikeDatabaseEmptyOnAutofillRemoveEverything);
-  FRIEND_TEST_ALL_PREFIXES(CreditCardSaveStrikeDatabaseTest,
-                           GetKeyForCreditCardSaveTest);
-  FRIEND_TEST_ALL_PREFIXES(CreditCardSaveStrikeDatabaseTest,
-                           GetIdForCreditCardSaveTest);
-  FRIEND_TEST_ALL_PREFIXES(CreditCardSaveStrikeDatabaseTest,
+  FRIEND_TEST_ALL_PREFIXES(StrikeDatabaseIntegratorTestStrikeDatabaseTest,
                            RemoveExpiredStrikesTest);
-  FRIEND_TEST_ALL_PREFIXES(LocalCardMigrationStrikeDatabaseTest,
-                           RemoveExpiredStrikesTest);
+  FRIEND_TEST_ALL_PREFIXES(StrikeDatabaseIntegratorTestStrikeDatabaseTest,
+                           GetKeyForStrikeDatabaseIntegratorUniqueIdTest);
+  FRIEND_TEST_ALL_PREFIXES(StrikeDatabaseIntegratorTestStrikeDatabaseTest,
+                           RemoveExpiredStrikesUniqueIdTest);
   friend class StrikeDatabaseTest;
   friend class StrikeDatabaseTester;
 
diff --git a/components/autofill/core/browser/strike_database_integrator_test_strike_database.cc b/components/autofill/core/browser/strike_database_integrator_test_strike_database.cc
new file mode 100644
index 0000000..c5be513
--- /dev/null
+++ b/components/autofill/core/browser/strike_database_integrator_test_strike_database.cc
@@ -0,0 +1,46 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill/core/browser/strike_database_integrator_test_strike_database.h"
+
+#include "components/autofill/core/browser/proto/strike_data.pb.h"
+
+namespace autofill {
+
+const char kProjectPrefix[] = "StrikeDatabaseIntegratorTest";
+const int kMaxStrikesLimit = 6;
+// Expiry time is 1 year.
+const long long kExpiryTimeMicros = (long long)1000000 * 60 * 60 * 24 * 365;
+
+StrikeDatabaseIntegratorTestStrikeDatabase::
+    StrikeDatabaseIntegratorTestStrikeDatabase(StrikeDatabase* strike_database)
+    : StrikeDatabaseIntegratorBase(strike_database) {
+  RemoveExpiredStrikes();
+}
+
+StrikeDatabaseIntegratorTestStrikeDatabase::
+    ~StrikeDatabaseIntegratorTestStrikeDatabase() {}
+
+std::string StrikeDatabaseIntegratorTestStrikeDatabase::GetProjectPrefix() {
+  return kProjectPrefix;
+}
+
+int StrikeDatabaseIntegratorTestStrikeDatabase::GetMaxStrikesLimit() {
+  return kMaxStrikesLimit;
+}
+
+long long StrikeDatabaseIntegratorTestStrikeDatabase::GetExpiryTimeMicros() {
+  return kExpiryTimeMicros;
+}
+
+bool StrikeDatabaseIntegratorTestStrikeDatabase::UniqueIdsRequired() {
+  return unique_ids_required_;
+}
+
+void StrikeDatabaseIntegratorTestStrikeDatabase::SetUniqueIdsRequired(
+    bool unique_ids_required) {
+  unique_ids_required_ = unique_ids_required;
+}
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/strike_database_integrator_test_strike_database.h b/components/autofill/core/browser/strike_database_integrator_test_strike_database.h
new file mode 100644
index 0000000..3a50f6e
--- /dev/null
+++ b/components/autofill/core/browser/strike_database_integrator_test_strike_database.h
@@ -0,0 +1,36 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_STRIKE_DATABASE_INTEGRATOR_TEST_STRIKE_DATABASE_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_STRIKE_DATABASE_INTEGRATOR_TEST_STRIKE_DATABASE_H_
+
+#include <string>
+
+#include "components/autofill/core/browser/strike_database.h"
+#include "components/autofill/core/browser/strike_database_integrator_base.h"
+
+namespace autofill {
+
+// Mock per-project implementation of StrikeDatabase to test the functions in
+// StrikeDatabaseIntegrator.
+class StrikeDatabaseIntegratorTestStrikeDatabase
+    : public StrikeDatabaseIntegratorBase {
+ public:
+  StrikeDatabaseIntegratorTestStrikeDatabase(StrikeDatabase* strike_database);
+  ~StrikeDatabaseIntegratorTestStrikeDatabase() override;
+
+  std::string GetProjectPrefix() override;
+  int GetMaxStrikesLimit() override;
+  long long GetExpiryTimeMicros() override;
+  bool UniqueIdsRequired() override;
+
+  void SetUniqueIdsRequired(bool unique_ids_required);
+
+ private:
+  bool unique_ids_required_ = false;
+};
+
+}  // namespace autofill
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_STRIKE_DATABASE_INTEGRATOR_TEST_STRIKE_DATABASE_H_
diff --git a/components/autofill/core/browser/strike_database_integrator_test_strike_database_unittest.cc b/components/autofill/core/browser/strike_database_integrator_test_strike_database_unittest.cc
new file mode 100644
index 0000000..d183b93b
--- /dev/null
+++ b/components/autofill/core/browser/strike_database_integrator_test_strike_database_unittest.cc
@@ -0,0 +1,229 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill/core/browser/strike_database_integrator_test_strike_database.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/autofill/core/browser/proto/strike_data.pb.h"
+#include "components/autofill/core/browser/test_autofill_clock.h"
+#include "components/autofill/core/common/autofill_clock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace autofill {
+
+class StrikeDatabaseIntegratorTestStrikeDatabaseTest : public ::testing::Test {
+ public:
+  StrikeDatabaseIntegratorTestStrikeDatabaseTest()
+      : strike_database_(new StrikeDatabase(InitFilePath())) {}
+
+ protected:
+  base::HistogramTester* GetHistogramTester() { return &histogram_tester_; }
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  StrikeDatabaseIntegratorTestStrikeDatabase strike_database_;
+
+ private:
+  static const base::FilePath InitFilePath() {
+    base::ScopedTempDir temp_dir_;
+    EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
+    const base::FilePath file_path =
+        temp_dir_.GetPath().AppendASCII("StrikeDatabaseTest");
+    return file_path;
+  }
+
+  base::HistogramTester histogram_tester_;
+};
+
+TEST_F(StrikeDatabaseIntegratorTestStrikeDatabaseTest,
+       MaxStrikesLimitReachedTest) {
+  EXPECT_EQ(false, strike_database_.IsMaxStrikesLimitReached());
+  // 3 strikes added.
+  strike_database_.AddStrikes(3);
+  EXPECT_EQ(false, strike_database_.IsMaxStrikesLimitReached());
+  // 4 strike added, total strike count is 7.
+  strike_database_.AddStrikes(4);
+  EXPECT_EQ(true, strike_database_.IsMaxStrikesLimitReached());
+}
+
+TEST_F(StrikeDatabaseIntegratorTestStrikeDatabaseTest,
+       StrikeDatabaseIntegratorTestNthStrikeAddedHistogram) {
+  // 2 strikes logged.
+  strike_database_.AddStrikes(2);
+  strike_database_.RemoveStrikes(2);
+  // 1 strike logged.
+  strike_database_.AddStrike();
+  // 2 strikes logged.
+  strike_database_.AddStrike();
+  std::vector<base::Bucket> buckets = GetHistogramTester()->GetAllSamples(
+      "Autofill.StrikeDatabase.NthStrikeAdded.StrikeDatabaseIntegratorTest");
+  // There should be two buckets, for strike counts of 1 and 2.
+  ASSERT_EQ(2U, buckets.size());
+  // Bucket for 1 strike should have count of 1.
+  EXPECT_EQ(1, buckets[0].count);
+  // Bucket for 2 strikes should have count of 2.
+  EXPECT_EQ(2, buckets[1].count);
+}
+
+TEST_F(StrikeDatabaseIntegratorTestStrikeDatabaseTest,
+       AddStrikeForZeroAndNonZeroStrikesTest) {
+  EXPECT_EQ(0, strike_database_.GetStrikes());
+  strike_database_.AddStrike();
+  EXPECT_EQ(1, strike_database_.GetStrikes());
+  strike_database_.AddStrikes(2);
+  EXPECT_EQ(3, strike_database_.GetStrikes());
+}
+
+TEST_F(StrikeDatabaseIntegratorTestStrikeDatabaseTest,
+       ClearStrikesForNonZeroStrikesTest) {
+  strike_database_.AddStrikes(3);
+  EXPECT_EQ(3, strike_database_.GetStrikes());
+  strike_database_.ClearStrikes();
+  EXPECT_EQ(0, strike_database_.GetStrikes());
+}
+
+TEST_F(StrikeDatabaseIntegratorTestStrikeDatabaseTest,
+       ClearStrikesForZeroStrikesTest) {
+  strike_database_.ClearStrikes();
+  EXPECT_EQ(0, strike_database_.GetStrikes());
+}
+
+TEST_F(StrikeDatabaseIntegratorTestStrikeDatabaseTest,
+       RemoveExpiredStrikesTest) {
+  autofill::TestAutofillClock test_clock;
+  test_clock.SetNow(AutofillClock::Now());
+  strike_database_.AddStrikes(2);
+  EXPECT_EQ(2, strike_database_.GetStrikes());
+
+  // Advance clock to past expiry time.
+  test_clock.Advance(base::TimeDelta::FromMicroseconds(
+      strike_database_.GetExpiryTimeMicros() + 1));
+
+  // One strike should be removed.
+  strike_database_.RemoveExpiredStrikes();
+  EXPECT_EQ(1, strike_database_.GetStrikes());
+
+  // Strike count is past the max limit.
+  strike_database_.AddStrikes(10);
+  EXPECT_EQ(11, strike_database_.GetStrikes());
+
+  // Advance clock to past expiry time.
+  test_clock.Advance(base::TimeDelta::FromMicroseconds(
+      strike_database_.GetExpiryTimeMicros() + 1));
+
+  // Strike count should be one less than the max limit.
+  strike_database_.RemoveExpiredStrikes();
+  EXPECT_EQ(5, strike_database_.GetStrikes());
+}
+
+TEST_F(StrikeDatabaseIntegratorTestStrikeDatabaseTest,
+       GetKeyForStrikeDatabaseIntegratorUniqueIdTest) {
+  strike_database_.SetUniqueIdsRequired(true);
+  const std::string unique_id = "1234";
+  EXPECT_EQ("StrikeDatabaseIntegratorTest__1234",
+            strike_database_.GetKey(unique_id));
+}
+
+TEST_F(StrikeDatabaseIntegratorTestStrikeDatabaseTest,
+       MaxStrikesLimitReachedUniqueIdTest) {
+  strike_database_.SetUniqueIdsRequired(true);
+  const std::string unique_id = "1234";
+  EXPECT_EQ(false, strike_database_.IsMaxStrikesLimitReached(unique_id));
+  // 1 strike added for |unique_id|.
+  strike_database_.AddStrike(unique_id);
+  EXPECT_EQ(false, strike_database_.IsMaxStrikesLimitReached(unique_id));
+  // 6 strikes added for |unique_id|.
+  strike_database_.AddStrikes(6, unique_id);
+  EXPECT_EQ(true, strike_database_.IsMaxStrikesLimitReached(unique_id));
+}
+
+TEST_F(StrikeDatabaseIntegratorTestStrikeDatabaseTest,
+       StrikeDatabaseIntegratorUniqueIdTestNthStrikeAddedHistogram) {
+  strike_database_.SetUniqueIdsRequired(true);
+  const std::string unique_id_1 = "1234";
+  const std::string unique_id_2 = "9876";
+  // 1st strike added for |unique_id_1|.
+  strike_database_.AddStrike(unique_id_1);
+  // 2nd strike added for |unique_id_1|.
+  strike_database_.AddStrike(unique_id_1);
+  // 1st strike added for |unique_id_2|.
+  strike_database_.AddStrike(unique_id_2);
+  std::vector<base::Bucket> buckets = GetHistogramTester()->GetAllSamples(
+      "Autofill.StrikeDatabase.NthStrikeAdded."
+      "StrikeDatabaseIntegratorTest");
+  // There should be two buckets, one for 1st strike, one for 2nd strike count.
+  ASSERT_EQ(2U, buckets.size());
+  // Both |unique_id_1| and |unique_id_2| have 1st strikes recorded.
+  EXPECT_EQ(2, buckets[0].count);
+  // Only |unique_id_1| has 2nd strike recorded.
+  EXPECT_EQ(1, buckets[1].count);
+}
+
+TEST_F(StrikeDatabaseIntegratorTestStrikeDatabaseTest,
+       AddStrikeForZeroAndNonZeroStrikesUniqueIdTest) {
+  strike_database_.SetUniqueIdsRequired(true);
+  const std::string unique_id = "1234";
+  EXPECT_EQ(0, strike_database_.GetStrikes(unique_id));
+  strike_database_.AddStrike(unique_id);
+  EXPECT_EQ(1, strike_database_.GetStrikes(unique_id));
+  strike_database_.AddStrike(unique_id);
+  EXPECT_EQ(2, strike_database_.GetStrikes(unique_id));
+}
+
+TEST_F(StrikeDatabaseIntegratorTestStrikeDatabaseTest,
+       ClearStrikesForNonZeroStrikesUniqueIdTest) {
+  strike_database_.SetUniqueIdsRequired(true);
+  const std::string unique_id = "1234";
+  strike_database_.AddStrike(unique_id);
+  EXPECT_EQ(1, strike_database_.GetStrikes(unique_id));
+  strike_database_.ClearStrikes(unique_id);
+  EXPECT_EQ(0, strike_database_.GetStrikes(unique_id));
+}
+
+TEST_F(StrikeDatabaseIntegratorTestStrikeDatabaseTest,
+       ClearStrikesForZeroStrikesUniqueIdTest) {
+  strike_database_.SetUniqueIdsRequired(true);
+  const std::string unique_id = "1234";
+  strike_database_.ClearStrikes(unique_id);
+  EXPECT_EQ(0, strike_database_.GetStrikes(unique_id));
+}
+
+TEST_F(StrikeDatabaseIntegratorTestStrikeDatabaseTest,
+       RemoveExpiredStrikesUniqueIdTest) {
+  strike_database_.SetUniqueIdsRequired(true);
+  autofill::TestAutofillClock test_clock;
+  test_clock.SetNow(AutofillClock::Now());
+  const std::string unique_id_1 = "1234";
+  const std::string unique_id_2 = "9876";
+  strike_database_.AddStrike(unique_id_1);
+
+  // Advance clock to past the entry for |unique_id_1|'s expiry time.
+  test_clock.Advance(base::TimeDelta::FromMicroseconds(
+      strike_database_.GetExpiryTimeMicros() + 1));
+
+  strike_database_.AddStrike(unique_id_2);
+  strike_database_.RemoveExpiredStrikes();
+
+  // |unique_id_1|'s entry should have its most recent strike expire, but
+  // |unique_id_2|'s should not.
+  EXPECT_EQ(0, strike_database_.GetStrikes(unique_id_1));
+  EXPECT_EQ(1, strike_database_.GetStrikes(unique_id_2));
+
+  // Advance clock to past |unique_id_2|'s expiry time.
+  test_clock.Advance(base::TimeDelta::FromMicroseconds(
+      strike_database_.GetExpiryTimeMicros() + 1));
+
+  strike_database_.RemoveExpiredStrikes();
+
+  // |unique_id_1| and |unique_id_2| should have no more unexpired strikes.
+  EXPECT_EQ(0, strike_database_.GetStrikes(unique_id_1));
+  EXPECT_EQ(0, strike_database_.GetStrikes(unique_id_2));
+}
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/test_local_card_migration_strike_database.cc b/components/autofill/core/browser/test_local_card_migration_strike_database.cc
deleted file mode 100644
index cc212712..0000000
--- a/components/autofill/core/browser/test_local_card_migration_strike_database.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/autofill/core/browser/test_local_card_migration_strike_database.h"
-
-namespace autofill {
-
-TestLocalCardMigrationStrikeDatabase::TestLocalCardMigrationStrikeDatabase(
-    StrikeDatabase* strike_database)
-    : LocalCardMigrationStrikeDatabase(strike_database) {}
-
-}  // namespace autofill
diff --git a/components/autofill/core/browser/test_local_card_migration_strike_database.h b/components/autofill/core/browser/test_local_card_migration_strike_database.h
deleted file mode 100644
index c1cac18..0000000
--- a/components/autofill/core/browser/test_local_card_migration_strike_database.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_LOCAL_CARD_MIGRATION_STRIKE_DATABASE_H_
-#define COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_LOCAL_CARD_MIGRATION_STRIKE_DATABASE_H_
-
-#include "components/autofill/core/browser/local_card_migration_strike_database.h"
-
-namespace autofill {
-
-class TestLocalCardMigrationStrikeDatabase
-    : public LocalCardMigrationStrikeDatabase {
- public:
-  TestLocalCardMigrationStrikeDatabase(StrikeDatabase* strike_database);
-};
-
-}  // namespace autofill
-
-#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_LOCAL_CARD_MIGRATION_STRIKE_DATABASE_H_
diff --git a/components/content_settings/core/browser/content_settings_registry.cc b/components/content_settings/core/browser/content_settings_registry.cc
index 912fdeba2..03d0c89f 100644
--- a/components/content_settings/core/browser/content_settings_registry.cc
+++ b/components/content_settings/core/browser/content_settings_registry.cc
@@ -440,6 +440,16 @@
            ContentSettingsInfo::INHERIT_IF_LESS_PERMISSIVE,
            ContentSettingsInfo::PERSISTENT,
            ContentSettingsInfo::EXCEPTIONS_ON_SECURE_ORIGINS_ONLY);
+
+  Register(CONTENT_SETTINGS_TYPE_SERIAL_GUARD, "serial-guard",
+           CONTENT_SETTING_ASK, WebsiteSettingsInfo::UNSYNCABLE,
+           WhitelistedSchemes(),
+           ValidSettings(CONTENT_SETTING_ASK, CONTENT_SETTING_BLOCK),
+           WebsiteSettingsInfo::SINGLE_ORIGIN_WITH_EMBEDDED_EXCEPTIONS_SCOPE,
+           WebsiteSettingsRegistry::DESKTOP,
+           ContentSettingsInfo::INHERIT_IF_LESS_PERMISSIVE,
+           ContentSettingsInfo::PERSISTENT,
+           ContentSettingsInfo::EXCEPTIONS_ON_SECURE_ORIGINS_ONLY);
 }
 
 void ContentSettingsRegistry::Register(
diff --git a/components/content_settings/core/browser/website_settings_registry.cc b/components/content_settings/core/browser/website_settings_registry.cc
index 052b8db..085efe9 100644
--- a/components/content_settings/core/browser/website_settings_registry.cc
+++ b/components/content_settings/core/browser/website_settings_registry.cc
@@ -194,6 +194,11 @@
            WebsiteSettingsInfo::UNSYNCABLE, WebsiteSettingsInfo::NOT_LOSSY,
            WebsiteSettingsInfo::SINGLE_ORIGIN_WITH_EMBEDDED_EXCEPTIONS_SCOPE,
            DESKTOP, WebsiteSettingsInfo::DONT_INHERIT_IN_INCOGNITO);
+  Register(CONTENT_SETTINGS_TYPE_SERIAL_CHOOSER_DATA, "serial-chooser-data",
+           nullptr, WebsiteSettingsInfo::UNSYNCABLE,
+           WebsiteSettingsInfo::NOT_LOSSY,
+           WebsiteSettingsInfo::REQUESTING_ORIGIN_AND_TOP_LEVEL_ORIGIN_SCOPE,
+           DESKTOP, WebsiteSettingsInfo::DONT_INHERIT_IN_INCOGNITO);
 }
 
 }  // namespace content_settings
diff --git a/components/content_settings/core/common/content_settings.cc b/components/content_settings/core/common/content_settings.cc
index 28e51c0..cac5b0c 100644
--- a/components/content_settings/core/common/content_settings.cc
+++ b/components/content_settings/core/common/content_settings.cc
@@ -29,7 +29,7 @@
 // content settings type name instead.
 //
 // The array size must be explicit for the static_asserts below.
-constexpr size_t kNumHistogramValues = 41;
+constexpr size_t kNumHistogramValues = 43;
 constexpr HistogramValue kHistogramValue[kNumHistogramValues] = {
     {CONTENT_SETTINGS_TYPE_COOKIES, 0},
     {CONTENT_SETTINGS_TYPE_IMAGES, 1},
@@ -72,6 +72,8 @@
     {CONTENT_SETTINGS_TYPE_BACKGROUND_FETCH, 45},
     {CONTENT_SETTINGS_TYPE_INTENT_PICKER_DISPLAY, 46},
     {CONTENT_SETTINGS_TYPE_IDLE_DETECTION, 47},
+    {CONTENT_SETTINGS_TYPE_SERIAL_GUARD, 48},
+    {CONTENT_SETTINGS_TYPE_SERIAL_CHOOSER_DATA, 49},
 };
 
 }  // namespace
diff --git a/components/content_settings/core/common/content_settings_types.h b/components/content_settings/core/common/content_settings_types.h
index 17b6ab6..338fdd7e 100644
--- a/components/content_settings/core/common/content_settings_types.h
+++ b/components/content_settings/core/common/content_settings_types.h
@@ -129,6 +129,13 @@
   // Used to store whether to allow a website to detect user active/idle state.
   CONTENT_SETTINGS_TYPE_IDLE_DETECTION,
 
+  // Content settings for access to serial ports. The "guard" content setting
+  // stores whether to allow sites to ask for permission to access a port. The
+  // permissions granted to access particular ports are stored in the "chooser
+  // data" website setting.
+  CONTENT_SETTINGS_TYPE_SERIAL_GUARD,
+  CONTENT_SETTINGS_TYPE_SERIAL_CHOOSER_DATA,
+
   CONTENT_SETTINGS_NUM_TYPES,
 };
 
diff --git a/components/cronet/BUILD.gn b/components/cronet/BUILD.gn
index 794bad1..df22474f 100644
--- a/components/cronet/BUILD.gn
+++ b/components/cronet/BUILD.gn
@@ -217,7 +217,7 @@
   # Copy boiler-plate files into the package.
   copy("cronet_package_copy") {
     sources = [
-      "$root_out_dir/$_cronet_shared_lib_file_name",
+      "${root_out_dir}${shlib_subdir}/${_cronet_shared_lib_file_name}",
       "//AUTHORS",
       "//chrome/VERSION",
     ]
diff --git a/components/download/internal/common/download_stats.cc b/components/download/internal/common/download_stats.cc
index 11ac2934..3046017 100644
--- a/components/download/internal/common/download_stats.cc
+++ b/components/download/internal/common/download_stats.cc
@@ -404,6 +404,26 @@
     FILE_PATH_LITERAL(".pyo"),      // 319
     FILE_PATH_LITERAL(".desktop"),  // 320
     FILE_PATH_LITERAL(".cpi"),      // 321
+    FILE_PATH_LITERAL(".jpg"),      // 322
+    FILE_PATH_LITERAL(".jpeg"),     // 323
+    FILE_PATH_LITERAL(".mp3"),      // 324
+    FILE_PATH_LITERAL(".mp4"),      // 325
+    FILE_PATH_LITERAL(".png"),      // 326
+    FILE_PATH_LITERAL(".xls"),      // 327
+    FILE_PATH_LITERAL(".doc"),      // 328
+    FILE_PATH_LITERAL(".pptx"),     // 329
+    FILE_PATH_LITERAL(".csv"),      // 330
+    FILE_PATH_LITERAL(".ica"),      // 331
+    FILE_PATH_LITERAL(".ppt"),      // 332
+    FILE_PATH_LITERAL(".gif"),      // 333
+    FILE_PATH_LITERAL(".txt"),      // 334
+    FILE_PATH_LITERAL(".package"),  // 335
+    FILE_PATH_LITERAL(".tif"),      // 336
+    FILE_PATH_LITERAL(".rtf"),      // 337
+    FILE_PATH_LITERAL(".webp"),     // 338
+    FILE_PATH_LITERAL(".mkv"),      // 339
+    FILE_PATH_LITERAL(".wav"),      // 340
+    FILE_PATH_LITERAL(".mov"),      // 341
     // NOTE! When you add a type here, please add the UMA value as a comment.
     // These must all match DownloadItem.DangerousFileType in
     // enums.xml. From 263 onward, they should also match
diff --git a/components/flags_ui/resources/flags.js b/components/flags_ui/resources/flags.js
index ba4750f..dea68e8 100644
--- a/components/flags_ui/resources/flags.js
+++ b/components/flags_ui/resources/flags.js
@@ -44,9 +44,10 @@
     };
   }
 
-  elements = document.getElementsByClassName('experiment-restart-button');
-  for (var i = 0; i < elements.length; ++i) {
-    elements[i].onclick = restartBrowser;
+  var element = $('experiment-restart-button');
+  assert(element || cr.isIOS);
+  if (element) {
+    element.onclick = restartBrowser;
   }
 
   // Tab panel selection.
diff --git a/components/omnibox/browser/document_provider.cc b/components/omnibox/browser/document_provider.cc
index e33a769..d5424709 100644
--- a/components/omnibox/browser/document_provider.cc
+++ b/components/omnibox/browser/document_provider.cc
@@ -14,6 +14,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/feature_list.h"
+#include "base/i18n/case_conversion.h"
 #include "base/i18n/time_formatting.h"
 #include "base/json/json_reader.h"
 #include "base/metrics/field_trial_params.h"
@@ -24,14 +25,18 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
+#include "components/bookmarks/browser/bookmark_utils.h"
 #include "components/data_use_measurement/core/data_use_user_data.h"
 #include "components/omnibox/browser/autocomplete_input.h"
 #include "components/omnibox/browser/autocomplete_match.h"
 #include "components/omnibox/browser/autocomplete_provider_client.h"
 #include "components/omnibox/browser/autocomplete_provider_listener.h"
 #include "components/omnibox/browser/document_suggestions_service.h"
+#include "components/omnibox/browser/history_provider.h"
+#include "components/omnibox/browser/in_memory_url_index_types.h"
 #include "components/omnibox/browser/omnibox_field_trial.h"
 #include "components/omnibox/browser/omnibox_pref_names.h"
+#include "components/omnibox/browser/scored_history_match.h"
 #include "components/omnibox/browser/search_provider.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_service.h"
@@ -208,6 +213,8 @@
 
   Stop(true, false);
 
+  input_ = input;
+
   // Create a request for suggestions, routing completion to
   base::BindOnce(&DocumentProvider::OnDocumentSuggestionsLoaderAvailable,
                  weak_ptr_factory_.GetWeakPtr()),
@@ -458,8 +465,7 @@
       match.stripped_destination_url = GURL(original_url);
     }
     match.contents = AutocompleteMatch::SanitizeString(title);
-    AutocompleteMatch::AddLastClassificationIfNecessary(
-        &match.contents_class, 0, ACMatchClassification::NONE);
+    match.contents_class = Classify(match.contents, input_.text());
     const base::DictionaryValue* metadata = nullptr;
     if (result->GetDictionary("metadata", &metadata)) {
       if (metadata->GetString("mimeType", &mimetype)) {
@@ -497,3 +503,32 @@
   }
   return true;
 }
+
+// static
+ACMatchClassifications DocumentProvider::Classify(
+    const base::string16& text,
+    const base::string16& input_text) {
+  base::string16 clean_text = bookmarks::CleanUpTitleForMatching(text);
+  base::string16 lower_input_text(base::i18n::ToLower(input_text));
+  String16Vector input_terms =
+      base::SplitString(lower_input_text, base::kWhitespaceUTF16,
+                        base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+  TermMatches matches;
+  for (size_t i = 0; i < input_terms.size(); ++i) {
+    TermMatches term_matches = MatchTermInString(input_terms[i], clean_text, i);
+    matches.insert(matches.end(), term_matches.begin(), term_matches.end());
+  }
+  matches = SortMatches(matches);
+  matches = DeoverlapMatches(matches);
+
+  WordStarts word_starts;
+  String16VectorFromString16(clean_text, false, &word_starts);
+
+  WordStarts terms_to_word_starts_offsets(input_terms.size(), 0);
+  matches = ScoredHistoryMatch::FilterTermMatchesByWordStarts(
+      matches, terms_to_word_starts_offsets, word_starts, 0, std::string::npos);
+
+  return HistoryProvider::SpansFromTermMatch(matches, clean_text.length(),
+                                             false);
+}
diff --git a/components/omnibox/browser/document_provider.h b/components/omnibox/browser/document_provider.h
index 2e8fbe1..711a5e1f 100644
--- a/components/omnibox/browser/document_provider.h
+++ b/components/omnibox/browser/document_provider.h
@@ -119,6 +119,18 @@
       const std::string& modified_timestamp_string,
       base::Time now);
 
+  // Returns a set of classifications that highlight all the occurrences of
+  // |input_text| at word breaks in |text|. E.g., given |input_text|
+  // "rain if you dare" and |text| "how to tell if your kitten is a rainbow",
+  // will return the classifications:
+  //             __ ___              ____
+  // how to tell if your kitten is a rainbow
+  // ^           ^ ^^   ^            ^  ^
+  // NONE        M |M   |            |  NONE
+  //               NONE NONE         MATCH
+  static ACMatchClassifications Classify(const base::string16& input_text,
+                                         const base::string16& text);
+
   // Whether a field trial has triggered for this query and this session,
   // respectively. Works similarly to BaseSearchProvider, though this class does
   // not inherit from it.
@@ -135,6 +147,10 @@
   // Listener to notify when results are available.
   AutocompleteProviderListener* listener_;
 
+  // Saved when starting a new autocomplete request so that it can be retrieved
+  // when responses return asynchronously.
+  AutocompleteInput input_;
+
   // Loader used to retrieve results.
   std::unique_ptr<network::SimpleURLLoader> loader_;
 
diff --git a/components/omnibox/browser/history_provider.cc b/components/omnibox/browser/history_provider.cc
index d4b3528..12524533 100644
--- a/components/omnibox/browser/history_provider.cc
+++ b/components/omnibox/browser/history_provider.cc
@@ -39,10 +39,41 @@
       (!input.text().empty() && base::IsUnicodeWhitespace(input.text().back()));
 }
 
+// static
+ACMatchClassifications HistoryProvider::SpansFromTermMatch(
+    const TermMatches& matches,
+    size_t text_length,
+    bool is_url) {
+  ACMatchClassification::Style url_style =
+      is_url ? ACMatchClassification::URL : ACMatchClassification::NONE;
+  ACMatchClassifications spans;
+  if (matches.empty()) {
+    if (text_length)
+      spans.push_back(ACMatchClassification(0, url_style));
+    return spans;
+  }
+  if (matches[0].offset)
+    spans.push_back(ACMatchClassification(0, url_style));
+  size_t match_count = matches.size();
+  for (size_t i = 0; i < match_count;) {
+    size_t offset = matches[i].offset;
+    spans.push_back(ACMatchClassification(
+        offset, ACMatchClassification::MATCH | url_style));
+    // Skip all adjacent matches.
+    do {
+      offset += matches[i].length;
+      ++i;
+    } while ((i < match_count) && (offset == matches[i].offset));
+    if (offset < text_length)
+      spans.push_back(ACMatchClassification(offset, url_style));
+  }
+
+  return spans;
+}
+
 HistoryProvider::HistoryProvider(AutocompleteProvider::Type type,
                                  AutocompleteProviderClient* client)
-    : AutocompleteProvider(type), client_(client) {
-}
+    : AutocompleteProvider(type), client_(client) {}
 
 HistoryProvider::~HistoryProvider() {}
 
@@ -68,35 +99,3 @@
   }
   DCHECK(found) << "Asked to delete a URL that isn't in our set of matches";
 }
-
-// static
-ACMatchClassifications HistoryProvider::SpansFromTermMatch(
-    const TermMatches& matches,
-    size_t text_length,
-    bool is_url) {
-  ACMatchClassification::Style url_style =
-      is_url ? ACMatchClassification::URL : ACMatchClassification::NONE;
-  ACMatchClassifications spans;
-  if (matches.empty()) {
-    if (text_length)
-      spans.push_back(ACMatchClassification(0, url_style));
-    return spans;
-  }
-  if (matches[0].offset)
-    spans.push_back(ACMatchClassification(0, url_style));
-  size_t match_count = matches.size();
-  for (size_t i = 0; i < match_count;) {
-    size_t offset = matches[i].offset;
-    spans.push_back(ACMatchClassification(offset,
-        ACMatchClassification::MATCH | url_style));
-    // Skip all adjacent matches.
-    do {
-      offset += matches[i].length;
-      ++i;
-    } while ((i < match_count) && (offset == matches[i].offset));
-    if (offset < text_length)
-      spans.push_back(ACMatchClassification(offset, url_style));
-  }
-
-  return spans;
-}
diff --git a/components/omnibox/browser/history_provider.h b/components/omnibox/browser/history_provider.h
index 9cf3414..e93b952 100644
--- a/components/omnibox/browser/history_provider.h
+++ b/components/omnibox/browser/history_provider.h
@@ -27,6 +27,12 @@
   // is true or the input text contains trailing whitespace.
   static bool PreventInlineAutocomplete(const AutocompleteInput& input);
 
+  // Fill and return an ACMatchClassifications structure given the |matches|
+  // to highlight.
+  static ACMatchClassifications SpansFromTermMatch(const TermMatches& matches,
+                                                   size_t text_length,
+                                                   bool is_url);
+
  protected:
   HistoryProvider(AutocompleteProvider::Type type,
                   AutocompleteProviderClient* client);
@@ -37,12 +43,6 @@
   // backing data.
   void DeleteMatchFromMatches(const AutocompleteMatch& match);
 
-  // Fill and return an ACMatchClassifications structure given the |matches|
-  // to highlight.
-  static ACMatchClassifications SpansFromTermMatch(const TermMatches& matches,
-                                                   size_t text_length,
-                                                   bool is_url);
-
   AutocompleteProviderClient* client() { return client_; }
 
  private:
diff --git a/components/omnibox/browser/vector_icons/drive_forms.icon b/components/omnibox/browser/vector_icons/drive_forms.icon
index f174d9a..33b0f0d 100644
--- a/components/omnibox/browser/vector_icons/drive_forms.icon
+++ b/components/omnibox/browser/vector_icons/drive_forms.icon
@@ -1,46 +1,46 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.

-// Use of this source code is governed by a BSD-style license that can be

-// found in the LICENSE file.

-

-CANVAS_DIMENSIONS, 18,

-PATH_COLOR_ARGB, 0xFF, 0x67, 0x3A, 0xB7,

-MOVE_TO, 16, 0,

-H_LINE_TO, 2,

-CUBIC_TO, 0.9f, 0, 0, 0.9f, 0, 2,

-R_V_LINE_TO, 14,

-R_CUBIC_TO, 0, 1.1f, 0.9f, 2, 2, 2,

-R_H_LINE_TO, 14,

-R_CUBIC_TO, 1.1f, 0, 2, -0.9f, 2, -2,

-V_LINE_TO, 2,

-R_CUBIC_TO, 0, -1.1f, -0.9f, -2, -2, -2,

-CLOSE,

-MOVE_TO, 6, 14,

-H_LINE_TO, 4,

-R_V_LINE_TO, -2,

-R_H_LINE_TO, 2,

-CLOSE,

-R_MOVE_TO, 0, -4,

-H_LINE_TO, 4,

-V_LINE_TO, 8,

-R_H_LINE_TO, 2,

-CLOSE,

-R_MOVE_TO, 0, -4,

-H_LINE_TO, 4,

-V_LINE_TO, 4,

-R_H_LINE_TO, 2,

-CLOSE,

-R_MOVE_TO, 8, 8,

-H_LINE_TO, 7,

-R_V_LINE_TO, -2,

-R_H_LINE_TO, 7,

-CLOSE,

-R_MOVE_TO, 0, -4,

-H_LINE_TO, 7,

-V_LINE_TO, 8,

-R_H_LINE_TO, 7,

-CLOSE,

-R_MOVE_TO, 0, -4,

-H_LINE_TO, 7,

-V_LINE_TO, 4,

-R_H_LINE_TO, 7,

+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+CANVAS_DIMENSIONS, 18,
+PATH_COLOR_ARGB, 0xFF, 0x67, 0x3A, 0xB7,
+MOVE_TO, 16, 0,
+H_LINE_TO, 2,
+CUBIC_TO, 0.9f, 0, 0, 0.9f, 0, 2,
+R_V_LINE_TO, 14,
+R_CUBIC_TO, 0, 1.1f, 0.9f, 2, 2, 2,
+R_H_LINE_TO, 14,
+R_CUBIC_TO, 1.1f, 0, 2, -0.9f, 2, -2,
+V_LINE_TO, 2,
+R_CUBIC_TO, 0, -1.1f, -0.9f, -2, -2, -2,
+CLOSE,
+MOVE_TO, 6, 14,
+H_LINE_TO, 4,
+R_V_LINE_TO, -2,
+R_H_LINE_TO, 2,
+CLOSE,
+R_MOVE_TO, 0, -4,
+H_LINE_TO, 4,
+V_LINE_TO, 8,
+R_H_LINE_TO, 2,
+CLOSE,
+R_MOVE_TO, 0, -4,
+H_LINE_TO, 4,
+V_LINE_TO, 4,
+R_H_LINE_TO, 2,
+CLOSE,
+R_MOVE_TO, 8, 8,
+H_LINE_TO, 7,
+R_V_LINE_TO, -2,
+R_H_LINE_TO, 7,
+CLOSE,
+R_MOVE_TO, 0, -4,
+H_LINE_TO, 7,
+V_LINE_TO, 8,
+R_H_LINE_TO, 7,
+CLOSE,
+R_MOVE_TO, 0, -4,
+H_LINE_TO, 7,
+V_LINE_TO, 4,
+R_H_LINE_TO, 7,
 CLOSE
\ No newline at end of file
diff --git a/components/omnibox/browser/vector_icons/pedal.icon b/components/omnibox/browser/vector_icons/pedal.icon
index e6bb15f..8a33051 100644
--- a/components/omnibox/browser/vector_icons/pedal.icon
+++ b/components/omnibox/browser/vector_icons/pedal.icon
@@ -4,78 +4,78 @@
 
 // Source: bicycle-pedal.svg -> SVGOMG -> Skiafy
 
-CANVAS_DIMENSIONS, 100,

-MOVE_TO, 97.5f, 49.85f,

-R_ARC_TO, 2.23f, 2.23f, 0, 0, 0, -0.28f, -0.95f,

-LINE_TO, 78.76f, 16.92f,

-R_ARC_TO, 2.26f, 2.26f, 0, 0, 0, -1.94f, -1.12f,

-R_H_LINE_TO, -36.91f,

-R_CUBIC_TO, -0.77f, 0, -1.53f, 0.44f, -1.92f, 1.12f,

-LINE_TO, 19.53f, 48.9f,

-R_ARC_TO, 2.26f, 2.26f, 0, 0, 0, 0, 2.22f,

-R_LINE_TO, 18.46f, 31.98f,

-R_ARC_TO, 2.26f, 2.26f, 0, 0, 0, 1.92f, 1.1f,

-R_H_LINE_TO, 36.91f,

-R_ARC_TO, 2.26f, 2.26f, 0, 0, 0, 1.94f, -1.1f,

-LINE_TO, 97.22f, 51.11f,

-R_CUBIC_TO, 0.18f, -0.32f, 0.27f, -0.69f, 0.28f, -1.06f,

-R_ARC_TO, 2.25f, 2.25f, 0, 0, 0, 0, -0.21f,

-CLOSE,

-R_MOVE_TO, -9.17f, -0.18f,

-R_CUBIC_TO, 0.01f, 0.35f, -0.07f, 0.7f, -0.23f, 1.01f,

-R_LINE_TO, -14.14f, 24.66f,

-H_LINE_TO, 42.78f,

-LINE_TO, 28.73f, 51.4f,

-R_ARC_TO, 2.25f, 2.25f, 0, 0, 1, -0.11f, -1.99f,

-R_LINE_TO, 14.15f, -24.74f,

-R_H_LINE_TO, 31.18f,

-LINE_TO, 88.03f, 48.61f,

-R_CUBIC_TO, 0.19f, 0.32f, 0.29f, 0.69f, 0.3f, 1.06f,

-CLOSE,

-R_MOVE_TO, -1.82f, 0.09f,

-R_ARC_TO, 2.23f, 2.23f, 0, 0, 0, -0.3f, -1.16f,

-R_LINE_TO, -3.9f, -6.76f,

-H_LINE_TO, 34.79f,

-R_LINE_TO, -4.24f, 7.33f,

-R_ARC_TO, 2.26f, 2.26f, 0, 0, 0, 0, 2.22f,

-R_LINE_TO, 3.91f, 6.77f,

-H_LINE_TO, 81.97f,

-R_LINE_TO, 4.24f, -7.34f,

-R_CUBIC_TO, 0.19f, -0.32f, 0.29f, -0.69f, 0.3f, -1.06f,

-CLOSE,

-MOVE_TO, 73.96f, 20.24f,

-R_ARC_TO, 1.58f, 1.58f, 0, 1, 1, -3.17f, 0,

-R_ARC_TO, 1.58f, 1.58f, 0, 0, 1, 3.17f, 0,

-CLOSE,

-R_MOVE_TO, 0, 59.53f,

-R_ARC_TO, 1.58f, 1.58f, 0, 1, 1, -3.17f, 0,

-R_ARC_TO, 1.58f, 1.58f, 0, 0, 1, 3.17f, 0,

-CLOSE,

-MOVE_TO, 59.95f, 20.24f,

-R_ARC_TO, 1.58f, 1.58f, 0, 1, 1, -3.17f, 0,

-R_ARC_TO, 1.58f, 1.58f, 0, 0, 1, 3.17f, 0,

-CLOSE,

-R_MOVE_TO, 0, 59.53f,

-R_ARC_TO, 1.58f, 1.58f, 0, 1, 1, -3.17f, 0,

-R_ARC_TO, 1.58f, 1.58f, 0, 0, 1, 3.17f, 0,

-CLOSE,

-MOVE_TO, 45.94f, 20.24f,

-R_ARC_TO, 1.58f, 1.58f, 0, 1, 1, -3.17f, 0,

-R_ARC_TO, 1.58f, 1.58f, 0, 0, 1, 3.17f, 0,

-CLOSE,

-R_MOVE_TO, 0, 59.53f,

-R_ARC_TO, 1.58f, 1.58f, 0, 1, 1, -3.17f, 0,

-R_ARC_TO, 1.58f, 1.58f, 0, 0, 1, 3.17f, 0,

-CLOSE,

-MOVE_TO, 22, 41.46f,

-H_LINE_TO, 13.91f,

-R_V_LINE_TO, 2.22f,

-H_LINE_TO, 2.5f,

-R_V_LINE_TO, 12.67f,

-R_H_LINE_TO, 11.41f,

-R_V_LINE_TO, 2.21f,

-R_H_LINE_TO, 8.09f,

-R_LINE_TO, -4.29f, -7.43f,

-R_ARC_TO, 2.22f, 2.22f, 0, 0, 1, 0, -2.22f,

-R_LINE_TO, 4.29f, -7.44f,

-CLOSE

+CANVAS_DIMENSIONS, 100,
+MOVE_TO, 97.5f, 49.85f,
+R_ARC_TO, 2.23f, 2.23f, 0, 0, 0, -0.28f, -0.95f,
+LINE_TO, 78.76f, 16.92f,
+R_ARC_TO, 2.26f, 2.26f, 0, 0, 0, -1.94f, -1.12f,
+R_H_LINE_TO, -36.91f,
+R_CUBIC_TO, -0.77f, 0, -1.53f, 0.44f, -1.92f, 1.12f,
+LINE_TO, 19.53f, 48.9f,
+R_ARC_TO, 2.26f, 2.26f, 0, 0, 0, 0, 2.22f,
+R_LINE_TO, 18.46f, 31.98f,
+R_ARC_TO, 2.26f, 2.26f, 0, 0, 0, 1.92f, 1.1f,
+R_H_LINE_TO, 36.91f,
+R_ARC_TO, 2.26f, 2.26f, 0, 0, 0, 1.94f, -1.1f,
+LINE_TO, 97.22f, 51.11f,
+R_CUBIC_TO, 0.18f, -0.32f, 0.27f, -0.69f, 0.28f, -1.06f,
+R_ARC_TO, 2.25f, 2.25f, 0, 0, 0, 0, -0.21f,
+CLOSE,
+R_MOVE_TO, -9.17f, -0.18f,
+R_CUBIC_TO, 0.01f, 0.35f, -0.07f, 0.7f, -0.23f, 1.01f,
+R_LINE_TO, -14.14f, 24.66f,
+H_LINE_TO, 42.78f,
+LINE_TO, 28.73f, 51.4f,
+R_ARC_TO, 2.25f, 2.25f, 0, 0, 1, -0.11f, -1.99f,
+R_LINE_TO, 14.15f, -24.74f,
+R_H_LINE_TO, 31.18f,
+LINE_TO, 88.03f, 48.61f,
+R_CUBIC_TO, 0.19f, 0.32f, 0.29f, 0.69f, 0.3f, 1.06f,
+CLOSE,
+R_MOVE_TO, -1.82f, 0.09f,
+R_ARC_TO, 2.23f, 2.23f, 0, 0, 0, -0.3f, -1.16f,
+R_LINE_TO, -3.9f, -6.76f,
+H_LINE_TO, 34.79f,
+R_LINE_TO, -4.24f, 7.33f,
+R_ARC_TO, 2.26f, 2.26f, 0, 0, 0, 0, 2.22f,
+R_LINE_TO, 3.91f, 6.77f,
+H_LINE_TO, 81.97f,
+R_LINE_TO, 4.24f, -7.34f,
+R_CUBIC_TO, 0.19f, -0.32f, 0.29f, -0.69f, 0.3f, -1.06f,
+CLOSE,
+MOVE_TO, 73.96f, 20.24f,
+R_ARC_TO, 1.58f, 1.58f, 0, 1, 1, -3.17f, 0,
+R_ARC_TO, 1.58f, 1.58f, 0, 0, 1, 3.17f, 0,
+CLOSE,
+R_MOVE_TO, 0, 59.53f,
+R_ARC_TO, 1.58f, 1.58f, 0, 1, 1, -3.17f, 0,
+R_ARC_TO, 1.58f, 1.58f, 0, 0, 1, 3.17f, 0,
+CLOSE,
+MOVE_TO, 59.95f, 20.24f,
+R_ARC_TO, 1.58f, 1.58f, 0, 1, 1, -3.17f, 0,
+R_ARC_TO, 1.58f, 1.58f, 0, 0, 1, 3.17f, 0,
+CLOSE,
+R_MOVE_TO, 0, 59.53f,
+R_ARC_TO, 1.58f, 1.58f, 0, 1, 1, -3.17f, 0,
+R_ARC_TO, 1.58f, 1.58f, 0, 0, 1, 3.17f, 0,
+CLOSE,
+MOVE_TO, 45.94f, 20.24f,
+R_ARC_TO, 1.58f, 1.58f, 0, 1, 1, -3.17f, 0,
+R_ARC_TO, 1.58f, 1.58f, 0, 0, 1, 3.17f, 0,
+CLOSE,
+R_MOVE_TO, 0, 59.53f,
+R_ARC_TO, 1.58f, 1.58f, 0, 1, 1, -3.17f, 0,
+R_ARC_TO, 1.58f, 1.58f, 0, 0, 1, 3.17f, 0,
+CLOSE,
+MOVE_TO, 22, 41.46f,
+H_LINE_TO, 13.91f,
+R_V_LINE_TO, 2.22f,
+H_LINE_TO, 2.5f,
+R_V_LINE_TO, 12.67f,
+R_H_LINE_TO, 11.41f,
+R_V_LINE_TO, 2.21f,
+R_H_LINE_TO, 8.09f,
+R_LINE_TO, -4.29f, -7.43f,
+R_ARC_TO, 2.22f, 2.22f, 0, 0, 1, 0, -2.22f,
+R_LINE_TO, 4.29f, -7.44f,
+CLOSE
diff --git a/components/services/filesystem/BUILD.gn b/components/services/filesystem/BUILD.gn
index 21d8a9f5..b251f7e9 100644
--- a/components/services/filesystem/BUILD.gn
+++ b/components/services/filesystem/BUILD.gn
@@ -2,9 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//services/catalog/public/tools/catalog.gni")
 import("//services/service_manager/public/cpp/service_executable.gni")
-import("//services/service_manager/public/service_manifest.gni")
 import("//testing/test.gni")
 
 static_library("lib") {
@@ -52,11 +50,6 @@
     ]
   }
 
-  service_manifest("manifest") {
-    name = "filesystem"
-    source = "manifest.json"
-  }
-
   test("filesystem_service_unittests") {
     sources = [
       "directory_impl_unittest.cc",
@@ -66,9 +59,9 @@
     ]
 
     deps = [
-      ":filesystem_service_unittests_catalog_source",
       "//base",
       "//base/test:test_support",
+      "//components/services/filesystem/public/cpp:manifest",
       "//components/services/filesystem/public/interfaces",
       "//mojo/core/test:run_all_unittests",
       "//mojo/public/cpp/bindings",
@@ -82,20 +75,4 @@
       ":filesystem",
     ]
   }
-
-  service_manifest("test_manifest") {
-    name = "filesystem_service_unittests"
-    source = "test_manifest.json"
-  }
-
-  catalog("filesystem_service_unittests_catalog") {
-    embedded_services = [ ":test_manifest" ]
-    standalone_services = [ ":manifest" ]
-  }
-
-  catalog_cpp_source("filesystem_service_unittests_catalog_source") {
-    testonly = true
-    catalog = ":filesystem_service_unittests_catalog"
-    generated_function_name = "filesystem::test::CreateTestCatalog"
-  }
 }
diff --git a/components/services/filesystem/OWNERS b/components/services/filesystem/OWNERS
deleted file mode 100644
index 21beb1aa..0000000
--- a/components/services/filesystem/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-per-file manifest.json=set noparent
-per-file manifest.json=file://ipc/SECURITY_OWNERS
-
-per-file test_manifest.json=set noparent
-per-file test_manifest.json=file://ipc/SECURITY_OWNERS
diff --git a/components/services/filesystem/files_test_base.cc b/components/services/filesystem/files_test_base.cc
index 1429cfd5..349bedf 100644
--- a/components/services/filesystem/files_test_base.cc
+++ b/components/services/filesystem/files_test_base.cc
@@ -6,17 +6,25 @@
 
 #include <utility>
 
-#include "components/services/filesystem/filesystem_service_unittests_catalog_source.h"
+#include "components/services/filesystem/public/cpp/manifest.h"
 #include "components/services/filesystem/public/interfaces/directory.mojom.h"
 #include "components/services/filesystem/public/interfaces/types.mojom.h"
 #include "services/service_manager/public/cpp/connector.h"
+#include "services/service_manager/public/cpp/manifest_builder.h"
 
 namespace filesystem {
 
+const char kTestServiceName[] = "filesystem_service_unittests";
+
 FilesTestBase::FilesTestBase()
-    : test_service_manager_(test::CreateTestCatalog()),
-      test_service_(test_service_manager_.RegisterTestInstance(
-          "filesystem_service_unittests")) {}
+    : test_service_manager_(
+          {GetManifest(),
+           service_manager::ManifestBuilder()
+               .WithServiceName(kTestServiceName)
+               .RequireCapability("filesystem", "filesystem:filesystem")
+               .Build()}),
+      test_service_(
+          test_service_manager_.RegisterTestInstance(kTestServiceName)) {}
 
 FilesTestBase::~FilesTestBase() {}
 
diff --git a/components/services/filesystem/manifest.json b/components/services/filesystem/manifest.json
deleted file mode 100644
index 5e2e1647..0000000
--- a/components/services/filesystem/manifest.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "name": "filesystem",
-  "display_name": "File System Service",
-  "sandbox_type": "none",
-  "interface_provider_specs": {
-    "service_manager:connector": {
-      "provides": {
-        "filesystem:filesystem": [ "filesystem.mojom.FileSystem" ]
-      },
-      "requires": {
-        "*": [ "app" ]
-      }
-    }
-  }
-}
diff --git a/components/services/filesystem/public/cpp/BUILD.gn b/components/services/filesystem/public/cpp/BUILD.gn
new file mode 100644
index 0000000..e72b732
--- /dev/null
+++ b/components/services/filesystem/public/cpp/BUILD.gn
@@ -0,0 +1,16 @@
+# 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.
+
+source_set("manifest") {
+  sources = [
+    "manifest.cc",
+    "manifest.h",
+  ]
+
+  deps = [
+    "//base",
+    "//components/services/filesystem/public/interfaces",
+    "//services/service_manager/public/cpp",
+  ]
+}
diff --git a/components/services/filesystem/public/cpp/OWNERS b/components/services/filesystem/public/cpp/OWNERS
new file mode 100644
index 0000000..6faeaa47
--- /dev/null
+++ b/components/services/filesystem/public/cpp/OWNERS
@@ -0,0 +1,4 @@
+per-file manifest.cc=set noparent
+per-file manifest.cc=file://ipc/SECURITY_OWNERS
+per-file manifest.h=set noparent
+per-file manifest.h=file://ipc/SECURITY_OWNERS
diff --git a/components/services/filesystem/public/cpp/manifest.cc b/components/services/filesystem/public/cpp/manifest.cc
new file mode 100644
index 0000000..b27bfe37
--- /dev/null
+++ b/components/services/filesystem/public/cpp/manifest.cc
@@ -0,0 +1,27 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/services/filesystem/public/cpp/manifest.h"
+
+#include "base/no_destructor.h"
+#include "components/services/filesystem/public/interfaces/file_system.mojom.h"
+#include "services/service_manager/public/cpp/manifest_builder.h"
+
+namespace filesystem {
+
+const service_manager::Manifest& GetManifest() {
+  static base::NoDestructor<service_manager::Manifest> manifest{
+      service_manager::ManifestBuilder()
+          .WithServiceName("filesystem")
+          .WithOptions(service_manager::ManifestOptionsBuilder()
+                           .WithSandboxType("none")
+                           .Build())
+          .ExposeCapability(
+              "filesystem:filesystem",
+              service_manager::Manifest::InterfaceList<mojom::FileSystem>())
+          .Build()};
+  return *manifest;
+}
+
+}  // namespace filesystem
diff --git a/components/services/filesystem/public/cpp/manifest.h b/components/services/filesystem/public/cpp/manifest.h
new file mode 100644
index 0000000..2009ebf
--- /dev/null
+++ b/components/services/filesystem/public/cpp/manifest.h
@@ -0,0 +1,16 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_SERVICES_FILESYSTEM_PUBLIC_CPP_MANIFEST_H_
+#define COMPONENTS_SERVICES_FILESYSTEM_PUBLIC_CPP_MANIFEST_H_
+
+#include "services/service_manager/public/cpp/manifest.h"
+
+namespace filesystem {
+
+const service_manager::Manifest& GetManifest();
+
+}  // namespace filesystem
+
+#endif  // COMPONENTS_SERVICES_FILESYSTEM_PUBLIC_CPP_MANIFEST_H_
diff --git a/components/services/filesystem/test_manifest.json b/components/services/filesystem/test_manifest.json
deleted file mode 100644
index 219c700..0000000
--- a/components/services/filesystem/test_manifest.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
-  "name": "filesystem_service_unittests",
-  "display_name": "Filesystem Service Unittests",
-  "interface_provider_specs": {
-    "service_manager:connector": {
-      "requires": {
-        "filesystem": [ "filesystem:filesystem" ]
-      }
-    }
-  }
-}
diff --git a/components/services/leveldb/BUILD.gn b/components/services/leveldb/BUILD.gn
index 29c62d4..9f2629f 100644
--- a/components/services/leveldb/BUILD.gn
+++ b/components/services/leveldb/BUILD.gn
@@ -2,9 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//services/catalog/public/tools/catalog.gni")
 import("//services/service_manager/public/cpp/service_executable.gni")
-import("//services/service_manager/public/service_manifest.gni")
 import("//testing/test.gni")
 
 static_library("lib") {
@@ -48,11 +46,6 @@
   ]
 }
 
-service_manifest("manifest") {
-  name = "leveldb"
-  source = "manifest.json"
-}
-
 test("leveldb_service_unittests") {
   sources = [
     "leveldb_mojo_unittest.cc",
@@ -61,11 +54,12 @@
   ]
 
   deps = [
-    ":leveldb_service_unittests_catalog_source",
     "//base",
     "//base/test:test_support",
+    "//components/services/filesystem/public/cpp:manifest",
     "//components/services/filesystem/public/interfaces",
     "//components/services/leveldb/public/cpp",
+    "//components/services/leveldb/public/cpp:manifest",
     "//components/services/leveldb/public/interfaces",
     "//mojo/core/test:run_all_unittests",
     "//mojo/public/cpp/bindings",
@@ -81,22 +75,3 @@
     "//components/services/filesystem:filesystem",
   ]
 }
-
-service_manifest("test_manifest") {
-  name = "leveldb_service_unittests"
-  source = "test_manifest.json"
-}
-
-catalog("leveldb_service_unittests_catalog") {
-  embedded_services = [ ":test_manifest" ]
-  standalone_services = [
-    ":manifest",
-    "//components/services/filesystem:manifest",
-  ]
-}
-
-catalog_cpp_source("leveldb_service_unittests_catalog_source") {
-  testonly = true
-  catalog = ":leveldb_service_unittests_catalog"
-  generated_function_name = "leveldb::test::CreateTestCatalog"
-}
diff --git a/components/services/leveldb/OWNERS b/components/services/leveldb/OWNERS
index be35d98..f1bfd1e 100644
--- a/components/services/leveldb/OWNERS
+++ b/components/services/leveldb/OWNERS
@@ -1,13 +1,7 @@
 mek@chromium.org
 
-per-file manifest.json=set noparent
-per-file manifest.json=file://ipc/SECURITY_OWNERS
-
 per-file *_struct_traits*.*=set noparent
 per-file *_struct_traits*.*=file://ipc/SECURITY_OWNERS
 
-per-file test_manifest.json=set noparent
-per-file test_manifest.json=file://ipc/SECURITY_OWNERS
-
 per-file *.typemap=set noparent
 per-file *.typemap=file://ipc/SECURITY_OWNERS
diff --git a/components/services/leveldb/leveldb_service_unittest.cc b/components/services/leveldb/leveldb_service_unittest.cc
index 6fea47d1..79b57e3 100644
--- a/components/services/leveldb/leveldb_service_unittest.cc
+++ b/components/services/leveldb/leveldb_service_unittest.cc
@@ -7,13 +7,15 @@
 #include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_task_environment.h"
+#include "components/services/filesystem/public/cpp/manifest.h"
 #include "components/services/filesystem/public/interfaces/directory.mojom.h"
 #include "components/services/filesystem/public/interfaces/file_system.mojom.h"
 #include "components/services/filesystem/public/interfaces/types.mojom.h"
-#include "components/services/leveldb/leveldb_service_unittests_catalog_source.h"
+#include "components/services/leveldb/public/cpp/manifest.h"
 #include "components/services/leveldb/public/cpp/util.h"
 #include "components/services/leveldb/public/interfaces/leveldb.mojom.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
+#include "services/service_manager/public/cpp/manifest_builder.h"
 #include "services/service_manager/public/cpp/test/test_service.h"
 #include "services/service_manager/public/cpp/test/test_service_manager.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -134,12 +136,20 @@
   run_loop.Run();
 }
 
+const char kTestServiceName[] = "leveldb_service_unittests";
+
 class LevelDBServiceTest : public testing::Test {
  public:
   LevelDBServiceTest()
-      : test_service_manager_(test::CreateTestCatalog()),
-        test_service_(test_service_manager_.RegisterTestInstance(
-            "leveldb_service_unittests")) {}
+      : test_service_manager_(
+            {GetManifest(), filesystem::GetManifest(),
+             service_manager::ManifestBuilder()
+                 .WithServiceName(kTestServiceName)
+                 .RequireCapability("filesystem", "filesystem:filesystem")
+                 .RequireCapability("leveldb", "leveldb:leveldb")
+                 .Build()}),
+        test_service_(
+            test_service_manager_.RegisterTestInstance(kTestServiceName)) {}
   ~LevelDBServiceTest() override = default;
 
  protected:
diff --git a/components/services/leveldb/manifest.json b/components/services/leveldb/manifest.json
deleted file mode 100644
index 41102cc4..0000000
--- a/components/services/leveldb/manifest.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-  "name": "leveldb",
-  "display_name": "LevelDB Service",
-  "sandbox_type": "none",
-  "interface_provider_specs": {
-    "service_manager:connector": {
-      "provides": {
-        "leveldb:leveldb": [ "leveldb.mojom.LevelDBService" ]
-      },
-      "requires": {
-        "*": [ "app" ]
-      }
-    }
-  }
-}
diff --git a/components/services/leveldb/public/cpp/BUILD.gn b/components/services/leveldb/public/cpp/BUILD.gn
index aab9403..af0830e5 100644
--- a/components/services/leveldb/public/cpp/BUILD.gn
+++ b/components/services/leveldb/public/cpp/BUILD.gn
@@ -17,3 +17,16 @@
     "//third_party/leveldatabase",
   ]
 }
+
+source_set("manifest") {
+  sources = [
+    "manifest.cc",
+    "manifest.h",
+  ]
+
+  deps = [
+    "//base",
+    "//components/services/leveldb/public/interfaces",
+    "//services/service_manager/public/cpp",
+  ]
+}
diff --git a/components/services/leveldb/public/cpp/OWNERS b/components/services/leveldb/public/cpp/OWNERS
new file mode 100644
index 0000000..6faeaa47
--- /dev/null
+++ b/components/services/leveldb/public/cpp/OWNERS
@@ -0,0 +1,4 @@
+per-file manifest.cc=set noparent
+per-file manifest.cc=file://ipc/SECURITY_OWNERS
+per-file manifest.h=set noparent
+per-file manifest.h=file://ipc/SECURITY_OWNERS
diff --git a/components/services/leveldb/public/cpp/manifest.cc b/components/services/leveldb/public/cpp/manifest.cc
new file mode 100644
index 0000000..fc81f0c4
--- /dev/null
+++ b/components/services/leveldb/public/cpp/manifest.cc
@@ -0,0 +1,28 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/services/leveldb/public/cpp/manifest.h"
+
+#include "base/no_destructor.h"
+#include "components/services/leveldb/public/interfaces/leveldb.mojom.h"
+#include "services/service_manager/public/cpp/manifest_builder.h"
+
+namespace leveldb {
+
+const service_manager::Manifest& GetManifest() {
+  static base::NoDestructor<service_manager::Manifest> manifest{
+      service_manager::ManifestBuilder()
+          .WithServiceName("leveldb")
+          .WithDisplayName("LevelDB Service")
+          .WithOptions(service_manager::ManifestOptionsBuilder()
+                           .WithSandboxType("none")
+                           .Build())
+          .ExposeCapability(
+              "leveldb:leveldb",
+              service_manager::Manifest::InterfaceList<mojom::LevelDBService>())
+          .Build()};
+  return *manifest;
+}
+
+}  // namespace leveldb
diff --git a/components/services/leveldb/public/cpp/manifest.h b/components/services/leveldb/public/cpp/manifest.h
new file mode 100644
index 0000000..09d6c6a
--- /dev/null
+++ b/components/services/leveldb/public/cpp/manifest.h
@@ -0,0 +1,16 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_SERVICES_LEVELDB_PUBLIC_CPP_MANIFEST_H_
+#define COMPONENTS_SERVICES_LEVELDB_PUBLIC_CPP_MANIFEST_H_
+
+#include "services/service_manager/public/cpp/manifest.h"
+
+namespace leveldb {
+
+const service_manager::Manifest& GetManifest();
+
+}  // namespace leveldb
+
+#endif  // COMPONENTS_SERVICES_LEVELDB_PUBLIC_CPP_MANIFEST_H_
diff --git a/components/services/leveldb/remote_iterator_unittest.cc b/components/services/leveldb/remote_iterator_unittest.cc
index a209a46..4e482fe 100644
--- a/components/services/leveldb/remote_iterator_unittest.cc
+++ b/components/services/leveldb/remote_iterator_unittest.cc
@@ -8,10 +8,11 @@
 #include "base/macros.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
-#include "components/services/leveldb/leveldb_service_unittests_catalog_source.h"
+#include "components/services/leveldb/public/cpp/manifest.h"
 #include "components/services/leveldb/public/cpp/remote_iterator.h"
 #include "components/services/leveldb/public/cpp/util.h"
 #include "components/services/leveldb/public/interfaces/leveldb.mojom.h"
+#include "services/service_manager/public/cpp/manifest_builder.h"
 #include "services/service_manager/public/cpp/test/test_service.h"
 #include "services/service_manager/public/cpp/test/test_service_manager.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -42,12 +43,18 @@
                     quit_closure);
 }
 
+const char kTestServiceName[] = "leveldb_service_unittests";
+
 class RemoteIteratorTest : public testing::Test {
  public:
   RemoteIteratorTest()
-      : test_service_manager_(test::CreateTestCatalog()),
-        test_service_(test_service_manager_.RegisterTestInstance(
-            "leveldb_service_unittests")) {}
+      : test_service_manager_(
+            {GetManifest(), service_manager::ManifestBuilder()
+                                .WithServiceName(kTestServiceName)
+                                .RequireCapability("leveldb", "leveldb:leveldb")
+                                .Build()}),
+        test_service_(
+            test_service_manager_.RegisterTestInstance(kTestServiceName)) {}
   ~RemoteIteratorTest() override = default;
 
  protected:
diff --git a/components/services/leveldb/test_manifest.json b/components/services/leveldb/test_manifest.json
deleted file mode 100644
index 30a2da69..0000000
--- a/components/services/leveldb/test_manifest.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-  "name": "leveldb_service_unittests",
-  "display_name": "LevelDB Service Unittests",
-  "interface_provider_specs": {
-    "service_manager:connector": {
-      "requires": {
-        "filesystem": [ "filesystem:filesystem" ],
-        "leveldb": [ "leveldb:leveldb" ]
-      }
-    }
-  }
-}
diff --git a/components/test/BUILD.gn b/components/test/BUILD.gn
index 870351a..59e4418 100644
--- a/components/test/BUILD.gn
+++ b/components/test/BUILD.gn
@@ -3,8 +3,6 @@
 # found in the LICENSE file.
 
 import("//printing/buildflags/buildflags.gni")
-import("//services/catalog/public/tools/catalog.gni")
-import("//services/service_manager/public/service_manifest.gni")
 import("//ui/base/ui_features.gni")
 
 source_set("test_support") {
diff --git a/components/url_formatter/idn_spoof_checker.cc b/components/url_formatter/idn_spoof_checker.cc
index def420e..644c1f23 100644
--- a/components/url_formatter/idn_spoof_checker.cc
+++ b/components/url_formatter/idn_spoof_checker.cc
@@ -35,24 +35,22 @@
     if (!reader->Next(&is_same_skeleton))
       return false;
 
-    if (is_same_skeleton) {
-      *out_found = true;
-      result_ = search;
-      return true;
-    }
-
-    bool has_com_suffix = false;
-    if (!reader->Next(&has_com_suffix))
-      return false;
-
     std::string top_domain;
-    for (char c;; top_domain += c) {
-      huffman_decoder().Decode(reader, &c);
-      if (c == net::extras::PreloadDecoder::kEndOfTable)
-        break;
+    if (is_same_skeleton) {
+      top_domain = search;
+    } else {
+      bool has_com_suffix = false;
+      if (!reader->Next(&has_com_suffix))
+        return false;
+
+      for (char c;; top_domain += c) {
+        huffman_decoder().Decode(reader, &c);
+        if (c == net::extras::PreloadDecoder::kEndOfTable)
+          break;
+      }
+      if (has_com_suffix)
+        top_domain += ".com";
     }
-    if (has_com_suffix)
-      top_domain += ".com";
 
     if (current_search_offset == 0) {
       *out_found = true;
diff --git a/components/url_formatter/url_formatter_unittest.cc b/components/url_formatter/url_formatter_unittest.cc
index dbced74..e935fe50 100644
--- a/components/url_formatter/url_formatter_unittest.cc
+++ b/components/url_formatter/url_formatter_unittest.cc
@@ -1012,8 +1012,21 @@
 
     // Test that top domains whose skeletons are the same as the domain name are
     // handled properly. In this case, tést.net should match test.net top
-    // domain.
+    // domain and not be converted to unicode.
     {"xn--tst-bma.net", L"t\x00e9st.net", false},
+    // Variations of the above, for testing crbug.com/925199.
+    // some.tést.net should match test.net.
+    {"some.xn--tst-bma.net", L"some.t\x00e9st.net", false},
+    // The following should not match test.net, so should be converted to
+    // unicode.
+    // ést.net (a suffix of tést.net).
+    {"xn--st-9ia.net", L"\x00e9st.net", true},
+    // some.ést.net
+    {"some.xn--st-9ia.net", L"some.\x00e9st.net", true},
+    // atést.net (tést.net is a suffix of atést.net)
+    {"xn--atst-cpa.net", L"at\x00e9st.net", true},
+    // some.atést.net
+    {"some.xn--atst-cpa.net", L"some.at\x00e9st.net", true},
 
     // Modifier-letter-voicing should be blocked (wwwˬtest.com).
     {"xn--wwwtest-2be.com", L"www\x02ectest.com", false},
diff --git a/components/variations/service/variations_service.cc b/components/variations/service/variations_service.cc
index 6c53f8b..13d1bca 100644
--- a/components/variations/service/variations_service.cc
+++ b/components/variations/service/variations_service.cc
@@ -25,6 +25,7 @@
 #include "base/task/post_task.h"
 #include "base/task_runner_util.h"
 #include "base/timer/elapsed_timer.h"
+#include "base/trace_event/trace_event.h"
 #include "base/values.h"
 #include "base/version.h"
 #include "build/build_config.h"
@@ -692,6 +693,7 @@
 void VariationsService::OnSimpleLoaderCompleteOrRedirect(
     std::unique_ptr<std::string> response_body,
     bool was_redirect) {
+  TRACE_EVENT0("browser", "VariationsService::OnSimpleLoaderCompleteOrRedirect");
   const bool is_first_request = !initial_request_completed_;
   initial_request_completed_ = true;
 
diff --git a/components/viz/service/display_embedder/gpu_display_provider.cc b/components/viz/service/display_embedder/gpu_display_provider.cc
index c8f499e..28bc28a 100644
--- a/components/viz/service/display_embedder/gpu_display_provider.cc
+++ b/components/viz/service/display_embedder/gpu_display_provider.cc
@@ -150,7 +150,14 @@
 
       if (IsFatalOrSurfaceFailure(context_result)) {
 #if defined(OS_ANDROID)
-        display_client->OnFatalOrSurfaceContextCreationFailure(context_result);
+        // Ignore context creation failures if exiting for lost context. We are
+        // about to lose the GPU process and Viz will re-create the context at
+        // that point.
+        if (!gpu_service_impl_->gpu_channel_manager()
+                 ->is_exiting_for_lost_context()) {
+          display_client->OnFatalOrSurfaceContextCreationFailure(
+              context_result);
+        }
 #endif
         gpu_service_impl_->DisableGpuCompositing();
         return nullptr;
diff --git a/components/viz/service/main/BUILD.gn b/components/viz/service/main/BUILD.gn
index 13b0a27..6a7c537 100644
--- a/components/viz/service/main/BUILD.gn
+++ b/components/viz/service/main/BUILD.gn
@@ -4,8 +4,6 @@
 
 import("//build/config/ui.gni")
 import("//components/ui_devtools/devtools.gni")
-import("//services/catalog/public/tools/catalog.gni")
-import("//services/service_manager/public/service_manifest.gni")
 import("//testing/test.gni")
 
 source_set("main") {
diff --git a/content/browser/bluetooth/web_bluetooth_service_impl.cc b/content/browser/bluetooth/web_bluetooth_service_impl.cc
index 8e975e9..6952610 100644
--- a/content/browser/bluetooth/web_bluetooth_service_impl.cc
+++ b/content/browser/bluetooth/web_bluetooth_service_impl.cc
@@ -585,9 +585,8 @@
     return;
   }
 
-  if (services_uuid &&
-      !allowed_devices().IsAllowedToAccessService(device_id,
-                                                  services_uuid.value())) {
+  if (services_uuid && !allowed_devices().IsAllowedToAccessService(
+                           device_id, services_uuid.value())) {
     std::move(callback).Run(
         blink::mojom::WebBluetoothResult::NOT_ALLOWED_TO_ACCESS_SERVICE,
         base::nullopt /* service */);
diff --git a/content/browser/content_service_delegate_impl.cc b/content/browser/content_service_delegate_impl.cc
index 5f86d42..f792a5e 100644
--- a/content/browser/content_service_delegate_impl.cc
+++ b/content/browser/content_service_delegate_impl.cc
@@ -58,6 +58,11 @@
     WebContentsObserver::Observe(nullptr);
   }
 
+  bool TakeFocus(WebContents* source, bool reverse) override {
+    client_->ClearViewFocus();
+    return true;
+  }
+
  private:
   void NotifyAXTreeChange() {
     auto* rfh = web_contents_->GetMainFrame();
diff --git a/content/browser/dom_storage/dom_storage_area_unittest.cc b/content/browser/dom_storage/dom_storage_area_unittest.cc
index b0c0ead..55f0ced 100644
--- a/content/browser/dom_storage/dom_storage_area_unittest.cc
+++ b/content/browser/dom_storage/dom_storage_area_unittest.cc
@@ -85,7 +85,7 @@
   ~DOMStorageAreaParamTest() override {}
 };
 
-INSTANTIATE_TEST_CASE_P(_, DOMStorageAreaParamTest, ::testing::Bool());
+INSTANTIATE_TEST_SUITE_P(_, DOMStorageAreaParamTest, ::testing::Bool());
 
 TEST_P(DOMStorageAreaParamTest, DOMStorageAreaBasics) {
   const std::string kFirstNamespaceId = "id1";
diff --git a/content/browser/dom_storage/storage_area_impl_unittest.cc b/content/browser/dom_storage/storage_area_impl_unittest.cc
index b1e8918..7357675 100644
--- a/content/browser/dom_storage/storage_area_impl_unittest.cc
+++ b/content/browser/dom_storage/storage_area_impl_unittest.cc
@@ -313,10 +313,10 @@
   ~StorageAreaImplParamTest() override {}
 };
 
-INSTANTIATE_TEST_CASE_P(StorageAreaImplTest,
-                        StorageAreaImplParamTest,
-                        testing::Values(CacheMode::KEYS_ONLY_WHEN_POSSIBLE,
-                                        CacheMode::KEYS_AND_VALUES));
+INSTANTIATE_TEST_SUITE_P(StorageAreaImplTest,
+                         StorageAreaImplParamTest,
+                         testing::Values(CacheMode::KEYS_ONLY_WHEN_POSSIBLE,
+                                         CacheMode::KEYS_AND_VALUES));
 
 TEST_F(StorageAreaImplTest, GetLoadedFromMap) {
   storage_area_impl()->SetCacheModeForTesting(CacheMode::KEYS_AND_VALUES);
diff --git a/content/browser/media/hardware_key_media_controller.cc b/content/browser/media/hardware_key_media_controller.cc
index 26be8a4..cb9b92a9 100644
--- a/content/browser/media/hardware_key_media_controller.cc
+++ b/content/browser/media/hardware_key_media_controller.cc
@@ -31,12 +31,12 @@
   controller_manager_ptr->CreateActiveMediaController(
       mojo::MakeRequest(&media_controller_ptr_));
 
-  // Observe the active media session for changes to playback state and
+  // Observe the active media controller for changes to playback state and
   // supported actions.
-  media_session::mojom::MediaSessionObserverPtr media_session_observer;
-  media_session_observer_binding_.Bind(
-      mojo::MakeRequest(&media_session_observer));
-  media_controller_ptr_->AddObserver(std::move(media_session_observer));
+  media_session::mojom::MediaControllerObserverPtr media_controller_observer;
+  media_controller_observer_binding_.Bind(
+      mojo::MakeRequest(&media_controller_observer));
+  media_controller_ptr_->AddObserver(std::move(media_controller_observer));
 }
 
 HardwareKeyMediaController::~HardwareKeyMediaController() = default;
@@ -175,4 +175,4 @@
   }
 }
 
-}  // namespace content
\ No newline at end of file
+}  // namespace content
diff --git a/content/browser/media/hardware_key_media_controller.h b/content/browser/media/hardware_key_media_controller.h
index 1e5d40e..d8150446 100644
--- a/content/browser/media/hardware_key_media_controller.h
+++ b/content/browser/media/hardware_key_media_controller.h
@@ -13,7 +13,6 @@
 #include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "services/media_session/public/mojom/media_controller.mojom.h"
-#include "services/media_session/public/mojom/media_session.mojom.h"
 #include "ui/base/accelerators/media_keys_listener.h"
 #include "ui/events/keycodes/keyboard_codes.h"
 
@@ -25,13 +24,13 @@
 
 // HardwareKeyMediaController controls media sessions via hardware media keys.
 class CONTENT_EXPORT HardwareKeyMediaController
-    : public media_session::mojom::MediaSessionObserver,
+    : public media_session::mojom::MediaControllerObserver,
       public ui::MediaKeysListener::Delegate {
  public:
   explicit HardwareKeyMediaController(service_manager::Connector* connector);
   ~HardwareKeyMediaController() override;
 
-  // media_session::mojom::MediaSessionObserver:
+  // media_session::mojom::MediaControllerObserver:
   void MediaSessionInfoChanged(
       media_session::mojom::MediaSessionInfoPtr session_info) override;
   void MediaSessionMetadataChanged(
@@ -72,13 +71,13 @@
   // Used to check which actions are currently supported.
   base::flat_set<media_session::mojom::MediaSessionAction> actions_;
 
-  // Used to receive updates to the active MediaSession.
-  mojo::Binding<media_session::mojom::MediaSessionObserver>
-      media_session_observer_binding_{this};
+  // Used to receive updates to the active media controller.
+  mojo::Binding<media_session::mojom::MediaControllerObserver>
+      media_controller_observer_binding_{this};
 
   DISALLOW_COPY_AND_ASSIGN(HardwareKeyMediaController);
 };
 
 }  // namespace content
 
-#endif  // CONTENT_BROWSER_MEDIA_HARDWARE_KEY_MEDIA_CONTROLLER_H_
\ No newline at end of file
+#endif  // CONTENT_BROWSER_MEDIA_HARDWARE_KEY_MEDIA_CONTROLLER_H_
diff --git a/content/browser/renderer_host/compositor_impl_android.cc b/content/browser/renderer_host/compositor_impl_android.cc
index e24b46ac..0d3a9e7 100644
--- a/content/browser/renderer_host/compositor_impl_android.cc
+++ b/content/browser/renderer_host/compositor_impl_android.cc
@@ -862,7 +862,7 @@
   host_->SetNeedsAnimate();
 }
 
-void CompositorImpl::UpdateLayerTreeHost(bool record_main_frame_metrics) {
+void CompositorImpl::UpdateLayerTreeHost() {
   client_->UpdateLayerTreeHost();
   if (needs_animate_) {
     needs_animate_ = false;
diff --git a/content/browser/renderer_host/compositor_impl_android.h b/content/browser/renderer_host/compositor_impl_android.h
index 9cd93e5..cce753d 100644
--- a/content/browser/renderer_host/compositor_impl_android.h
+++ b/content/browser/renderer_host/compositor_impl_android.h
@@ -113,7 +113,7 @@
   void BeginMainFrame(const viz::BeginFrameArgs& args) override {}
   void BeginMainFrameNotExpectedSoon() override {}
   void BeginMainFrameNotExpectedUntil(base::TimeTicks time) override {}
-  void UpdateLayerTreeHost(bool record_main_frame_metrics) override;
+  void UpdateLayerTreeHost() override;
   void ApplyViewportChanges(const cc::ApplyViewportChangesArgs& args) override {
   }
   void RecordWheelAndTouchScrollingCount(bool has_scrolled_by_wheel,
@@ -134,6 +134,7 @@
   void DidPresentCompositorFrame(
       uint32_t frame_token,
       const gfx::PresentationFeedback& feedback) override {}
+  void RecordStartOfFrameMetrics() override {}
   void RecordEndOfFrameMetrics(base::TimeTicks frame_begin_time) override {}
   void DidGenerateLocalSurfaceIdAllocation(
       const viz::LocalSurfaceIdAllocation& allocation) override {}
diff --git a/content/browser/renderer_host/media/video_capture_buffer_pool_unittest.cc b/content/browser/renderer_host/media/video_capture_buffer_pool_unittest.cc
index 80175b1..bcc0bb0 100644
--- a/content/browser/renderer_host/media/video_capture_buffer_pool_unittest.cc
+++ b/content/browser/renderer_host/media/video_capture_buffer_pool_unittest.cc
@@ -265,8 +265,8 @@
   buffer4.reset();
 }
 
-INSTANTIATE_TEST_CASE_P(,
-                        VideoCaptureBufferPoolTest,
-                        testing::ValuesIn(kCapturePixelFormats));
+INSTANTIATE_TEST_SUITE_P(,
+                         VideoCaptureBufferPoolTest,
+                         testing::ValuesIn(kCapturePixelFormats));
 
 } // namespace content
diff --git a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
index 7f04243d8..b8b10dd 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
@@ -1528,7 +1528,7 @@
   DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMacPinchTest);
 };
 
-INSTANTIATE_TEST_CASE_P(, RenderWidgetHostViewMacPinchTest, testing::Bool());
+INSTANTIATE_TEST_SUITE_P(, RenderWidgetHostViewMacPinchTest, testing::Bool());
 
 TEST_P(RenderWidgetHostViewMacPinchTest, PinchThresholding) {
   // Do a gesture that crosses the threshold.
diff --git a/content/browser/serial/serial_browsertest.cc b/content/browser/serial/serial_browsertest.cc
index 91b05d11..361bd9d7 100644
--- a/content/browser/serial/serial_browsertest.cc
+++ b/content/browser/serial/serial_browsertest.cc
@@ -7,7 +7,9 @@
 
 #include "base/command_line.h"
 #include "base/unguessable_token.h"
-#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/serial_chooser.h"
+#include "content/public/browser/serial_delegate.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test.h"
 #include "content/public/test/content_browser_test_utils.h"
@@ -15,6 +17,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+using testing::_;
 using testing::ByMove;
 using testing::Return;
 
@@ -22,34 +25,121 @@
 
 namespace {
 
-class MockWebContentsDelegate : public WebContentsDelegate {
+class FakeSerialPortManager : public device::mojom::SerialPortManager {
  public:
-  MockWebContentsDelegate() {}
-  ~MockWebContentsDelegate() override {}
+  FakeSerialPortManager() = default;
+  ~FakeSerialPortManager() override = default;
 
-  std::unique_ptr<SerialChooser> RunSerialChooser(
-      content::RenderFrameHost* frame,
+  void AddPort(device::mojom::SerialPortInfoPtr port) {
+    base::UnguessableToken token = port->token;
+    ports_[token] = std::move(port);
+  }
+
+  // device::mojom::SerialPortManager
+  void GetDevices(GetDevicesCallback callback) override {
+    std::vector<device::mojom::SerialPortInfoPtr> ports;
+    for (const auto& map_entry : ports_)
+      ports.push_back(map_entry.second.Clone());
+    std::move(callback).Run(std::move(ports));
+  }
+
+  void GetPort(const base::UnguessableToken& token,
+               device::mojom::SerialPortRequest request) override {}
+
+ private:
+  std::map<base::UnguessableToken, device::mojom::SerialPortInfoPtr> ports_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeSerialPortManager);
+};
+
+class MockSerialDelegate : public SerialDelegate {
+ public:
+  MockSerialDelegate() = default;
+  ~MockSerialDelegate() override = default;
+
+  std::unique_ptr<SerialChooser> RunChooser(
+      RenderFrameHost* frame,
       std::vector<blink::mojom::SerialPortFilterPtr> filters,
-      content::SerialChooser::Callback callback) override {
-    std::move(callback).Run(RunSerialChooserInternal());
+      SerialChooser::Callback callback) override {
+    std::move(callback).Run(RunChooserInternal());
     return nullptr;
   }
 
-  MOCK_METHOD0(RunSerialChooserInternal, blink::mojom::SerialPortInfoPtr());
+  MOCK_METHOD0(RunChooserInternal, device::mojom::SerialPortInfoPtr());
+  MOCK_METHOD2(HasPortPermission,
+               bool(content::RenderFrameHost* frame,
+                    const device::mojom::SerialPortInfo& port));
+  MOCK_METHOD1(
+      GetPortManager,
+      device::mojom::SerialPortManager*(content::RenderFrameHost* frame));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockSerialDelegate);
 };
 
-}  // namespace
+class TestContentBrowserClient : public ContentBrowserClient {
+ public:
+  TestContentBrowserClient() = default;
+  ~TestContentBrowserClient() override = default;
+
+  MockSerialDelegate& delegate() { return delegate_; }
+
+  SerialDelegate* GetSerialDelegate() override { return &delegate_; }
+
+ private:
+  MockSerialDelegate delegate_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestContentBrowserClient);
+};
 
 class SerialTest : public ContentBrowserTest {
+ public:
+  SerialTest() {
+    ON_CALL(delegate(), GetPortManager).WillByDefault(Return(&port_manager_));
+  }
+
+  ~SerialTest() override = default;
+
   void SetUpCommandLine(base::CommandLine* command_line) override {
     ContentBrowserTest::SetUpCommandLine(command_line);
     command_line->AppendSwitchASCII("enable-blink-features", "Serial");
   }
+
+  void SetUpOnMainThread() override {
+    original_client_ = SetBrowserClientForTesting(&test_client_);
+  }
+
+  void TearDownOnMainThread() override {
+    if (original_client_)
+      SetBrowserClientForTesting(original_client_);
+  }
+
+  MockSerialDelegate& delegate() { return test_client_.delegate(); }
+  FakeSerialPortManager* port_manager() { return &port_manager_; }
+
+ private:
+  TestContentBrowserClient test_client_;
+  ContentBrowserClient* original_client_ = nullptr;
+  FakeSerialPortManager port_manager_;
 };
 
+}  // namespace
+
 IN_PROC_BROWSER_TEST_F(SerialTest, GetPorts) {
   NavigateToURL(shell(), GetTestUrl(nullptr, "simple_page.html"));
 
+  // Three ports are added but only two will have permission granted.
+  for (size_t i = 0; i < 3; i++) {
+    auto port = device::mojom::SerialPortInfo::New();
+    port->token = base::UnguessableToken::Create();
+    port_manager()->AddPort(std::move(port));
+  }
+
+  EXPECT_CALL(delegate(), HasPortPermission(_, _))
+      .WillOnce(Return(true))
+      .WillOnce(Return(false))
+      .WillOnce(Return(true));
+
   int result;
   EXPECT_TRUE(ExecuteScriptAndExtractInt(
       shell(),
@@ -58,18 +148,15 @@
       "        domAutomationController.send(ports.length);"
       "    });",
       &result));
-  EXPECT_EQ(0, result);
+  EXPECT_EQ(2, result);
 }
 
 IN_PROC_BROWSER_TEST_F(SerialTest, RequestPort) {
   NavigateToURL(shell(), GetTestUrl(nullptr, "simple_page.html"));
 
-  MockWebContentsDelegate delegate;
-  shell()->web_contents()->SetDelegate(&delegate);
-
-  auto port = blink::mojom::SerialPortInfo::New();
+  auto port = device::mojom::SerialPortInfo::New();
   port->token = base::UnguessableToken::Create();
-  EXPECT_CALL(delegate, RunSerialChooserInternal)
+  EXPECT_CALL(delegate(), RunChooserInternal)
       .WillOnce(Return(ByMove(std::move(port))));
 
   bool result;
diff --git a/content/browser/serial/serial_service.cc b/content/browser/serial/serial_service.cc
index 0029965..8a6cf13 100644
--- a/content/browser/serial/serial_service.cc
+++ b/content/browser/serial/serial_service.cc
@@ -4,12 +4,31 @@
 
 #include "content/browser/serial/serial_service.h"
 
+#include <utility>
+
+#include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/serial_chooser.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/serial_delegate.h"
 
 namespace content {
 
+namespace {
+
+blink::mojom::SerialPortInfoPtr ToBlinkType(
+    const device::mojom::SerialPortInfo& port) {
+  auto info = blink::mojom::SerialPortInfo::New();
+  info->token = port.token;
+  info->has_vendor_id = port.has_vendor_id;
+  if (port.has_vendor_id)
+    info->vendor_id = port.vendor_id;
+  info->has_product_id = port.has_product_id;
+  if (port.has_product_id)
+    info->product_id = port.product_id;
+  return info;
+}
+
+}  // namespace
+
 SerialService::SerialService(RenderFrameHost* render_frame_host)
     : render_frame_host_(render_frame_host) {}
 
@@ -20,21 +39,60 @@
 }
 
 void SerialService::GetPorts(GetPortsCallback callback) {
-  std::move(callback).Run(std::vector<blink::mojom::SerialPortInfoPtr>());
+  SerialDelegate* delegate = GetContentClient()->browser()->GetSerialDelegate();
+  if (!delegate) {
+    std::move(callback).Run(std::vector<blink::mojom::SerialPortInfoPtr>());
+    return;
+  }
+
+  delegate->GetPortManager(render_frame_host_)
+      ->GetDevices(base::BindOnce(&SerialService::FinishGetPorts,
+                                  weak_factory_.GetWeakPtr(),
+                                  std::move(callback)));
 }
 
 void SerialService::RequestPort(
     std::vector<blink::mojom::SerialPortFilterPtr> filters,
     RequestPortCallback callback) {
-  WebContentsDelegate* delegate =
-      WebContents::FromRenderFrameHost(render_frame_host_)->GetDelegate();
+  SerialDelegate* delegate = GetContentClient()->browser()->GetSerialDelegate();
   if (!delegate) {
     std::move(callback).Run(nullptr);
     return;
   }
 
-  chooser_ = delegate->RunSerialChooser(render_frame_host_, std::move(filters),
-                                        std::move(callback));
+  chooser_ = delegate->RunChooser(
+      render_frame_host_, std::move(filters),
+      base::BindOnce(&SerialService::FinishRequestPort,
+                     weak_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void SerialService::FinishGetPorts(
+    GetPortsCallback callback,
+    std::vector<device::mojom::SerialPortInfoPtr> ports) {
+  std::vector<blink::mojom::SerialPortInfoPtr> result;
+  SerialDelegate* delegate = GetContentClient()->browser()->GetSerialDelegate();
+  if (!delegate) {
+    std::move(callback).Run(std::move(result));
+    return;
+  }
+
+  for (const auto& port : ports) {
+    if (delegate->HasPortPermission(render_frame_host_, *port))
+      result.push_back(ToBlinkType(*port));
+  }
+
+  std::move(callback).Run(std::move(result));
+}
+
+void SerialService::FinishRequestPort(RequestPortCallback callback,
+                                      device::mojom::SerialPortInfoPtr port) {
+  SerialDelegate* delegate = GetContentClient()->browser()->GetSerialDelegate();
+  if (!delegate || !port) {
+    std::move(callback).Run(nullptr);
+    return;
+  }
+
+  std::move(callback).Run(ToBlinkType(*port));
 }
 
 }  // namespace content
diff --git a/content/browser/serial/serial_service.h b/content/browser/serial/serial_service.h
index 959df98..81c8048 100644
--- a/content/browser/serial/serial_service.h
+++ b/content/browser/serial/serial_service.h
@@ -5,10 +5,13 @@
 #ifndef CONTENT_BROWSER_SERIAL_SERIAL_SERVICE_H_
 #define CONTENT_BROWSER_SERIAL_SERIAL_SERVICE_H_
 
+#include <memory>
 #include <vector>
 
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
+#include "services/device/public/mojom/serial.mojom.h"
 #include "third_party/blink/public/mojom/serial/serial.mojom.h"
 
 namespace content {
@@ -29,12 +32,19 @@
                    RequestPortCallback callback) override;
 
  private:
+  void FinishGetPorts(GetPortsCallback callback,
+                      std::vector<device::mojom::SerialPortInfoPtr> ports);
+  void FinishRequestPort(RequestPortCallback callback,
+                         device::mojom::SerialPortInfoPtr port);
+
   RenderFrameHost* const render_frame_host_;
   mojo::BindingSet<blink::mojom::SerialService> bindings_;
 
   // The last shown serial port chooser UI.
   std::unique_ptr<SerialChooser> chooser_;
 
+  base::WeakPtrFactory<SerialService> weak_factory_{this};
+
   DISALLOW_COPY_AND_ASSIGN(SerialService);
 };
 
diff --git a/content/browser/storage_partition_impl_unittest.cc b/content/browser/storage_partition_impl_unittest.cc
index 156186f8..ba78ffd 100644
--- a/content/browser/storage_partition_impl_unittest.cc
+++ b/content/browser/storage_partition_impl_unittest.cc
@@ -1391,7 +1391,8 @@
   base::RunLoop().RunUntilIdle();
 }
 
-TEST_F(StoragePartitionImplTest, ClearCodeCacheDateRange) {
+// TODO(https://crbug.com/925957): Flakes, especially under Fuchsia.
+TEST_F(StoragePartitionImplTest, DISABLED_ClearCodeCacheDateRange) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(net::features::kIsolatedCodeCache);
   ASSERT_TRUE(base::FeatureList::IsEnabled(net::features::kIsolatedCodeCache));
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn
index 916d52bc..0d9061f 100644
--- a/content/public/browser/BUILD.gn
+++ b/content/public/browser/BUILD.gn
@@ -263,6 +263,7 @@
     "security_style_explanations.h",
     "serial_chooser.cc",
     "serial_chooser.h",
+    "serial_delegate.h",
     "service_worker_context.h",
     "service_worker_context_observer.h",
     "session_storage_namespace.h",
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 984a6e00..61e4af6 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -803,6 +803,10 @@
     RenderFrameHost* render_frame_host,
     mojo::InterfaceRequest<blink::mojom::WebUsbService> request) {}
 
+SerialDelegate* ContentBrowserClient::GetSerialDelegate() {
+  return nullptr;
+}
+
 bool ContentBrowserClient::ShowPaymentHandlerWindow(
     content::BrowserContext* browser_context,
     const GURL& url,
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 468d989..6d141289 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -171,6 +171,7 @@
 class RenderProcessHost;
 class RenderViewHost;
 class ResourceContext;
+class SerialDelegate;
 class ServiceManagerConnection;
 class SiteInstance;
 class SpeechRecognitionManagerDelegate;
@@ -1313,6 +1314,9 @@
       RenderFrameHost* render_frame_host,
       mojo::InterfaceRequest<blink::mojom::WebUsbService> request);
 
+  // Allows the embedder to provide an implementation of the Serial API.
+  virtual SerialDelegate* GetSerialDelegate();
+
   // Attempt to open the Payment Handler window inside its corresponding
   // PaymentRequest UI surface. Returns true if the ContentBrowserClient
   // implementation supports this operation (desktop Chrome) or false otherwise.
diff --git a/content/public/browser/serial_chooser.h b/content/public/browser/serial_chooser.h
index 4d4401d..415d0db4 100644
--- a/content/public/browser/serial_chooser.h
+++ b/content/public/browser/serial_chooser.h
@@ -8,7 +8,7 @@
 #include "base/callback_forward.h"
 #include "base/macros.h"
 #include "content/common/content_export.h"
-#include "third_party/blink/public/mojom/serial/serial.mojom.h"
+#include "services/device/public/mojom/serial.mojom.h"
 
 namespace content {
 
@@ -18,7 +18,7 @@
  public:
   // Callback type used to report the user action. Passed |nullptr| if no port
   // was selected.
-  using Callback = base::OnceCallback<void(blink::mojom::SerialPortInfoPtr)>;
+  using Callback = base::OnceCallback<void(device::mojom::SerialPortInfoPtr)>;
 
   SerialChooser();
   virtual ~SerialChooser();
diff --git a/content/public/browser/serial_delegate.h b/content/public/browser/serial_delegate.h
new file mode 100644
index 0000000..aaea94ba
--- /dev/null
+++ b/content/public/browser/serial_delegate.h
@@ -0,0 +1,49 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_PUBLIC_BROWSER_SERIAL_DELEGATE_H_
+#define CONTENT_PUBLIC_BROWSER_SERIAL_DELEGATE_H_
+
+#include <memory>
+#include <vector>
+
+#include "content/common/content_export.h"
+#include "content/public/browser/serial_chooser.h"
+#include "services/device/public/mojom/serial.mojom.h"
+#include "third_party/blink/public/mojom/serial/serial.mojom.h"
+
+namespace content {
+
+class RenderFrameHost;
+
+class CONTENT_EXPORT SerialDelegate {
+ public:
+  virtual ~SerialDelegate() = default;
+
+  // Shows a chooser for the user to select a serial port.  |callback| will be
+  // run when the prompt is closed. Deleting the returned object will cancel the
+  // prompt.
+  virtual std::unique_ptr<SerialChooser> RunChooser(
+      RenderFrameHost* frame,
+      std::vector<blink::mojom::SerialPortFilterPtr> filters,
+      SerialChooser::Callback callback) = 0;
+
+  // Returns whether |frame| has permission to access |port|.
+  virtual bool HasPortPermission(RenderFrameHost* frame,
+                                 const device::mojom::SerialPortInfo& port) = 0;
+
+  // Returns an open connection to the SerialPortManager interface owned by
+  // the embedder and being used to serve requests from |frame|.
+  //
+  // Content and the embedder must use the same connection so that the embedder
+  // can process connect/disconnect events for permissions management purposes
+  // before they are delivered to content. Otherwise race conditions are
+  // possible.
+  virtual device::mojom::SerialPortManager* GetPortManager(
+      RenderFrameHost* frame) = 0;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_PUBLIC_BROWSER_SERIAL_DELEGATE_H_
diff --git a/content/public/browser/web_contents_delegate.cc b/content/public/browser/web_contents_delegate.cc
index 3ce59d6..84bf210 100644
--- a/content/public/browser/web_contents_delegate.cc
+++ b/content/public/browser/web_contents_delegate.cc
@@ -143,14 +143,6 @@
   return nullptr;
 }
 
-std::unique_ptr<SerialChooser> WebContentsDelegate::RunSerialChooser(
-    RenderFrameHost* frame,
-    std::vector<blink::mojom::SerialPortFilterPtr> filters,
-    SerialChooser::Callback callback) {
-  std::move(callback).Run(nullptr);
-  return nullptr;
-}
-
 bool WebContentsDelegate::EmbedsFullscreenWidget() const {
   return false;
 }
diff --git a/content/public/browser/web_contents_delegate.h b/content/public/browser/web_contents_delegate.h
index 7e76c0e6..b02226b 100644
--- a/content/public/browser/web_contents_delegate.h
+++ b/content/public/browser/web_contents_delegate.h
@@ -395,14 +395,6 @@
       RenderFrameHost* frame,
       const BluetoothChooser::EventHandler& event_handler);
 
-  // Shows a chooser for the user to select a serial port.  |callback| will be
-  // run when the prompt is closed. Deleting the returned object will cancel the
-  // prompt.
-  virtual std::unique_ptr<SerialChooser> RunSerialChooser(
-      RenderFrameHost* frame,
-      std::vector<blink::mojom::SerialPortFilterPtr> filters,
-      SerialChooser::Callback callback);
-
   // Returns true if the delegate will embed a WebContents-owned fullscreen
   // render widget.  In this case, the delegate may access the widget by calling
   // WebContents::GetFullscreenRenderWidgetHostView().  If false is returned,
diff --git a/content/renderer/compositor/layer_tree_view.cc b/content/renderer/compositor/layer_tree_view.cc
index f9bbf582..86cca0a 100644
--- a/content/renderer/compositor/layer_tree_view.cc
+++ b/content/renderer/compositor/layer_tree_view.cc
@@ -500,7 +500,7 @@
     // frame, but the compositor does not support this. In this case, we only
     // run blink's lifecycle updates.
     delegate_->BeginMainFrame(base::TimeTicks::Now());
-    delegate_->UpdateVisualState(false /* record_main_frame_metrics */);
+    delegate_->UpdateVisualState();
     return;
   }
 
@@ -602,8 +602,8 @@
   web_main_thread_scheduler_->BeginMainFrameNotExpectedUntil(time);
 }
 
-void LayerTreeView::UpdateLayerTreeHost(bool record_main_frame_metrics) {
-  delegate_->UpdateVisualState(record_main_frame_metrics);
+void LayerTreeView::UpdateLayerTreeHost() {
+  delegate_->UpdateVisualState();
 }
 
 void LayerTreeView::ApplyViewportChanges(
@@ -704,6 +704,10 @@
   }
 }
 
+void LayerTreeView::RecordStartOfFrameMetrics() {
+  delegate_->RecordStartOfFrameMetrics();
+}
+
 void LayerTreeView::RecordEndOfFrameMetrics(base::TimeTicks frame_begin_time) {
   delegate_->RecordEndOfFrameMetrics(frame_begin_time);
 }
diff --git a/content/renderer/compositor/layer_tree_view.h b/content/renderer/compositor/layer_tree_view.h
index a9eb7ce..aa54b853 100644
--- a/content/renderer/compositor/layer_tree_view.h
+++ b/content/renderer/compositor/layer_tree_view.h
@@ -191,7 +191,7 @@
   void BeginMainFrame(const viz::BeginFrameArgs& args) override;
   void BeginMainFrameNotExpectedSoon() override;
   void BeginMainFrameNotExpectedUntil(base::TimeTicks time) override;
-  void UpdateLayerTreeHost(bool record_main_frame_metrics) override;
+  void UpdateLayerTreeHost() override;
   void ApplyViewportChanges(const cc::ApplyViewportChangesArgs& args) override;
   void RecordWheelAndTouchScrollingCount(bool has_scrolled_by_wheel,
                                          bool has_scrolled_by_touch) override;
@@ -211,6 +211,7 @@
   void DidPresentCompositorFrame(
       uint32_t frame_token,
       const gfx::PresentationFeedback& feedback) override;
+  void RecordStartOfFrameMetrics() override;
   void RecordEndOfFrameMetrics(base::TimeTicks frame_begin_time) override;
   void DidGenerateLocalSurfaceIdAllocation(
       const viz::LocalSurfaceIdAllocation& allocation) override {}
diff --git a/content/renderer/compositor/layer_tree_view_delegate.h b/content/renderer/compositor/layer_tree_view_delegate.h
index f3ef7a52..a5d2d82 100644
--- a/content/renderer/compositor/layer_tree_view_delegate.h
+++ b/content/renderer/compositor/layer_tree_view_delegate.h
@@ -53,7 +53,7 @@
   virtual void SendScrollEndEventFromImplSide(
       cc::ElementId scroll_latched_element_id) = 0;
 
-  // Notifies that the compositor has issed a BeginMainFrame.
+  // Notifies that the compositor has issued a BeginMainFrame.
   virtual void BeginMainFrame(base::TimeTicks frame_time) = 0;
 
   // Requests a LayerTreeFrameSink to submit CompositorFrames to.
@@ -69,20 +69,26 @@
   // Called by the compositor when page scale animation completed.
   virtual void DidCompletePageScaleAnimation() = 0;
 
-  // Requests that a UMA and UKM metric be recorded for the total frame time.
-  // Call this as soon as the total frame time becomes known for a given frame.
-  // For example, ProxyMain::BeginMainFrame calls it immediately before aborting
-  // or committing a frame (at the same time Tracing measurements are taken).
+  // Requests that a UMA and UKM metrics be recorded for the total frame time
+  // and the portion of frame time spent in various sub-systems.
+  // Call RecordStartOfFrameMetrics when a main frame is starting, and call
+  // RecordEndOfFrameMetrics as soon as the total frame time becomes known for
+  // a given frame. For example, ProxyMain::BeginMainFrame calls
+  // RecordStartOfFrameMetrics just be WillBeginCompositorFrame() and
+  // RecordEndOfFrameMetrics immediately before aborting or committing a frame
+  // (at the same time Tracing measurements are taken).
+  virtual void RecordStartOfFrameMetrics() = 0;
   virtual void RecordEndOfFrameMetrics(base::TimeTicks frame_begin_time) = 0;
 
   // Requests a visual frame-based update to the state of the delegate if there
-  // is an update available. |record_main_frame_metrics| will be true if
-  // this is a main frame for which we want metrics.
-  virtual void UpdateVisualState(bool record_main_frame_metrics) = 0;
+  // is an update available.
+  virtual void UpdateVisualState() = 0;
 
   // Indicates that the compositor is about to begin a frame. This is primarily
   // to signal to flow control mechanisms that a frame is beginning, not to
-  // perform actual painting work.
+  // perform actual painting work. When |record_main_frame_metrics| is true
+  // we are in a frame that shoujld capture metrics data, and the local frame's
+  // UKM aggregator must be informed that the frame is starting.
   virtual void WillBeginCompositorFrame() = 0;
 
   // For use in web test mode only, attempts to copy the full content of the
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index e51bcafa..f75bf59 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -206,6 +206,21 @@
   blink::WebWidget* webwidget_;
 };
 
+class ScopedUkmRafAlignedInputTimer {
+ public:
+  explicit ScopedUkmRafAlignedInputTimer(blink::WebWidget* webwidget)
+      : webwidget_(webwidget) {
+    webwidget_->BeginRafAlignedInput();
+  }
+
+  ~ScopedUkmRafAlignedInputTimer() { webwidget_->EndRafAlignedInput(); }
+
+ private:
+  blink::WebWidget* webwidget_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedUkmRafAlignedInputTimer);
+};
+
 bool IsDateTimeInput(ui::TextInputType type) {
   return type == ui::TEXT_INPUT_TYPE_DATE ||
          type == ui::TEXT_INPUT_TYPE_DATE_TIME ||
@@ -983,10 +998,19 @@
 void RenderWidget::BeginMainFrame(base::TimeTicks frame_time) {
   if (!GetWebWidget())
     return;
-  if (input_event_queue_)
-    input_event_queue_->DispatchRafAlignedInput(frame_time);
 
-  GetWebWidget()->BeginFrame(frame_time);
+  // We record metrics only when running in multi-threaded mode, not
+  // single-thread mode for testing.
+  bool record_main_frame_metrics =
+      !!compositor_deps_->GetCompositorImplThreadTaskRunner();
+  if (input_event_queue_) {
+    base::Optional<ScopedUkmRafAlignedInputTimer> ukm_timer;
+    if (record_main_frame_metrics)
+      ukm_timer.emplace(GetWebWidget());
+    input_event_queue_->DispatchRafAlignedInput(frame_time);
+  }
+
+  GetWebWidget()->BeginFrame(frame_time, record_main_frame_metrics);
 }
 
 void RenderWidget::RequestNewLayerTreeFrameSink(
@@ -1120,10 +1144,15 @@
   host->SetDebugState(debug_state);
 }
 
-void RenderWidget::UpdateVisualState(bool record_main_frame_metrics) {
+void RenderWidget::UpdateVisualState() {
   if (!GetWebWidget())
     return;
 
+  // We record metrics only when running in multi-threaded mode, not
+  // single-thread mode for testing.
+  bool record_main_frame_metrics =
+      !!compositor_deps_->GetCompositorImplThreadTaskRunner();
+
   // When recording main frame metrics set the lifecycle reason to
   // kBeginMainFrame, because this is the calller of UpdateLifecycle
   // for the main frame. Otherwise, set the reason to kTests, which is
@@ -1158,6 +1187,13 @@
   }
 }
 
+void RenderWidget::RecordStartOfFrameMetrics() {
+  if (!GetWebWidget())
+    return;
+
+  GetWebWidget()->RecordStartOfFrameMetrics();
+}
+
 void RenderWidget::RecordEndOfFrameMetrics(base::TimeTicks frame_begin_time) {
   if (!GetWebWidget())
     return;
diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h
index 9469c37b..a4fc4a0 100644
--- a/content/renderer/render_widget.h
+++ b/content/renderer/render_widget.h
@@ -313,8 +313,9 @@
   void DidCommitAndDrawCompositorFrame() override;
   void DidCommitCompositorFrame() override;
   void DidCompletePageScaleAnimation() override;
+  void RecordStartOfFrameMetrics() override;
   void RecordEndOfFrameMetrics(base::TimeTicks frame_begin_time) override;
-  void UpdateVisualState(bool record_main_frame_metrics) override;
+  void UpdateVisualState() override;
   void WillBeginCompositorFrame() override;
   std::unique_ptr<cc::SwapPromise> RequestCopyOfOutputForWebTest(
       std::unique_ptr<viz::CopyOutputRequest> request) override;
diff --git a/content/renderer/service_worker/service_worker_context_client.cc b/content/renderer/service_worker/service_worker_context_client.cc
index 6295a33d..b7b3bbe 100644
--- a/content/renderer/service_worker/service_worker_context_client.cc
+++ b/content/renderer/service_worker/service_worker_context_client.cc
@@ -680,11 +680,11 @@
 
   TRACE_EVENT_NESTABLE_ASYNC_END0("ServiceWorker", "START_WORKER_CONTEXT",
                                   this);
-  TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("ServiceWorker", "EVALUATE_SCRIPT", this);
 }
 
 void ServiceWorkerContextClient::WillEvaluateScript() {
   DCHECK(worker_task_runner_->RunsTasksInCurrentSequence());
+  TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("ServiceWorker", "EVALUATE_SCRIPT", this);
   start_timing_->script_evaluation_start_time = base::TimeTicks::Now();
 
   // Temporary CHECK for https://crbug.com/881100
diff --git a/content/shell/browser/shell_content_browser_client.cc b/content/shell/browser/shell_content_browser_client.cc
index 40dd1b2..78ca1e6 100644
--- a/content/shell/browser/shell_content_browser_client.cc
+++ b/content/shell/browser/shell_content_browser_client.cc
@@ -76,7 +76,7 @@
 #include "content/public/browser/context_factory.h"
 #include "content/public/browser/gpu_interface_provider_factory.h"
 #include "services/ws/public/mojom/constants.mojom.h"         // nogncheck
-#include "services/ws/test_ws/manifest.h"                     // nogncheck
+#include "services/ws/test_ws/test_manifest.h"                // nogncheck
 #include "services/ws/test_ws/test_window_service_factory.h"  // nogncheck
 #include "services/ws/test_ws/test_ws.mojom.h"                // nogncheck
 #endif
diff --git a/content/test/gpu/gpu_tests/pixel_expectations.py b/content/test/gpu/gpu_tests/pixel_expectations.py
index 7660dfb..c707c979 100644
--- a/content/test/gpu/gpu_tests/pixel_expectations.py
+++ b/content/test/gpu/gpu_tests/pixel_expectations.py
@@ -49,11 +49,16 @@
     # TODO(vmiura) check / generate reference images for Android devices
     self.Fail('Pixel_SolidColorBackground', ['mac', 'android'], bug=624256)
 
-    self.Fail('Pixel_CSSFilterEffects', ['mac', ('nvidia', 0xfe9)], bug=690277)
+    # TODO(wangxianzhu): This is commented out temporarily because of the entry
+    # for crbug.com/836884.
+    # self.Fail('Pixel_CSSFilterEffects', ['mac', ('nvidia', 0xfe9)],
+    #     bug=690277)
 
     # Became flaky on 10.13.6. When it flakes, it flakes 3 times, so
     # mark failing, unfortunately.
-    self.Fail('Pixel_CSSFilterEffects', ['highsierra', 'amd'], bug=872423)
+    # TODO(wangxianzhu): This is commented out temporarily because of the entry
+    # for crbug.com/836884.
+    # self.Fail('Pixel_CSSFilterEffects', ['highsierra', 'amd'], bug=872423)
 
     # TODO(kbr): flakily timing out on this configuration.
     self.Flaky('*', ['linux', 'intel', 'debug'], bug=648369)
@@ -112,6 +117,12 @@
     self.Fail('Pixel_BackgroundImage',
         ['android', ('qualcomm', 'Adreno (TM) 430')], bug=883500)
 
+    # TODO(wangxianzhu): Re-enable after and rebaselining
+    self.Fail('Pixel_CSSFilterEffects', bug=836884)
+    self.Fail('Pixel_CSSFilterEffects_NoOverlays', bug=836884)
+    self.Fail('Pixel_2DCanvasWebGL', bug=836884)
+    self.Fail('Pixel_CSS3DBlueBox', bug=836884)
+
     # Fails on Mac Pro FYI Release (AMD)
     self.Fail('Pixel_Video_MP4',
         ['mac', ('amd', 0x679e)], bug=925744)
diff --git a/content/test/gpu/gpu_tests/pixel_test_pages.py b/content/test/gpu/gpu_tests/pixel_test_pages.py
index 78a4d67c..6dc216bb 100644
--- a/content/test/gpu/gpu_tests/pixel_test_pages.py
+++ b/content/test/gpu/gpu_tests/pixel_test_pages.py
@@ -198,7 +198,7 @@
       'pixel_canvas2d_webgl.html',
       base_name + '_2DCanvasWebGL',
       test_rect=[0, 0, 300, 300],
-      revision=11),
+      revision=12),
 
     PixelTestPage(
       'pixel_background.html',
@@ -902,7 +902,7 @@
       'pixel_css3d.html',
       base_name + '_CSS3DBlueBox' + suffix,
       test_rect=[0, 0, 300, 300],
-      revision=1,
+      revision=2,
       browser_args=browser_args),
 
     PixelTestPage(
@@ -1012,12 +1012,12 @@
       'filter_effects.html',
       base_name + '_CSSFilterEffects',
       test_rect=[0, 0, 300, 300],
-      revision=9),
+      revision=10),
     PixelTestPage(
       'filter_effects.html',
       base_name + '_CSSFilterEffects_NoOverlays',
       test_rect=[0, 0, 300, 300],
-      revision=9,
+      revision=10,
       tolerance=10,
       browser_args=no_overlays_args),
 
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
index ba36d2f..ca9744d7 100644
--- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -492,6 +492,8 @@
     self.Flaky(
         'conformance/extensions/webgl-compressed-texture-size-limit.html',
         ['mac', ('nvidia', 0xfe9)], bug=483282)
+    self.Flaky('conformance/ogles/GL/exp2/exp2_001_to_008.html',
+        ['mac', ('nvidia', 0xfe9)], bug=923080)
     self.Fail('conformance/programs/' +
         'gl-bind-attrib-location-long-names-test.html',
         ['mac', ('nvidia', 0xfe9)], bug=483282)
diff --git a/content/test/gpu/gpu_tests/webgl_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
index c38577d..f81bb68 100644
--- a/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl_conformance_expectations.py
@@ -480,6 +480,8 @@
         ['win', 'passthrough', 'vulkan'], bug=2905) # ANGLE bug ID
     self.Fail('WebglExtension_WEBGL_draw_buffers',
         ['win', 'passthrough', 'vulkan'], bug=2394) # ANGLE bug ID
+    self.Skip('deqp/data/gles2/shaders/swizzles.html',
+        ['win', 'passthrough', 'vulkan'], bug=3111) # ANGLE bug ID
 
     # Vulkan / Win / NVIDIA / Passthough command decoder
     self.Fail('conformance/canvas/' +
@@ -501,9 +503,6 @@
         ['win', 'passthrough', 'vulkan', 'nvidia'], bug=2915) # ANGLE bug ID
     self.Fail('deqp/data/gles2/shaders/conversions.html',
         ['win', 'passthrough', 'vulkan', 'nvidia'], bug=2926) # ANGLE bug ID
-    self.Fail('deqp/data/gles2/shaders/swizzles.html',
-        ['win', 'passthrough', 'vulkan', 'nvidia', 'debug'],
-        bug=2940) # ANGLE bug ID
 
     # Vulkan / Win / Intel / Passthough command decoder
     self.Fail('conformance/rendering/clipping-wide-points.html',
diff --git a/content/test/stub_layer_tree_view_delegate.h b/content/test/stub_layer_tree_view_delegate.h
index 1e98a9b9c..a75b0eb 100644
--- a/content/test/stub_layer_tree_view_delegate.h
+++ b/content/test/stub_layer_tree_view_delegate.h
@@ -26,13 +26,14 @@
   void SendScrollEndEventFromImplSide(
       cc::ElementId scroll_latched_element_id) override {}
   void BeginMainFrame(base::TimeTicks frame_time) override {}
+  void RecordStartOfFrameMetrics() override {}
   void RecordEndOfFrameMetrics(base::TimeTicks) override {}
   void RequestNewLayerTreeFrameSink(
       LayerTreeFrameSinkCallback callback) override;
   void DidCommitAndDrawCompositorFrame() override {}
   void DidCommitCompositorFrame() override {}
   void DidCompletePageScaleAnimation() override {}
-  void UpdateVisualState(bool record_main_frame_metrics) override {}
+  void UpdateVisualState() override {}
   void WillBeginCompositorFrame() override {}
   std::unique_ptr<cc::SwapPromise> RequestCopyOfOutputForWebTest(
       std::unique_ptr<viz::CopyOutputRequest> request) override;
diff --git a/device/bluetooth/OWNERS b/device/bluetooth/OWNERS
index 8ac8597..3b9f2534 100644
--- a/device/bluetooth/OWNERS
+++ b/device/bluetooth/OWNERS
@@ -1,4 +1,7 @@
 ortuno@chromium.org
+reillyg@chromium.org
+odejesush@chromium.org
+dougt@chromium.org
 
 # For changes related to Chrome OS.
 rkc@chromium.org
diff --git a/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothAdapter.java b/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothAdapter.java
index 9256c38..b6067bfb 100644
--- a/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothAdapter.java
+++ b/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothAdapter.java
@@ -289,9 +289,10 @@
             // Object can be destroyed, but Android keeps calling onScanResult.
             if (mNativeBluetoothAdapterAndroid != 0) {
                 nativeCreateOrUpdateDeviceOnScan(mNativeBluetoothAdapterAndroid,
-                        result.getDevice().getAddress(), result.getDevice(), result.getRssi(),
-                        uuid_strings, result.getScanRecord_getTxPowerLevel(), serviceDataKeys,
-                        serviceDataValues, manufacturerDataKeys, manufacturerDataValues);
+                        result.getDevice().getAddress(), result.getDevice(),
+                        result.getScanRecord_getDeviceName(), result.getRssi(), uuid_strings,
+                        result.getScanRecord_getTxPowerLevel(), serviceDataKeys, serviceDataValues,
+                        manufacturerDataKeys, manufacturerDataValues);
             }
         }
 
@@ -349,8 +350,8 @@
 
     // Binds to BluetoothAdapterAndroid::CreateOrUpdateDeviceOnScan.
     private native void nativeCreateOrUpdateDeviceOnScan(long nativeBluetoothAdapterAndroid,
-            String address, Wrappers.BluetoothDeviceWrapper deviceWrapper, int rssi,
-            String[] advertisedUuids, int txPower, String[] serviceDataKeys,
+            String address, Wrappers.BluetoothDeviceWrapper deviceWrapper, String localName,
+            int rssi, String[] advertisedUuids, int txPower, String[] serviceDataKeys,
             Object[] serviceDataValues, int[] manufacturerDataKeys,
             Object[] manufacturerDataValues);
 
diff --git a/device/bluetooth/android/java/src/org/chromium/device/bluetooth/Wrappers.java b/device/bluetooth/android/java/src/org/chromium/device/bluetooth/Wrappers.java
index c8bf2f82..e46dd20 100644
--- a/device/bluetooth/android/java/src/org/chromium/device/bluetooth/Wrappers.java
+++ b/device/bluetooth/android/java/src/org/chromium/device/bluetooth/Wrappers.java
@@ -310,6 +310,10 @@
         public int getScanRecord_getTxPowerLevel() {
             return mScanResult.getScanRecord().getTxPowerLevel();
         }
+
+        public String getScanRecord_getDeviceName() {
+            return mScanResult.getScanRecord().getDeviceName();
+        }
     }
 
     /**
diff --git a/device/bluetooth/bluetooth_adapter.cc b/device/bluetooth/bluetooth_adapter.cc
index efac70e..d445aadb 100644
--- a/device/bluetooth/bluetooth_adapter.cc
+++ b/device/bluetooth/bluetooth_adapter.cc
@@ -5,6 +5,7 @@
 #include "device/bluetooth/bluetooth_adapter.h"
 
 #include <memory>
+#include <string>
 #include <utility>
 
 #include "base/bind.h"
diff --git a/device/bluetooth/bluetooth_adapter_android.cc b/device/bluetooth/bluetooth_adapter_android.cc
index 1b6b29cd..5b95e2d 100644
--- a/device/bluetooth/bluetooth_adapter_android.cc
+++ b/device/bluetooth/bluetooth_adapter_android.cc
@@ -5,6 +5,7 @@
 #include "device/bluetooth/bluetooth_adapter_android.h"
 
 #include <memory>
+#include <string>
 
 #include "base/android/jni_android.h"
 #include "base/android/jni_array.h"
@@ -167,6 +168,7 @@
     const JavaParamRef<jstring>& address,
     const JavaParamRef<jobject>&
         bluetooth_device_wrapper,  // Java Type: bluetoothDeviceWrapper
+    const JavaParamRef<jstring>& local_name,
     int32_t rssi,
     const JavaParamRef<jobjectArray>& advertised_uuids,  // Java Type: String[]
     int32_t tx_power,
@@ -175,7 +177,7 @@
     const JavaParamRef<jintArray>& manufacturer_data_keys,  // Java Type: int[]
     const JavaParamRef<jobjectArray>&
         manufacturer_data_values  // Java Type: byte[]
-    ) {
+) {
   std::string device_address = ConvertJavaStringToUTF8(env, address);
   auto iter = devices_.find(device_address);
 
@@ -235,13 +237,30 @@
 
   device_android->UpdateAdvertisementData(
       BluetoothDevice::ClampPower(rssi), base::nullopt /* flags */,
-      std::move(advertised_bluetooth_uuids),
+      advertised_bluetooth_uuids,
       // Android uses INT32_MIN to indicate no Advertised Tx Power.
       // https://developer.android.com/reference/android/bluetooth/le/ScanRecord.html#getTxPowerLevel()
       tx_power == INT32_MIN ? base::nullopt
                             : base::make_optional(clamped_tx_power),
       service_data_map, manufacturer_data_map);
 
+  for (auto& observer : observers_) {
+    base::Optional<std::string> device_name_opt = device_android->GetName();
+    base::Optional<std::string> advertisement_name_opt;
+    if (local_name)
+      advertisement_name_opt = ConvertJavaStringToUTF8(env, local_name);
+
+    observer.DeviceAdvertisementReceived(
+        device_android->GetAddress(), device_name_opt, advertisement_name_opt,
+        BluetoothDevice::ClampPower(rssi),
+        // Android uses INT32_MIN to indicate no Advertised Tx Power.
+        // https://developer.android.com/reference/android/bluetooth/le/ScanRecord.html#getTxPowerLevel()
+        tx_power == INT32_MIN ? base::nullopt
+                              : base::make_optional(clamped_tx_power),
+        base::nullopt, /* TODO(crbug.com/588083) Implement appearance */
+        advertised_bluetooth_uuids, service_data_map, manufacturer_data_map);
+  }
+
   if (is_new_device) {
     devices_[device_address] = std::move(device_android_owner);
     for (auto& observer : observers_)
diff --git a/device/bluetooth/bluetooth_adapter_android.h b/device/bluetooth/bluetooth_adapter_android.h
index 113f9c9..90ba958 100644
--- a/device/bluetooth/bluetooth_adapter_android.h
+++ b/device/bluetooth/bluetooth_adapter_android.h
@@ -92,6 +92,7 @@
       const base::android::JavaParamRef<jstring>& address,
       const base::android::JavaParamRef<jobject>&
           bluetooth_device_wrapper,  // Java Type: bluetoothDeviceWrapper
+      const base::android::JavaParamRef<jstring>& local_name,
       int32_t rssi,
       const base::android::JavaParamRef<jobjectArray>&
           advertised_uuids,  // Java Type: String[]
@@ -104,7 +105,7 @@
           manufacturer_data_keys,  // Java Type: int[]
       const base::android::JavaParamRef<jobjectArray>&
           manufacturer_data_values  // Java Type: byte[]
-      );
+  );
 
  protected:
   BluetoothAdapterAndroid();
diff --git a/device/bluetooth/bluetooth_adapter_unittest.cc b/device/bluetooth/bluetooth_adapter_unittest.cc
index 25c7256..9eaeeb7 100644
--- a/device/bluetooth/bluetooth_adapter_unittest.cc
+++ b/device/bluetooth/bluetooth_adapter_unittest.cc
@@ -1794,12 +1794,12 @@
 #endif  // defined(OS_MACOSX)
 
 #if defined(OS_WIN)
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     /* no prefix */,
     BluetoothTestWinrt,
     ::testing::Bool());
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     /* no prefix */,
     BluetoothTestWinrtOnly,
     ::testing::Values(true));
diff --git a/device/bluetooth/bluetooth_device_unittest.cc b/device/bluetooth/bluetooth_device_unittest.cc
index 5a21ab62..83a0e79 100644
--- a/device/bluetooth/bluetooth_device_unittest.cc
+++ b/device/bluetooth/bluetooth_device_unittest.cc
@@ -588,7 +588,7 @@
   EXPECT_EQ(ToInt8(TestTxPower::LOWEST), device->GetInquiryTxPower().value());
 }
 
-#if defined(OS_MACOSX)
+#if defined(OS_ANDROID) || defined(OS_MACOSX)
 // TODO(dougt) As I turn on new platforms for WebBluetooth Scanning,
 // I will relax this #ifdef
 TEST_F(BluetoothTest, DeviceAdvertisementReceived) {
diff --git a/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc b/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc
index 967f2dc..96fbe64 100644
--- a/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc
@@ -3743,17 +3743,17 @@
 #endif  // defined(OS_MACOSX)
 
 #if defined(OS_WIN)
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     /* no prefix */,
     BluetoothRemoteGattCharacteristicTestWinrt,
     ::testing::Bool());
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     /* no prefix */,
     BluetoothRemoteGattCharacteristicTestWin32Only,
     ::testing::Values(false));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     /* no prefix */,
     BluetoothRemoteGattCharacteristicTestWinrtOnly,
     ::testing::Values(true));
diff --git a/device/bluetooth/bluetooth_remote_gatt_descriptor_unittest.cc b/device/bluetooth/bluetooth_remote_gatt_descriptor_unittest.cc
index f1a79bf..9052dc7 100644
--- a/device/bluetooth/bluetooth_remote_gatt_descriptor_unittest.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_descriptor_unittest.cc
@@ -996,7 +996,7 @@
 #endif  // defined(OS_MACOSX)
 
 #if defined(OS_WIN)
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     /* no prefix */,
     BluetoothRemoteGattDescriptorTestWinrtOnly,
     ::testing::Values(true));
diff --git a/device/bluetooth/bluetooth_remote_gatt_service_unittest.cc b/device/bluetooth/bluetooth_remote_gatt_service_unittest.cc
index 9969ed5..591c1d5a 100644
--- a/device/bluetooth/bluetooth_remote_gatt_service_unittest.cc
+++ b/device/bluetooth/bluetooth_remote_gatt_service_unittest.cc
@@ -597,7 +597,7 @@
 #endif  // defined(OS_MACOSX)
 
 #if defined(OS_WIN)
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     /* no prefix */,
     BluetoothRemoteGattServiceTestWinrt,
     ::testing::Bool());
diff --git a/device/bluetooth/test/android/java/src/org/chromium/device/bluetooth/Fakes.java b/device/bluetooth/test/android/java/src/org/chromium/device/bluetooth/Fakes.java
index 856f947..5ded1ca 100644
--- a/device/bluetooth/test/android/java/src/org/chromium/device/bluetooth/Fakes.java
+++ b/device/bluetooth/test/android/java/src/org/chromium/device/bluetooth/Fakes.java
@@ -152,8 +152,8 @@
                     mFakeScanner.mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES,
                             new FakeScanResult(new FakeBluetoothDevice(this, "01:00:00:90:1E:BE",
                                                        "FakeBluetoothDevice"),
-                                    TestRSSI.LOWEST, uuids, TestTxPower.LOWEST, serviceData,
-                                    manufacturerData));
+                                    "FakeBluetoothDevice", TestRSSI.LOWEST, uuids,
+                                    TestTxPower.LOWEST, serviceData, manufacturerData));
                     break;
                 }
                 case 2: {
@@ -173,8 +173,8 @@
                     mFakeScanner.mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES,
                             new FakeScanResult(new FakeBluetoothDevice(this, "01:00:00:90:1E:BE",
                                                        "FakeBluetoothDevice"),
-                                    TestRSSI.LOWER, uuids, TestTxPower.LOWER, serviceData,
-                                    manufacturerData));
+                                    "Local Device Name", TestRSSI.LOWER, uuids, TestTxPower.LOWER,
+                                    serviceData, manufacturerData));
                     break;
                 }
                 case 3: {
@@ -182,7 +182,8 @@
                     mFakeScanner.mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES,
                             new FakeScanResult(
                                     new FakeBluetoothDevice(this, "01:00:00:90:1E:BE", ""),
-                                    TestRSSI.LOW, uuids, NO_TX_POWER, null, null));
+                                    "Local Device Name", TestRSSI.LOW, uuids, NO_TX_POWER, null,
+                                    null));
 
                     break;
                 }
@@ -191,7 +192,8 @@
                     mFakeScanner.mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES,
                             new FakeScanResult(
                                     new FakeBluetoothDevice(this, "02:00:00:8B:74:63", ""),
-                                    TestRSSI.MEDIUM, uuids, NO_TX_POWER, null, null));
+                                    "Local Device Name", TestRSSI.MEDIUM, uuids, NO_TX_POWER, null,
+                                    null));
 
                     break;
                 }
@@ -200,7 +202,8 @@
                     mFakeScanner.mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES,
                             new FakeScanResult(
                                     new FakeBluetoothDevice(this, "01:00:00:90:1E:BE", null),
-                                    TestRSSI.HIGH, uuids, NO_TX_POWER, null, null));
+                                    "Local Device Name", TestRSSI.HIGH, uuids, NO_TX_POWER, null,
+                                    null));
                     break;
                 }
                 case 6: {
@@ -208,7 +211,8 @@
                     mFakeScanner.mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES,
                             new FakeScanResult(
                                     new FakeBluetoothDevice(this, "02:00:00:8B:74:63", null),
-                                    TestRSSI.LOWEST, uuids, NO_TX_POWER, null, null));
+                                    "Local Device Name", TestRSSI.LOWEST, uuids, NO_TX_POWER, null,
+                                    null));
                     break;
                 }
                 case 7: {
@@ -222,7 +226,8 @@
                     mFakeScanner.mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES,
                             new FakeScanResult(new FakeBluetoothDevice(
                                                        this, "01:00:00:90:1E:BE", "U2F FakeDevice"),
-                                    TestRSSI.LOWEST, uuids, NO_TX_POWER, serviceData, null));
+                                    "Local Device Name", TestRSSI.LOWEST, uuids, NO_TX_POWER,
+                                    serviceData, null));
                     break;
                 }
             }
@@ -362,17 +367,19 @@
      */
     static class FakeScanResult extends Wrappers.ScanResultWrapper {
         private final FakeBluetoothDevice mDevice;
+        private final String mLocalName;
         private final int mRssi;
         private final int mTxPower;
         private final ArrayList<ParcelUuid> mUuids;
         private final Map<ParcelUuid, byte[]> mServiceData;
         private final SparseArray<byte[]> mManufacturerData;
 
-        FakeScanResult(FakeBluetoothDevice device, int rssi, ArrayList<ParcelUuid> uuids,
-                int txPower, Map<ParcelUuid, byte[]> serviceData,
+        FakeScanResult(FakeBluetoothDevice device, String localName, int rssi,
+                ArrayList<ParcelUuid> uuids, int txPower, Map<ParcelUuid, byte[]> serviceData,
                 SparseArray<byte[]> manufacturerData) {
             super(null);
             mDevice = device;
+            mLocalName = localName;
             mRssi = rssi;
             mUuids = uuids;
             mTxPower = txPower;
@@ -409,6 +416,11 @@
         public SparseArray<byte[]> getScanRecord_getManufacturerSpecificData() {
             return mManufacturerData;
         }
+
+        @Override
+        public String getScanRecord_getDeviceName() {
+            return mLocalName;
+        }
     }
 
     /**
diff --git a/gpu/command_buffer/common/gpu_memory_buffer_support.cc b/gpu/command_buffer/common/gpu_memory_buffer_support.cc
index e6bb4c7..48808be1 100644
--- a/gpu/command_buffer/common/gpu_memory_buffer_support.cc
+++ b/gpu/command_buffer/common/gpu_memory_buffer_support.cc
@@ -106,4 +106,18 @@
   return found ? gpu::GetPlatformSpecificTextureTarget() : GL_TEXTURE_2D;
 }
 
+GPU_EXPORT bool NativeBufferNeedsPlatformSpecificTextureTarget(
+    gfx::BufferFormat format) {
+#if defined(USE_OZONE)
+  // Always use GL_TEXTURE_2D as the target for RGB textures.
+  // https://crbug.com/916728
+  if (format == gfx::BufferFormat::RGBA_8888 ||
+      format == gfx::BufferFormat::RGBX_8888 ||
+      format == gfx::BufferFormat::BGRX_8888) {
+    return false;
+  }
+#endif
+  return true;
+}
+
 }  // namespace gpu
diff --git a/gpu/command_buffer/common/gpu_memory_buffer_support.h b/gpu/command_buffer/common/gpu_memory_buffer_support.h
index 3cd069b..c14b9e07 100644
--- a/gpu/command_buffer/common/gpu_memory_buffer_support.h
+++ b/gpu/command_buffer/common/gpu_memory_buffer_support.h
@@ -63,6 +63,11 @@
                                            gfx::BufferFormat format,
                                            const Capabilities& capabilities);
 
+// Returns whether a native GMB with the given format needs to be bound to the
+// platform-specfic texture target or GL_TEXTURE_2D.
+GPU_EXPORT bool NativeBufferNeedsPlatformSpecificTextureTarget(
+    gfx::BufferFormat format);
+
 }  // namespace gpu
 
 #endif  // GPU_COMMAND_BUFFER_COMMON_GPU_MEMORY_BUFFER_SUPPORT_H_
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.cc b/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.cc
index b60c7fbc..56090006 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_gl_texture.cc
@@ -792,15 +792,26 @@
     return nullptr;
   }
 
-  GLenum target = handle.type == gfx::SHARED_MEMORY_BUFFER
-                      ? GL_TEXTURE_2D
-                      : gpu::GetPlatformSpecificTextureTarget();
+  GLenum target =
+      (handle.type == gfx::SHARED_MEMORY_BUFFER ||
+       !NativeBufferNeedsPlatformSpecificTextureTarget(buffer_format))
+          ? GL_TEXTURE_2D
+          : gpu::GetPlatformSpecificTextureTarget();
   scoped_refptr<gl::GLImage> image = MakeGLImage(
       client_id, std::move(handle), buffer_format, surface_handle, size);
   if (!image) {
     LOG(ERROR) << "Failed to create image.";
     return nullptr;
   }
+  // If we decide to use GL_TEXTURE_2D at the target for a native buffer, we
+  // would like to verify that it will actually work. If the image expects to be
+  // copied, there is no way to do this verification here, because copying is
+  // done lazily after the SharedImage is created, so require that the image is
+  // bindable. Currently NativeBufferNeedsPlatformSpecificTextureTarget can
+  // only return false on Chrome OS where GLImageNativePixmap is used which is
+  // always bindable.
+  DCHECK(handle.type == gfx::SHARED_MEMORY_BUFFER || target != GL_TEXTURE_2D ||
+         image->ShouldBindOrCopy() == gl::GLImage::BIND);
   if (color_space.IsValid())
     image->SetColorSpace(color_space);
 
diff --git a/gpu/ipc/common/gpu_memory_buffer_impl_io_surface_unittest.cc b/gpu/ipc/common/gpu_memory_buffer_impl_io_surface_unittest.cc
index 44a53ae..2855e9f5 100644
--- a/gpu/ipc/common/gpu_memory_buffer_impl_io_surface_unittest.cc
+++ b/gpu/ipc/common/gpu_memory_buffer_impl_io_surface_unittest.cc
@@ -8,9 +8,9 @@
 namespace gpu {
 namespace {
 
-INSTANTIATE_TYPED_TEST_CASE_P(GpuMemoryBufferImplIOSurface,
-                              GpuMemoryBufferImplTest,
-                              GpuMemoryBufferImplIOSurface);
+INSTANTIATE_TYPED_TEST_SUITE_P(GpuMemoryBufferImplIOSurface,
+                               GpuMemoryBufferImplTest,
+                               GpuMemoryBufferImplIOSurface);
 
 }  // namespace
 }  // namespace gpu
diff --git a/gpu/ipc/host/gpu_memory_buffer_support.cc b/gpu/ipc/host/gpu_memory_buffer_support.cc
index d35beb1..af8eb2a 100644
--- a/gpu/ipc/host/gpu_memory_buffer_support.cc
+++ b/gpu/ipc/host/gpu_memory_buffer_support.cc
@@ -88,6 +88,8 @@
 
 bool GetImageNeedsPlatformSpecificTextureTarget(gfx::BufferFormat format,
                                                 gfx::BufferUsage usage) {
+  if (!NativeBufferNeedsPlatformSpecificTextureTarget(format))
+    return false;
 #if defined(USE_OZONE) || defined(OS_MACOSX) || defined(OS_WIN) || \
     defined(OS_ANDROID)
   GpuMemoryBufferSupport support;
diff --git a/headless/test/data/protocol/emulation/virtual-time-dialog-while-loading-expected.txt b/headless/test/data/protocol/emulation/virtual-time-dialog-while-loading-expected.txt
new file mode 100644
index 0000000..6ed18a3
--- /dev/null
+++ b/headless/test/data/protocol/emulation/virtual-time-dialog-while-loading-expected.txt
@@ -0,0 +1 @@
+Tests that virtual time pausing during loading of main resource works correctly when dialog is shown while page loads.
diff --git a/headless/test/data/protocol/emulation/virtual-time-dialog-while-loading.js b/headless/test/data/protocol/emulation/virtual-time-dialog-while-loading.js
new file mode 100644
index 0000000..1b8d1fd
--- /dev/null
+++ b/headless/test/data/protocol/emulation/virtual-time-dialog-while-loading.js
@@ -0,0 +1,37 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+const server = new Map([
+  ['http://test.com/index.html',
+     '<html><body><script>alert("No pasarán!");</script></body></html>']]);
+
+(async function(testRunner) {
+  var {page, session, dp} = await testRunner.startBlank(
+      'Tests that virtual time pausing during loading of main resource ' +
+      'works correctly when dialog is shown while page loads.');
+  await dp.Network.enable();
+  await dp.Network.setRequestInterception({ patterns: [{ urlPattern: '*' }] });
+  dp.Network.onRequestIntercepted(event => {
+    let body = server.get(event.params.request.url);
+    dp.Network.continueInterceptedRequest({
+      interceptionId: event.params.interceptionId,
+      rawResponse: btoa(body)
+    });
+  });
+
+  dp.Page.onJavascriptDialogOpening(event => {
+    dp.Page.handleJavaScritpDialog({accept: true});
+  });
+
+  dp.Emulation.onVirtualTimeBudgetExpired(async data => {
+    testRunner.log(await session.evaluate('document.title'));
+    testRunner.completeTest();
+  });
+
+  await dp.Emulation.setVirtualTimePolicy({policy: 'pause'});
+  await dp.Emulation.setVirtualTimePolicy({
+      policy: 'pauseIfNetworkFetchesPending', budget: 5000,
+      waitForNavigation: true});
+  await dp.Page.navigate({url: 'http://test.com/index.html'});
+})
diff --git a/headless/test/headless_protocol_browsertest.cc b/headless/test/headless_protocol_browsertest.cc
index 5507899..1bd21fa 100644
--- a/headless/test/headless_protocol_browsertest.cc
+++ b/headless/test/headless_protocol_browsertest.cc
@@ -231,6 +231,8 @@
                        "emulation/virtual-time-error-loop.js");
 HEADLESS_PROTOCOL_TEST(VirtualTimeFetchStream,
                        "emulation/virtual-time-fetch-stream.js");
+HEADLESS_PROTOCOL_TEST(VirtualTimeDialogWhileLoading,
+                       "emulation/virtual-time-dialog-while-loading.js");
 
 // Flaky Test crbug.com/859382
 HEADLESS_PROTOCOL_TEST(DISABLED_VirtualTimeHistoryNavigation,
diff --git a/ios/chrome/browser/infobars/BUILD.gn b/ios/chrome/browser/infobars/BUILD.gn
index 1329252d..8cffdfb 100644
--- a/ios/chrome/browser/infobars/BUILD.gn
+++ b/ios/chrome/browser/infobars/BUILD.gn
@@ -26,7 +26,7 @@
     "//components/translate/core/browser",
     "//ios/chrome/browser",
     "//ios/chrome/browser/ui/infobars:infobars_ui",
-    "//ios/chrome/browser/ui/infobars/confirm_infobar",
+    "//ios/chrome/browser/ui/infobars/banners",
     "//ios/web",
     "//ui/base",
     "//ui/gfx",
diff --git a/ios/chrome/browser/infobars/infobar_utils.mm b/ios/chrome/browser/infobars/infobar_utils.mm
index 5ac2013..de274e6 100644
--- a/ios/chrome/browser/infobars/infobar_utils.mm
+++ b/ios/chrome/browser/infobars/infobar_utils.mm
@@ -11,7 +11,7 @@
 #include "ios/chrome/browser/experimental_flags.h"
 #include "ios/chrome/browser/infobars/confirm_infobar_controller.h"
 #include "ios/chrome/browser/infobars/infobar.h"
-#import "ios/chrome/browser/ui/infobars/confirm_infobar/confirm_infobar_view_controller.h"
+#import "ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -20,8 +20,8 @@
 std::unique_ptr<infobars::InfoBar> CreateConfirmInfoBar(
     std::unique_ptr<ConfirmInfoBarDelegate> delegate) {
   if (experimental_flags::IsInfobarUIRebootEnabled()) {
-    ConfirmInfobarViewController* controller =
-        [[ConfirmInfobarViewController alloc]
+    InfobarBannerViewController* controller =
+        [[InfobarBannerViewController alloc]
             initWithInfoBarDelegate:delegate.get()];
     return std::make_unique<InfoBarIOS>(controller, std::move(delegate));
   } else {
diff --git a/ios/chrome/browser/ui/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view_controller.mm
index 39b32d5..213b8db 100644
--- a/ios/chrome/browser/ui/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view_controller.mm
@@ -2845,8 +2845,12 @@
   id nativeController =
       [self nativeControllerForTab:[self.tabModel currentTab]];
   if ([nativeController conformsToProtocol:@protocol(NewTabPageOwning)] &&
-      [nativeController respondsToSelector:@selector(scrollOffset)]) {
-    return [nativeController scrollOffset].y == 0;
+      [nativeController respondsToSelector:@selector(contentOffset)]) {
+    CGFloat scrolledToTopOffset =
+        [nativeController respondsToSelector:@selector(contentInset)]
+            ? [nativeController contentInset].top
+            : 0.0;
+    return [nativeController contentOffset].y == scrolledToTopOffset;
   }
 
   CRWWebViewScrollViewProxy* scrollProxy =
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
index cbe6bf4..37be9d31 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
@@ -281,7 +281,11 @@
   [self.NTPMediator dismissModals];
 }
 
-- (CGPoint)scrollOffset {
+- (UIEdgeInsets)contentInset {
+  return self.suggestionsViewController.collectionView.contentInset;
+}
+
+- (CGPoint)contentOffset {
   CGPoint collectionOffset =
       self.suggestionsViewController.collectionView.contentOffset;
   collectionOffset.y -=
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.mm b/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.mm
index 37ab0e7..a04fd368 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.mm
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.mm
@@ -573,12 +573,14 @@
   web::NavigationManager* manager = webState->GetNavigationManager();
   web::NavigationItem* item = manager->GetLastCommittedItem();
   web::PageDisplayState displayState;
-  CGPoint scrollOffset =
+  UIEdgeInsets contentInset =
+      self.suggestionsViewController.collectionView.contentInset;
+  CGPoint contentOffset =
       self.suggestionsViewController.collectionView.contentOffset;
-  scrollOffset.y -=
+  contentOffset.y -=
       self.headerCollectionInteractionHandler.collectionShiftingOffset;
-  displayState.scroll_state().set_offset_x(scrollOffset.x);
-  displayState.scroll_state().set_offset_y(scrollOffset.y);
+  displayState.scroll_state() =
+      web::PageScrollState(contentOffset, contentInset);
   item->SetPageDisplayState(displayState);
 }
 
@@ -589,10 +591,10 @@
   }
   web::NavigationManager* navigationManager = webState->GetNavigationManager();
   web::NavigationItem* item = navigationManager->GetVisibleItem();
-  if (item && item->GetPageDisplayState().scroll_state().offset_y() > 0) {
-    CGFloat offset = item->GetPageDisplayState().scroll_state().offset_y();
+  CGFloat offset =
+      item ? item->GetPageDisplayState().scroll_state().content_offset().y : 0;
+  if (offset > 0)
     [self.suggestionsViewController setContentOffset:offset];
-  }
 }
 
 @end
diff --git a/ios/chrome/browser/ui/dialogs/BUILD.gn b/ios/chrome/browser/ui/dialogs/BUILD.gn
index f992244..4f054c6 100644
--- a/ios/chrome/browser/ui/dialogs/BUILD.gn
+++ b/ios/chrome/browser/ui/dialogs/BUILD.gn
@@ -110,6 +110,7 @@
     "//base",
     "//base/test:test_support",
     "//components/strings",
+    "//components/url_formatter",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/ui",
     "//ios/chrome/test/app:test_support",
diff --git a/ios/chrome/browser/ui/dialogs/dialog_presenter.mm b/ios/chrome/browser/ui/dialogs/dialog_presenter.mm
index 4b3d5bc..a60d95b5 100644
--- a/ios/chrome/browser/ui/dialogs/dialog_presenter.mm
+++ b/ios/chrome/browser/ui/dialogs/dialog_presenter.mm
@@ -9,8 +9,8 @@
 #include "base/containers/circular_deque.h"
 #import "base/ios/block_types.h"
 #include "base/logging.h"
-#include "base/strings/sys_string_conversions.h"
 #include "components/strings/grit/components_strings.h"
+#include "components/url_formatter/elide_url.h"
 #import "ios/chrome/browser/ui/alert_coordinator/action_sheet_coordinator.h"
 #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
 #import "ios/chrome/browser/ui/alert_coordinator/input_alert_coordinator.h"
@@ -376,19 +376,14 @@
 + (NSString*)localizedTitleForJavaScriptAlertFromPage:(const GURL&)pageURL
                                          mainFrameURL:
                                              (const GURL&)mainFrameURL {
-  NSString* localizedTitle = nil;
-  NSString* hostname = base::SysUTF8ToNSString(pageURL.host());
-
   bool sameOriginAsMainFrame = pageURL.GetOrigin() == mainFrameURL.GetOrigin();
-
   if (!sameOriginAsMainFrame) {
-    localizedTitle = l10n_util::GetNSString(
+    return l10n_util::GetNSString(
         IDS_JAVASCRIPT_MESSAGEBOX_TITLE_NONSTANDARD_URL_IFRAME);
-  } else {
-    localizedTitle = l10n_util::GetNSStringF(
-        IDS_JAVASCRIPT_MESSAGEBOX_TITLE, base::SysNSStringToUTF16(hostname));
   }
-  return localizedTitle;
+  base::string16 title = url_formatter::FormatUrlForSecurityDisplay(
+      pageURL, url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS);
+  return l10n_util::GetNSStringF(IDS_JAVASCRIPT_MESSAGEBOX_TITLE, title);
 }
 
 #pragma mark - Private methods.
diff --git a/ios/chrome/browser/ui/dialogs/dialog_presenter_unittest.mm b/ios/chrome/browser/ui/dialogs/dialog_presenter_unittest.mm
index 5fda247..51906f8 100644
--- a/ios/chrome/browser/ui/dialogs/dialog_presenter_unittest.mm
+++ b/ios/chrome/browser/ui/dialogs/dialog_presenter_unittest.mm
@@ -5,6 +5,7 @@
 #import "ios/chrome/browser/ui/dialogs/dialog_presenter.h"
 
 #include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
 #include "components/strings/grit/components_strings.h"
 #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
@@ -138,6 +139,23 @@
   EXPECT_NSEQ(expected_title, different_origin_title);
 }
 
+// Tests that JavaScript dialogs have correct title when they are presented from
+// about:blank page.
+TEST_F(DialogPresenterTest, AboutBlankTest) {
+  DialogPresenterTestWebState web_state;
+  web_state.SetCurrentURL(GURL(url::kAboutBlankURL));
+  [presenter() runJavaScriptAlertPanelWithMessage:@""
+                                       requestURL:GURL(url::kAboutBlankURL)
+                                         webState:&web_state
+                                completionHandler:nil];
+
+  NSString* expected_title = l10n_util::GetNSStringF(
+      IDS_JAVASCRIPT_MESSAGEBOX_TITLE, base::UTF8ToUTF16(url::kAboutBlankURL));
+  NSString* actual_title =
+      [presenter() presentedDialogCoordinator].alertController.title;
+  EXPECT_NSEQ(expected_title, actual_title);
+}
+
 // Tests that multiple JavaScript dialogs are queued
 TEST_F(DialogPresenterTest, QueueTest) {
   // Tests that the dialog for |webState1| has been shown.
diff --git a/ios/chrome/browser/ui/dialogs/javascript_dialog_egtest.mm b/ios/chrome/browser/ui/dialogs/javascript_dialog_egtest.mm
index c1717724..6b52a38c 100644
--- a/ios/chrome/browser/ui/dialogs/javascript_dialog_egtest.mm
+++ b/ios/chrome/browser/ui/dialogs/javascript_dialog_egtest.mm
@@ -12,6 +12,7 @@
 #include "base/strings/utf_string_conversions.h"
 #import "base/test/ios/wait_util.h"
 #include "components/strings/grit/components_strings.h"
+#include "components/url_formatter/elide_url.h"
 #import "ios/chrome/browser/ui/dialogs/dialog_presenter.h"
 #include "ios/chrome/browser/ui/util/ui_util.h"
 #include "ios/chrome/grit/ios_strings.h"
@@ -233,10 +234,10 @@
 
 // Waits for a JavaScript dialog to be shown from the page at |url|.
 void WaitForJavaScriptDialogToBeShown(const GURL& url) {
-  NSString* hostname = base::SysUTF8ToNSString(url.host());
-  NSString* expectedTitle = l10n_util::GetNSStringF(
-      IDS_JAVASCRIPT_MESSAGEBOX_TITLE, base::SysNSStringToUTF16(hostname));
-
+  base::string16 URLString = url_formatter::FormatUrlForSecurityDisplay(
+      url, url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS);
+  NSString* expectedTitle =
+      l10n_util::GetNSStringF(IDS_JAVASCRIPT_MESSAGEBOX_TITLE, URLString);
   WaitForAlertToBeShown(expectedTitle);
 }
 
@@ -259,9 +260,10 @@
 void AssertJavaScriptAlertNotPresent(const GURL& url) {
   ConditionBlock condition = ^{
     NSError* error = nil;
-    NSString* hostname = base::SysUTF8ToNSString(url.host());
-    NSString* expectedTitle = l10n_util::GetNSStringF(
-        IDS_JAVASCRIPT_MESSAGEBOX_TITLE, base::SysNSStringToUTF16(hostname));
+    base::string16 URLString = url_formatter::FormatUrlForSecurityDisplay(
+        url, url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS);
+    NSString* expectedTitle =
+        l10n_util::GetNSStringF(IDS_JAVASCRIPT_MESSAGEBOX_TITLE, URLString);
 
     id<GREYMatcher> titleLabel =
         chrome_test_util::StaticTextWithAccessibilityLabel(expectedTitle);
@@ -608,11 +610,7 @@
   }
 
   // Wait for the alert to be shown.
-  NSString* hostname = base::SysUTF8ToNSString(self.onLoadPageURL.host());
-  NSString* expectedTitle = l10n_util::GetNSStringF(
-      IDS_JAVASCRIPT_MESSAGEBOX_TITLE, base::SysNSStringToUTF16(hostname));
-
-  WaitForAlertToBeShown(expectedTitle);
+  WaitForJavaScriptDialogToBeShown(self.onLoadPageURL);
 
   // Verify that the omnibox shows the correct URL when the dialog is visible.
   std::string title =
diff --git a/ios/chrome/browser/ui/infobars/BUILD.gn b/ios/chrome/browser/ui/infobars/BUILD.gn
index 2de84bac..a157317 100644
--- a/ios/chrome/browser/ui/infobars/BUILD.gn
+++ b/ios/chrome/browser/ui/infobars/BUILD.gn
@@ -20,6 +20,7 @@
     "//ios/chrome/browser/ui/authentication",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/coordinators:chrome_coordinators",
+    "//ios/chrome/browser/ui/infobars/presentation",
     "//ios/chrome/browser/ui/settings/sync_utils",
     "//ios/chrome/browser/ui/signin_interaction/public",
     "//ios/chrome/browser/ui/translate",
diff --git a/ios/chrome/browser/ui/infobars/confirm_infobar/BUILD.gn b/ios/chrome/browser/ui/infobars/banners/BUILD.gn
similarity index 62%
rename from ios/chrome/browser/ui/infobars/confirm_infobar/BUILD.gn
rename to ios/chrome/browser/ui/infobars/banners/BUILD.gn
index b349587..6f9f657 100644
--- a/ios/chrome/browser/ui/infobars/confirm_infobar/BUILD.gn
+++ b/ios/chrome/browser/ui/infobars/banners/BUILD.gn
@@ -1,12 +1,12 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
+# 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.
 
-source_set("confirm_infobar") {
+source_set("banners") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
-    "confirm_infobar_view_controller.h",
-    "confirm_infobar_view_controller.mm",
+    "infobar_banner_view_controller.h",
+    "infobar_banner_view_controller.mm",
   ]
   deps = [
     "//base",
diff --git a/ios/chrome/browser/ui/infobars/confirm_infobar/confirm_infobar_view_controller.h b/ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller.h
similarity index 67%
rename from ios/chrome/browser/ui/infobars/confirm_infobar/confirm_infobar_view_controller.h
rename to ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller.h
index 001d5bc5..05f40005 100644
--- a/ios/chrome/browser/ui/infobars/confirm_infobar/confirm_infobar_view_controller.h
+++ b/ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller.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 IOS_CHROME_BROWSER_UI_INFOBARS_CONFIRM_INFOBAR_CONFIRM_INFOBAR_VIEW_CONTROLLER_H_
-#define IOS_CHROME_BROWSER_UI_INFOBARS_CONFIRM_INFOBAR_CONFIRM_INFOBAR_VIEW_CONTROLLER_H_
+#ifndef IOS_CHROME_BROWSER_UI_INFOBARS_BANNERS_INFOBAR_BANNER_VIEW_CONTROLLER_H_
+#define IOS_CHROME_BROWSER_UI_INFOBARS_BANNERS_INFOBAR_BANNER_VIEW_CONTROLLER_H_
 
 #import <UIKit/UIKit.h>
 
@@ -13,7 +13,7 @@
 
 // TODO(crbug.com/911864): PLACEHOLDER Work in Progress class for the new
 // InfobarUI.
-@interface ConfirmInfobarViewController : UIViewController <InfobarUIDelegate>
+@interface InfobarBannerViewController : UIViewController <InfobarUIDelegate>
 
 - (instancetype)initWithInfoBarDelegate:(ConfirmInfoBarDelegate*)infoBarDelegate
     NS_DESIGNATED_INITIALIZER;
@@ -25,4 +25,4 @@
 
 @end
 
-#endif  // IOS_CHROME_BROWSER_UI_INFOBARS_CONFIRM_INFOBAR_CONFIRM_INFOBAR_VIEW_CONTROLLER_H_
+#endif  // IOS_CHROME_BROWSER_UI_INFOBARS_BANNERS_INFOBAR_BANNER_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller.mm b/ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller.mm
new file mode 100644
index 0000000..15a0a90b
--- /dev/null
+++ b/ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller.mm
@@ -0,0 +1,123 @@
+// 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 "ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller.h"
+
+#include "base/strings/sys_string_conversions.h"
+#include "components/infobars/core/confirm_infobar_delegate.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface InfobarBannerViewController ()
+
+@property(nonatomic, readonly) ConfirmInfoBarDelegate* infoBarDelegate;
+@property(nonatomic, assign) CGPoint originalCenter;
+
+@end
+
+// TODO(crbug.com/1372916): PLACEHOLDER Work in Progress class for the new
+// InfobarUI.
+@implementation InfobarBannerViewController
+@synthesize delegate = _delegate;
+
+- (instancetype)initWithInfoBarDelegate:
+    (ConfirmInfoBarDelegate*)infoBarDelegate {
+  self = [super initWithNibName:nil bundle:nil];
+  if (self) {
+    _infoBarDelegate = infoBarDelegate;
+  }
+  return self;
+}
+
+#pragma mark - View Lifecycle
+
+- (void)viewDidLoad {
+  [super viewDidLoad];
+
+  NSString* messageText =
+      base::SysUTF16ToNSString(self.infoBarDelegate->GetMessageText());
+  UILabel* messageLabel = [[UILabel alloc] init];
+  messageLabel.text = messageText;
+  messageLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
+  messageLabel.adjustsFontForContentSizeCategory = YES;
+  messageLabel.textColor = [UIColor blackColor];
+  messageLabel.translatesAutoresizingMaskIntoConstraints = NO;
+  messageLabel.numberOfLines = 0;
+
+  NSString* buttonText = base::SysUTF16ToNSString(
+      self.infoBarDelegate->GetButtonLabel(ConfirmInfoBarDelegate::BUTTON_OK));
+  UIButton* infobarButton = [UIButton buttonWithType:UIButtonTypeSystem];
+  [infobarButton setTitle:buttonText forState:UIControlStateNormal];
+  [infobarButton addTarget:self
+                    action:@selector(buttonTapped:)
+          forControlEvents:UIControlEventTouchUpInside];
+  infobarButton.translatesAutoresizingMaskIntoConstraints = NO;
+
+  UIPanGestureRecognizer* panGestureRecognizer =
+      [[UIPanGestureRecognizer alloc] init];
+  [panGestureRecognizer addTarget:self action:@selector(handlePanGesture:)];
+  [panGestureRecognizer setMaximumNumberOfTouches:1];
+  [self.view addGestureRecognizer:panGestureRecognizer];
+
+  [self.view addSubview:messageLabel];
+  [self.view addSubview:infobarButton];
+  self.view.backgroundColor = [UIColor lightGrayColor];
+
+  [NSLayoutConstraint activateConstraints:@[
+    [messageLabel.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor
+                                               constant:30],
+    [messageLabel.trailingAnchor
+        constraintEqualToAnchor:infobarButton.leadingAnchor],
+    [messageLabel.centerYAnchor
+        constraintEqualToAnchor:self.view.centerYAnchor],
+    [infobarButton.centerYAnchor
+        constraintEqualToAnchor:self.view.centerYAnchor],
+    [infobarButton.trailingAnchor
+        constraintEqualToAnchor:self.view.trailingAnchor],
+    [infobarButton.widthAnchor constraintEqualToConstant:100]
+  ]];
+}
+
+#pragma mark - InfobarUIDelegate
+
+- (void)removeView {
+  [self dismissViewControllerAnimated:YES completion:nil];
+}
+
+- (void)detachView {
+  [self dismissViewControllerAnimated:YES completion:nil];
+}
+
+#pragma mark - Private Methods
+
+- (void)buttonTapped:(id)sender {
+  [self dismissViewControllerAnimated:YES completion:nil];
+}
+
+- (void)handlePanGesture:(UIPanGestureRecognizer*)gesture {
+  CGPoint translation = [gesture translationInView:self.view];
+
+  if (gesture.state == UIGestureRecognizerStateBegan) {
+    self.originalCenter = self.view.center;
+
+  } else if (gesture.state == UIGestureRecognizerStateChanged) {
+    self.view.center =
+        CGPointMake(self.view.center.x, self.view.center.y + translation.y);
+  }
+
+  if (gesture.state == UIGestureRecognizerStateEnded ||
+      gesture.state == UIGestureRecognizerStateCancelled) {
+    if (self.view.center.y > self.originalCenter.y) {
+      self.view.center = self.originalCenter;
+    } else {
+      [self dismissViewControllerAnimated:YES completion:nil];
+    }
+  }
+
+  [gesture setTranslation:CGPointZero inView:self.view];
+}
+
+@end
diff --git a/ios/chrome/browser/ui/infobars/confirm_infobar/confirm_infobar_view_controller.mm b/ios/chrome/browser/ui/infobars/confirm_infobar/confirm_infobar_view_controller.mm
deleted file mode 100644
index c935b75c..0000000
--- a/ios/chrome/browser/ui/infobars/confirm_infobar/confirm_infobar_view_controller.mm
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/ui/infobars/confirm_infobar/confirm_infobar_view_controller.h"
-
-#include "base/strings/sys_string_conversions.h"
-#include "components/infobars/core/confirm_infobar_delegate.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@interface ConfirmInfobarViewController ()
-
-@property(nonatomic, readonly) ConfirmInfoBarDelegate* infoBarDelegate;
-
-@end
-
-// TODO(crbug.com/1372916): PLACEHOLDER Work in Progress class for the new
-// InfobarUI.
-@implementation ConfirmInfobarViewController
-@synthesize delegate = _delegate;
-
-- (instancetype)initWithInfoBarDelegate:
-    (ConfirmInfoBarDelegate*)infoBarDelegate {
-  self = [super initWithNibName:nil bundle:nil];
-  if (self) {
-    _infoBarDelegate = infoBarDelegate;
-  }
-  return self;
-}
-
-#pragma mark - View Lifecycle
-
-- (void)viewDidLoad {
-  [super viewDidLoad];
-  base::string16 messageText = self.infoBarDelegate->GetMessageText();
-  NSString* message = base::SysUTF16ToNSString(messageText);
-
-  UILabel* messageLabel = [[UILabel alloc] init];
-  messageLabel.text = message;
-  messageLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleTitle1];
-  messageLabel.adjustsFontForContentSizeCategory = YES;
-  messageLabel.textColor = [UIColor blackColor];
-  messageLabel.translatesAutoresizingMaskIntoConstraints = NO;
-
-  [self.view addSubview:messageLabel];
-  self.view.backgroundColor = [UIColor grayColor];
-
-  [NSLayoutConstraint activateConstraints:@[
-    [messageLabel.leadingAnchor
-        constraintEqualToAnchor:self.view.leadingAnchor],
-    [messageLabel.topAnchor constraintEqualToAnchor:self.view.topAnchor],
-    [messageLabel.heightAnchor constraintEqualToConstant:30],
-    [messageLabel.widthAnchor constraintEqualToConstant:200],
-  ]];
-}
-
-#pragma mark - InfobarUIDelegate
-
-- (void)removeView {
-  // TO-DO
-}
-
-- (void)detachView {
-  // TO-DO
-}
-
-@end
diff --git a/ios/chrome/browser/ui/infobars/infobar_container_coordinator.mm b/ios/chrome/browser/ui/infobars/infobar_container_coordinator.mm
index 799b86c..9d4c2e5e 100644
--- a/ios/chrome/browser/ui/infobars/infobar_container_coordinator.mm
+++ b/ios/chrome/browser/ui/infobars/infobar_container_coordinator.mm
@@ -13,6 +13,8 @@
 #include "ios/chrome/browser/ui/infobars/infobar_container_view_controller.h"
 #import "ios/chrome/browser/ui/infobars/infobar_positioner.h"
 #include "ios/chrome/browser/ui/infobars/legacy_infobar_container_view_controller.h"
+#import "ios/chrome/browser/ui/infobars/presentation/infobar_banner_animator.h"
+#import "ios/chrome/browser/ui/infobars/presentation/infobar_banner_presentation_controller.h"
 #import "ios/chrome/browser/ui/signin_interaction/public/signin_presenter.h"
 #include "ios/chrome/browser/upgrade/upgrade_center.h"
 
@@ -20,7 +22,10 @@
 #error "This file requires ARC support."
 #endif
 
-@interface InfobarContainerCoordinator () <SigninPresenter>
+@interface InfobarContainerCoordinator () <
+    InfobarContainerPresenter,
+    UIViewControllerTransitioningDelegate,
+    SigninPresenter>
 
 @property(nonatomic, assign) TabModel* tabModel;
 
@@ -52,13 +57,10 @@
 
   // Create and setup the ViewController.
   if (experimental_flags::IsInfobarUIRebootEnabled()) {
-    self.containerViewController =
+    InfobarContainerViewController* container =
         [[InfobarContainerViewController alloc] init];
-    [self.baseViewController
-        addChildViewController:self.containerViewController];
-    [self.baseViewController.view addSubview:self.containerViewController.view];
-    [self.containerViewController
-        didMoveToParentViewController:self.baseViewController];
+    container.presenter = self;
+    self.containerViewController = container;
   } else {
     LegacyInfobarContainerViewController* legacyContainer =
         [[LegacyInfobarContainerViewController alloc] init];
@@ -108,6 +110,51 @@
   return NO;
 }
 
+#pragma mark - InfobarContainerPresenter
+
+- (void)presentInfobarContainer {
+  // Should only be called in the new UI implementation.
+  DCHECK(experimental_flags::IsInfobarUIRebootEnabled());
+
+  self.containerViewController.transitioningDelegate = self;
+  [self.containerViewController
+      setModalPresentationStyle:UIModalPresentationCustom];
+  [self.baseViewController presentViewController:self.containerViewController
+                                        animated:YES
+                                      completion:nil];
+}
+
+#pragma mark - UIViewControllerTransitioningDelegate
+
+- (UIPresentationController*)
+    presentationControllerForPresentedViewController:
+        (UIViewController*)presented
+                            presentingViewController:
+                                (UIViewController*)presenting
+                                sourceViewController:(UIViewController*)source {
+  InfobarBannerPresentationController* presentationController =
+      [[InfobarBannerPresentationController alloc]
+          initWithPresentedViewController:presented
+                 presentingViewController:presenting];
+  return presentationController;
+}
+
+- (id<UIViewControllerAnimatedTransitioning>)
+    animationControllerForPresentedController:(UIViewController*)presented
+                         presentingController:(UIViewController*)presenting
+                             sourceController:(UIViewController*)source {
+  InfobarBannerAnimator* animator = [[InfobarBannerAnimator alloc] init];
+  animator.presenting = YES;
+  return animator;
+}
+
+- (id<UIViewControllerAnimatedTransitioning>)
+    animationControllerForDismissedController:(UIViewController*)dismissed {
+  InfobarBannerAnimator* animator = [[InfobarBannerAnimator alloc] init];
+  animator.presenting = NO;
+  return animator;
+}
+
 #pragma mark - SigninPresenter
 
 - (void)showSignin:(ShowSigninCommand*)command {
diff --git a/ios/chrome/browser/ui/infobars/infobar_container_view_controller.h b/ios/chrome/browser/ui/infobars/infobar_container_view_controller.h
index e3a2bd774..ca38aff 100644
--- a/ios/chrome/browser/ui/infobars/infobar_container_view_controller.h
+++ b/ios/chrome/browser/ui/infobars/infobar_container_view_controller.h
@@ -9,11 +9,24 @@
 
 #import "ios/chrome/browser/ui/infobars/infobar_container_consumer.h"
 
+// TODO(crbug.com/1372916): PLACEHOLDER Work in Progress protocol for the new
+// InfobarUI.
+// Manages the InfobarContainer presentation.
+@protocol InfobarContainerPresenter
+
+// Presents the InfobarContainerViewController.
+- (void)presentInfobarContainer;
+
+@end
+
 // TODO(crbug.com/1372916): PLACEHOLDER Work in Progress class for the new
 // InfobarUI. ViewController that contains all Infobars.
 @interface InfobarContainerViewController
     : UIViewController <InfobarContainerConsumer>
 
+// Delegate to present this ViewController.
+@property(nonatomic, strong) id<InfobarContainerPresenter> presenter;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_INFOBARS_INFOBAR_CONTAINER_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/infobars/infobar_container_view_controller.mm b/ios/chrome/browser/ui/infobars/infobar_container_view_controller.mm
index efb5197..cf07408d 100644
--- a/ios/chrome/browser/ui/infobars/infobar_container_view_controller.mm
+++ b/ios/chrome/browser/ui/infobars/infobar_container_view_controller.mm
@@ -6,7 +6,6 @@
 
 #include "base/ios/block_types.h"
 #include "base/logging.h"
-#import "ios/chrome/browser/ui/util/rtl_geometry.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -14,34 +13,48 @@
 
 // TODO(crbug.com/1372916): PLACEHOLDER Work in Progress class for the new
 // InfobarUI.
+@interface InfobarContainerViewController ()
+
+@property(nonatomic, strong) NSMutableArray<UIViewController*>* currentInfobars;
+
+@end
+
 @implementation InfobarContainerViewController
 
+#pragma mark - UIViewController
+
 - (void)viewDidLoad {
-  self.view = [[UIView alloc] initWithFrame:CGRectMake(20, 100, 360, 75)];
+  self.view = [[UIView alloc] initWithFrame:CGRectZero];
 }
 
 #pragma mark - InfobarConsumer
 
 - (void)addInfoBarWithDelegate:(id<InfobarUIDelegate>)infoBarDelegate
                       position:(NSInteger)position {
-  UIViewController* infoBarViewController =
+  UIViewController* infobarViewController =
       static_cast<UIViewController*>(infoBarDelegate);
 
-  [self addChildViewController:infoBarViewController];
-  [self.view addSubview:infoBarViewController.view];
-  infoBarViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
-  [infoBarViewController didMoveToParentViewController:self];
-
+  [self addChildViewController:infobarViewController];
+  [self.view addSubview:infobarViewController.view];
+  [infobarViewController didMoveToParentViewController:self];
+  infobarViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
   [NSLayoutConstraint activateConstraints:@[
-    [infoBarViewController.view.leadingAnchor
+    [infobarViewController.view.topAnchor
+        constraintEqualToAnchor:self.view.topAnchor
+                       constant:10],
+    [infobarViewController.view.bottomAnchor
+        constraintEqualToAnchor:self.view.bottomAnchor],
+    [infobarViewController.view.leadingAnchor
         constraintEqualToAnchor:self.view.leadingAnchor],
-    [infoBarViewController.view.trailingAnchor
-        constraintEqualToAnchor:self.view.trailingAnchor],
-    [infoBarViewController.view.topAnchor
-        constraintEqualToAnchor:self.view.topAnchor],
-    [infoBarViewController.view.bottomAnchor
-        constraintEqualToAnchor:self.view.bottomAnchor]
+    [infobarViewController.view.trailingAnchor
+        constraintEqualToAnchor:self.view.trailingAnchor]
   ]];
+
+  if (![self.currentInfobars count]) {
+    [self.presenter presentInfobarContainer];
+  }
+
+  [self.currentInfobars addObject:infobarViewController];
 }
 
 - (void)setUserInteractionEnabled:(BOOL)enabled {
@@ -49,8 +62,17 @@
 }
 
 - (void)updateLayoutAnimated:(BOOL)animated {
-  // NO-OP - This shouldn't be need in the new UI since we use autolayout for
+  // NO-OP - This shouldn't be needed in the new UI since we use autolayout for
   // the contained Infobars.
 }
 
+#pragma mark - Private Methods
+
+- (NSMutableArray<UIViewController*>*)currentInfobars {
+  if (!_currentInfobars) {
+    _currentInfobars = [[NSMutableArray alloc] init];
+  }
+  return _currentInfobars;
+}
+
 @end
diff --git a/ios/chrome/browser/ui/infobars/presentation/BUILD.gn b/ios/chrome/browser/ui/infobars/presentation/BUILD.gn
new file mode 100644
index 0000000..963665c
--- /dev/null
+++ b/ios/chrome/browser/ui/infobars/presentation/BUILD.gn
@@ -0,0 +1,16 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("presentation") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "infobar_banner_animator.h",
+    "infobar_banner_animator.mm",
+    "infobar_banner_presentation_controller.h",
+    "infobar_banner_presentation_controller.mm",
+  ]
+  deps = [
+    "//base",
+  ]
+}
diff --git a/ios/chrome/browser/ui/infobars/presentation/infobar_banner_animator.h b/ios/chrome/browser/ui/infobars/presentation/infobar_banner_animator.h
new file mode 100644
index 0000000..96fd220
--- /dev/null
+++ b/ios/chrome/browser/ui/infobars/presentation/infobar_banner_animator.h
@@ -0,0 +1,21 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_INFOBARS_PRESENTATION_INFOBAR_BANNER_ANIMATOR_H_
+#define IOS_CHROME_BROWSER_UI_INFOBARS_PRESENTATION_INFOBAR_BANNER_ANIMATOR_H_
+
+#import <UIKit/UIKit.h>
+
+// TODO(crbug.com/1372916): PLACEHOLDER Work in Progress class for the new
+// InfobarUI.
+@interface InfobarBannerAnimator
+    : NSObject <UIViewControllerAnimatedTransitioning>
+
+// YES if this animator is presenting a view controller, NO if it is dismissing
+// one.
+@property(nonatomic, assign) BOOL presenting;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_INFOBARS_PRESENTATION_INFOBAR_BANNER_ANIMATOR_H_
diff --git a/ios/chrome/browser/ui/infobars/presentation/infobar_banner_animator.mm b/ios/chrome/browser/ui/infobars/presentation/infobar_banner_animator.mm
new file mode 100644
index 0000000..df8df12
--- /dev/null
+++ b/ios/chrome/browser/ui/infobars/presentation/infobar_banner_animator.mm
@@ -0,0 +1,80 @@
+// 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 "ios/chrome/browser/ui/infobars/presentation/infobar_banner_animator.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@implementation InfobarBannerAnimator
+
+- (NSTimeInterval)transitionDuration:
+    (id<UIViewControllerContextTransitioning>)transitionContext {
+  return 1;
+}
+
+- (void)animateTransition:
+    (id<UIViewControllerContextTransitioning>)transitionContext {
+  // Set up the keys for the "base" view/VC and the "presented" view/VC. These
+  // will be used to fetch the associated objects later.
+  NSString* baseViewKey = self.presenting ? UITransitionContextFromViewKey
+                                          : UITransitionContextToViewKey;
+  NSString* presentedViewControllerKey =
+      self.presenting ? UITransitionContextToViewControllerKey
+                      : UITransitionContextFromViewControllerKey;
+  NSString* presentedViewKey = self.presenting ? UITransitionContextToViewKey
+                                               : UITransitionContextFromViewKey;
+
+  // Get views and view controllers for this transition.
+  UIView* baseView = [transitionContext viewForKey:baseViewKey];
+  UIViewController* presentedViewController =
+      [transitionContext viewControllerForKey:presentedViewControllerKey];
+  UIView* presentedView = [transitionContext viewForKey:presentedViewKey];
+
+  // Always add the destination view to the container.
+  UIView* containerView = [transitionContext containerView];
+  if (self.presenting) {
+    [containerView addSubview:presentedView];
+  } else {
+    [containerView addSubview:baseView];
+  }
+
+  // Set the initial frame and Compute the final frame for the presented view.
+  CGRect presentedViewFinalFrame = CGRectZero;
+
+  if (self.presenting) {
+    presentedViewFinalFrame =
+        [transitionContext finalFrameForViewController:presentedViewController];
+    CGRect presentedViewStartFrame = presentedViewFinalFrame;
+    presentedViewStartFrame.origin.y = -CGRectGetWidth(containerView.bounds);
+    presentedView.frame = presentedViewStartFrame;
+  } else {
+    presentedViewFinalFrame = presentedView.frame;
+    presentedViewFinalFrame.origin.y = -CGRectGetWidth(containerView.bounds);
+  }
+
+  // Animate using the animator's own duration value.
+  [UIView animateWithDuration:[self transitionDuration:transitionContext]
+      delay:0
+      usingSpringWithDamping:0.85
+      initialSpringVelocity:0
+      options:UIViewAnimationOptionTransitionNone
+      animations:^{
+        presentedView.frame = presentedViewFinalFrame;
+      }
+      completion:^(BOOL finished) {
+        BOOL success = ![transitionContext transitionWasCancelled];
+
+        // If presentation failed, remove the view.
+        if (self.presenting && !success) {
+          [presentedView removeFromSuperview];
+        }
+
+        // Notify UIKit that the transition has finished
+        [transitionContext completeTransition:success];
+      }];
+}
+
+@end
diff --git a/ios/chrome/browser/ui/infobars/presentation/infobar_banner_presentation_controller.h b/ios/chrome/browser/ui/infobars/presentation/infobar_banner_presentation_controller.h
new file mode 100644
index 0000000..bbe80a8
--- /dev/null
+++ b/ios/chrome/browser/ui/infobars/presentation/infobar_banner_presentation_controller.h
@@ -0,0 +1,16 @@
+// 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 IOS_CHROME_BROWSER_UI_INFOBARS_PRESENTATION_INFOBAR_BANNER_PRESENTATION_CONTROLLER_H_
+#define IOS_CHROME_BROWSER_UI_INFOBARS_PRESENTATION_INFOBAR_BANNER_PRESENTATION_CONTROLLER_H_
+
+#import <UIKit/UIKit.h>
+
+// TODO(crbug.com/1372916): PLACEHOLDER Work in Progress class for the new
+// InfobarUI.
+@interface InfobarBannerPresentationController : UIPresentationController
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_INFOBARS_PRESENTATION_INFOBAR_BANNER_PRESENTATION_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/infobars/presentation/infobar_banner_presentation_controller.mm b/ios/chrome/browser/ui/infobars/presentation/infobar_banner_presentation_controller.mm
new file mode 100644
index 0000000..1bf914e
--- /dev/null
+++ b/ios/chrome/browser/ui/infobars/presentation/infobar_banner_presentation_controller.mm
@@ -0,0 +1,54 @@
+// 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 "ios/chrome/browser/ui/infobars/presentation/infobar_banner_presentation_controller.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+// Presented frame size and position.
+const CGFloat kContainerHeight = 75;
+const CGFloat kContainerHorizontalPadding = 20;
+const CGFloat kContainerTopPadding = 80;
+
+}
+
+@interface InfobarBannerPresentationController ()
+
+// UIView that contains information about the position and size of the container
+// and presented views.
+@property(nonatomic, strong) UIView* viewForPresentedView;
+
+@end
+
+@implementation InfobarBannerPresentationController
+
+- (CGRect)frameOfPresentedViewInContainerView {
+  return self.viewForPresentedView.bounds;
+}
+
+- (void)presentationTransitionWillBegin {
+  self.containerView.frame = self.viewForPresentedView.frame;
+}
+
+- (void)containerViewWillLayoutSubviews {
+  self.presentedView.frame = [self frameOfPresentedViewInContainerView];
+}
+
+- (UIView*)viewForPresentedView {
+  if (!_viewForPresentedView) {
+    CGFloat safeAreaWidth =
+        CGRectGetWidth(self.containerView.safeAreaLayoutGuide.layoutFrame);
+    CGFloat maxAvailableWidth = safeAreaWidth - 2 * kContainerHorizontalPadding;
+    _viewForPresentedView = [[UIView alloc]
+        initWithFrame:CGRectMake(kContainerHorizontalPadding,
+                                 kContainerTopPadding, maxAvailableWidth,
+                                 kContainerHeight)];
+  }
+  return _viewForPresentedView;
+}
+
+@end
diff --git a/ios/chrome/browser/ui/ntp/incognito_view_controller.mm b/ios/chrome/browser/ui/ntp/incognito_view_controller.mm
index 0b568df..705a60c 100644
--- a/ios/chrome/browser/ui/ntp/incognito_view_controller.mm
+++ b/ios/chrome/browser/ui/ntp/incognito_view_controller.mm
@@ -63,7 +63,11 @@
 - (void)wasHidden {
 }
 
-- (CGPoint)scrollOffset {
+- (UIEdgeInsets)contentInset {
+  return UIEdgeInsetsZero;
+}
+
+- (CGPoint)contentOffset {
   return CGPointZero;
 }
 
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_controller.mm b/ios/chrome/browser/ui/ntp/new_tab_page_controller.mm
index 23fe33c0..22429ba 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_controller.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_controller.mm
@@ -233,8 +233,8 @@
   [_currentController willUpdateSnapshot];
 }
 
-- (CGPoint)scrollOffset {
-  return [_currentController scrollOffset];
+- (CGPoint)contentOffset {
+  return [_currentController contentOffset];
 }
 
 #pragma mark - LogoAnimationControllerOwnerOwner
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm b/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
index 268f13d..7ca4021 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
@@ -102,19 +102,18 @@
   return self.viewController.view;
 }
 
-- (CGPoint)scrollOffset {
-  return [self.contentSuggestionsCoordinator scrollOffset];
+- (UIEdgeInsets)contentInset {
+  return [self.contentSuggestionsCoordinator contentInset];
+}
+
+- (CGPoint)contentOffset {
+  return [self.contentSuggestionsCoordinator contentOffset];
 }
 
 - (void)willUpdateSnapshot {
   [self.contentSuggestionsCoordinator willUpdateSnapshot];
 }
 
-- (UIEdgeInsets)contentInset {
-  return self.contentSuggestionsCoordinator.viewController.collectionView
-      .contentInset;
-}
-
 - (void)setContentInset:(UIEdgeInsets)contentInset {
   // UIKit will adjust the contentOffset sometimes when changing the
   // contentInset.bottom.  We don't want the NTP to scroll, so store and re-set
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_owning.h b/ios/chrome/browser/ui/ntp/new_tab_page_owning.h
index 1d5d8fc..8838b7f 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_owning.h
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_owning.h
@@ -23,8 +23,9 @@
 // Called when a snapshot of the content will be taken.
 - (void)willUpdateSnapshot;
 
-// The scroll offset of this native view.
-- (CGPoint)scrollOffset;
+// The content inset and offset of the scroll view.
+- (UIEdgeInsets)contentInset;
+- (CGPoint)contentOffset;
 
 // The current NTP view.
 - (UIView*)view;
diff --git a/ios/web/navigation/crw_navigation_item_storage_unittest.mm b/ios/web/navigation/crw_navigation_item_storage_unittest.mm
index 7660b243..76da767 100644
--- a/ios/web/navigation/crw_navigation_item_storage_unittest.mm
+++ b/ios/web/navigation/crw_navigation_item_storage_unittest.mm
@@ -34,7 +34,8 @@
     [item_storage_ setTimestamp:base::Time::Now()];
     [item_storage_ setTitle:base::SysNSStringToUTF16(@"Title")];
     [item_storage_
-        setDisplayState:web::PageDisplayState(0.0, 0.0, 0.0, 0.0, 0.0)];
+        setDisplayState:web::PageDisplayState(CGPointZero, UIEdgeInsetsZero,
+                                              0.0, 0.0, 0.0)];
     [item_storage_
         setPOSTData:[@"Test data" dataUsingEncoding:NSUTF8StringEncoding]];
     [item_storage_ setHTTPRequestHeaders:@{ @"HeaderKey" : @"HeaderValue" }];
diff --git a/ios/web/navigation/crw_session_storage_unittest.mm b/ios/web/navigation/crw_session_storage_unittest.mm
index 3ade210..fa6d9e1 100644
--- a/ios/web/navigation/crw_session_storage_unittest.mm
+++ b/ios/web/navigation/crw_session_storage_unittest.mm
@@ -75,7 +75,8 @@
     [item_storage setTimestamp:base::Time::Now()];
     [item_storage setTitle:base::SysNSStringToUTF16(@"Title")];
     [item_storage
-        setDisplayState:web::PageDisplayState(0.0, 0.0, 0.0, 0.0, 0.0)];
+        setDisplayState:web::PageDisplayState(CGPointZero, UIEdgeInsetsZero,
+                                              0.0, 0.0, 0.0)];
     [item_storage
         setPOSTData:[@"Test data" dataUsingEncoding:NSUTF8StringEncoding]];
     [item_storage setHTTPRequestHeaders:@{ @"HeaderKey" : @"HeaderValue" }];
diff --git a/ios/web/public/web_state/page_display_state.h b/ios/web/public/web_state/page_display_state.h
index 063b504..7c975c9c 100644
--- a/ios/web/public/web_state/page_display_state.h
+++ b/ios/web/public/web_state/page_display_state.h
@@ -5,42 +5,49 @@
 #ifndef IOS_WEB_PUBLIC_WEB_STATE_PAGE_DISPLAY_STATE_H_
 #define IOS_WEB_PUBLIC_WEB_STATE_PAGE_DISPLAY_STATE_H_
 
-#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
 
 namespace web {
 
-// Class used to represent the scrolling offset of a webview.  The offsets
-// stored are offsets from the scrolled-to-top-left resting position.  That
-// means an |offset_y| value of 0 corresponds to a UIScrollView.contentOffset.y
-// value of -UIScrollView.contentInset.top.
-// TODO(crbug.com/925073): Update this class to store contentInset and
-// contentOffset directly.
+// Class used to represent the scrolling state of a webview.
 class PageScrollState {
  public:
-  // Default constructor.  Initializes scroll offsets to NAN.
+  // Default constructor.  Initializes scroll values to NAN.
   PageScrollState();
   // Constructor with initial values.
-  PageScrollState(double offset_x, double offset_y);
+  PageScrollState(const CGPoint& content_offset,
+                  const UIEdgeInsets& content_inset);
   ~PageScrollState();
 
-  // The scroll offset is valid if its x and y values are both non-NAN.
+  // The scroll offset is valid if its content inset and offset contain only
+  // non-NAN values.
   bool IsValid() const;
 
-  // Accessors for scroll offsets.
-  double offset_x() const { return offset_x_; }
-  void set_offset_x(double offset_x) { offset_x_ = offset_x; }
-  double offset_y() const { return offset_y_; }
-  void set_offset_y(double offset_y) { offset_y_ = offset_y; }
+  // Returns the content offset that produces an equivalent scroll offset when
+  // applied to a UIScrollView whose contentInset is |content_inset|.
+  CGPoint GetEffectiveContentOffsetForContentInset(
+      UIEdgeInsets content_inset) const;
+
+  // Accessors for scroll offsets and zoom scale.
+  const CGPoint& content_offset() const { return content_offset_; }
+  CGPoint& content_offset() { return content_offset_; }
+  void set_content_offset(const CGPoint& content_offset) {
+    content_offset_ = content_offset;
+  }
+  const UIEdgeInsets& content_inset() const { return content_inset_; }
+  UIEdgeInsets& content_inset() { return content_inset_; }
+  void set_content_inset(const UIEdgeInsets& content_inset) {
+    content_inset_ = content_inset;
+  }
 
   // Comparator operators.
   bool operator==(const PageScrollState& other) const;
   bool operator!=(const PageScrollState& other) const;
 
  private:
-  // The x value of the page's UIScrollView contentOffset.
-  double offset_x_;
-  // The y value of the page's UIScrollView contentOffset.
-  double offset_y_;
+  // The content offset and content inset of the web view scroll view.
+  CGPoint content_offset_;
+  UIEdgeInsets content_inset_;
 };
 
 // Class used to represent the scrolling offset and the zoom scale of a webview.
@@ -49,9 +56,9 @@
   // Default constructor.  Initializes scroll offsets and zoom scales to NAN.
   PageZoomState();
   // Constructor with initial values.
-  PageZoomState(double minimum_zoom_scale,
-                double maximum_zoom_scale,
-                double zoom_scale);
+  PageZoomState(CGFloat minimum_zoom_scale,
+                CGFloat maximum_zoom_scale,
+                CGFloat zoom_scale);
   ~PageZoomState();
 
   // Non-legacy zoom scales are valid if all three values are non-NAN and the
@@ -61,21 +68,21 @@
   bool IsValid() const;
 
   // Returns the allowed zoom scale range for this scroll state.
-  double GetMinMaxZoomDifference() const {
+  CGFloat GetMinMaxZoomDifference() const {
     return maximum_zoom_scale_ - minimum_zoom_scale_;
   }
 
   // Accessors.
-  double minimum_zoom_scale() const { return minimum_zoom_scale_; }
-  void set_minimum_zoom_scale(double minimum_zoom_scale) {
+  CGFloat minimum_zoom_scale() const { return minimum_zoom_scale_; }
+  void set_minimum_zoom_scale(CGFloat minimum_zoom_scale) {
     minimum_zoom_scale_ = minimum_zoom_scale;
   }
-  double maximum_zoom_scale() const { return maximum_zoom_scale_; }
-  void set_maximum_zoom_scale(double maximum_zoom_scale) {
+  CGFloat maximum_zoom_scale() const { return maximum_zoom_scale_; }
+  void set_maximum_zoom_scale(CGFloat maximum_zoom_scale) {
     maximum_zoom_scale_ = maximum_zoom_scale;
   }
-  double zoom_scale() const { return zoom_scale_; }
-  void set_zoom_scale(double zoom_scale) { zoom_scale_ = zoom_scale; }
+  CGFloat zoom_scale() const { return zoom_scale_; }
+  void set_zoom_scale(CGFloat zoom_scale) { zoom_scale_ = zoom_scale; }
 
   // Comparator operators.
   bool operator==(const PageZoomState& other) const;
@@ -83,11 +90,11 @@
 
  private:
   // The minimumZoomScale value of the page's UIScrollView.
-  double minimum_zoom_scale_;
+  CGFloat minimum_zoom_scale_;
   // The maximumZoomScale value of the page's UIScrollView.
-  double maximum_zoom_scale_;
+  CGFloat maximum_zoom_scale_;
   // The zoomScale value of the page's UIScrollView.
-  double zoom_scale_;
+  CGFloat zoom_scale_;
 };
 
 // Class used to represent the scroll offset and zoom scale of a webview.
@@ -98,11 +105,11 @@
   // Constructor with initial values.
   PageDisplayState(const PageScrollState& scroll_state,
                    const PageZoomState& zoom_state);
-  PageDisplayState(double offset_x,
-                   double offset_y,
-                   double minimum_zoom_scale,
-                   double maximum_zoom_scale,
-                   double zoom_scale);
+  PageDisplayState(const CGPoint& content_offset,
+                   const UIEdgeInsets& content_inset,
+                   CGFloat minimum_zoom_scale,
+                   CGFloat maximum_zoom_scale,
+                   CGFloat zoom_scale);
   PageDisplayState(NSDictionary* serialization);
   ~PageDisplayState();
 
diff --git a/ios/web/public/web_state/page_display_state.mm b/ios/web/public/web_state/page_display_state.mm
index cb1ac33..b9307a3 100644
--- a/ios/web/public/web_state/page_display_state.mm
+++ b/ios/web/public/web_state/page_display_state.mm
@@ -14,42 +14,103 @@
 
 namespace {
 // Serialiation keys.
-NSString* const kXOffsetKey = @"scrollX";
-NSString* const kYOffsetKey = @"scrollY";
+NSString* const kContentOffsetKey = @"contentOffset";
+NSString* const kContentInsetKey = @"contentInset";
 NSString* const kMinZoomKey = @"minZoom";
 NSString* const kMaxZoomKey = @"maxZoom";
 NSString* const kZoomKey = @"zoom";
-// Returns true if:
-// - both |value1| and |value2| are NAN, or
-// - |value1| and |value2| are equal non-NAN values.
-inline bool StateValuesAreEqual(double value1, double value2) {
+// Deprecated serialization keys.
+// TODO(crbug.com/926041): Remove these keys.
+NSString* const kDeprecatedXOffsetKey = @"scrollX";
+NSString* const kDeprecatedYOffsetKey = @"scrollY";
+// Invalid consts.
+const CGPoint kInvalidContentOffset = CGPointMake(NAN, NAN);
+const UIEdgeInsets kInvalidContentInset = UIEdgeInsetsMake(NAN, NAN, NAN, NAN);
+// Equality checkers.  Return true if both values are NAN or equivalent.
+inline bool StateValuesAreEqual(CGFloat value1, CGFloat value2) {
   return std::isnan(value1) ? std::isnan(value2) : value1 == value2;
 }
-// Returns the double stored under |key| in |serialization|, or NAN if it is not
-// set.
-inline double GetValue(NSString* key, NSDictionary* serialization) {
+inline bool StateContentOffsetsAreEqual(const CGPoint& offset1,
+                                        const CGPoint& offset2) {
+  return StateValuesAreEqual(offset1.x, offset2.x) &&
+         StateValuesAreEqual(offset1.y, offset2.y);
+}
+inline bool StateContentInsetsAreEqual(const UIEdgeInsets& inset1,
+                                       const UIEdgeInsets& inset2) {
+  return StateValuesAreEqual(inset1.top, inset2.top) &&
+         StateValuesAreEqual(inset1.left, inset2.left) &&
+         StateValuesAreEqual(inset1.bottom, inset2.bottom) &&
+         StateValuesAreEqual(inset1.right, inset2.right);
+}
+// Validity checker util functions.
+inline bool IsContentOffsetValid(const CGPoint& content_offset) {
+  return !std::isnan(content_offset.x) && !std::isnan(content_offset.y);
+}
+inline bool IsContentInsetValid(const UIEdgeInsets& content_inset) {
+  return !std::isnan(content_inset.top) && !std::isnan(content_inset.left) &&
+         !std::isnan(content_inset.bottom) && !std::isnan(content_inset.right);
+}
+// Returns the CGFloat stored under |key| in |serialization|, or NAN if it is
+// not set.
+inline CGFloat GetFloatValue(NSString* key, NSDictionary* serialization) {
   NSNumber* value = serialization[key];
   return value ? [value doubleValue] : NAN;
 }
+// Returns the contentOffset stored in |serialization|, or a NAN offset if it is
+// not set.
+inline CGPoint GetContentOffset(NSDictionary* serialization) {
+  NSValue* value = serialization[kContentOffsetKey];
+  if (value)
+    return [value CGPointValue];
+  // TODO(crbug.com/926041): Return kInvalidContentOffset when legacy keys are
+  // removed.
+  return CGPointMake(GetFloatValue(kDeprecatedXOffsetKey, serialization),
+                     GetFloatValue(kDeprecatedYOffsetKey, serialization));
+}
+// Returns the contentInset stored in |serialization|, or a NAN inset if it is
+// not set.
+inline UIEdgeInsets GetContentInset(NSDictionary* serialization) {
+  NSValue* value = serialization[kContentInsetKey];
+  if (value)
+    return [value UIEdgeInsetsValue];
+  if (serialization[kDeprecatedXOffsetKey] &&
+      serialization[kDeprecatedYOffsetKey]) {
+    // When restoring PageScrollStates created using the deprecated
+    // serialization keyes, use UIEdgeInsetsZero as default.
+    // TODO(crbug.com/926041): Just return kInvalidContentInset when legacy keys
+    // are removed.
+    return UIEdgeInsetsZero;
+  }
+  // Return an invalid inset if neither the new nor legacy keys were contained.
+  return kInvalidContentInset;
+}
 }  // namespace
 
-PageScrollState::PageScrollState() : offset_x_(NAN), offset_y_(NAN) {
-}
+PageScrollState::PageScrollState()
+    : content_offset_(kInvalidContentOffset),
+      content_inset_(kInvalidContentInset) {}
 
-PageScrollState::PageScrollState(double offset_x, double offset_y)
-    : offset_x_(offset_x), offset_y_(offset_y) {
-}
+PageScrollState::PageScrollState(const CGPoint& content_offset,
+                                 const UIEdgeInsets& content_inset)
+    : content_offset_(content_offset), content_inset_(content_inset) {}
 
-PageScrollState::~PageScrollState() {
-}
+PageScrollState::~PageScrollState() = default;
 
 bool PageScrollState::IsValid() const {
-  return !std::isnan(offset_x_) && !std::isnan(offset_y_);
+  return IsContentOffsetValid(content_offset_) &&
+         IsContentInsetValid(content_inset_);
+}
+
+CGPoint PageScrollState::GetEffectiveContentOffsetForContentInset(
+    UIEdgeInsets content_inset) const {
+  return CGPointMake(
+      content_offset_.x + content_inset_.left - content_inset.left,
+      content_offset_.y + content_inset_.top - content_inset.top);
 }
 
 bool PageScrollState::operator==(const PageScrollState& other) const {
-  return StateValuesAreEqual(offset_x_, other.offset_x_) &&
-         StateValuesAreEqual(offset_y_, other.offset_y_);
+  return StateContentOffsetsAreEqual(content_offset_, other.content_offset_) &&
+         StateContentInsetsAreEqual(content_inset_, other.content_inset_);
 }
 
 bool PageScrollState::operator!=(const PageScrollState& other) const {
@@ -60,13 +121,12 @@
     : minimum_zoom_scale_(NAN), maximum_zoom_scale_(NAN), zoom_scale_(NAN) {
 }
 
-PageZoomState::PageZoomState(double minimum_zoom_scale,
-                             double maximum_zoom_scale,
-                             double zoom_scale)
+PageZoomState::PageZoomState(CGFloat minimum_zoom_scale,
+                             CGFloat maximum_zoom_scale,
+                             CGFloat zoom_scale)
     : minimum_zoom_scale_(minimum_zoom_scale),
       maximum_zoom_scale_(maximum_zoom_scale),
-      zoom_scale_(zoom_scale) {
-}
+      zoom_scale_(zoom_scale) {}
 
 PageZoomState::~PageZoomState() {
 }
@@ -96,21 +156,20 @@
     : scroll_state_(scroll_state), zoom_state_(zoom_state) {
 }
 
-PageDisplayState::PageDisplayState(double offset_x,
-                                   double offset_y,
-                                   double minimum_zoom_scale,
-                                   double maximum_zoom_scale,
-                                   double zoom_scale)
-    : scroll_state_(offset_x, offset_y),
-      zoom_state_(minimum_zoom_scale, maximum_zoom_scale, zoom_scale) {
-}
+PageDisplayState::PageDisplayState(const CGPoint& content_offset,
+                                   const UIEdgeInsets& content_inset,
+                                   CGFloat minimum_zoom_scale,
+                                   CGFloat maximum_zoom_scale,
+                                   CGFloat zoom_scale)
+    : scroll_state_(content_offset, content_inset),
+      zoom_state_(minimum_zoom_scale, maximum_zoom_scale, zoom_scale) {}
 
 PageDisplayState::PageDisplayState(NSDictionary* serialization)
-    : PageDisplayState(GetValue(kXOffsetKey, serialization),
-                       GetValue(kYOffsetKey, serialization),
-                       GetValue(kMinZoomKey, serialization),
-                       GetValue(kMaxZoomKey, serialization),
-                       GetValue(kZoomKey, serialization)) {}
+    : PageDisplayState(GetContentOffset(serialization),
+                       GetContentInset(serialization),
+                       GetFloatValue(kMinZoomKey, serialization),
+                       GetFloatValue(kMaxZoomKey, serialization),
+                       GetFloatValue(kZoomKey, serialization)) {}
 
 PageDisplayState::~PageDisplayState() {
 }
@@ -130,8 +189,10 @@
 
 NSDictionary* PageDisplayState::GetSerialization() const {
   return @{
-    kXOffsetKey : @(scroll_state_.offset_x()),
-    kYOffsetKey : @(scroll_state_.offset_y()),
+    kContentOffsetKey :
+        [NSValue valueWithCGPoint:scroll_state_.content_offset()],
+    kContentInsetKey :
+        [NSValue valueWithUIEdgeInsets:scroll_state_.content_inset()],
     kMinZoomKey : @(zoom_state_.minimum_zoom_scale()),
     kMaxZoomKey : @(zoom_state_.maximum_zoom_scale()),
     kZoomKey : @(zoom_state_.zoom_scale())
@@ -140,14 +201,15 @@
 
 NSString* PageDisplayState::GetDescription() const {
   NSString* const kPageScrollStateDescriptionFormat =
-      @"{ scrollOffset:(%0.2f, %0.2f), zoomScaleRange:(%0.2f, %0.2f), "
+      @"{ contentOffset:%@, contentInset:%@, zoomScaleRange:(%0.2f, %0.2f), "
       @"zoomScale:%0.2f }";
-  return [NSString stringWithFormat:kPageScrollStateDescriptionFormat,
-                                    scroll_state_.offset_x(),
-                                    scroll_state_.offset_y(),
-                                    zoom_state_.minimum_zoom_scale(),
-                                    zoom_state_.maximum_zoom_scale(),
-                                    zoom_state_.zoom_scale()];
+  return [NSString
+      stringWithFormat:kPageScrollStateDescriptionFormat,
+                       NSStringFromCGPoint(scroll_state_.content_offset()),
+                       NSStringFromUIEdgeInsets(scroll_state_.content_inset()),
+                       zoom_state_.minimum_zoom_scale(),
+                       zoom_state_.maximum_zoom_scale(),
+                       zoom_state_.zoom_scale()];
 }
 
 }  // namespace web
diff --git a/ios/web/public/web_state/ui/crw_native_content.h b/ios/web/public/web_state/ui/crw_native_content.h
index ec5f23c..42798dd 100644
--- a/ios/web/public/web_state/ui/crw_native_content.h
+++ b/ios/web/public/web_state/ui/crw_native_content.h
@@ -80,8 +80,9 @@
 // content.
 - (GURL)virtualURL;
 
-// The scroll offset of this native view.
-- (CGPoint)scrollOffset;
+// The content inset and offset of this native view.
+- (CGPoint)contentOffset;
+- (UIEdgeInsets)contentInset;
 
 @end
 
diff --git a/ios/web/web_state/page_display_state_unittest.mm b/ios/web/web_state/page_display_state_unittest.mm
index 726673d..07353e8 100644
--- a/ios/web/web_state/page_display_state_unittest.mm
+++ b/ios/web/web_state/page_display_state_unittest.mm
@@ -19,8 +19,12 @@
 // NAN values.
 TEST_F(PageDisplayStateTest, EmptyConstructor) {
   web::PageDisplayState state;
-  EXPECT_NAN(state.scroll_state().offset_x());
-  EXPECT_NAN(state.scroll_state().offset_y());
+  EXPECT_NAN(state.scroll_state().content_offset().y);
+  EXPECT_NAN(state.scroll_state().content_offset().x);
+  EXPECT_NAN(state.scroll_state().content_inset().top);
+  EXPECT_NAN(state.scroll_state().content_inset().left);
+  EXPECT_NAN(state.scroll_state().content_inset().bottom);
+  EXPECT_NAN(state.scroll_state().content_inset().right);
   EXPECT_NAN(state.zoom_state().minimum_zoom_scale());
   EXPECT_NAN(state.zoom_state().maximum_zoom_scale());
   EXPECT_NAN(state.zoom_state().zoom_scale());
@@ -30,9 +34,13 @@
 // Tests that the constructor with input states correctly populates the display
 // state.
 TEST_F(PageDisplayStateTest, StatesConstructor) {
-  web::PageScrollState scroll_state(0.0, 1.0);
-  EXPECT_EQ(0.0, scroll_state.offset_x());
-  EXPECT_EQ(1.0, scroll_state.offset_y());
+  const CGPoint kContentOffset = CGPointMake(0.0, 1.0);
+  const UIEdgeInsets kContentInset = UIEdgeInsetsMake(0.0, 1.0, 2.0, 3.0);
+  web::PageScrollState scroll_state(kContentOffset, kContentInset);
+  EXPECT_TRUE(
+      CGPointEqualToPoint(scroll_state.content_offset(), kContentOffset));
+  EXPECT_TRUE(UIEdgeInsetsEqualToEdgeInsets(scroll_state.content_inset(),
+                                            kContentInset));
   EXPECT_TRUE(scroll_state.IsValid());
   web::PageZoomState zoom_state(1.0, 5.0, 1.0);
   EXPECT_EQ(1.0, zoom_state.minimum_zoom_scale());
@@ -40,30 +48,48 @@
   EXPECT_EQ(1.0, zoom_state.zoom_scale());
   EXPECT_TRUE(zoom_state.IsValid());
   web::PageDisplayState state(scroll_state, zoom_state);
-  EXPECT_EQ(scroll_state.offset_x(), state.scroll_state().offset_x());
-  EXPECT_EQ(scroll_state.offset_y(), state.scroll_state().offset_y());
-  EXPECT_EQ(zoom_state.minimum_zoom_scale(),
-            state.zoom_state().minimum_zoom_scale());
-  EXPECT_EQ(zoom_state.maximum_zoom_scale(),
-            state.zoom_state().maximum_zoom_scale());
-  EXPECT_EQ(zoom_state.zoom_scale(), state.zoom_state().zoom_scale());
-  EXPECT_TRUE(state.IsValid());
-}
-
-// Tests the constructor with value inputs.
-TEST_F(PageDisplayStateTest, ValuesConstructor) {
-  web::PageDisplayState state(0.0, 1.0, 1.0, 5.0, 1.0);
-  EXPECT_EQ(0.0, state.scroll_state().offset_x());
-  EXPECT_EQ(1.0, state.scroll_state().offset_y());
-  EXPECT_EQ(1.0, state.zoom_state().minimum_zoom_scale());
-  EXPECT_EQ(5.0, state.zoom_state().maximum_zoom_scale());
-  EXPECT_EQ(1.0, state.zoom_state().zoom_scale());
+  EXPECT_EQ(scroll_state, state.scroll_state());
+  EXPECT_EQ(zoom_state, state.zoom_state());
   EXPECT_TRUE(state.IsValid());
 }
 
 // Tests converting between a PageDisplayState, its serialization, and back.
 TEST_F(PageDisplayStateTest, Serialization) {
-  web::PageDisplayState state(0.0, 1.0, 1.0, 5.0, 1.0);
+  web::PageDisplayState state(CGPointMake(0.0, 1.0),
+                              UIEdgeInsetsMake(0.0, 1.0, 2.0, 3.0), 1.0, 5.0,
+                              1.0);
   web::PageDisplayState new_state(state.GetSerialization());
   EXPECT_EQ(state, new_state);
 }
+
+// Tests that the PageScrollState is updated correctly when restored from the
+// deprecated serialization keys.
+// TODO(crbug.com/926041): Delete this test when legacy keys are removed.
+TEST_F(PageDisplayStateTest, LegacySerialization) {
+  const CGPoint kContentOffset = CGPointMake(25.0, 100.0);
+  web::PageDisplayState state(
+      @{@"scrollX" : @(kContentOffset.x),
+        @"scrollY" : @(kContentOffset.y)});
+  EXPECT_TRUE(CGPointEqualToPoint(kContentOffset,
+                                  state.scroll_state().content_offset()));
+  EXPECT_TRUE(UIEdgeInsetsEqualToEdgeInsets(
+      state.scroll_state().content_inset(), UIEdgeInsetsZero));
+}
+
+// Tests PageScrollState::GetEffectiveContentOffsetForContentInset().
+TEST_F(PageDisplayStateTest, EffectiveContentOffset) {
+  // kContentOffset is chosen such that a page with kTopInset is scrolled to the
+  // top.
+  const CGFloat kTopInset = 100;
+  const CGPoint kContentOffset = CGPointMake(0.0, -kTopInset);
+  const UIEdgeInsets kContentInset = UIEdgeInsetsMake(kTopInset, 0.0, 0.0, 0.0);
+  web::PageScrollState scroll_state(kContentOffset, kContentInset);
+  // Tests that GetEffectiveContentOffsetForContentInset() returns the scrolled-
+  // to-top content offset for kNewTopInset.
+  const CGFloat kNewTopInset = 50.0;
+  const UIEdgeInsets kNewContentInset =
+      UIEdgeInsetsMake(kNewTopInset, 0.0, 0.0, 0.0);
+  CGPoint effective_content_offset =
+      scroll_state.GetEffectiveContentOffsetForContentInset(kNewContentInset);
+  EXPECT_EQ(effective_content_offset.y, -kNewTopInset);
+}
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index 0a77771..de7e5d43 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -3588,19 +3588,17 @@
   // If a native controller is present, record its display state instead of that
   // of the underlying placeholder webview.
   if (self.nativeController) {
-    if ([self.nativeController respondsToSelector:@selector(scrollOffset)]) {
-      displayState.scroll_state().set_offset_x(
-          [self.nativeController scrollOffset].x);
-      displayState.scroll_state().set_offset_y(
-          [self.nativeController scrollOffset].y);
+    if ([self.nativeController respondsToSelector:@selector(contentOffset)]) {
+      displayState.scroll_state().set_content_offset(
+          [self.nativeController contentOffset]);
+    }
+    if ([self.nativeController respondsToSelector:@selector(contentInset)]) {
+      displayState.scroll_state().set_content_inset(
+          [self.nativeController contentInset]);
     }
   } else if (_webView) {
-    CGPoint scrollOffset = [self scrollPosition];
-    UIEdgeInsets contentInset = self.webScrollView.contentInset;
-    displayState.scroll_state().set_offset_x(
-        std::floor(scrollOffset.x + contentInset.left));
-    displayState.scroll_state().set_offset_y(
-        std::floor(scrollOffset.y + contentInset.top));
+    displayState.set_scroll_state(web::PageScrollState(
+        self.scrollPosition, self.webScrollView.contentInset));
     UIScrollView* scrollView = self.webScrollView;
     displayState.zoom_state().set_minimum_zoom_scale(
         scrollView.minimumZoomScale);
@@ -3619,10 +3617,8 @@
     // scrolled or changed the zoom scale while the page is still loading, don't
     // restore any state since it will confuse the user.
     web::PageDisplayState currentPageDisplayState = self.pageDisplayState;
-    if (currentPageDisplayState.scroll_state().offset_x() ==
-            _displayStateOnStartLoading.scroll_state().offset_x() &&
-        currentPageDisplayState.scroll_state().offset_y() ==
-            _displayStateOnStartLoading.scroll_state().offset_y() &&
+    if (currentPageDisplayState.scroll_state() ==
+            _displayStateOnStartLoading.scroll_state() &&
         !_pageHasZoomed) {
       [self applyPageDisplayState:displayState];
     }
@@ -3769,18 +3765,17 @@
 - (void)applyWebViewScrollOffsetFromScrollState:
     (const web::PageScrollState&)scrollState {
   DCHECK(scrollState.IsValid());
-  UIEdgeInsets contentInset = self.webScrollView.contentInset;
-  CGPoint scrollOffset = CGPointMake(scrollState.offset_x() - contentInset.left,
-                                     scrollState.offset_y() - contentInset.top);
+  CGPoint contentOffset = scrollState.GetEffectiveContentOffsetForContentInset(
+      self.webScrollView.contentInset);
   if (_loadPhase == web::PAGE_LOADED) {
     // If the page is loaded, update the scroll immediately.
-    [self.webScrollView setContentOffset:scrollOffset];
+    self.webScrollView.contentOffset = contentOffset;
   } else {
     // If the page isn't loaded, store the action to update the scroll
     // when the page finishes loading.
     __weak UIScrollView* weakScrollView = self.webScrollView;
     ProceduralBlock action = [^{
-      [weakScrollView setContentOffset:scrollOffset];
+      weakScrollView.contentOffset = contentOffset;
     } copy];
     [_pendingLoadCompleteActions addObject:action];
   }
diff --git a/media/filters/fuchsia/fuchsia_video_decoder.cc b/media/filters/fuchsia/fuchsia_video_decoder.cc
index ef261f9..731a619b 100644
--- a/media/filters/fuchsia/fuchsia_video_decoder.cc
+++ b/media/filters/fuchsia/fuchsia_video_decoder.cc
@@ -226,7 +226,7 @@
 
     zx_status_t status = zx::vmar::root_self()->map(
         /*vmar_offset=*/0, buffer_.vmo(), 0, buffer_.size(),
-        ZX_VM_REQUIRE_NON_RESIZABLE | ZX_VM_FLAG_PERM_READ, &mapped_memory_);
+        ZX_VM_REQUIRE_NON_RESIZABLE | ZX_VM_PERM_READ, &mapped_memory_);
 
     if (status != ZX_OK) {
       ZX_DLOG(ERROR, status) << "zx_vmar_map";
diff --git a/media/gpu/android/image_reader_gl_owner.cc b/media/gpu/android/image_reader_gl_owner.cc
index 7767d3e5..690d5de 100644
--- a/media/gpu/android/image_reader_gl_owner.cc
+++ b/media/gpu/android/image_reader_gl_owner.cc
@@ -60,10 +60,16 @@
         texture_owner_(std::move(texture_owner)),
         image_(image) {}
   ~ScopedHardwareBufferImpl() override {
-    texture_owner_->ReleaseRefOnImage(image_);
+    texture_owner_->ReleaseRefOnImage(image_, std::move(read_fence_));
+  }
+
+  void SetReadFence(base::ScopedFD fence_fd) final {
+    DCHECK(!read_fence_.is_valid());
+    read_fence_ = std::move(fence_fd);
   }
 
  private:
+  base::ScopedFD read_fence_;
   scoped_refptr<ImageReaderGLOwner> texture_owner_;
   AImage* image_;
 };
@@ -292,35 +298,42 @@
   auto fence_fd = base::ScopedFD(HANDLE_EINTR(dup(current_image_fence_.get())));
 
   // Add a ref that the caller will release.
-  auto it = external_image_refs_.find(current_image_);
-  if (it == external_image_refs_.end())
-    external_image_refs_[current_image_] = 1;
-  else
-    it->second++;
-
+  external_image_refs_[current_image_].count++;
   return std::make_unique<ScopedHardwareBufferImpl>(
       this, current_image_,
       base::android::ScopedHardwareBufferHandle::Create(buffer),
       std::move(fence_fd));
 }
 
-void ImageReaderGLOwner::ReleaseRefOnImage(AImage* image) {
+void ImageReaderGLOwner::ReleaseRefOnImage(AImage* image,
+                                           base::ScopedFD fence_fd) {
   auto it = external_image_refs_.find(image);
   DCHECK(it != external_image_refs_.end());
-  DCHECK_GT(it->second, 0u);
-  it->second--;
 
-  if (it->second > 0)
+  auto& image_ref = it->second;
+  DCHECK_GT(image_ref.count, 0u);
+  image_ref.count--;
+
+  // TODO(khushalsagar): We should probably merge this fence with any
+  // pre-existing fence, and there are also a couple of other cases that are
+  // being ignored here (delete image async if it is the |current_image| using
+  // this fence, combining display compositor fence with the |fence_fd| here).
+  // But all of this is going to be automagically fixed with SharedImages, so
+  // need to do the proper thing once media switches to that.
+  image_ref.fence_fd = std::move(fence_fd);
+
+  if (image_ref.count > 0)
     return;
+
+  // Delete the image if it has no pending refs and it is not the current image.
+  if (image != current_image_) {
+    if (image_ref.fence_fd.is_valid())
+      loader_.AImage_deleteAsync(image, image_ref.fence_fd.release());
+    else
+      loader_.AImage_delete(image);
+  }
+
   external_image_refs_.erase(it);
-
-  if (image == current_image_)
-    return;
-
-  // No refs on the image. If it is no longer current, delete it. Note that this
-  // can be deleted synchronously here since the caller ensures that any pending
-  // GPU work for the image is finished before marking it for release.
-  loader_.AImage_delete(image);
 }
 
 void ImageReaderGLOwner::GetTransformMatrix(float mtx[]) {
@@ -397,4 +410,10 @@
   }
 }
 
+ImageReaderGLOwner::ImageRef::ImageRef() = default;
+ImageReaderGLOwner::ImageRef::~ImageRef() = default;
+ImageReaderGLOwner::ImageRef::ImageRef(ImageRef&& other) = default;
+ImageReaderGLOwner::ImageRef& ImageReaderGLOwner::ImageRef::operator=(
+    ImageRef&& other) = default;
+
 }  // namespace media
diff --git a/media/gpu/android/image_reader_gl_owner.h b/media/gpu/android/image_reader_gl_owner.h
index 77377dd..f3d4857 100644
--- a/media/gpu/android/image_reader_gl_owner.h
+++ b/media/gpu/android/image_reader_gl_owner.h
@@ -63,7 +63,10 @@
   bool MaybeDeleteCurrentImage();
 
   void EnsureTexImageBound();
-  void ReleaseRefOnImage(AImage* image);
+
+  // Releases an external ref on the image, with the fence that must be signaled
+  // before the |image| can be resued by the AImageReader.
+  void ReleaseRefOnImage(AImage* image, base::ScopedFD fence_fd);
 
   // AImageReader instance
   AImageReader* image_reader_;
@@ -80,7 +83,19 @@
   // A map consisting of pending external refs on an AImage. If an image has any
   // external refs, it is automatically released once the ref-count is 0 and the
   // image is no longer current.
-  using AImageRefMap = base::flat_map<AImage*, size_t>;
+  struct ImageRef {
+    ImageRef();
+    ~ImageRef();
+
+    ImageRef(ImageRef&& other);
+    ImageRef& operator=(ImageRef&& other);
+
+    size_t count = 0u;
+    base::ScopedFD fence_fd;
+
+    DISALLOW_COPY_AND_ASSIGN(ImageRef);
+  };
+  using AImageRefMap = base::flat_map<AImage*, ImageRef>;
   AImageRefMap external_image_refs_;
 
   // reference to the class instance which is used to dynamically
diff --git a/media/gpu/image_processor_test.cc b/media/gpu/image_processor_test.cc
index 8480373..bc17118 100644
--- a/media/gpu/image_processor_test.cc
+++ b/media/gpu/image_processor_test.cc
@@ -99,10 +99,10 @@
 };
 
 // I420->NV12
-INSTANTIATE_TEST_CASE_P(ConvertI420ToNV12,
-                        ImageProcessorSimpleParamTest,
-                        ::testing::Values(std::make_tuple(kI420Image,
-                                                          kNV12Image)));
+INSTANTIATE_TEST_SUITE_P(ConvertI420ToNV12,
+                         ImageProcessorSimpleParamTest,
+                         ::testing::Values(std::make_tuple(kI420Image,
+                                                           kNV12Image)));
 
 #if defined(OS_CHROMEOS)
 // TODO(hiroh): Add more tests.
diff --git a/media/gpu/ipc/service/vda_video_decoder_unittest.cc b/media/gpu/ipc/service/vda_video_decoder_unittest.cc
index 4031b27..963380d 100644
--- a/media/gpu/ipc/service/vda_video_decoder_unittest.cc
+++ b/media/gpu/ipc/service/vda_video_decoder_unittest.cc
@@ -475,8 +475,8 @@
   NotifyFlushDone();
 }
 
-INSTANTIATE_TEST_CASE_P(VdaVideoDecoder,
-                        VdaVideoDecoderTest,
-                        ::testing::Values(false, true));
+INSTANTIATE_TEST_SUITE_P(VdaVideoDecoder,
+                         VdaVideoDecoderTest,
+                         ::testing::Values(false, true));
 
 }  // namespace media
diff --git a/media/gpu/test/video_frame_file_writer.cc b/media/gpu/test/video_frame_file_writer.cc
index fb1f35e..6fa87a6 100644
--- a/media/gpu/test/video_frame_file_writer.cc
+++ b/media/gpu/test/video_frame_file_writer.cc
@@ -97,8 +97,6 @@
 void VideoFrameFileWriter::ProcessVideoFrame(
     scoped_refptr<const VideoFrame> video_frame,
     size_t frame_index) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(writer_sequence_checker_);
-
   base::AutoLock auto_lock(frame_writer_lock_);
   num_frames_writing_++;
 
diff --git a/media/gpu/test/video_player/frame_renderer_dummy.cc b/media/gpu/test/video_player/frame_renderer_dummy.cc
index 594b890..020b747 100644
--- a/media/gpu/test/video_player/frame_renderer_dummy.cc
+++ b/media/gpu/test/video_player/frame_renderer_dummy.cc
@@ -76,15 +76,11 @@
 }
 
 gl::GLContext* FrameRendererDummy::GetGLContext() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
   // As no actual rendering is done we don't have a GLContext.
   return nullptr;
 }
 
-void FrameRendererDummy::RenderFrame(scoped_refptr<VideoFrame> video_frame) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-}
+void FrameRendererDummy::RenderFrame(scoped_refptr<VideoFrame> video_frame) {}
 
 }  // namespace test
 }  // namespace media
diff --git a/media/gpu/test/video_player/video_player.h b/media/gpu/test/video_player/video_player.h
index 9736bb1..2a233d3 100644
--- a/media/gpu/test/video_player/video_player.h
+++ b/media/gpu/test/video_player/video_player.h
@@ -26,7 +26,7 @@
 class VideoFrameProcessor;
 
 // Default timeout used when waiting for events.
-constexpr base::TimeDelta kDefaultTimeout = base::TimeDelta::FromSeconds(10);
+constexpr base::TimeDelta kDefaultTimeout = base::TimeDelta::FromSeconds(30);
 
 enum class VideoPlayerState : size_t {
   kUninitialized = 0,
diff --git a/media/gpu/video_decode_accelerator_tests.cc b/media/gpu/video_decode_accelerator_tests.cc
index 99a6c080..7f06e12 100644
--- a/media/gpu/video_decode_accelerator_tests.cc
+++ b/media/gpu/video_decode_accelerator_tests.cc
@@ -84,12 +84,12 @@
   std::unique_ptr<VideoPlayer> CreateVideoPlayer(
       const Video* video,
       const VideoDecoderClientConfig& config = VideoDecoderClientConfig()) {
-    frame_validator_ =
-        media::test::VideoFrameValidator::Create(video->FrameChecksums());
-    // TODO(dstaessens@) allow enabling/disabling file writing
+    frame_validators_.push_back(
+        media::test::VideoFrameValidator::Create(video->FrameChecksums()));
     return VideoPlayer::Create(
         video, g_env->dummy_frame_renderer_.get(),
-        {frame_validator_.get(), g_env->frame_file_writer_.get()}, config);
+        {frame_validators_.back().get(), g_env->frame_file_writer_.get()},
+        config);
   }
 
   void SetUp() override {
@@ -104,8 +104,12 @@
 
   void TearDown() override { g_env->frame_file_writer_->WaitUntilDone(); }
 
+  const VideoFrameValidator* GetFrameValidator(size_t index = 0) {
+    return frame_validators_[index].get();
+  }
+
  protected:
-  std::unique_ptr<VideoFrameValidator> frame_validator_;
+  std::vector<std::unique_ptr<VideoFrameValidator>> frame_validators_;
 };
 
 }  // namespace
@@ -120,7 +124,7 @@
 
   EXPECT_EQ(tvp->GetFlushDoneCount(), 1u);
   EXPECT_EQ(tvp->GetFrameDecodedCount(), g_env->video_->NumFrames());
-  EXPECT_TRUE(frame_validator_->WaitUntilValidated());
+  EXPECT_TRUE(GetFrameValidator()->WaitUntilValidated());
 }
 
 // Flush the decoder immediately after initialization.
@@ -134,7 +138,7 @@
 
   EXPECT_EQ(tvp->GetFlushDoneCount(), 2u);
   EXPECT_EQ(tvp->GetFrameDecodedCount(), g_env->video_->NumFrames());
-  EXPECT_TRUE(frame_validator_->WaitUntilValidated());
+  EXPECT_TRUE(GetFrameValidator()->WaitUntilValidated());
 }
 
 // Flush the decoder immediately after doing a mid-stream reset, without waiting
@@ -156,7 +160,7 @@
   EXPECT_EQ(tvp->GetResetDoneCount(), 1u);
   EXPECT_EQ(tvp->GetFlushDoneCount(), 1u);
   EXPECT_LE(tvp->GetFrameDecodedCount(), g_env->video_->NumFrames());
-  EXPECT_TRUE(frame_validator_->WaitUntilValidated());
+  EXPECT_TRUE(GetFrameValidator()->WaitUntilValidated());
 }
 
 // Reset the decoder immediately after initialization.
@@ -171,7 +175,7 @@
   EXPECT_EQ(tvp->GetResetDoneCount(), 1u);
   EXPECT_EQ(tvp->GetFlushDoneCount(), 1u);
   EXPECT_EQ(tvp->GetFrameDecodedCount(), g_env->video_->NumFrames());
-  EXPECT_TRUE(frame_validator_->WaitUntilValidated());
+  EXPECT_TRUE(GetFrameValidator()->WaitUntilValidated());
 }
 
 // Reset the decoder when the middle of the stream is reached.
@@ -190,7 +194,7 @@
   EXPECT_EQ(tvp->GetFlushDoneCount(), 1u);
   EXPECT_EQ(tvp->GetFrameDecodedCount(),
             numFramesDecoded + g_env->video_->NumFrames());
-  EXPECT_TRUE(frame_validator_->WaitUntilValidated());
+  EXPECT_TRUE(GetFrameValidator()->WaitUntilValidated());
 }
 
 // Reset the decoder when the end of the stream is reached.
@@ -208,7 +212,7 @@
   EXPECT_EQ(tvp->GetResetDoneCount(), 1u);
   EXPECT_EQ(tvp->GetFlushDoneCount(), 2u);
   EXPECT_EQ(tvp->GetFrameDecodedCount(), g_env->video_->NumFrames() * 2);
-  EXPECT_TRUE(frame_validator_->WaitUntilValidated());
+  EXPECT_TRUE(GetFrameValidator()->WaitUntilValidated());
 }
 
 // Reset the decoder immediately when the end-of-stream flush starts, without
@@ -230,22 +234,7 @@
   EXPECT_LE(tvp->GetFlushDoneCount(), 1u);
   EXPECT_EQ(tvp->GetResetDoneCount(), 1u);
   EXPECT_LE(tvp->GetFrameDecodedCount(), g_env->video_->NumFrames());
-  EXPECT_TRUE(frame_validator_->WaitUntilValidated());
-}
-
-// Play video from start to end. Multiple buffer decodes will be queued in the
-// decoder, without waiting for the result of the previous decode requests.
-TEST_F(VideoDecoderTest, FlushAtEndOfStream_MultipleOutstandingDecodes) {
-  VideoDecoderClientConfig config;
-  config.max_outstanding_decode_requests = 5;
-  auto tvp = CreateVideoPlayer(g_env->video_, config);
-
-  tvp->Play();
-  EXPECT_TRUE(tvp->WaitForFlushDone());
-
-  EXPECT_EQ(tvp->GetFlushDoneCount(), 1u);
-  EXPECT_EQ(tvp->GetFrameDecodedCount(), g_env->video_->NumFrames());
-  EXPECT_TRUE(frame_validator_->WaitUntilValidated());
+  EXPECT_TRUE(GetFrameValidator()->WaitUntilValidated());
 }
 
 // Reset the decoder immediately when encountering the first config info in a
@@ -271,7 +260,43 @@
   EXPECT_EQ(tvp->GetFrameDecodedCount(),
             numFramesDecoded + g_env->video_->NumFrames());
   EXPECT_GE(tvp->GetEventCount(VideoPlayerEvent::kConfigInfo), 1u);
-  EXPECT_EQ(0u, frame_validator_->GetMismatchedFramesCount());
+  EXPECT_EQ(0u, GetFrameValidator()->GetMismatchedFramesCount());
+}
+
+// Play video from start to end. Multiple buffer decodes will be queued in the
+// decoder, without waiting for the result of the previous decode requests.
+TEST_F(VideoDecoderTest, FlushAtEndOfStream_MultipleOutstandingDecodes) {
+  VideoDecoderClientConfig config;
+  config.max_outstanding_decode_requests = 5;
+  auto tvp = CreateVideoPlayer(g_env->video_, config);
+
+  tvp->Play();
+  EXPECT_TRUE(tvp->WaitForFlushDone());
+
+  EXPECT_EQ(tvp->GetFlushDoneCount(), 1u);
+  EXPECT_EQ(tvp->GetFrameDecodedCount(), g_env->video_->NumFrames());
+  EXPECT_TRUE(GetFrameValidator()->WaitUntilValidated());
+}
+
+// Play multiple videos simultaneously from start to finish.
+TEST_F(VideoDecoderTest, FlushAtEndOfStream_MultipleConcurrentDecodes) {
+  // The minimal number of concurrent decoders we expect to be supported.
+  constexpr size_t kMinSupportedConcurrentDecoders = 3;
+
+  std::vector<std::unique_ptr<VideoPlayer>> tvps(
+      kMinSupportedConcurrentDecoders);
+  for (size_t i = 0; i < kMinSupportedConcurrentDecoders; ++i)
+    tvps[i] = CreateVideoPlayer(g_env->video_);
+
+  for (size_t i = 0; i < kMinSupportedConcurrentDecoders; ++i)
+    tvps[i]->Play();
+
+  for (size_t i = 0; i < kMinSupportedConcurrentDecoders; ++i) {
+    EXPECT_TRUE(tvps[i]->WaitForFlushDone());
+    EXPECT_EQ(tvps[i]->GetFlushDoneCount(), 1u);
+    EXPECT_EQ(tvps[i]->GetFrameDecodedCount(), g_env->video_->NumFrames());
+    EXPECT_TRUE(GetFrameValidator()->WaitUntilValidated());
+  }
 }
 
 }  // namespace test
diff --git a/media/gpu/video_decode_accelerator_unittest.cc b/media/gpu/video_decode_accelerator_unittest.cc
index 096c1d7..e7dc801 100644
--- a/media/gpu/video_decode_accelerator_unittest.cc
+++ b/media/gpu/video_decode_accelerator_unittest.cc
@@ -1431,25 +1431,26 @@
 };
 
 // Test that replay after EOS works fine.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ReplayAfterEOS,
     VideoDecodeAcceleratorParamTest,
     ::testing::Values(
         std::make_tuple(1, 1, 4, END_OF_STREAM_RESET, CS_RESET, false, false)));
 
 // Test that Reset() before the first Decode() works fine.
-INSTANTIATE_TEST_CASE_P(ResetBeforeDecode,
-                        VideoDecodeAcceleratorParamTest,
-                        ::testing::Values(std::make_tuple(1,
-                                                          1,
-                                                          1,
-                                                          START_OF_STREAM_RESET,
-                                                          CS_RESET,
-                                                          false,
-                                                          false)));
+INSTANTIATE_TEST_SUITE_P(
+    ResetBeforeDecode,
+    VideoDecodeAcceleratorParamTest,
+    ::testing::Values(std::make_tuple(1,
+                                      1,
+                                      1,
+                                      START_OF_STREAM_RESET,
+                                      CS_RESET,
+                                      false,
+                                      false)));
 
 // Test Reset() immediately after Decode() containing config info.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ResetAfterFirstConfigInfo,
     VideoDecodeAcceleratorParamTest,
     ::testing::Values(std::make_tuple(1,
@@ -1461,7 +1462,7 @@
                                       false)));
 
 // Test Reset() immediately after Flush() and before NotifyFlushDone().
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ResetBeforeNotifyFlushDone,
     VideoDecodeAcceleratorParamTest,
     ::testing::Values(std::make_tuple(1,
@@ -1474,13 +1475,13 @@
 
 // Test that Reset() mid-stream works fine and doesn't affect decoding even when
 // Decode() calls are made during the reset.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MidStreamReset,
     VideoDecodeAcceleratorParamTest,
     ::testing::Values(
         std::make_tuple(1, 1, 1, MID_STREAM_RESET, CS_RESET, false, false)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SlowRendering,
     VideoDecodeAcceleratorParamTest,
     ::testing::Values(
@@ -1488,7 +1489,7 @@
 
 // Test that Destroy() mid-stream works fine (primarily this is testing that no
 // crashes occur).
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     TearDownTiming,
     VideoDecodeAcceleratorParamTest,
     ::testing::Values(
@@ -1544,7 +1545,7 @@
                         false)));
 
 // Test that decoding various variation works with multiple in-flight decodes.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DecodeVariations,
     VideoDecodeAcceleratorParamTest,
     ::testing::Values(
@@ -1561,7 +1562,7 @@
 
 // Find out how many concurrent decoders can go before we exhaust system
 // resources.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ResourceExhaustion,
     VideoDecodeAcceleratorParamTest,
     ::testing::Values(std::make_tuple(kMinSupportedNumConcurrentDecoders,
@@ -1580,8 +1581,8 @@
                                       false)));
 
 // Allow MAYBE macro substitution.
-#define WRAPPED_INSTANTIATE_TEST_CASE_P(a, b, c) \
-  INSTANTIATE_TEST_CASE_P(a, b, c)
+#define WRAPPED_INSTANTIATE_TEST_SUITE_P(a, b, c) \
+  INSTANTIATE_TEST_SUITE_P(a, b, c)
 
 #if defined(OS_WIN)
 // There are no reference images for windows.
@@ -1590,7 +1591,7 @@
 #define MAYBE_Thumbnail Thumbnail
 #endif
 // Thumbnailing test
-WRAPPED_INSTANTIATE_TEST_CASE_P(
+WRAPPED_INSTANTIATE_TEST_SUITE_P(
     MAYBE_Thumbnail,
     VideoDecodeAcceleratorParamTest,
     ::testing::Values(
diff --git a/media/gpu/video_encode_accelerator_unittest.cc b/media/gpu/video_encode_accelerator_unittest.cc
index 7de4f403..9cdf9252 100644
--- a/media/gpu/video_encode_accelerator_unittest.cc
+++ b/media/gpu/video_encode_accelerator_unittest.cc
@@ -2522,104 +2522,104 @@
 #if defined(OS_CHROMEOS) || defined(OS_LINUX)
 // TODO(kcwu): add back test of verify_output=true after
 // https://crbug.com/694131 fixed.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SimpleEncode,
     VideoEncodeAcceleratorTest,
     ::testing::Values(
         std::make_tuple(1, true, 0, false, false, false, false, false, false)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     EncoderPerf,
     VideoEncodeAcceleratorTest,
     ::testing::Values(
         std::make_tuple(1, false, 0, false, true, false, false, false, false)));
 
-INSTANTIATE_TEST_CASE_P(ForceKeyframes,
-                        VideoEncodeAcceleratorTest,
-                        ::testing::Values(std::make_tuple(1,
-                                                          false,
-                                                          10,
-                                                          false,
-                                                          false,
-                                                          false,
-                                                          false,
-                                                          false,
-                                                          false)));
+INSTANTIATE_TEST_SUITE_P(ForceKeyframes,
+                         VideoEncodeAcceleratorTest,
+                         ::testing::Values(std::make_tuple(1,
+                                                           false,
+                                                           10,
+                                                           false,
+                                                           false,
+                                                           false,
+                                                           false,
+                                                           false,
+                                                           false)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ForceBitrate,
     VideoEncodeAcceleratorTest,
     ::testing::Values(
         std::make_tuple(1, false, 0, true, false, false, false, false, false)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MidStreamParamSwitchBitrate,
     VideoEncodeAcceleratorTest,
     ::testing::Values(
         std::make_tuple(1, false, 0, true, false, true, false, false, false)));
 
 // TODO(kcwu): add back bitrate test after https://crbug.com/693336 fixed.
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     DISABLED_MidStreamParamSwitchFPS,
     VideoEncodeAcceleratorTest,
     ::testing::Values(
         std::make_tuple(1, false, 0, true, false, false, true, false, false)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     MultipleEncoders,
     VideoEncodeAcceleratorTest,
     ::testing::Values(
         std::make_tuple(3, false, 0, false, false, false, false, false, false),
         std::make_tuple(3, false, 0, true, false, true, false, false, false)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     VerifyTimestamp,
     VideoEncodeAcceleratorTest,
     ::testing::Values(
         std::make_tuple(1, false, 0, false, false, false, false, false, true)));
 
-INSTANTIATE_TEST_CASE_P(NoInputTest,
-                        VideoEncodeAcceleratorSimpleTest,
-                        ::testing::Values(0));
+INSTANTIATE_TEST_SUITE_P(NoInputTest,
+                         VideoEncodeAcceleratorSimpleTest,
+                         ::testing::Values(0));
 
-INSTANTIATE_TEST_CASE_P(CacheLineUnalignedInputTest,
-                        VideoEncodeAcceleratorSimpleTest,
-                        ::testing::Values(1));
+INSTANTIATE_TEST_SUITE_P(CacheLineUnalignedInputTest,
+                         VideoEncodeAcceleratorSimpleTest,
+                         ::testing::Values(1));
 
 #elif defined(OS_MACOSX) || defined(OS_WIN)
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     SimpleEncode,
     VideoEncodeAcceleratorTest,
     ::testing::Values(
         std::make_tuple(1, true, 0, false, false, false, false, false, false),
         std::make_tuple(1, true, 0, false, false, false, false, true, false)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     EncoderPerf,
     VideoEncodeAcceleratorTest,
     ::testing::Values(
         std::make_tuple(1, false, 0, false, true, false, false, false, false)));
 
-INSTANTIATE_TEST_CASE_P(MultipleEncoders,
-                        VideoEncodeAcceleratorTest,
-                        ::testing::Values(std::make_tuple(3,
-                                                          false,
-                                                          0,
-                                                          false,
-                                                          false,
-                                                          false,
-                                                          false,
-                                                          false,
-                                                          false)));
+INSTANTIATE_TEST_SUITE_P(MultipleEncoders,
+                         VideoEncodeAcceleratorTest,
+                         ::testing::Values(std::make_tuple(3,
+                                                           false,
+                                                           0,
+                                                           false,
+                                                           false,
+                                                           false,
+                                                           false,
+                                                           false,
+                                                           false)));
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     VerifyTimestamp,
     VideoEncodeAcceleratorTest,
     ::testing::Values(
         std::make_tuple(1, false, 0, false, false, false, false, false, true)));
 
 #if defined(OS_WIN)
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ForceBitrate,
     VideoEncodeAcceleratorTest,
     ::testing::Values(
diff --git a/media/muxers/webm_muxer_unittest.cc b/media/muxers/webm_muxer_unittest.cc
index 71eb083..f819ecd48 100644
--- a/media/muxers/webm_muxer_unittest.cc
+++ b/media/muxers/webm_muxer_unittest.cc
@@ -327,6 +327,6 @@
     {kCodecVP8, kCodecPCM, 1, 1},
 };
 
-INSTANTIATE_TEST_CASE_P(, WebmMuxerTest, ValuesIn(kTestCases));
+INSTANTIATE_TEST_SUITE_P(, WebmMuxerTest, ValuesIn(kTestCases));
 
 }  // namespace media
diff --git a/mojo/public/tools/bindings/generators/js_templates/lite/interface_externs.tmpl b/mojo/public/tools/bindings/generators/js_templates/lite/interface_externs.tmpl
index 813684e..72c3cd0d 100644
--- a/mojo/public/tools/bindings/generators/js_templates/lite/interface_externs.tmpl
+++ b/mojo/public/tools/bindings/generators/js_templates/lite/interface_externs.tmpl
@@ -54,6 +54,11 @@
    * @return {!{{module.namespace}}.{{interface.name}}Request}
    */
   createRequest() {}
+
+  /**
+   * @return {Promise}
+   */
+  flushForTesting() {}
 };
 
 {{module.namespace}}.{{interface.name}} = class {
diff --git a/net/http/transport_security_state_static.json b/net/http/transport_security_state_static.json
index 8e37704a..1eb2cd3 100644
--- a/net/http/transport_security_state_static.json
+++ b/net/http/transport_security_state_static.json
@@ -6818,7 +6818,6 @@
     { "name": "tonage.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "toncusters.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "toomanypillows.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "tooolroc.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "topmarine.se", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "tosteberg.se", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "toucedo.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -8161,7 +8160,6 @@
     { "name": "imusic.dk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "indusfastremit-us.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "indusfastremit.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "insighti.eu", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "insighti.sk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "iprice.co.id", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "iprice.hk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -11403,7 +11401,6 @@
     { "name": "convergemagazine.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "creativeartifice.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "cookiesoft.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "computersystems.guru", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "comotalk.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "crumbcontrol.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "crtvmgmt.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -11493,7 +11490,6 @@
     { "name": "eintageinzug.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "elaintehtaat.fi", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "elemprendedor.com.ve", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "eit-web.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "elonbase.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "emi-air-comprime.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "endlessdiy.ca", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -11548,7 +11544,6 @@
     { "name": "foreveralone.io", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "fourchin.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "flexapplications.se", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "forschbach-janssen.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "frickenate.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "fumiware.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "freebus.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -11883,7 +11878,6 @@
     { "name": "newtonhaus.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "nicocourts.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "nikksno.io", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "niagaraschoice.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "nitropur.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "nicolasklotz.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "nicolasbettag.me", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -11908,7 +11902,6 @@
     { "name": "oh14.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "ojls.co", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "ons.ca", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "onlinebizdirect.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "onearth.one", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "onarto.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "newline.online", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -11934,7 +11927,6 @@
     { "name": "overalglas.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "opensourcehouse.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "oxynux.xyz", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "partnercardservices.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "papa-webzeit.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "pastenib.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "penguinclientsystem.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -11946,7 +11938,6 @@
     { "name": "phantasie.cc", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "perroud.pro", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "perthdevicelab.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "pgpmail.cc", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "peterfolta.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "pfolta.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "performancesantafe.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -11993,7 +11984,6 @@
     { "name": "psncardplus.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "quail.solutions", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "radar.sx", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "r0uzic.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "qkka.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "psncardplus.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "qldformulaford.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -12011,12 +12001,10 @@
     { "name": "refreshingserum.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "ray-works.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "rayworks.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "recepty.eu", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "refill-roboter.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "redra.ws", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "relayawards.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "rem.pe", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "remitatm.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "res-rheingau.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "regionalcoalition.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "rentacarcluj.xyz", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -12032,7 +12020,6 @@
     { "name": "rhapsodhy.hu", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "rochman.id", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "rointe.online", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "rotozen.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "royalhop.co", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "rokort.dk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "rogue-e.xyz", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -12045,19 +12032,12 @@
     { "name": "runawebinar.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "rr105.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "samraskauskas.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "s-mdb.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "saraleebread.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "sagsmarseille.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "sagedocumentmanager.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "sanissimo.com.mx", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "ruh-veit.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "saleslift.pl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "saba-piserver.info", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "sangwon.io", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "saml-gateway.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "sandobygg.se", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "sandogruppen.se", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "saro.me", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "sbirecruitment.co.in", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "sansemea.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "samsen.club", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -12148,7 +12128,6 @@
     { "name": "stigroom.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "studiozelden.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "svatba-frantovi.cz", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "sumoatm.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "tails.com.ar", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "sv-turm-hohenlimburg.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "taravancil.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -13002,7 +12981,6 @@
     { "name": "tocaro.im", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "toursandtransfers.it", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "tracetracker.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "transport.eu", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "trefpuntdemeent.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "tronatic-studio.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "tsaro.io", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -16332,7 +16310,6 @@
     { "name": "tetrarch.co", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "studio-panic.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "sudaraka.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "teamx-gaming.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "teamtrack.uk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "solariiknight.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "techace.jp", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -16577,7 +16554,6 @@
     { "name": "xn--werner-schffer-fib.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "ylinternal.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "yourgame.co.il", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "xyndrac.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "ytuquelees.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "youlend.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "vanderziel.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -17956,7 +17932,6 @@
     { "name": "yuzu.tk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "zlc1994.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "woufbox.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "zenycosta.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "worldsbeststory.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "yantrasthal.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "zefiris.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -18724,7 +18699,6 @@
     { "name": "europapier.rs", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "europapier.ba", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "europapier.hu", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "francisli.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "foxterrier.com.br", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "feld.saarland", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "foo.fo", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -19433,7 +19407,6 @@
     { "name": "mysqldump-secure.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "nadyaolcer.fr", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "namereel.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "n-un.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "myepass.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "neatous.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mydebian.in.ua", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -19677,7 +19650,6 @@
     { "name": "rc-offi.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "remedica.fr", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "professors.ee", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "puurwonengeldrop.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "pruikshop.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "robototes.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "questionable.host", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -20437,7 +20409,6 @@
     { "name": "a-little-linux-box.at", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "8ackprotect.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "alicialab.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "adamek.online", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "4u2ore.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "akalashnikov.ru", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "advokat-romanov.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -22230,7 +22201,6 @@
     { "name": "projectnom.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "queryplayground.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "project-rune.tech", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "oblast45.ru", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "mrning.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "pollet-ghys.be", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "quantumwebs.co", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -22260,7 +22230,6 @@
     { "name": "rbti.me", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "recreation.gov", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "pfeuffer-elektro.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "proteinnuts.cz", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "ps4all.nl", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "rachelreagan.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "qrpth.eu", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -22666,7 +22635,6 @@
     { "name": "t4x.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "thailandpropertylisting.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "tacklog.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "tepitus.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "thewp.pro", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "teknotes.co.uk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "technotonic.co.uk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -23113,7 +23081,6 @@
     { "name": "aux-arts-de-la-table.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "avticket.ru", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "bacimg.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "backgroundchecks.online", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "badseacoffee.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "bagiobella.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "bakkerinjebuurt.be", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -25065,7 +25032,6 @@
     { "name": "deepvision.com.ua", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "defrax.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "defrax.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "deftek.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "deftnerd.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "degen-elektrotechnik.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "deinewebsite.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -26788,7 +26754,6 @@
     { "name": "panpsychist.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "panzer72.ru", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "papakatsu-life.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "paperhaven.com.au", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "parachute70.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "paradise-engineer.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "paradise-engineers.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -28526,7 +28491,6 @@
     { "name": "bentphotos.se", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "biaggeo.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "arfad.ch", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "beyond-infinity.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "beraru.tk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "birdbrowser.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "bestwarezone.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -28893,7 +28857,6 @@
     { "name": "crunchy.rocks", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "congobunkering.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "cuongthach.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "crows.io", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "currynissanmaparts.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "customwritingservice.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "chasafilli.ch", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -29545,7 +29508,6 @@
     { "name": "geneve-naturisme.ch", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "gilmoreid.com.au", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "govtjobs.blog", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "grandefratellonews.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "gmx.ch", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "gmx.at", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "groentefruitzeep.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -31558,7 +31520,6 @@
     { "name": "tsumi.moe", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "travel-to-nature.ch", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "tradietrove.com.au", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "tniad.mil.id", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "trekfriend.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "troedelhannes.at", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "thelostyankee.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -34490,7 +34451,6 @@
     { "name": "apn-dz.org", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "aquarium-supplement.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "arcenergy.co.uk", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "arcobalabs.ca", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "arian.io", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "artsinthevalley.net.au", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "atlantahairsurgeon.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -36075,7 +36035,6 @@
     { "name": "domengrad.ru", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "domian.cz", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "dominik-schlueter.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "donnoval.ru", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "donpaginasweb.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "donzool.es", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "doopdidoop.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -36984,7 +36943,6 @@
     { "name": "rwky.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "ryzhov.me", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "sabine-forschbach.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
-    { "name": "sabineforschbach.de", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "saclier.at", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "sacred-knights.net", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
     { "name": "saint-astier-triathlon.com", "policy": "bulk-18-weeks", "mode": "force-https", "include_subdomains": true },
@@ -38976,7 +38934,6 @@
     { "name": "leveluprails.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "lidel.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "lifemarque.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "lifeventure.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "lignemalin.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "lilysbouncycastles.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "limberg.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -39095,7 +39052,6 @@
     { "name": "multikalender.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "muusika.fun", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "mvandek.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "mxlife.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "myjumparoo.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "mymun.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "myowndisk.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -40117,7 +40073,6 @@
     { "name": "monkeytek.ca", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "moonkin.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "moorewelliver.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "moppy.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "morespacestorage.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "moshwire.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "mp3donusturucu.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -42719,7 +42674,6 @@
     { "name": "konventa.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "kopio.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "kopjethee.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "koppelvlak.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "korben.info", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "kotly-marten.com.ua", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "kouten-jp.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -49481,7 +49435,6 @@
     { "name": "kolcsey.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "kotonoha.cafe", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "kovuthehusky.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "kryha.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ksk-agentur.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "kuops.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "lacetsfun.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -50335,7 +50288,6 @@
     { "name": "lunanova.moe", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "luteijn.biz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "luteijn.pro", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "mailum.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "makera.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "maketheneighborsjealous.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "malscan.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -52247,7 +52199,6 @@
     { "name": "portugal-a-programar.pt", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "pour-la-culture-aulnay.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "pouwels-oss.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "prettynode.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "priorityelectric-agourahills.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "priorityelectric-calabasas.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "priorityelectric-camarillo.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -52300,7 +52251,6 @@
     { "name": "sadev.co.za", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sadou.kyoto.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "safepay.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "samip.fi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sanantoniolocksmithinc.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "santensautomatics.be", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sap-inc.co.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -53563,7 +53513,6 @@
     { "name": "smart-media-gmbh.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "smartwoodczech.cz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "smcbox.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "smith.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "smplr.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sms.storage", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "snowyluma.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -54023,7 +53972,6 @@
     { "name": "romatrip.it", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "ronniegane.kiwi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "rossmacphee.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "rustralasia.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "saastopankki.fi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sac-shop.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "sacrome.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -62686,7 +62634,6 @@
     { "name": "qkzy.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "qlcvea.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "qpcna.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "qtap.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "quadra.srl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "rachurch.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "radiobox.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -65877,6 +65824,446 @@
     { "name": "zstu.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "zumub.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "zzbnet.cn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "021002.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "077768.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "081115.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "0x41.us", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "138000.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "161263.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "162361.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "177603.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "1android.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "1gp.us", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "1zombie.team", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "22delta.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "411quest.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "4hmediaproductions.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "62314.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "662607.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "6bwcp.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "8212p.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "877027.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "908.la", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "929349.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "9hosts.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "a-pro-pos.info", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "acchicocchi.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ace-aegon.cloud", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "acg1080.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "adnmb1.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "aimd.tech", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "alhost.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "alloutofgum.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "allrad-buck.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "almamet.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "alonas.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "americanindiancoc.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ames.gq", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "amj74-informatique.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "amyria.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "android-tv.3utilities.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "androzoom.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "appspace.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "arslankaynakmetal.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "asmeets.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "auto1.fi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "autolawetawroclaw.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "avtek.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "aylavblog.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bagwrap.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "banderasdelmundo.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "barneveldcentrum.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "basics.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bawbby.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bazinga-events.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bcubic.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bebeautiful.business", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "beherit.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bestlooperpedalsguide.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bezlampowe.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "bitcert.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "blackmagicshaman.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "blogit.fi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "briograce.com.mx", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "buck-hydro.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "burakogun.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "burakogun.com.tr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "burakogun.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "burakogun.net.tr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "burakogun.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "burzum.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cabanactf.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "camping-le-pasquier.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "canopy.ninja", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "casinoportugal.pt", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cclasabana.com.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "charlylou.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "chemco.mu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ciclista.roma.it", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "citywidealarms.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "clo.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cloud255.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "codeandsupply.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "coincircle.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "coisabakana.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "colcomm.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "coldiario.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "comoaliviareldolor.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cooksecuritygroup.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "creativ-impuls-dekorateurin-muenchen.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "crismatthews.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "crypkit.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cslaboralistas.pe", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cuegee.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "cumtd.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "d4fx.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dadadani.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "danads.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dapianw.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dare.deals", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "darf.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dead-letter.email", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "decorumcomics.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "deepspace4.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "deplorablesdaily.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "depositart.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "derekbooth.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "developer.moe", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dickord.cloud", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dictionarypro.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "directoriostelefonicos.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "divineglowinghealth.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "donetsk24.su", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dotesports.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dpsg-hohenlinden.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "drros.ru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "dubstep.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "e-webos.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "eallion.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "eblog.ink", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "educacionvirtual.com.ar", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "educateyourskin.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ekaplast.com.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ekouniejow.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "electricgatemotorglenvista.co.za", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "eosolutions.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "esteladigital.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "estraks.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "estudiaryaprenderingles.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "excaliburtitle.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "excess-baggage.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "eyemagic.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "facingbipolar.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fantasysportsnews.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "farleybrass.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fb-feed.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "feross.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "festicle.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "filehash.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fili.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "firefense.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fletcherdigital.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "flexbuildingsystems.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "flowersquito.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fonzone.it", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "foreverclean.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "forself.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "freertomorrow.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "friedzombie.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "frsnpwr.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "fruityfitness.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gakdigital.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gamerwares.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "garbagedisposalguides.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gestsal.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "giftlist.guru", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gpyy.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "groundmc.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gurunpa.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "gyume.ir", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "h33t.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "harelmallac.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "harelmallacglobal.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hartkampforkids.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hawawa.kr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "healthyrecharge.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "heikohessenkemper.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "helpwithinsomnia.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hokung.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hostco.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hsg-kreuzberg.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "hugonote.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "icdp.org.ua", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ictussistemas.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "iinf.in", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "iiyama-bg.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ilmainensanakirja.fi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "indigolawnscape.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "innovere.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "instafuckfriend.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "instahub.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "institutomaritimocolombiano.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "internationalstudentassociation.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ishland.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "isif-ostewg.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "islamicmarkets.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "j0e.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "jamestmartin.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "jerrysretailstores.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "jix.im", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "joyfulhealthyeats.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "julestern.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "jwhite.network", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kalashcards.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "keeckee.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "keeckee.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kiasystems.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kileahh.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "klop.info", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "komp247.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "kosmos.org.tw", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lares.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "launcher-minecraft.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "leafland.co.nz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "left-baggage.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "legabot.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "leszonderstress.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "linasjourney.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "linkst.co", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "linkyou.top", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lippu1.fi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "lisahh-jayne.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "living-with-outlook-2010.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "localegroup.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ltlec.cn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ltlec.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ltmw.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "m2tm.fr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mailhardener.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mainhattan-handwerker.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "malibumodas.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mantuo.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "manwish.cn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "maorx.cn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "markhoodwrites.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "marron-dietrecipe.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "masterpassword.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "matematyka.wiki", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "matteobrenci.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "matthi3u.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mcfi.mu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "melodict.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mentecuriosa.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "michilaw.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mindmax.fi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mionerve.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mionerve.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "misterseguros.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mmgal.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mostcomfortableworkboots.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "motogb.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "motospaya.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mralonas.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mrdatenschutz.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mte.sk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mukyu.moe", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "murphycraftbeerfest.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mvbug.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mycreditunion.gov", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "mypt3.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nailsart.roma.it", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nan.ge", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "napkins-wholesale.co.za", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "napkins-wholesale.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "napkins-wholesale.in", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "napkins-wholesale.nz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "napkins-wholesale.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "napkins-wholesale.us", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nasosvdom.com.ua", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nauris.fi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ncua.gov", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nebras.ga", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "networkhane.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "new-vip.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "newfoundland-labradorflora.ca", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nextcloud-miyamoto.spdns.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nilgirispice.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nonx.pro", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "nook.my", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "northebridge.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "novengi.mu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "noxx.global", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "obec-krakovany.cz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "olandiz.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "olivemultispecialist.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ordbokpro.se", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "outdoorchoose.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "outdoorhole.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "oxz.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "p5on.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "paya.cat", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "paydigital.pt", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "perevedi.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pglaum.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pharmaquality.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "phrazor.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pinkmango.travel", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "pixeoapp.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "platinapump.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "podipod.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "postandfly.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "powersergdatasystems.tk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "powersergdynamic.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "projectmakeit.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "promuovi.tv", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "proximoconcurso.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ptasiepodroze.eu", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "qklshequ.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "qxzgssr.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "rbuddenhagen.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "reby.gq", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "redmangallpsychologists.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "revivalsstores.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "richbutler.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "riddler.com.ar", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ristisanat.fi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "robgorman.ie", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ruhnke.cloud", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "rvsuitlaatdelen.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "saatchiart.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sabbottlabs.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "safungerar.se", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "scevity.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "searchpartners.dk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "securevideo.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "seemomclick.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "seht.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sektor.ro", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "selber-coden.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sendingbee.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "senorporno.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "senseict.com.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "servetten-groothandel.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "servietten-grosshandel.at", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "servietten-grosshandel.be", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "servietten-grosshandel.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "servietten-grosshandel.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "serviettes-et-plus.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "servilletas-de-papel.es", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "servilletas-de-papel.mx", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "serwetki-papierowe.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "shavit.space", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "shdsub.xyz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "shiny.gift", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "shippinglabel.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "shopdongho.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sietejefes.com.ar", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "simulping.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sirihouse.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "skillside.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "skinandglamour.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "skinwhiteningoptions.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "skorpil.cz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "smaltimentoamianto.campania.it", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "smartphone-pliable.wtf", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "snwsjz.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "socialmedia-manager.gr", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sonaraamat.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "southwesteventhire.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sozialstation-ritterhude.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "spalnobelyo.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "spanch.cf", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "stgabrielavondalepa.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "stoerevrouwensporten.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "stretchmarkdestroyer.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "studio-n.pl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "suchem.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "superenduro.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "suppwatch.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "svenrath.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "sweetenedcondensed.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "systemctl.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tandartszilverschoon.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tease.email", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "techlr.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tedxyalesecondaryschool.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "telsu.fi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "testeri.fi", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "thea-team.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "thealonas.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "theappliancedepot.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "thecandyjam.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "thedermreport.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "thefriedzombie.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "theseoplatform.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "thesetwohands864.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "thoxyn.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tomorrowmuseum.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "topyachts.com.ua", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tovaglioli-di-carta.it", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tql.plus", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tracker.com.ar", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tradexport.cn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tradexport.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "transferbags.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "trinitycorporateservices.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "troxal.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "truecosmeticbeauty.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "trustees.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tryplo.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tryplo.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tryplo.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tryplo.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "turingmind.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "tusmedicamentos.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "twoleftsticks.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "u-chan.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "u29dc.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "uitvaartvrouwenfriesland.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ukrn.io", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ummati.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "unicmotos.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "unkn0wncat.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "upcloud.cz", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "uscis.gov", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "valimised.ee", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "valuehost.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "veggiesecret.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "velassoltas.pt", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "vexsoluciones.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "virtualizy.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "vista-research-group.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "viviendy.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "w3n14izy.ml", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "weymouthslowik.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "wijnimportjanssen.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "witch-spells.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "wordnietvindbaar.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "worldsy.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "wsp-center.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "ww-design.ch", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "wzilverschoon.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "xavierdmello.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "xgwap.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "xi.ht", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "xn-----6kcbjcgl1atjj7aadbkxfxfe7a9yia.xn--p1ai", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "xn--12c3bpr6bsv7c.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "xn--3st814ec8r.cn", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "xn--3stv82k.hk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "xn--3stv82k.tw", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "xn--bersetzung-8db.cc", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "xn--durhre-yxa.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "yachtlettering.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "yateshomesales.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "yesornut.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "yolandgao.me", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "yuhindo.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "zadania.wiki", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "zeno-dev.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "zhang.ge", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "zhina.wiki", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "zistemo.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
+    { "name": "zxssl.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     // END OF 1-YEAR BULK HSTS ENTRIES
 
     // Only eTLD+1 domains can be submitted automatically to hstspreload.org,
diff --git a/services/content/public/cpp/navigable_contents.cc b/services/content/public/cpp/navigable_contents.cc
index 791cb27..6a0e715 100644
--- a/services/content/public/cpp/navigable_contents.cc
+++ b/services/content/public/cpp/navigable_contents.cc
@@ -65,6 +65,11 @@
   contents_->FocusThroughTabTraversal(reverse);
 }
 
+void NavigableContents::ClearViewFocus() {
+  if (view_)
+    view_->ClearNativeFocus();
+}
+
 void NavigableContents::DidFinishNavigation(
     const GURL& url,
     bool is_main_frame,
diff --git a/services/content/public/cpp/navigable_contents.h b/services/content/public/cpp/navigable_contents.h
index 0f9772a..ae675b0 100644
--- a/services/content/public/cpp/navigable_contents.h
+++ b/services/content/public/cpp/navigable_contents.h
@@ -71,6 +71,7 @@
 
  private:
   // mojom::NavigableContentsClient:
+  void ClearViewFocus() override;
   void DidFinishNavigation(
       const GURL& url,
       bool is_main_frame,
diff --git a/services/content/public/cpp/navigable_contents_view.cc b/services/content/public/cpp/navigable_contents_view.cc
index 502b7a90..8c1ef02 100644
--- a/services/content/public/cpp/navigable_contents_view.cc
+++ b/services/content/public/cpp/navigable_contents_view.cc
@@ -17,6 +17,7 @@
 #include "ui/accessibility/ax_node_data.h"
 
 #if defined(TOOLKIT_VIEWS)
+#include "ui/views/focus/focus_manager.h"  // nogncheck
 #include "ui/views/layout/fill_layout.h"  // nogncheck
 #include "ui/views/view.h"                // nogncheck
 
@@ -145,6 +146,14 @@
   return GetInServiceProcessFlag().IsSet();
 }
 
+void NavigableContentsView::ClearNativeFocus() {
+#if defined(TOOLKIT_VIEWS) && defined(USE_AURA)
+  auto* focus_manager = view_->GetFocusManager();
+  if (focus_manager)
+    focus_manager->ClearNativeFocus();
+#endif  // defined(TOOLKIT_VIEWS) && defined(USE_AURA)
+}
+
 void NavigableContentsView::NotifyAccessibilityTreeChange() {
 #if defined(TOOLKIT_VIEWS) && defined(USE_AURA)
   view_->NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged, false);
diff --git a/services/content/public/cpp/navigable_contents_view.h b/services/content/public/cpp/navigable_contents_view.h
index 8900ac7..30a2913 100644
--- a/services/content/public/cpp/navigable_contents_view.h
+++ b/services/content/public/cpp/navigable_contents_view.h
@@ -78,6 +78,9 @@
   gfx::NativeView native_view() const { return view_->native_view(); }
 #endif  // defined(TOOLKIT_VIEWS) && defined(USE_AURA)
 
+  // Clears the native view having focus. See FocusManager::ClearNativeFocus.
+  void ClearNativeFocus();
+
   // Has this view notify the UI subsystem of an accessibility tree change.
   void NotifyAccessibilityTreeChange();
 
diff --git a/services/content/public/mojom/navigable_contents.mojom b/services/content/public/mojom/navigable_contents.mojom
index abbdfe4..d90f305 100644
--- a/services/content/public/mojom/navigable_contents.mojom
+++ b/services/content/public/mojom/navigable_contents.mojom
@@ -59,6 +59,9 @@
 // A client interface used by the Content Service to push contents-scoped events
 // back to the application.
 interface NavigableContentsClient {
+  // Requests that the client relinquish focus from the content area's view.
+  ClearViewFocus();
+
   // Notifies the client that a navigation has finished.
   DidFinishNavigation(url.mojom.Url url,
                       bool is_main_frame,
diff --git a/services/content/service_unittest.cc b/services/content/service_unittest.cc
index 04bf305..c6c2aab 100644
--- a/services/content/service_unittest.cc
+++ b/services/content/service_unittest.cc
@@ -30,6 +30,7 @@
 
  private:
   // mojom::NavigableContentsClient:
+  void ClearViewFocus() override {}
   void DidFinishNavigation(const GURL& url,
                            bool is_main_frame,
                            bool is_error_page,
diff --git a/services/device/fingerprint/fingerprint_chromeos.cc b/services/device/fingerprint/fingerprint_chromeos.cc
index 28a5e27..07173d2 100644
--- a/services/device/fingerprint/fingerprint_chromeos.cc
+++ b/services/device/fingerprint/fingerprint_chromeos.cc
@@ -115,11 +115,6 @@
   if (!result)
     return;
 
-  ScheduleStartEnroll(user_id, label);
-}
-
-void FingerprintChromeOS::ScheduleStartEnroll(const std::string& user_id,
-                                              const std::string& label) {
   GetBiodClient()->StartEnrollSession(
       user_id, label,
       base::Bind(&FingerprintChromeOS::OnStartEnrollSession,
@@ -166,7 +161,9 @@
         base::BindRepeating(&FingerprintChromeOS::OnCloseEnrollSessionForAuth,
                             weak_ptr_factory_.GetWeakPtr()));
   } else {
-    ScheduleStartAuth();
+    GetBiodClient()->StartAuthSession(
+        base::Bind(&FingerprintChromeOS::OnStartAuthSession,
+                   weak_ptr_factory_.GetWeakPtr()));
   }
 }
 
@@ -174,10 +171,6 @@
   if (!result)
     return;
 
-  ScheduleStartAuth();
-}
-
-void FingerprintChromeOS::ScheduleStartAuth() {
   GetBiodClient()->StartAuthSession(
       base::Bind(&FingerprintChromeOS::OnStartAuthSession,
                  weak_ptr_factory_.GetWeakPtr()));
diff --git a/services/device/fingerprint/fingerprint_chromeos.h b/services/device/fingerprint/fingerprint_chromeos.h
index 8f2623d1f..5d92809c 100644
--- a/services/device/fingerprint/fingerprint_chromeos.h
+++ b/services/device/fingerprint/fingerprint_chromeos.h
@@ -79,9 +79,6 @@
   void OnCloseAuthSessionForEnroll(const std::string& user_id,
                                    const std::string& label,
                                    bool result);
-  void ScheduleStartEnroll(const std::string& user_id,
-                           const std::string& label);
-  void ScheduleStartAuth();
 
   void RunGetRecordsForUser(const std::string& user_id,
                             GetRecordsForUserCallback callback);
diff --git a/services/media_session/media_controller.cc b/services/media_session/media_controller.cc
index 4d48b59..19afe2c 100644
--- a/services/media_session/media_controller.cc
+++ b/services/media_session/media_controller.cc
@@ -49,13 +49,11 @@
   }
 }
 
-void MediaController::AddObserver(mojom::MediaSessionObserverPtr observer) {
+void MediaController::AddObserver(mojom::MediaControllerObserverPtr observer) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  // Flush the new observer with the state. We always flush the metadata as that
-  // is optional so null is a valid value whereas the session info is required.
-  if (!session_info_.is_null())
-    observer->MediaSessionInfoChanged(session_info_.Clone());
+  // Flush the new observer with the current state.
+  observer->MediaSessionInfoChanged(session_info_.Clone());
   observer->MediaSessionMetadataChanged(session_metadata_);
   observer->MediaSessionActionsChanged(session_actions_);
 
@@ -65,7 +63,7 @@
 void MediaController::MediaSessionInfoChanged(mojom::MediaSessionInfoPtr info) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  observers_.ForAllPtrs([&info](mojom::MediaSessionObserver* observer) {
+  observers_.ForAllPtrs([&info](mojom::MediaControllerObserver* observer) {
     observer->MediaSessionInfoChanged(info.Clone());
   });
 
@@ -76,7 +74,7 @@
     const base::Optional<MediaMetadata>& metadata) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  observers_.ForAllPtrs([&metadata](mojom::MediaSessionObserver* observer) {
+  observers_.ForAllPtrs([&metadata](mojom::MediaControllerObserver* observer) {
     observer->MediaSessionMetadataChanged(metadata);
   });
 
@@ -87,7 +85,7 @@
     const std::vector<mojom::MediaSessionAction>& actions) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  observers_.ForAllPtrs([&actions](mojom::MediaSessionObserver* observer) {
+  observers_.ForAllPtrs([&actions](mojom::MediaControllerObserver* observer) {
     observer->MediaSessionActionsChanged(actions);
   });
 
@@ -131,6 +129,15 @@
       mojom::MediaSessionObserverPtr observer;
       session_binding_.Bind(mojo::MakeRequest(&observer));
       session->AddObserver(std::move(observer));
+    } else {
+      // If we are no longer bound to a session we should flush the observers
+      // with empty data.
+      observers_.ForAllPtrs([](mojom::MediaControllerObserver* observer) {
+        observer->MediaSessionInfoChanged(nullptr);
+        observer->MediaSessionMetadataChanged(base::nullopt);
+        observer->MediaSessionActionsChanged(
+            std::vector<mojom::MediaSessionAction>());
+      });
     }
   }
 
diff --git a/services/media_session/media_controller.h b/services/media_session/media_controller.h
index 0cf05d70..7cb0b0e 100644
--- a/services/media_session/media_controller.h
+++ b/services/media_session/media_controller.h
@@ -33,7 +33,7 @@
   void Resume() override;
   void Stop() override;
   void ToggleSuspendResume() override;
-  void AddObserver(mojom::MediaSessionObserverPtr observer) override;
+  void AddObserver(mojom::MediaControllerObserverPtr observer) override;
   void PreviousTrack() override;
   void NextTrack() override;
   void Seek(base::TimeDelta seek_time) override;
@@ -70,8 +70,8 @@
   // the underlying MediaSession.
   mojom::MediaSession* session_ = nullptr;
 
-  // Observers that are observing |session_|.
-  mojo::InterfacePtrSet<mojom::MediaSessionObserver> observers_;
+  // Observers that are observing |this|.
+  mojo::InterfacePtrSet<mojom::MediaControllerObserver> observers_;
 
   // Binding for |this| to act as an observer to |session_|.
   mojo::Binding<mojom::MediaSessionObserver> session_binding_{this};
diff --git a/services/media_session/media_controller_unittest.cc b/services/media_session/media_controller_unittest.cc
index 16a15db8..9e2f0d2 100644
--- a/services/media_session/media_controller_unittest.cc
+++ b/services/media_session/media_controller_unittest.cc
@@ -16,6 +16,7 @@
 #include "services/media_session/media_session_service.h"
 #include "services/media_session/public/cpp/media_metadata.h"
 #include "services/media_session/public/cpp/test/mock_media_session.h"
+#include "services/media_session/public/cpp/test/test_media_controller.h"
 #include "services/media_session/public/mojom/constants.mojom.h"
 #include "services/service_manager/public/cpp/test/test_connector_factory.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -416,18 +417,18 @@
   }
 
   {
-    test::MockMediaSessionMojoObserver observer(controller());
+    test::TestMediaControllerObserver observer(controller());
     observer.WaitForState(mojom::MediaSessionInfo::SessionState::kActive);
   }
 
   {
-    test::MockMediaSessionMojoObserver observer(controller());
+    test::TestMediaControllerObserver observer(controller());
     controller()->Suspend();
     observer.WaitForState(mojom::MediaSessionInfo::SessionState::kSuspended);
   }
 
   {
-    test::MockMediaSessionMojoObserver observer(controller());
+    test::TestMediaControllerObserver observer(controller());
     RequestAudioFocus(media_session_2, mojom::AudioFocusType::kGain);
     observer.WaitForState(mojom::MediaSessionInfo::SessionState::kActive);
   }
@@ -439,7 +440,7 @@
   }
 
   {
-    test::MockMediaSessionMojoObserver observer(controller());
+    test::TestMediaControllerObserver observer(controller());
     observer.WaitForState(mojom::MediaSessionInfo::SessionState::kActive);
   }
 }
@@ -523,8 +524,8 @@
   media_session.AbandonAudioFocusFromClient();
 
   {
-    test::MockMediaSessionMojoObserver observer(controller());
-    EXPECT_FALSE(observer.WaitForMetadata());
+    test::TestMediaControllerObserver observer(controller());
+    observer.WaitForEmptyMetadata();
   }
 }
 
@@ -541,9 +542,9 @@
   }
 
   {
-    test::MockMediaSessionMojoObserver observer(controller());
+    test::TestMediaControllerObserver observer(controller());
     media_session.SimulateMetadataChanged(test_metadata);
-    EXPECT_EQ(test_metadata, observer.WaitForMetadata());
+    observer.WaitForEmptyMetadata();
   }
 }
 
@@ -565,9 +566,10 @@
   }
 
   {
-    test::MockMediaSessionMojoObserver observer(controller());
+    test::TestMediaControllerObserver observer(controller());
     media_session.SimulateMetadataChanged(test_metadata);
-    EXPECT_EQ(metadata, observer.WaitForNonEmptyMetadata());
+    observer.WaitForNonEmptyMetadata();
+    EXPECT_EQ(metadata, observer.session_metadata());
   }
 }
 
@@ -586,8 +588,8 @@
   media_session.SimulateMetadataChanged(test_metadata);
 
   {
-    test::MockMediaSessionMojoObserver observer(controller());
-    EXPECT_EQ(test_metadata, observer.WaitForMetadata());
+    test::TestMediaControllerObserver observer(controller());
+    observer.WaitForEmptyMetadata();
   }
 }
 
@@ -611,8 +613,9 @@
   media_session.SimulateMetadataChanged(test_metadata);
 
   {
-    test::MockMediaSessionMojoObserver observer(controller());
-    EXPECT_EQ(metadata, *observer.WaitForMetadata());
+    test::TestMediaControllerObserver observer(controller());
+    observer.WaitForNonEmptyMetadata();
+    EXPECT_EQ(metadata, observer.session_metadata());
   }
 }
 
@@ -740,7 +743,7 @@
   }
 
   {
-    test::MockMediaSessionMojoObserver observer(controller());
+    test::TestMediaControllerObserver observer(controller());
     observer.WaitForActions();
     EXPECT_TRUE(observer.actions().empty());
   }
@@ -759,7 +762,7 @@
   media_session.EnableAction(mojom::MediaSessionAction::kPlay);
 
   {
-    test::MockMediaSessionMojoObserver observer(controller());
+    test::TestMediaControllerObserver observer(controller());
     observer.WaitForActions();
 
     EXPECT_EQ(1u, observer.actions().size());
@@ -779,9 +782,7 @@
   }
 
   {
-    test::MockMediaSessionMojoObserver observer(controller());
-    observer.WaitForActions();
-
+    test::TestMediaControllerObserver observer(controller());
     media_session.DisableAction(mojom::MediaSessionAction::kPlay);
     observer.WaitForActions();
 
@@ -800,9 +801,7 @@
   }
 
   {
-    test::MockMediaSessionMojoObserver observer(controller());
-    observer.WaitForActions();
-
+    test::TestMediaControllerObserver observer(controller());
     media_session.EnableAction(mojom::MediaSessionAction::kPlay);
     observer.WaitForActions();
 
@@ -825,7 +824,54 @@
   media_session.AbandonAudioFocusFromClient();
 
   {
-    test::MockMediaSessionMojoObserver observer(controller());
+    test::TestMediaControllerObserver observer(controller());
+    observer.WaitForActions();
+    EXPECT_TRUE(observer.actions().empty());
+  }
+}
+
+TEST_F(MediaControllerTest, ActiveController_Observer_Abandoned) {
+  test::MockMediaSession media_session;
+  media_session.SetIsControllable(true);
+
+  {
+    test::MockMediaSessionMojoObserver observer(media_session);
+    RequestAudioFocus(media_session, mojom::AudioFocusType::kGain);
+    observer.WaitForState(mojom::MediaSessionInfo::SessionState::kActive);
+  }
+
+  {
+    test::TestMediaControllerObserver observer(controller());
+    media_session.AbandonAudioFocusFromClient();
+
+    // We should see empty info, metadata and actions flushed since the active
+    // controller is no longer bound to a media session.
+    observer.WaitForEmptyInfo();
+    observer.WaitForEmptyMetadata();
+    observer.WaitForActions();
+    EXPECT_TRUE(observer.actions().empty());
+  }
+}
+
+TEST_F(MediaControllerTest, ActiveController_AddObserver_Abandoned) {
+  test::MockMediaSession media_session;
+  media_session.SetIsControllable(true);
+
+  {
+    test::MockMediaSessionMojoObserver observer(media_session);
+    RequestAudioFocus(media_session, mojom::AudioFocusType::kGain);
+    observer.WaitForState(mojom::MediaSessionInfo::SessionState::kActive);
+  }
+
+  media_session.AbandonAudioFocusFromClient();
+
+  {
+    test::TestMediaControllerObserver observer(controller());
+
+    // We should see empty info, metadata and actions since the active
+    // controller is no longer bound to a media session.
+    observer.WaitForEmptyInfo();
+    observer.WaitForEmptyMetadata();
     observer.WaitForActions();
     EXPECT_TRUE(observer.actions().empty());
   }
diff --git a/services/media_session/public/cpp/media_metadata.cc b/services/media_session/public/cpp/media_metadata.cc
index 0d07d04e8..42ed11b 100644
--- a/services/media_session/public/cpp/media_metadata.cc
+++ b/services/media_session/public/cpp/media_metadata.cc
@@ -36,4 +36,9 @@
   return !(*this == other);
 }
 
+bool MediaMetadata::IsEmpty() const {
+  return title.empty() && artist.empty() && album.empty() &&
+         source_title.empty() && artwork.empty();
+}
+
 }  // namespace media_session
diff --git a/services/media_session/public/cpp/media_metadata.h b/services/media_session/public/cpp/media_metadata.h
index 6716521..f209927c 100644
--- a/services/media_session/public/cpp/media_metadata.h
+++ b/services/media_session/public/cpp/media_metadata.h
@@ -80,6 +80,9 @@
   // session. This could be the name of the app or the name of the site playing
   // media.
   base::string16 source_title;
+
+  // Returns whether |this| contains no metadata.
+  bool IsEmpty() const;
 };
 
 }  // namespace media_session
diff --git a/services/media_session/public/cpp/test/mock_media_session.cc b/services/media_session/public/cpp/test/mock_media_session.cc
index 92f2ece1..b3ccd79 100644
--- a/services/media_session/public/cpp/test/mock_media_session.cc
+++ b/services/media_session/public/cpp/test/mock_media_session.cc
@@ -12,19 +12,6 @@
 namespace media_session {
 namespace test {
 
-namespace {
-
-bool IsMetadataNonEmpty(const base::Optional<MediaMetadata>& metadata) {
-  if (!metadata.has_value())
-    return false;
-
-  return !metadata->title.empty() || !metadata->artist.empty() ||
-         !metadata->album.empty() || !metadata->source_title.empty() ||
-         !metadata->artwork.empty();
-}
-
-}  // namespace
-
 MockMediaSessionMojoObserver::MockMediaSessionMojoObserver(
     mojom::MediaSession& media_session)
     : binding_(this) {
@@ -33,14 +20,6 @@
   media_session.AddObserver(std::move(observer));
 }
 
-MockMediaSessionMojoObserver::MockMediaSessionMojoObserver(
-    mojom::MediaControllerPtr& controller)
-    : binding_(this) {
-  mojom::MediaSessionObserverPtr observer;
-  binding_.Bind(mojo::MakeRequest(&observer));
-  controller->AddObserver(std::move(observer));
-}
-
 MockMediaSessionMojoObserver::~MockMediaSessionMojoObserver() = default;
 
 void MockMediaSessionMojoObserver::MediaSessionInfoChanged(
@@ -60,7 +39,8 @@
   if (waiting_for_metadata_) {
     run_loop_->Quit();
     waiting_for_metadata_ = false;
-  } else if (waiting_for_non_empty_metadata_ && IsMetadataNonEmpty(metadata)) {
+  } else if (waiting_for_non_empty_metadata_ && metadata.has_value() &&
+             !metadata->IsEmpty()) {
     run_loop_->Quit();
     waiting_for_non_empty_metadata_ = false;
   }
diff --git a/services/media_session/public/cpp/test/mock_media_session.h b/services/media_session/public/cpp/test/mock_media_session.h
index 904e7d1..a226ffa 100644
--- a/services/media_session/public/cpp/test/mock_media_session.h
+++ b/services/media_session/public/cpp/test/mock_media_session.h
@@ -27,10 +27,7 @@
 class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP)
     MockMediaSessionMojoObserver : public mojom::MediaSessionObserver {
  public:
-  // A MediaSessionObserver can observe a MediaSession directly or through a
-  // MediaController.
   explicit MockMediaSessionMojoObserver(mojom::MediaSession& media_session);
-  explicit MockMediaSessionMojoObserver(mojom::MediaControllerPtr& controller);
 
   ~MockMediaSessionMojoObserver() override;
 
diff --git a/services/media_session/public/cpp/test/test_media_controller.cc b/services/media_session/public/cpp/test/test_media_controller.cc
index 0d8de275..905c153 100644
--- a/services/media_session/public/cpp/test/test_media_controller.cc
+++ b/services/media_session/public/cpp/test/test_media_controller.cc
@@ -7,6 +7,116 @@
 namespace media_session {
 namespace test {
 
+TestMediaControllerObserver::TestMediaControllerObserver(
+    mojom::MediaControllerPtr& media_controller)
+    : binding_(this) {
+  mojom::MediaControllerObserverPtr observer;
+  binding_.Bind(mojo::MakeRequest(&observer));
+  media_controller->AddObserver(std::move(observer));
+}
+
+TestMediaControllerObserver::~TestMediaControllerObserver() = default;
+
+void TestMediaControllerObserver::MediaSessionInfoChanged(
+    mojom::MediaSessionInfoPtr session) {
+  session_info_ = std::move(session);
+
+  if (session_info_.has_value() && !session_info_->is_null()) {
+    if (wanted_state_ == session_info()->state ||
+        session_info()->playback_state == wanted_playback_state_) {
+      run_loop_->Quit();
+    }
+  } else if (waiting_for_empty_info_) {
+    waiting_for_empty_info_ = false;
+    run_loop_->Quit();
+  }
+}
+
+void TestMediaControllerObserver::MediaSessionMetadataChanged(
+    const base::Optional<MediaMetadata>& metadata) {
+  session_metadata_ = metadata;
+
+  if (waiting_for_empty_metadata_ &&
+      (!metadata.has_value() || metadata->IsEmpty())) {
+    run_loop_->Quit();
+    waiting_for_empty_metadata_ = false;
+  } else if (waiting_for_non_empty_metadata_ && metadata.has_value() &&
+             !metadata->IsEmpty()) {
+    run_loop_->Quit();
+    waiting_for_non_empty_metadata_ = false;
+  }
+}
+
+void TestMediaControllerObserver::MediaSessionActionsChanged(
+    const std::vector<mojom::MediaSessionAction>& actions) {
+  session_actions_ = actions;
+  session_actions_set_ =
+      std::set<mojom::MediaSessionAction>(actions.begin(), actions.end());
+
+  if (waiting_for_actions_) {
+    run_loop_->Quit();
+    waiting_for_actions_ = false;
+  }
+}
+
+void TestMediaControllerObserver::WaitForState(
+    mojom::MediaSessionInfo::SessionState wanted_state) {
+  if (session_info_ && session_info()->state == wanted_state)
+    return;
+
+  wanted_state_ = wanted_state;
+  StartWaiting();
+}
+
+void TestMediaControllerObserver::WaitForPlaybackState(
+    mojom::MediaPlaybackState wanted_state) {
+  if (session_info_ && session_info()->playback_state == wanted_state)
+    return;
+
+  wanted_playback_state_ = wanted_state;
+  StartWaiting();
+}
+
+void TestMediaControllerObserver::WaitForEmptyInfo() {
+  if (session_info_.has_value() && session_info_->is_null())
+    return;
+
+  waiting_for_empty_info_ = true;
+  StartWaiting();
+}
+
+void TestMediaControllerObserver::WaitForEmptyMetadata() {
+  if (session_metadata_.has_value())
+    return;
+
+  waiting_for_empty_metadata_ = true;
+  StartWaiting();
+}
+
+void TestMediaControllerObserver::WaitForNonEmptyMetadata() {
+  if (session_metadata_.has_value() && !session_metadata_.value()->IsEmpty())
+    return;
+
+  waiting_for_non_empty_metadata_ = true;
+  StartWaiting();
+}
+
+void TestMediaControllerObserver::WaitForActions() {
+  if (session_actions_.has_value())
+    return;
+
+  waiting_for_actions_ = true;
+  StartWaiting();
+}
+
+void TestMediaControllerObserver::StartWaiting() {
+  DCHECK(!run_loop_);
+
+  run_loop_ = std::make_unique<base::RunLoop>();
+  run_loop_->Run();
+  run_loop_.reset();
+}
+
 TestMediaController::TestMediaController() = default;
 
 TestMediaController::~TestMediaController() = default;
@@ -29,7 +139,8 @@
   ++toggle_suspend_resume_count_;
 }
 
-void TestMediaController::AddObserver(mojom::MediaSessionObserverPtr observer) {
+void TestMediaController::AddObserver(
+    mojom::MediaControllerObserverPtr observer) {
   ++add_observer_count_;
   observers_.AddPtr(std::move(observer));
 }
@@ -54,7 +165,7 @@
 
 void TestMediaController::SimulateMediaSessionActionsChanged(
     const std::vector<mojom::MediaSessionAction>& actions) {
-  observers_.ForAllPtrs([&actions](mojom::MediaSessionObserver* observer) {
+  observers_.ForAllPtrs([&actions](mojom::MediaControllerObserver* observer) {
     observer->MediaSessionActionsChanged(actions);
   });
 }
diff --git a/services/media_session/public/cpp/test/test_media_controller.h b/services/media_session/public/cpp/test/test_media_controller.h
index bc24564..8801a67d 100644
--- a/services/media_session/public/cpp/test/test_media_controller.h
+++ b/services/media_session/public/cpp/test/test_media_controller.h
@@ -6,6 +6,7 @@
 #define SERVICES_MEDIA_SESSION_PUBLIC_CPP_TEST_TEST_MEDIA_CONTROLLER_H_
 
 #include "base/component_export.h"
+#include "base/run_loop.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/interface_ptr_set.h"
 #include "services/media_session/public/mojom/media_controller.mojom.h"
@@ -13,6 +14,70 @@
 namespace media_session {
 namespace test {
 
+// A mock MediaControllerObsever that can be used for waiting for state changes.
+class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP)
+    TestMediaControllerObserver : public mojom::MediaControllerObserver {
+ public:
+  explicit TestMediaControllerObserver(
+      mojom::MediaControllerPtr& media_controller);
+
+  ~TestMediaControllerObserver() override;
+
+  // mojom::MediaControllerObserver overrides.
+  void MediaSessionInfoChanged(mojom::MediaSessionInfoPtr session) override;
+  void MediaSessionMetadataChanged(
+      const base::Optional<MediaMetadata>& metadata) override;
+  void MediaSessionActionsChanged(
+      const std::vector<mojom::MediaSessionAction>& actions) override;
+
+  void WaitForState(mojom::MediaSessionInfo::SessionState wanted_state);
+  void WaitForPlaybackState(mojom::MediaPlaybackState wanted_state);
+  void WaitForEmptyInfo();
+
+  void WaitForEmptyMetadata();
+  void WaitForNonEmptyMetadata();
+
+  void WaitForActions();
+
+  const mojom::MediaSessionInfoPtr& session_info() const {
+    return *session_info_;
+  }
+
+  const base::Optional<base::Optional<MediaMetadata>>& session_metadata()
+      const {
+    return session_metadata_;
+  }
+
+  const std::vector<mojom::MediaSessionAction>& actions() const {
+    return *session_actions_;
+  }
+
+  const std::set<mojom::MediaSessionAction>& actions_set() const {
+    return session_actions_set_;
+  }
+
+ private:
+  void StartWaiting();
+
+  base::Optional<mojom::MediaSessionInfoPtr> session_info_;
+  base::Optional<base::Optional<MediaMetadata>> session_metadata_;
+  base::Optional<std::vector<mojom::MediaSessionAction>> session_actions_;
+  std::set<mojom::MediaSessionAction> session_actions_set_;
+
+  bool waiting_for_empty_metadata_ = false;
+  bool waiting_for_non_empty_metadata_ = false;
+
+  bool waiting_for_actions_ = false;
+
+  bool waiting_for_empty_info_ = false;
+  base::Optional<mojom::MediaSessionInfo::SessionState> wanted_state_;
+  base::Optional<mojom::MediaPlaybackState> wanted_playback_state_;
+
+  std::unique_ptr<base::RunLoop> run_loop_;
+
+  mojo::Binding<mojom::MediaControllerObserver> binding_;
+};
+
 // Implements the MediaController mojo interface for tests.
 class COMPONENT_EXPORT(MEDIA_SESSION_TEST_SUPPORT_CPP) TestMediaController
     : public mojom::MediaController {
@@ -27,7 +92,7 @@
   void Resume() override;
   void Stop() override {}
   void ToggleSuspendResume() override;
-  void AddObserver(mojom::MediaSessionObserverPtr observer) override;
+  void AddObserver(mojom::MediaControllerObserverPtr observer) override;
   void PreviousTrack() override;
   void NextTrack() override;
   void Seek(base::TimeDelta seek_time) override;
@@ -58,7 +123,7 @@
   int seek_backward_count_ = 0;
   int seek_forward_count_ = 0;
 
-  mojo::InterfacePtrSet<mojom::MediaSessionObserver> observers_;
+  mojo::InterfacePtrSet<mojom::MediaControllerObserver> observers_;
 
   mojo::Binding<mojom::MediaController> binding_{this};
 
diff --git a/services/media_session/public/mojom/media_controller.mojom b/services/media_session/public/mojom/media_controller.mojom
index 07a854b3..2aa9af7b 100644
--- a/services/media_session/public/mojom/media_controller.mojom
+++ b/services/media_session/public/mojom/media_controller.mojom
@@ -40,7 +40,7 @@
   // Adds an observer that will forward events from the active media session.
   // If the active session changes then observers do not need to be readded.
   // Adding the observer will update the observer with the latest state.
-  AddObserver(MediaSessionObserver observer);
+  AddObserver(MediaControllerObserver observer);
 
   // Skip to the previous track. If there is no previous track then this will be
   // a no-op.
@@ -57,3 +57,22 @@
   // few seconds.
   Seek(mojo_base.mojom.TimeDelta seek_time);
 };
+
+// The observer for observing media controller events. This is different to a
+// MediaSessionObserver because a media controller can have nullable session
+// info which will be null if it is not bound to a media session. This would
+// be invalid for a media session because it must always have some state.
+interface MediaControllerObserver {
+  // Called when the state of the bound media session changes. If |info| is
+  // empty then the controller is no longer bound to a media session.
+  MediaSessionInfoChanged(MediaSessionInfo? info);
+
+  // Called when the bound media session has changed metadata. If |metadata|
+  // is null then it can be reset, e.g. the media that ws being played has
+  // been stopped.
+  MediaSessionMetadataChanged(MediaMetadata? metadata);
+
+  // Called when the bound media session action list has changed. This tells
+  // the observer which actions can be used to control the session.
+  MediaSessionActionsChanged(array<MediaSessionAction> action);
+};
diff --git a/services/network/url_loader.cc b/services/network/url_loader.cc
index deda168..1cd4aad 100644
--- a/services/network/url_loader.cc
+++ b/services/network/url_loader.cc
@@ -12,8 +12,6 @@
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/command_line.h"
-#include "base/debug/alias.h"
-#include "base/debug/dump_without_crashing.h"
 #include "base/files/file.h"
 #include "base/logging.h"
 #include "base/memory/weak_ptr.h"
@@ -52,16 +50,12 @@
 namespace network {
 
 namespace {
-
 constexpr size_t kDefaultAllocationSize = 512 * 1024;
 
 // Cannot use 0, because this means "default" in
 // mojo::core::Core::CreateDataPipe
 constexpr size_t kBlockedBodyAllocationSize = 1;
 
-// Used to dump when we get too many requests, once.
-bool g_reported_too_many_requests = false;
-
 // TODO: this duplicates some of PopulateResourceResponse in
 // content/browser/loader/resource_loader.cc
 void PopulateResourceResponse(net::URLRequest* request,
@@ -445,18 +439,6 @@
   if (keepalive_ && keepalive_statistics_recorder_)
     keepalive_statistics_recorder_->OnLoadStarted(factory_params_->process_id);
 
-  // Record some debug info in hope of tracing down leaks.
-  int32_t annotation_hash =
-      url_request_->traffic_annotation().unique_id_hash_code;
-  size_t num_running_requests = url_request_context_->url_requests()->size();
-  base::debug::Alias(&annotation_hash);
-  base::debug::Alias(&num_running_requests);
-  DEBUG_ALIAS_FOR_GURL(url_buf, url_request_->url());
-  if (!g_reported_too_many_requests && num_running_requests > 10000) {
-    g_reported_too_many_requests = true;
-    base::debug::DumpWithoutCrashing();
-  }
-
   // Resolve elements from request_body and prepare upload data.
   if (request.request_body.get()) {
     OpenFilesForUpload(request);
diff --git a/services/ws/ime/ime_unittest.cc b/services/ws/ime/ime_unittest.cc
index b9031e1..6ac270a 100644
--- a/services/ws/ime/ime_unittest.cc
+++ b/services/ws/ime/ime_unittest.cc
@@ -17,7 +17,7 @@
 #include "services/ws/ime/test_ime_driver/public/mojom/constants.mojom.h"
 #include "services/ws/public/mojom/constants.mojom.h"
 #include "services/ws/public/mojom/ime/ime.mojom.h"
-#include "services/ws/test_ws/manifest.h"
+#include "services/ws/test_ws/test_manifest.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/events/event.h"
 #include "ui/events/keycodes/dom/dom_code.h"
diff --git a/services/ws/test_ws/BUILD.gn b/services/ws/test_ws/BUILD.gn
index ac94174..710801dd 100644
--- a/services/ws/test_ws/BUILD.gn
+++ b/services/ws/test_ws/BUILD.gn
@@ -54,12 +54,19 @@
   ]
 }
 
-service_manifest("manifest") {
+source_set("manifest") {
   testonly = true
-
-  name = "test_ws"
-  source = "manifest.json"
-  packaged_services = [ "//services/ws:manifest" ]
+  sources = [
+    "test_manifest.cc",
+    "test_manifest.h",
+  ]
+  deps = [
+    ":mojom",
+    "//base",
+    "//services/service_manager/public/cpp",
+    "//services/service_manager/public/mojom",
+    "//services/ws:manifest",
+  ]
 }
 
 mojom("mojom") {
diff --git a/services/ws/test_ws/manifest.json b/services/ws/test_ws/manifest.json
deleted file mode 100644
index b1ea0bd9..0000000
--- a/services/ws/test_ws/manifest.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
-  "name": "test_ws",
-  "sandbox_type": "none",
-  "display_name": "Test Window Service",
-  "interface_provider_specs": {
-    "service_manager:connector": {
-      "provides": {
-        "service_manager:service_factory": [
-          "service_manager.mojom.ServiceFactory"
-        ],
-        "test": [
-          "test_ws.mojom.TestWs"
-        ]
-      }
-    }
-  }
-}
diff --git a/services/ws/test_ws/test_manifest.cc b/services/ws/test_ws/test_manifest.cc
new file mode 100644
index 0000000..58dcb8d
--- /dev/null
+++ b/services/ws/test_ws/test_manifest.cc
@@ -0,0 +1,33 @@
+// 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 "services/ws/test_ws/test_manifest.h"
+
+#include "base/no_destructor.h"
+#include "services/service_manager/public/cpp/manifest_builder.h"
+#include "services/service_manager/public/mojom/service_factory.mojom.h"
+#include "services/ws/manifest.h"
+#include "services/ws/test_ws/test_ws.mojom.h"
+
+namespace test_ws {
+
+const service_manager::Manifest& GetManifest() {
+  static base::NoDestructor<service_manager::Manifest> manifest{
+      service_manager::ManifestBuilder()
+          .WithServiceName(mojom::kServiceName)
+          .WithDisplayName("Test Window Service")
+          .WithOptions(service_manager::ManifestOptionsBuilder()
+                           .WithSandboxType("none")
+                           .Build())
+          .ExposeCapability(
+              "test", service_manager::Manifest::InterfaceList<mojom::TestWs>())
+          .ExposeCapability("service_manager:service_factory",
+                            service_manager::Manifest::InterfaceList<
+                                service_manager::mojom::ServiceFactory>())
+          .PackageService(ui::GetManifest())
+          .Build()};
+  return *manifest;
+}
+
+}  // namespace test_ws
diff --git a/services/ws/test_ws/test_manifest.h b/services/ws/test_ws/test_manifest.h
new file mode 100644
index 0000000..a9115db
--- /dev/null
+++ b/services/ws/test_ws/test_manifest.h
@@ -0,0 +1,16 @@
+// 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 SERVICES_WS_TEST_WS_TEST_MANIFEST_H_
+#define SERVICES_WS_TEST_WS_TEST_MANIFEST_H_
+
+#include "services/service_manager/public/cpp/manifest.h"
+
+namespace test_ws {
+
+const service_manager::Manifest& GetManifest();
+
+}  // namespace test_ws
+
+#endif  // SERVICES_WS_TEST_WS_TEST_MANIFEST_H_
diff --git a/services/ws/window_server_service_test_base.cc b/services/ws/window_server_service_test_base.cc
index c6245b0d..1caa360 100644
--- a/services/ws/window_server_service_test_base.cc
+++ b/services/ws/window_server_service_test_base.cc
@@ -12,7 +12,7 @@
 #include "services/ws/common/switches.h"
 #include "services/ws/public/mojom/constants.mojom.h"
 #include "services/ws/public/mojom/window_tree.mojom.h"
-#include "services/ws/test_ws/manifest.h"
+#include "services/ws/test_ws/test_manifest.h"
 #include "ui/gl/gl_switches.h"
 
 namespace ws {
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index a51c431..a92c1014 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -23694,6 +23694,9 @@
     ],
     "isolated_scripts": [
       {
+        "args": [
+          "--gtest-benchmark-name=components_perftests"
+        ],
         "isolate_name": "components_perftests",
         "merge": {
           "args": [
diff --git a/testing/buildbot/chromium.clang.json b/testing/buildbot/chromium.clang.json
index e4fe884..9334f2f 100644
--- a/testing/buildbot/chromium.clang.json
+++ b/testing/buildbot/chromium.clang.json
@@ -13696,6 +13696,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=components_perftests"
+        ],
         "isolate_name": "components_perftests",
         "merge": {
           "args": [
@@ -13754,6 +13757,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=views_perftests"
+        ],
         "isolate_name": "views_perftests",
         "merge": {
           "args": [
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 428c4cd5..f1b61b1 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -891,6 +891,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=components_perftests"
+        ],
         "isolate_name": "components_perftests",
         "merge": {
           "args": [
@@ -949,6 +952,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=views_perftests"
+        ],
         "isolate_name": "views_perftests",
         "merge": {
           "args": [
@@ -6654,6 +6660,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=views_perftests"
+        ],
         "isolate_name": "views_perftests",
         "merge": {
           "args": [
@@ -10805,6 +10814,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=views_perftests"
+        ],
         "isolate_name": "views_perftests",
         "merge": {
           "args": [
@@ -12220,6 +12232,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=components_perftests"
+        ],
         "isolate_name": "components_perftests",
         "merge": {
           "args": [
@@ -12320,6 +12335,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=views_perftests"
+        ],
         "isolate_name": "views_perftests",
         "merge": {
           "args": [
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index 7347cb3..99dd07e 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -206,10 +206,17 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -682,10 +689,17 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -728,12 +742,19 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name=passthrough_command_buffer_perftests",
           "-v",
           "--use-cmd-decoder=passthrough",
           "--use-angle=gl-null",
           "--fast-run"
         ],
         "isolate_name": "command_buffer_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "passthrough_command_buffer_perftests",
         "should_retry_with_patch": false,
         "swarming": {
@@ -749,12 +770,19 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name=validating_command_buffer_perftests",
           "-v",
           "--use-cmd-decoder=validating",
           "--use-stub",
           "--fast-run"
         ],
         "isolate_name": "command_buffer_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "validating_command_buffer_perftests",
         "should_retry_with_patch": false,
         "swarming": {
@@ -1055,11 +1083,18 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only",
           "--shard-timeout=500"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -1154,11 +1189,18 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only",
           "--shard-timeout=500"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -1253,11 +1295,18 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only",
           "--shard-timeout=500"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -1487,11 +1536,18 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only",
           "--shard-timeout=500"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -1586,11 +1642,18 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only",
           "--shard-timeout=500"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -1685,11 +1748,18 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only",
           "--shard-timeout=500"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -2024,11 +2094,18 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only",
           "--shard-timeout=500"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -2825,11 +2902,18 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only",
           "--shard-timeout=500"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -3749,11 +3833,18 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only",
           "--shard-timeout=500"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -4195,11 +4286,18 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only",
           "--shard-timeout=500"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -5383,10 +5481,17 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -5890,10 +5995,17 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -7070,10 +7182,17 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -14569,10 +14688,17 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -15324,10 +15450,17 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -15864,10 +15997,17 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -15928,12 +16068,19 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name=passthrough_command_buffer_perftests",
           "-v",
           "--use-cmd-decoder=passthrough",
           "--use-angle=gl-null",
           "--fast-run"
         ],
         "isolate_name": "command_buffer_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "passthrough_command_buffer_perftests",
         "should_retry_with_patch": false,
         "swarming": {
@@ -15958,12 +16105,19 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name=validating_command_buffer_perftests",
           "-v",
           "--use-cmd-decoder=validating",
           "--use-stub",
           "--fast-run"
         ],
         "isolate_name": "command_buffer_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "validating_command_buffer_perftests",
         "should_retry_with_patch": false,
         "swarming": {
@@ -17195,10 +17349,17 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -18031,10 +18192,17 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -19000,10 +19168,17 @@
     "isolated_scripts": [
       {
         "args": [
+          "--gtest-benchmark-name=angle_perftests",
           "-v",
           "--one-frame-only"
         ],
         "isolate_name": "angle_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "angle_perftests",
         "swarming": {
           "can_use_on_swarming_builders": true,
@@ -19236,12 +19411,19 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name=passthrough_command_buffer_perftests",
           "-v",
           "--use-cmd-decoder=passthrough",
           "--use-angle=gl-null",
           "--fast-run"
         ],
         "isolate_name": "command_buffer_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "passthrough_command_buffer_perftests",
         "should_retry_with_patch": false,
         "swarming": {
@@ -19381,12 +19563,19 @@
       },
       {
         "args": [
+          "--gtest-benchmark-name=validating_command_buffer_perftests",
           "-v",
           "--use-cmd-decoder=validating",
           "--use-stub",
           "--fast-run"
         ],
         "isolate_name": "command_buffer_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
         "name": "validating_command_buffer_perftests",
         "should_retry_with_patch": false,
         "swarming": {
diff --git a/testing/buildbot/chromium.linux.json b/testing/buildbot/chromium.linux.json
index 52f54cc..249c697 100644
--- a/testing/buildbot/chromium.linux.json
+++ b/testing/buildbot/chromium.linux.json
@@ -1731,6 +1731,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=views_perftests"
+        ],
         "isolate_name": "views_perftests",
         "merge": {
           "args": [
@@ -2447,6 +2450,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=views_perftests"
+        ],
         "isolate_name": "views_perftests",
         "merge": {
           "args": [
@@ -3119,6 +3125,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=views_perftests"
+        ],
         "isolate_name": "views_perftests",
         "merge": {
           "args": [
@@ -4375,6 +4384,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=views_perftests"
+        ],
         "isolate_name": "views_perftests",
         "merge": {
           "args": [
diff --git a/testing/buildbot/chromium.mac.json b/testing/buildbot/chromium.mac.json
index 350713f..88d47af 100644
--- a/testing/buildbot/chromium.mac.json
+++ b/testing/buildbot/chromium.mac.json
@@ -995,6 +995,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=components_perftests"
+        ],
         "isolate_name": "components_perftests",
         "merge": {
           "args": [
@@ -1092,6 +1095,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=views_perftests"
+        ],
         "isolate_name": "views_perftests",
         "merge": {
           "args": [
@@ -2133,6 +2139,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=components_perftests"
+        ],
         "isolate_name": "components_perftests",
         "merge": {
           "args": [
@@ -2230,6 +2239,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=views_perftests"
+        ],
         "isolate_name": "views_perftests",
         "merge": {
           "args": [
@@ -3354,6 +3366,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=components_perftests"
+        ],
         "isolate_name": "components_perftests",
         "merge": {
           "args": [
@@ -3451,6 +3466,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=views_perftests"
+        ],
         "isolate_name": "views_perftests",
         "merge": {
           "args": [
@@ -4492,6 +4510,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=components_perftests"
+        ],
         "isolate_name": "components_perftests",
         "merge": {
           "args": [
@@ -4570,6 +4591,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=views_perftests"
+        ],
         "isolate_name": "views_perftests",
         "merge": {
           "args": [
@@ -5679,6 +5703,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=views_perftests"
+        ],
         "isolate_name": "views_perftests",
         "merge": {
           "args": [
diff --git a/testing/buildbot/chromium.perf.json b/testing/buildbot/chromium.perf.json
index c95e65de..0aa98c44 100644
--- a/testing/buildbot/chromium.perf.json
+++ b/testing/buildbot/chromium.perf.json
@@ -6,9 +6,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "components_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "components_perftests"
         ],
         "isolate_name": "components_perftests",
         "merge": {
@@ -47,9 +45,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "gpu_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "gpu_perftests"
         ],
         "isolate_name": "gpu_perftests",
         "merge": {
@@ -88,9 +84,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "tracing_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "tracing_perftests"
         ],
         "isolate_name": "tracing_perftests",
         "merge": {
@@ -270,9 +264,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "angle_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "angle_perftests"
         ],
         "isolate_name": "angle_perftests",
         "merge": {
@@ -309,9 +301,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "load_library_perf_tests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "load_library_perf_tests"
         ],
         "isolate_name": "load_library_perf_tests",
         "merge": {
@@ -348,9 +338,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "media_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "media_perftests"
         ],
         "isolate_name": "media_perftests",
         "merge": {
@@ -388,8 +376,6 @@
         "args": [
           "--gtest-benchmark-name",
           "passthrough_command_buffer_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true",
           "--use-cmd-decoder=passthrough",
           "--use-angle=gl-null"
         ],
@@ -429,8 +415,6 @@
         "args": [
           "--gtest-benchmark-name",
           "validating_command_buffer_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true",
           "--use-cmd-decoder=validating",
           "--use-stub"
         ],
@@ -514,9 +498,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "components_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "components_perftests"
         ],
         "isolate_name": "components_perftests",
         "merge": {
@@ -553,9 +535,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "load_library_perf_tests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "load_library_perf_tests"
         ],
         "isolate_name": "load_library_perf_tests",
         "merge": {
@@ -592,9 +572,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "media_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "media_perftests"
         ],
         "isolate_name": "media_perftests",
         "merge": {
@@ -729,8 +707,6 @@
         "args": [
           "--gtest-benchmark-name",
           "angle_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true",
           "--shard-timeout=300"
         ],
         "isolate_name": "angle_perftests",
@@ -770,9 +746,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "base_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "base_perftests"
         ],
         "isolate_name": "base_perftests",
         "merge": {
@@ -811,9 +785,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "components_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "components_perftests"
         ],
         "isolate_name": "components_perftests",
         "merge": {
@@ -852,9 +824,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "gpu_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "gpu_perftests"
         ],
         "isolate_name": "gpu_perftests",
         "merge": {
@@ -893,9 +863,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "media_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "media_perftests"
         ],
         "isolate_name": "media_perftests",
         "merge": {
@@ -934,9 +902,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "tracing_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "tracing_perftests"
         ],
         "isolate_name": "tracing_perftests",
         "merge": {
@@ -1034,9 +1000,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "base_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "base_perftests"
         ],
         "isolate_name": "base_perftests",
         "merge": {
@@ -1073,9 +1037,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "load_library_perf_tests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "load_library_perf_tests"
         ],
         "isolate_name": "load_library_perf_tests",
         "merge": {
@@ -1112,9 +1074,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "media_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "media_perftests"
         ],
         "isolate_name": "media_perftests",
         "merge": {
@@ -1151,9 +1111,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "net_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "net_perftests"
         ],
         "isolate_name": "net_perftests",
         "merge": {
@@ -1190,9 +1148,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "performance_browser_tests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "performance_browser_tests"
         ],
         "isolate_name": "performance_browser_tests",
         "merge": {
@@ -1229,9 +1185,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "tracing_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "tracing_perftests"
         ],
         "isolate_name": "tracing_perftests",
         "merge": {
@@ -1313,9 +1267,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "load_library_perf_tests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "load_library_perf_tests"
         ],
         "isolate_name": "load_library_perf_tests",
         "merge": {
@@ -1352,9 +1304,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "performance_browser_tests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "performance_browser_tests"
         ],
         "isolate_name": "performance_browser_tests",
         "merge": {
@@ -1436,9 +1386,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "base_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "base_perftests"
         ],
         "isolate_name": "base_perftests",
         "merge": {
@@ -1475,9 +1423,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "media_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "media_perftests"
         ],
         "isolate_name": "media_perftests",
         "merge": {
@@ -1514,9 +1460,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "net_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "net_perftests"
         ],
         "isolate_name": "net_perftests",
         "merge": {
@@ -1553,9 +1497,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "performance_browser_tests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "performance_browser_tests"
         ],
         "isolate_name": "performance_browser_tests",
         "merge": {
@@ -1592,9 +1534,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "views_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "views_perftests"
         ],
         "isolate_name": "views_perftests",
         "merge": {
@@ -1682,8 +1622,6 @@
         "args": [
           "--gtest-benchmark-name",
           "angle_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true",
           "--shard-timeout=300"
         ],
         "isolate_name": "angle_perftests",
@@ -1721,9 +1659,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "base_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "base_perftests"
         ],
         "isolate_name": "base_perftests",
         "merge": {
@@ -1760,9 +1696,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "components_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "components_perftests"
         ],
         "isolate_name": "components_perftests",
         "merge": {
@@ -1799,9 +1733,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "media_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "media_perftests"
         ],
         "isolate_name": "media_perftests",
         "merge": {
@@ -1838,9 +1770,7 @@
       {
         "args": [
           "--gtest-benchmark-name",
-          "views_perftests",
-          "--non-telemetry=true",
-          "--migrated-test=true"
+          "views_perftests"
         ],
         "isolate_name": "views_perftests",
         "merge": {
diff --git a/testing/buildbot/chromium.win.json b/testing/buildbot/chromium.win.json
index bb6d83a..f93abd10 100644
--- a/testing/buildbot/chromium.win.json
+++ b/testing/buildbot/chromium.win.json
@@ -716,6 +716,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=components_perftests"
+        ],
         "isolate_name": "components_perftests",
         "merge": {
           "args": [
@@ -786,6 +789,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=views_perftests"
+        ],
         "isolate_name": "views_perftests",
         "merge": {
           "args": [
@@ -3382,6 +3388,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=views_perftests"
+        ],
         "isolate_name": "views_perftests",
         "merge": {
           "args": [
@@ -4116,6 +4125,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=components_perftests"
+        ],
         "isolate_name": "components_perftests",
         "merge": {
           "args": [
@@ -4186,6 +4198,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=views_perftests"
+        ],
         "isolate_name": "views_perftests",
         "merge": {
           "args": [
@@ -4972,6 +4987,9 @@
         }
       },
       {
+        "args": [
+          "--gtest-benchmark-name=views_perftests"
+        ],
         "isolate_name": "views_perftests",
         "merge": {
           "args": [
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index 772fb5f8..13487c28 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -127,6 +127,8 @@
   "angle_perftests": {
     "args": [
       "angle_perftests",
+      "--non-telemetry=true",
+      "--migrated-test=true",
       "--test-launcher-print-test-stdio=always",
       "--test-launcher-jobs=1",
       "--test-launcher-retry-limit=0",
@@ -251,6 +253,8 @@
   "base_perftests": {
     "args": [
       "base_perftests",
+      "--non-telemetry=true",
+      "--migrated-test=true",
       "--test-launcher-print-test-stdio=always",
       "--test-launcher-jobs=1",
       "--test-launcher-retry-limit=0",
@@ -619,6 +623,8 @@
   "command_buffer_perftests": {
     "args": [
       "command_buffer_perftests",
+      "--non-telemetry=true",
+      "--migrated-test=true",
       "--adb-path",
       "src/third_party/android_tools/sdk/platform-tools/adb",
     ],
@@ -649,7 +655,6 @@
   "components_perftests": {
     "args": [
       "--xvfb",
-      "--gtest-benchmark-name=components_perftests",
       "--non-telemetry=true",
       "--migrated-test=true",
       "components_perftests",
@@ -1028,6 +1033,8 @@
   "gpu_perftests": {
     "args": [
       "gpu_perftests",
+      "--non-telemetry=true",
+      "--migrated-test=true",
       "--adb-path",
       "src/third_party/android_tools/sdk/platform-tools/adb",
     ],
@@ -1353,6 +1360,8 @@
   "load_library_perf_tests": {
     "args": [
       "load_library_perf_tests",
+      "--non-telemetry=true",
+      "--migrated-test=true",
       "--test-launcher-print-test-stdio=always",
     ],
     "label": "//chrome/test:load_library_perf_tests",
@@ -1390,6 +1399,8 @@
   "media_perftests": {
     "args": [
       "media_perftests",
+      "--non-telemetry=true",
+      "--migrated-test=true",
       "--single-process-tests",
       "--test-launcher-retry-limit=0",
       "--isolated-script-test-filter=*::-*_unoptimized::*_unaligned::*unoptimized_aligned",
@@ -1789,6 +1800,8 @@
   "net_perftests": {
     "args": [
       "net_perftests",
+      "--non-telemetry=true",
+      "--migrated-test=true",
     ],
     "label": "//net:net_perftests",
     "script": "//testing/scripts/run_performance_tests_wrapper.py",
@@ -2018,6 +2031,8 @@
   "performance_browser_tests": {
     "args": [
       "performance_browser_tests",
+      "--non-telemetry=true",
+      "--migrated-test=true",
       "--test-launcher-print-test-stdio=always",
       # TODO(crbug.com/759866): Figure out why CastV2PerformanceTest/0 sometimes
       # takes 15-30 seconds to start up and, once fixed, remove this workaround
@@ -2434,6 +2449,8 @@
   "tracing_perftests": {
     "args": [
       "tracing_perftests",
+      "--non-telemetry=true",
+      "--migrated-test=true",
       "--test-launcher-print-test-stdio=always",
       "--adb-path",
       "src/third_party/android_tools/sdk/platform-tools/adb",
@@ -2574,7 +2591,6 @@
   "views_perftests": {
     "args": [
       "--xvfb",
-      "--gtest-benchmark-name=views_perftests",
       "--non-telemetry=true",
       "--migrated-test=true",
       "views_perftests",
@@ -2614,8 +2630,7 @@
   "vr_common_perftests": {
     "args": [
       "vr_common_perftests",
-      "--non-telemetry=1",
-      "--gtest-benchmark-name=xr.vr.common_perftests",
+      "--non-telemetry=true",
       "--adb-path",
       "src/third_party/android_tools/sdk/platform-tools/adb",
     ],
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 8c9eb62..b29400c 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -2657,6 +2657,9 @@
             '--smoke-test-mode',
           ],
         },
+        'args': [
+          '--gtest-benchmark-name=components_perftests',
+        ],
       },
     },
 
@@ -2707,6 +2710,9 @@
             '--smoke-test-mode',
           ],
         },
+        'args': [
+          '--gtest-benchmark-name=views_perftests',
+        ],
       },
       'webkit_layout_tests': {
         # layout test failures are retried 3 times when '--test-list' is not
@@ -3054,6 +3060,7 @@
     'gpu_angle_perftests': {
       'angle_perftests': {
         'args': [
+          '--gtest-benchmark-name=angle_perftests',
           '-v',
           # Tell the tests to exit after one frame for faster iteration.
           '--one-frame-only',
@@ -3061,6 +3068,12 @@
         'android_args': [
           '--shard-timeout=500',
         ],
+        'merge': {
+          'script': '//tools/perf/process_perf_results.py',
+          'args': [
+            '--smoke-test-mode',
+          ],
+        },
       },
     },
 
@@ -3096,12 +3109,19 @@
     'gpu_command_buffer_perftests_passthrough': {
       'passthrough_command_buffer_perftests': {
         'args': [
+          '--gtest-benchmark-name=passthrough_command_buffer_perftests',
           '-v',
           '--use-cmd-decoder=passthrough',
           '--use-angle=gl-null',
           '--fast-run',
         ],
         'isolate_name': 'command_buffer_perftests',
+        'merge': {
+          'script': '//tools/perf/process_perf_results.py',
+          'args': [
+            '--smoke-test-mode',
+          ],
+        },
         'should_retry_with_patch': False,
       },
     },
@@ -3109,12 +3129,19 @@
     'gpu_command_buffer_perftests_validating': {
       'validating_command_buffer_perftests': {
         'args': [
+          '--gtest-benchmark-name=validating_command_buffer_perftests',
           '-v',
           '--use-cmd-decoder=validating',
           '--use-stub',
           '--fast-run',
         ],
         'isolate_name': 'command_buffer_perftests',
+        'merge': {
+          'script': '//tools/perf/process_perf_results.py',
+          'args': [
+            '--smoke-test-mode',
+          ],
+        },
         'should_retry_with_patch': False,
       },
     },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 0be8968..1b75e321 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -970,6 +970,25 @@
             ]
         }
     ],
+    "BlinkGenPropertyTrees": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "BlinkGenPropertyTrees"
+                    ]
+                }
+            ]
+        }
+    ],
     "BlinkSchedulerDedicatedWorkerThrottling": [
         {
             "platforms": [
diff --git a/third_party/blink/public/platform/task_type.h b/third_party/blink/public/platform/task_type.h
index 718031f..19abed3 100644
--- a/third_party/blink/public/platform/task_type.h
+++ b/third_party/blink/public/platform/task_type.h
@@ -143,6 +143,9 @@
   // https://www.w3.org/TR/permissions/
   kPermission = 59,
 
+  // https://w3c.github.io/ServiceWorker/#dfn-client-message-queue
+  kServiceWorkerClientMessage = 60,
+
   ///////////////////////////////////////
   // Not-speced tasks should use one of the following task types
   ///////////////////////////////////////
@@ -217,7 +220,7 @@
   kWorkerThreadTaskQueueV8 = 47,
   kWorkerThreadTaskQueueCompositor = 48,
 
-  kCount = 60,
+  kCount = 61,
 };
 
 }  // namespace blink
diff --git a/third_party/blink/public/web/web_widget.h b/third_party/blink/public/web/web_widget.h
index 2e90d02f..7064cd2 100644
--- a/third_party/blink/public/web/web_widget.h
+++ b/third_party/blink/public/web/web_widget.h
@@ -95,13 +95,29 @@
 
   // Called to update imperative animation state. This should be called before
   // paint, although the client can rate-limit these calls.
-  // |last_frame_time| is in seconds.
-  virtual void BeginFrame(base::TimeTicks last_frame_time) {}
+  // |last_frame_time| is in seconds. |record_main_frame_metrics| is true when
+  // UMA and UKM metrics should be emitted for animation work.
+  virtual void BeginFrame(base::TimeTicks last_frame_time,
+                          bool record_main_frame_metrics) {}
+
+  // Called when main frame metrics are desired. The local frame's UKM
+  // aggregator must be informed that collection is starting for the
+  // frame.
+  virtual void RecordStartOfFrameMetrics() {}
 
   // Called when a main frame time metric should be emitted, along with
   // any metrics that depend upon the main frame total time.
   virtual void RecordEndOfFrameMetrics(base::TimeTicks frame_begin_time) {}
 
+  // Methods called to mark the beginning and end of input processing work
+  // before rAF scripts are executed. Only called when gathering main frame
+  // UMA and UKM. That is, when RecordStartOfFrameMetrics has been called, and
+  // before RecordEndOfFrameMetrics has been called. Only implement if the
+  // rAF input update will be called as part of a layer tree view main frame
+  // update.
+  virtual void BeginRafAlignedInput() {}
+  virtual void EndRafAlignedInput() {}
+
   // Called to run through the entire set of document lifecycle phases needed
   // to render a frame of the web widget. This MUST be called before Paint,
   // and it may result in calls to WebWidgetClient::DidInvalidateRect.
diff --git a/third_party/blink/renderer/bindings/core/v8/v8_wasm_response_extensions.cc b/third_party/blink/renderer/bindings/core/v8/v8_wasm_response_extensions.cc
index c50c77d..7cbc75cf 100644
--- a/third_party/blink/renderer/bindings/core/v8/v8_wasm_response_extensions.cc
+++ b/third_party/blink/renderer/bindings/core/v8/v8_wasm_response_extensions.cc
@@ -8,19 +8,31 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_response.h"
+#include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/fetch/body_stream_buffer.h"
 #include "third_party/blink/renderer/core/fetch/fetch_data_loader.h"
+#include "third_party/blink/renderer/core/frame/local_dom_window.h"
+#include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/bindings/trace_wrapper_member.h"
 #include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
+#include "third_party/blink/renderer/platform/loader/fetch/cached_metadata.h"
+#include "third_party/blink/renderer/platform/loader/fetch/raw_resource.h"
+#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
+#include "third_party/blink/renderer/platform/loader/fetch/script_cached_metadata_handler.h"
+#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 
 namespace blink {
 
 namespace {
 
+// Wasm only has a single metadata type, but we need to tag it.
+static const int kWasmModuleTag = 1;
+
 // The |FetchDataLoader| for streaming compilation of WebAssembly code. The
 // received bytes get forwarded to the V8 API class |WasmStreaming|.
 class FetchDataLoaderForWasmStreaming final : public FetchDataLoader,
@@ -28,10 +40,12 @@
   USING_GARBAGE_COLLECTED_MIXIN(FetchDataLoaderForWasmStreaming);
 
  public:
-  FetchDataLoaderForWasmStreaming(ScriptState* script_state,
-                                  std::shared_ptr<v8::WasmStreaming> streaming)
+  FetchDataLoaderForWasmStreaming(std::shared_ptr<v8::WasmStreaming> streaming,
+                                  ScriptState* script_state)
       : streaming_(std::move(streaming)), script_state_(script_state) {}
 
+  v8::WasmStreaming* streaming() const { return streaming_.get(); }
+
   void Start(BytesConsumer* consumer,
              FetchDataLoader::Client* client) override {
     DCHECK(!consumer_);
@@ -112,7 +126,8 @@
       streaming_->Abort(v8::Local<v8::Value>());
     }
   }
-  TraceWrapperMember<BytesConsumer> consumer_;
+
+  Member<BytesConsumer> consumer_;
   Member<FetchDataLoader::Client> client_;
   std::shared_ptr<v8::WasmStreaming> streaming_;
   const Member<ScriptState> script_state_;
@@ -167,8 +182,87 @@
   ExceptionState& exception_state_;
 };
 
+SingleCachedMetadataHandler* GetCachedMetadataHandler(ScriptState* script_state,
+                                                      const KURL& url) {
+  if (!RuntimeEnabledFeatures::WasmCodeCacheEnabled())
+    return nullptr;
+  ExecutionContext* execution_context = ExecutionContext::From(script_state);
+  if (!execution_context)
+    return nullptr;
+  ResourceFetcher* fetcher = execution_context->Fetcher();
+  if (!fetcher)
+    return nullptr;
+  if (!url.IsValid())
+    return nullptr;
+  Resource* resource = fetcher->CachedResource(url);
+  if (!resource)
+    return nullptr;
+
+  // Wasm modules should be fetched as raw resources.
+  DCHECK_EQ(ResourceType::kRaw, resource->GetType());
+  RawResource* raw_resource = ToRawResource(resource);
+  return raw_resource->ScriptCacheHandler();
+}
+
+class WasmStreamingClient : public v8::WasmStreaming::Client {
+ public:
+  WasmStreamingClient(const KURL& url,
+                      v8::Isolate* isolate,
+                      v8::Local<v8::Context> context)
+      : url_(url), isolate_(isolate), context_(isolate, context) {
+    context_.SetWeak();
+  }
+
+  void OnModuleCompiled(v8::CompiledWasmModule compiled_module) override {
+    TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
+                         "v8.wasm.compiledModule", TRACE_EVENT_SCOPE_THREAD,
+                         "url", url_.GetString().Utf8());
+
+    // Don't cache if Context has been destroyed.
+    if (context_.IsEmpty())
+      return;
+
+    v8::HandleScope handle_scope(isolate_);
+    auto context = context_.Get(isolate_);
+    ScriptState* script_state = ScriptState::From(context);
+    SingleCachedMetadataHandler* cache_handler =
+        GetCachedMetadataHandler(script_state, url_);
+    if (!cache_handler)
+      return;
+
+    v8::MemorySpan<const uint8_t> wire_bytes =
+        compiled_module.GetWireBytesRef();
+    // Our heuristic for whether it's worthwhile to cache is that the module
+    // was fully compiled and it is "large". Wire bytes size is likely to be
+    // highly correlated with compiled module size so we use it to avoid the
+    // cost of serializing when not caching.
+    const size_t kWireBytesSizeThresholdBytes = 1UL << 17;  // 128 KB.
+    if (wire_bytes.size() < kWireBytesSizeThresholdBytes)
+      return;
+
+    v8::OwnedBuffer serialized_module = compiled_module.Serialize();
+    TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
+                         "v8.wasm.cachedModule", TRACE_EVENT_SCOPE_THREAD,
+                         "producedCacheSize", serialized_module.size);
+    cache_handler->SetCachedMetadata(
+        kWasmModuleTag,
+        reinterpret_cast<const uint8_t*>(serialized_module.buffer.get()),
+        serialized_module.size);
+  }
+
+ private:
+  KURL url_;
+  v8::Isolate* isolate_;
+  v8::Global<v8::Context> context_;
+
+  DISALLOW_COPY_AND_ASSIGN(WasmStreamingClient);
+};
+
 void StreamFromResponseCallback(
     const v8::FunctionCallbackInfo<v8::Value>& args) {
+  TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
+                       "v8.wasm.streamFromResponseCallback",
+                       TRACE_EVENT_SCOPE_THREAD);
   ExceptionState exception_state(args.GetIsolate(),
                                  ExceptionState::kExecutionContext,
                                  "WebAssembly", "compile");
@@ -224,9 +318,35 @@
     return;
   }
 
+  KURL url(response->url());
+  SingleCachedMetadataHandler* cache_handler =
+      GetCachedMetadataHandler(script_state, url);
+  if (cache_handler) {
+    streaming->SetClient(std::make_shared<WasmStreamingClient>(
+        url, args.GetIsolate(), script_state->GetContext()));
+    scoped_refptr<CachedMetadata> cached_module =
+        cache_handler->GetCachedMetadata(kWasmModuleTag);
+    if (cached_module) {
+      TRACE_EVENT_INSTANT2(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
+                           "v8.wasm.moduleCacheHit", TRACE_EVENT_SCOPE_THREAD,
+                           "url", url.GetString().Utf8(), "consumedCacheSize",
+                           cached_module->size());
+      bool is_valid = streaming->SetCompiledModuleBytes(
+          reinterpret_cast<const uint8_t*>(cached_module->Data()),
+          cached_module->size());
+      if (!is_valid) {
+        TRACE_EVENT_INSTANT0(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
+                             "v8.wasm.moduleCacheInvalid",
+                             TRACE_EVENT_SCOPE_THREAD);
+        cache_handler->ClearCachedMetadata(
+            CachedMetadataHandler::kSendToPlatform);
+      }
+    }
+  }
+
   FetchDataLoaderForWasmStreaming* loader =
-      MakeGarbageCollected<FetchDataLoaderForWasmStreaming>(script_state,
-                                                            streaming);
+      MakeGarbageCollected<FetchDataLoaderForWasmStreaming>(streaming,
+                                                            script_state);
   response->BodyBuffer()->StartLoading(
       loader, MakeGarbageCollected<WasmDataLoaderClient>(), exception_state);
 }
diff --git a/third_party/blink/renderer/controller/crash_memory_metrics_reporter_impl.cc b/third_party/blink/renderer/controller/crash_memory_metrics_reporter_impl.cc
index 0fb41b12..7f33f0e 100644
--- a/third_party/blink/renderer/controller/crash_memory_metrics_reporter_impl.cc
+++ b/third_party/blink/renderer/controller/crash_memory_metrics_reporter_impl.cc
@@ -4,10 +4,6 @@
 
 #include "third_party/blink/renderer/controller/crash_memory_metrics_reporter_impl.h"
 
-#include <ctype.h>
-#include <fcntl.h>
-#include <unistd.h>
-
 #include "base/allocator/partition_allocator/oom_callback.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/process/memory.h"
@@ -20,25 +16,6 @@
 
 namespace blink {
 
-namespace {
-
-// Roughly calculates amount of memory which is used to execute pages.
-uint64_t BlinkMemoryWorkloadCalculator() {
-  v8::Isolate* isolate = V8PerIsolateData::MainThreadIsolate();
-  DCHECK(isolate);
-  v8::HeapStatistics heap_statistics;
-  isolate->GetHeapStatistics(&heap_statistics);
-  // TODO: Add memory usage for worker threads.
-  size_t v8_size =
-      heap_statistics.total_heap_size() + heap_statistics.malloced_memory();
-  size_t blink_gc_size = ProcessHeap::TotalAllocatedObjectSize() +
-                         ProcessHeap::TotalMarkedObjectSize();
-  size_t partition_alloc_size = WTF::Partitions::TotalSizeOfCommittedPages();
-  return v8_size + blink_gc_size + partition_alloc_size;
-}
-
-}  // namespace
-
 // static
 void CrashMemoryMetricsReporterImpl::Bind(
     mojom::blink::CrashMemoryMetricsReporterRequest request) {
@@ -68,6 +45,11 @@
   shared_metrics_mapping_ = shared_metrics_buffer.Map();
 }
 
+void CrashMemoryMetricsReporterImpl::OnMemoryPing(MemoryUsage usage) {
+  WriteIntoSharedMemory(
+      CrashMemoryMetricsReporterImpl::MemoryUsageToMetrics(usage));
+}
+
 void CrashMemoryMetricsReporterImpl::WriteIntoSharedMemory(
     const OomInterventionMetrics& metrics) {
   if (!shared_metrics_mapping_.IsValid())
@@ -96,31 +78,30 @@
 
 OomInterventionMetrics
 CrashMemoryMetricsReporterImpl::GetCurrentMemoryMetrics() {
-  // This can only be called after ResetFileDescriptors().
-  DCHECK(statm_fd_.is_valid() && status_fd_.is_valid());
-
-  OomInterventionMetrics metrics = {};
-  metrics.current_blink_usage_kb = BlinkMemoryWorkloadCalculator() / 1024;
-  uint64_t private_footprint, swap, vm_size;
-  if (MemoryUsageMonitorAndroid::CalculateProcessMemoryFootprint(
-          statm_fd_.get(), status_fd_.get(), &private_footprint, &swap,
-          &vm_size)) {
-    metrics.current_private_footprint_kb = private_footprint / 1024;
-    metrics.current_swap_kb = swap / 1024;
-    metrics.current_vm_size_kb = vm_size / 1024;
-  }
-  metrics.allocation_failed = 0;  // false
-  return metrics;
+  return MemoryUsageToMetrics(
+      MemoryUsageMonitor::Instance().GetCurrentMemoryUsage());
 }
 
-bool CrashMemoryMetricsReporterImpl::ResetFileDiscriptors() {
-  // See https://goo.gl/KjWnZP For details about why we read these files from
-  // sandboxed renderer. Keep these files open when detection is enabled.
-  if (!statm_fd_.is_valid())
-    statm_fd_.reset(open("/proc/self/statm", O_RDONLY));
-  if (!status_fd_.is_valid())
-    status_fd_.reset(open("/proc/self/status", O_RDONLY));
-  return !statm_fd_.is_valid() || !status_fd_.is_valid();
+// static
+OomInterventionMetrics CrashMemoryMetricsReporterImpl::MemoryUsageToMetrics(
+    MemoryUsage usage) {
+  OomInterventionMetrics metrics = {};
+
+  DCHECK(!std::isnan(usage.private_footprint_bytes));
+  DCHECK(!std::isnan(usage.swap_bytes));
+  DCHECK(!std::isnan(usage.vm_size_bytes));
+  metrics.current_blink_usage_kb =
+      (usage.v8_bytes + usage.blink_gc_bytes + usage.partition_alloc_bytes) /
+      1024;
+
+  DCHECK(!std::isnan(usage.private_footprint_bytes));
+  DCHECK(!std::isnan(usage.swap_bytes));
+  DCHECK(!std::isnan(usage.vm_size_bytes));
+  metrics.current_private_footprint_kb = usage.private_footprint_bytes / 1024;
+  metrics.current_swap_kb = usage.swap_bytes / 1024;
+  metrics.current_vm_size_kb = usage.vm_size_bytes / 1024;
+  metrics.allocation_failed = 0;  // false
+  return metrics;
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/controller/crash_memory_metrics_reporter_impl.h b/third_party/blink/renderer/controller/crash_memory_metrics_reporter_impl.h
index bcf6de72..aa70ac6 100644
--- a/third_party/blink/renderer/controller/crash_memory_metrics_reporter_impl.h
+++ b/third_party/blink/renderer/controller/crash_memory_metrics_reporter_impl.h
@@ -10,15 +10,18 @@
 #include "third_party/blink/public/common/oom_intervention/oom_intervention_types.h"
 #include "third_party/blink/public/mojom/crash/crash_memory_metrics_reporter.mojom-blink.h"
 #include "third_party/blink/renderer/controller/controller_export.h"
+#include "third_party/blink/renderer/controller/memory_usage_monitor.h"
 
 namespace blink {
 
 // Writes data about renderer into shared memory that will be read by browser.
 class CONTROLLER_EXPORT CrashMemoryMetricsReporterImpl
-    : public mojom::blink::CrashMemoryMetricsReporter {
+    : public mojom::blink::CrashMemoryMetricsReporter,
+      public MemoryUsageMonitor::Observer {
  public:
   static CrashMemoryMetricsReporterImpl& Instance();
   static void Bind(mojom::blink::CrashMemoryMetricsReporterRequest);
+  static OomInterventionMetrics MemoryUsageToMetrics(MemoryUsage);
 
   ~CrashMemoryMetricsReporterImpl() override;
 
@@ -26,7 +29,8 @@
   void SetSharedMemory(
       base::UnsafeSharedMemoryRegion shared_metrics_buffer) override;
 
-  void WriteIntoSharedMemory(const OomInterventionMetrics& metrics);
+  // MemoryUsageMonitor::Observer:
+  void OnMemoryPing(MemoryUsage) override;
 
   // This method tracks when an allocation failure occurs. It should be hooked
   // into all platform allocation failure handlers in a process such as
@@ -38,22 +42,16 @@
   // This function needs to be called after ResetFileDescriptors.
   OomInterventionMetrics GetCurrentMemoryMetrics();
 
-  // This function resets statm_fd_ & status_fd_ to prepare for getting metrics.
-  bool ResetFileDiscriptors();
-
  protected:
   CrashMemoryMetricsReporterImpl();
 
  private:
   FRIEND_TEST_ALL_PREFIXES(OomInterventionImplTest, CalculateProcessFootprint);
 
+  void WriteIntoSharedMemory(const OomInterventionMetrics& metrics);
+
   base::WritableSharedMemoryMapping shared_metrics_mapping_;
   mojo::Binding<mojom::blink::CrashMemoryMetricsReporter> binding_;
-
-  // The file descriptor to current process proc files. The files are kept open
-  // when detection is on to reduce measurement overhead.
-  base::ScopedFD statm_fd_;
-  base::ScopedFD status_fd_;
 };
 }  // namespace blink
 
diff --git a/third_party/blink/renderer/controller/memory_usage_monitor.cc b/third_party/blink/renderer/controller/memory_usage_monitor.cc
index 16e9978e..8e60ff0 100644
--- a/third_party/blink/renderer/controller/memory_usage_monitor.cc
+++ b/third_party/blink/renderer/controller/memory_usage_monitor.cc
@@ -28,6 +28,10 @@
   observers_.RemoveObserver(observer);
 }
 
+bool MemoryUsageMonitor::HasObserver(Observer* observer) {
+  return observers_.HasObserver(observer);
+}
+
 void MemoryUsageMonitor::StartMonitoringIfNeeded() {
   if (timer_.IsActive())
     return;
diff --git a/third_party/blink/renderer/controller/memory_usage_monitor.h b/third_party/blink/renderer/controller/memory_usage_monitor.h
index 86e8706..62b26a3 100644
--- a/third_party/blink/renderer/controller/memory_usage_monitor.h
+++ b/third_party/blink/renderer/controller/memory_usage_monitor.h
@@ -36,12 +36,13 @@
   virtual ~MemoryUsageMonitor() = default;
 
   // Returns the current memory usage.
-  MemoryUsage GetCurrentMemoryUsage();
+  virtual MemoryUsage GetCurrentMemoryUsage();
 
   // Ensures that an observer is only added once.
   void AddObserver(Observer*);
   // Observers must be removed before they are destroyed.
   void RemoveObserver(Observer*);
+  bool HasObserver(Observer*);
 
   bool TimerIsActive() const { return timer_.IsActive(); }
 
diff --git a/third_party/blink/renderer/controller/oom_intervention_impl.cc b/third_party/blink/renderer/controller/oom_intervention_impl.cc
index 708b5a8..6ca589b 100644
--- a/third_party/blink/renderer/controller/oom_intervention_impl.cc
+++ b/third_party/blink/renderer/controller/oom_intervention_impl.cc
@@ -11,10 +11,10 @@
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_gc_for_context_dispose.h"
 #include "third_party/blink/renderer/controller/crash_memory_metrics_reporter_impl.h"
+#include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
-#include "third_party/blink/renderer/platform/wtf/allocator/partitions.h"
 
 namespace blink {
 
@@ -25,14 +25,13 @@
 }
 
 OomInterventionImpl::OomInterventionImpl()
-    : timer_(Thread::MainThread()->GetTaskRunner(),
-             this,
-             &OomInterventionImpl::Check),
-      delayed_report_timer_(Thread::MainThread()->GetTaskRunner(),
+    : delayed_report_timer_(Thread::MainThread()->GetTaskRunner(),
                             this,
                             &OomInterventionImpl::TimerFiredUMAReport) {}
 
-OomInterventionImpl::~OomInterventionImpl() {}
+OomInterventionImpl::~OomInterventionImpl() {
+  MemoryUsageMonitorInstance().RemoveObserver(this);
+}
 
 void OomInterventionImpl::StartDetection(
     mojom::blink::OomInterventionHostPtr host,
@@ -42,26 +41,29 @@
     bool purge_v8_memory_enabled) {
   host_ = std::move(host);
 
-  // Disable intervention if we cannot get memory details of current process.
-  if (CrashMemoryMetricsReporterImpl::Instance().ResetFileDiscriptors())
-    return;
-
   detection_args_ = std::move(detection_args);
   renderer_pause_enabled_ = renderer_pause_enabled;
   navigate_ads_enabled_ = navigate_ads_enabled;
   purge_v8_memory_enabled_ = purge_v8_memory_enabled;
 
-  timer_.Start(TimeDelta(), TimeDelta::FromSeconds(1), FROM_HERE);
+  MemoryUsageMonitorInstance().AddObserver(this);
 }
 
-OomInterventionMetrics OomInterventionImpl::GetCurrentMemoryMetrics() {
-  return CrashMemoryMetricsReporterImpl::Instance().GetCurrentMemoryMetrics();
+MemoryUsageMonitor& OomInterventionImpl::MemoryUsageMonitorInstance() {
+  return MemoryUsageMonitor::Instance();
 }
 
-void OomInterventionImpl::Check(TimerBase*) {
+void OomInterventionImpl::OnMemoryPing(MemoryUsage usage) {
+  // Ignore pings without process memory usage information.
+  if (std::isnan(usage.private_footprint_bytes) ||
+      std::isnan(usage.swap_bytes) || std::isnan(usage.vm_size_bytes))
+    return;
+  Check(CrashMemoryMetricsReporterImpl::MemoryUsageToMetrics(usage));
+}
+
+void OomInterventionImpl::Check(OomInterventionMetrics current_memory) {
   DCHECK(host_);
 
-  OomInterventionMetrics current_memory = GetCurrentMemoryMetrics();
   bool oom_detected = false;
 
   oom_detected |= detection_args_->blink_workload_threshold > 0 &&
@@ -100,7 +102,7 @@
     }
 
     host_->OnHighMemoryUsage();
-    timer_.Stop();
+    MemoryUsageMonitorInstance().RemoveObserver(this);
     // Send memory pressure notification to trigger GC.
     Thread::MainThread()->GetTaskRunner()->PostTask(FROM_HERE,
                                                     base::BindOnce(&TriggerGC));
@@ -134,13 +136,11 @@
       "Memory.Experimental.OomIntervention.RendererVmSize",
       base::saturated_cast<base::Histogram::Sample>(
           current_memory.current_vm_size_kb / 1024));
-
-  CrashMemoryMetricsReporterImpl::Instance().WriteIntoSharedMemory(
-      current_memory);
 }
 
 void OomInterventionImpl::TimerFiredUMAReport(TimerBase*) {
-  OomInterventionMetrics current_memory = GetCurrentMemoryMetrics();
+  OomInterventionMetrics current_memory =
+      CrashMemoryMetricsReporterImpl::Instance().GetCurrentMemoryMetrics();
   switch (number_of_report_needed_--) {
     case 3:
       base::UmaHistogramSparse(
diff --git a/third_party/blink/renderer/controller/oom_intervention_impl.h b/third_party/blink/renderer/controller/oom_intervention_impl.h
index 21ed667e..8c59240f1 100644
--- a/third_party/blink/renderer/controller/oom_intervention_impl.h
+++ b/third_party/blink/renderer/controller/oom_intervention_impl.h
@@ -5,11 +5,10 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CONTROLLER_OOM_INTERVENTION_IMPL_H_
 #define THIRD_PARTY_BLINK_RENDERER_CONTROLLER_OOM_INTERVENTION_IMPL_H_
 
-#include "base/files/scoped_file.h"
 #include "third_party/blink/public/common/oom_intervention/oom_intervention_types.h"
 #include "third_party/blink/public/platform/oom_intervention.mojom-blink.h"
 #include "third_party/blink/renderer/controller/controller_export.h"
-#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/controller/memory_usage_monitor.h"
 #include "third_party/blink/renderer/core/page/scoped_page_pauser.h"
 #include "third_party/blink/renderer/platform/timer.h"
 
@@ -20,7 +19,8 @@
 // Implementation of OOM intervention. This pauses all pages by using
 // ScopedPagePauser when near-OOM situation is detected.
 class CONTROLLER_EXPORT OomInterventionImpl
-    : public mojom::blink::OomIntervention {
+    : public mojom::blink::OomIntervention,
+      public MemoryUsageMonitor::Observer {
  public:
   static void Create(mojom::blink::OomInterventionRequest);
 
@@ -34,17 +34,20 @@
                       bool navigate_ads_enabled,
                       bool purge_v8_memory_enabled) override;
 
+  // MemoryUsageMonitor::Observer:
+  void OnMemoryPing(MemoryUsage) override;
+
  private:
   FRIEND_TEST_ALL_PREFIXES(OomInterventionImplTest, DetectedAndDeclined);
-  FRIEND_TEST_ALL_PREFIXES(OomInterventionImplTest, CalculateProcessFootprint);
   FRIEND_TEST_ALL_PREFIXES(OomInterventionImplTest, StopWatchingAfterDetection);
   FRIEND_TEST_ALL_PREFIXES(OomInterventionImplTest,
                            ContinueWatchingWithoutDetection);
   FRIEND_TEST_ALL_PREFIXES(OomInterventionImplTest, V1DetectionAdsNavigation);
 
   // Overridden by test.
-  virtual OomInterventionMetrics GetCurrentMemoryMetrics();
-  void Check(TimerBase*);
+  virtual MemoryUsageMonitor& MemoryUsageMonitorInstance();
+
+  void Check(OomInterventionMetrics);
 
   void ReportMemoryStats(OomInterventionMetrics& current_memory);
 
@@ -58,7 +61,6 @@
   bool renderer_pause_enabled_ = false;
   bool navigate_ads_enabled_ = false;
   bool purge_v8_memory_enabled_ = false;
-  TaskRunnerTimer<OomInterventionImpl> timer_;
   std::unique_ptr<ScopedPagePauser> pauser_;
   OomInterventionMetrics metrics_at_intervention_;
   int number_of_report_needed_ = 0;
diff --git a/third_party/blink/renderer/controller/oom_intervention_impl_test.cc b/third_party/blink/renderer/controller/oom_intervention_impl_test.cc
index 28fb8dc..230bc89b 100644
--- a/third_party/blink/renderer/controller/oom_intervention_impl_test.cc
+++ b/third_party/blink/renderer/controller/oom_intervention_impl_test.cc
@@ -44,28 +44,39 @@
   mojo::Binding<mojom::blink::OomInterventionHost> binding_;
 };
 
-// Mock intervention class that has custom method for fetching metrics.
+// Mock that allows setting mock memory usage.
+class MockMemoryUsageMonitor : public MemoryUsageMonitor {
+ public:
+  MockMemoryUsageMonitor() = default;
+
+  MemoryUsage GetCurrentMemoryUsage() override { return mock_memory_usage_; }
+
+  // MemoryUsageMonitor will report the current memory usage as this value.
+  void SetMockMemoryUsage(MemoryUsage usage) { mock_memory_usage_ = usage; }
+
+ private:
+  MemoryUsage mock_memory_usage_;
+};
+
+// Mock intervention class that uses a mock MemoryUsageMonitor.
 class MockOomInterventionImpl : public OomInterventionImpl {
  public:
-  MockOomInterventionImpl() {}
+  MockOomInterventionImpl()
+      : mock_memory_usage_monitor_(std::make_unique<MockMemoryUsageMonitor>()) {
+  }
   ~MockOomInterventionImpl() override {}
 
-  // If metrics are set by calling this method, then GetCurrentMemoryMetrics()
-  // will return the given metrics, else it will calculate metrics from
-  // providers.
-  void SetMetrics(OomInterventionMetrics metrics) {
-    metrics_ = std::make_unique<OomInterventionMetrics>();
-    *metrics_ = metrics;
+  MemoryUsageMonitor& MemoryUsageMonitorInstance() override {
+    return *mock_memory_usage_monitor_;
+  }
+
+  MockMemoryUsageMonitor* mock_memory_usage_monitor() {
+    return mock_memory_usage_monitor_.get();
   }
 
  private:
-  OomInterventionMetrics GetCurrentMemoryMetrics() override {
-    if (metrics_)
-      return *metrics_;
-    return CrashMemoryMetricsReporterImpl::Instance().GetCurrentMemoryMetrics();
-  }
-
   std::unique_ptr<OomInterventionMetrics> metrics_;
+  std::unique_ptr<MockMemoryUsageMonitor> mock_memory_usage_monitor_;
 };
 
 }  // namespace
@@ -109,13 +120,15 @@
 };
 
 TEST_F(OomInterventionImplTest, NoDetectionOnBelowThreshold) {
-  OomInterventionMetrics mock_metrics = {};
+  MemoryUsage usage;
   // Set value less than the threshold to not trigger intervention.
-  mock_metrics.current_blink_usage_kb = (kTestBlinkThreshold / 1024) - 1;
-  mock_metrics.current_private_footprint_kb = (kTestPMFThreshold / 1024) - 1;
-  mock_metrics.current_swap_kb = (kTestSwapThreshold / 1024) - 1;
-  mock_metrics.current_vm_size_kb = (kTestVmSizeThreshold / 1024) - 1;
-  intervention_->SetMetrics(mock_metrics);
+  usage.v8_bytes = kTestBlinkThreshold - 1024;
+  usage.blink_gc_bytes = 0;
+  usage.partition_alloc_bytes = 0;
+  usage.private_footprint_bytes = kTestPMFThreshold - 1024;
+  usage.swap_bytes = kTestSwapThreshold - 1024;
+  usage.vm_size_bytes = kTestVmSizeThreshold - 1024;
+  intervention_->mock_memory_usage_monitor()->SetMockMemoryUsage(usage);
 
   Page* page = DetectOnceOnBlankPage();
 
@@ -123,13 +136,15 @@
 }
 
 TEST_F(OomInterventionImplTest, BlinkThresholdDetection) {
-  OomInterventionMetrics mock_metrics = {};
-  // Set value more than the threshold to not trigger intervention.
-  mock_metrics.current_blink_usage_kb = (kTestBlinkThreshold / 1024) + 1;
-  mock_metrics.current_private_footprint_kb = (kTestPMFThreshold / 1024) - 1;
-  mock_metrics.current_swap_kb = (kTestSwapThreshold / 1024) - 1;
-  mock_metrics.current_vm_size_kb = (kTestVmSizeThreshold / 1024) - 1;
-  intervention_->SetMetrics(mock_metrics);
+  MemoryUsage usage;
+  // Set value more than the threshold to trigger intervention.
+  usage.v8_bytes = kTestBlinkThreshold + 1024;
+  usage.blink_gc_bytes = 0;
+  usage.partition_alloc_bytes = 0;
+  usage.private_footprint_bytes = 0;
+  usage.swap_bytes = 0;
+  usage.vm_size_bytes = 0;
+  intervention_->mock_memory_usage_monitor()->SetMockMemoryUsage(usage);
 
   Page* page = DetectOnceOnBlankPage();
 
@@ -139,13 +154,15 @@
 }
 
 TEST_F(OomInterventionImplTest, PmfThresholdDetection) {
-  OomInterventionMetrics mock_metrics = {};
-  mock_metrics.current_blink_usage_kb = (kTestBlinkThreshold / 1024) - 1;
+  MemoryUsage usage;
+  usage.v8_bytes = 0;
+  usage.blink_gc_bytes = 0;
+  usage.partition_alloc_bytes = 0;
   // Set value more than the threshold to trigger intervention.
-  mock_metrics.current_private_footprint_kb = (kTestPMFThreshold / 1024) + 1;
-  mock_metrics.current_swap_kb = (kTestSwapThreshold / 1024) - 1;
-  mock_metrics.current_vm_size_kb = (kTestVmSizeThreshold / 1024) - 1;
-  intervention_->SetMetrics(mock_metrics);
+  usage.private_footprint_bytes = kTestPMFThreshold + 1024;
+  usage.swap_bytes = 0;
+  usage.vm_size_bytes = 0;
+  intervention_->mock_memory_usage_monitor()->SetMockMemoryUsage(usage);
 
   Page* page = DetectOnceOnBlankPage();
 
@@ -155,13 +172,15 @@
 }
 
 TEST_F(OomInterventionImplTest, SwapThresholdDetection) {
-  OomInterventionMetrics mock_metrics = {};
-  mock_metrics.current_blink_usage_kb = (kTestBlinkThreshold / 1024) - 1;
-  mock_metrics.current_private_footprint_kb = (kTestPMFThreshold / 1024) - 1;
+  MemoryUsage usage;
+  usage.v8_bytes = 0;
+  usage.blink_gc_bytes = 0;
+  usage.partition_alloc_bytes = 0;
+  usage.private_footprint_bytes = 0;
   // Set value more than the threshold to trigger intervention.
-  mock_metrics.current_swap_kb = (kTestSwapThreshold / 1024) + 1;
-  mock_metrics.current_vm_size_kb = (kTestVmSizeThreshold / 1024) - 1;
-  intervention_->SetMetrics(mock_metrics);
+  usage.swap_bytes = kTestSwapThreshold + 1024;
+  usage.vm_size_bytes = 0;
+  intervention_->mock_memory_usage_monitor()->SetMockMemoryUsage(usage);
 
   Page* page = DetectOnceOnBlankPage();
 
@@ -171,13 +190,15 @@
 }
 
 TEST_F(OomInterventionImplTest, VmSizeThresholdDetection) {
-  OomInterventionMetrics mock_metrics = {};
-  mock_metrics.current_blink_usage_kb = (kTestBlinkThreshold / 1024) - 1;
-  mock_metrics.current_private_footprint_kb = (kTestPMFThreshold / 1024) - 1;
-  mock_metrics.current_swap_kb = (kTestSwapThreshold / 1024) - 1;
+  MemoryUsage usage;
+  usage.v8_bytes = 0;
+  usage.blink_gc_bytes = 0;
+  usage.partition_alloc_bytes = 0;
+  usage.private_footprint_bytes = 0;
+  usage.swap_bytes = 0;
   // Set value more than the threshold to trigger intervention.
-  mock_metrics.current_vm_size_kb = (kTestVmSizeThreshold / 1024) + 1;
-  intervention_->SetMetrics(mock_metrics);
+  usage.vm_size_bytes = kTestVmSizeThreshold + 1024;
+  intervention_->mock_memory_usage_monitor()->SetMockMemoryUsage(usage);
 
   Page* page = DetectOnceOnBlankPage();
 
@@ -187,94 +208,51 @@
 }
 
 TEST_F(OomInterventionImplTest, StopWatchingAfterDetection) {
-  OomInterventionMetrics mock_metrics = {};
+  MemoryUsage usage;
+  usage.v8_bytes = 0;
   // Set value more than the threshold to trigger intervention.
-  mock_metrics.current_blink_usage_kb = (kTestBlinkThreshold / 1024) + 1;
-  mock_metrics.current_private_footprint_kb = (kTestPMFThreshold / 1024) - 1;
-  mock_metrics.current_swap_kb = (kTestSwapThreshold / 1024) - 1;
-  mock_metrics.current_vm_size_kb = (kTestVmSizeThreshold / 1024) - 1;
-  intervention_->SetMetrics(mock_metrics);
+  usage.blink_gc_bytes = kTestBlinkThreshold + 1024;
+  usage.partition_alloc_bytes = 0;
+  usage.private_footprint_bytes = 0;
+  usage.swap_bytes = 0;
+  usage.vm_size_bytes = 0;
+  intervention_->mock_memory_usage_monitor()->SetMockMemoryUsage(usage);
 
   DetectOnceOnBlankPage();
 
-  EXPECT_FALSE(intervention_->timer_.IsActive());
+  EXPECT_FALSE(intervention_->mock_memory_usage_monitor()->HasObserver(
+      intervention_.get()));
 }
 
 TEST_F(OomInterventionImplTest, ContinueWatchingWithoutDetection) {
-  OomInterventionMetrics mock_metrics = {};
+  MemoryUsage usage;
   // Set value less than the threshold to not trigger intervention.
-  mock_metrics.current_blink_usage_kb = (kTestBlinkThreshold / 1024) - 1;
-  mock_metrics.current_private_footprint_kb = (kTestPMFThreshold / 1024) - 1;
-  mock_metrics.current_swap_kb = (kTestSwapThreshold / 1024) - 1;
-  mock_metrics.current_vm_size_kb = (kTestVmSizeThreshold / 1024) - 1;
-  intervention_->SetMetrics(mock_metrics);
+  usage.v8_bytes = 0;
+  usage.blink_gc_bytes = 0;
+  usage.partition_alloc_bytes = 0;
+  usage.private_footprint_bytes = 0;
+  usage.swap_bytes = 0;
+  usage.vm_size_bytes = 0;
+  intervention_->mock_memory_usage_monitor()->SetMockMemoryUsage(usage);
 
   DetectOnceOnBlankPage();
 
-  EXPECT_TRUE(intervention_->timer_.IsActive());
-}
-
-TEST_F(OomInterventionImplTest, CalculateProcessFootprint) {
-  const char kStatusFile[] =
-      "First:  1\n Second: 2 kB\nVmSwap: 10 kB \n Third: 10 kB\n Last: 8";
-  const char kStatmFile[] = "100 40 25 0 0";
-  uint64_t expected_swap_kb = 10;
-  uint64_t expected_private_footprint_kb =
-      (40 - 25) * getpagesize() / 1024 + expected_swap_kb;
-  uint64_t expected_vm_size_kb = 100 * getpagesize() / 1024;
-
-  base::FilePath statm_path;
-  EXPECT_TRUE(base::CreateTemporaryFile(&statm_path));
-  EXPECT_EQ(static_cast<int>(sizeof(kStatmFile)),
-            base::WriteFile(statm_path, kStatmFile, sizeof(kStatmFile)));
-  base::File statm_file(statm_path,
-                        base::File::FLAG_OPEN | base::File::FLAG_READ);
-  base::FilePath status_path;
-  EXPECT_TRUE(base::CreateTemporaryFile(&status_path));
-  EXPECT_EQ(static_cast<int>(sizeof(kStatusFile)),
-            base::WriteFile(status_path, kStatusFile, sizeof(kStatusFile)));
-  base::File status_file(status_path,
-                         base::File::FLAG_OPEN | base::File::FLAG_READ);
-
-  CrashMemoryMetricsReporterImpl::Instance().statm_fd_.reset(
-      statm_file.TakePlatformFile());
-  CrashMemoryMetricsReporterImpl::Instance().status_fd_.reset(
-      status_file.TakePlatformFile());
-
-  mojom::blink::OomInterventionHostPtr host_ptr;
-  MockOomInterventionHost mock_host(mojo::MakeRequest(&host_ptr));
-  mojom::blink::DetectionArgsPtr args(mojom::blink::DetectionArgs::New());
-  intervention_->StartDetection(
-      std::move(host_ptr), std::move(args), true /*renderer_pause_enabled*/,
-      true /*navigate_ads_enabled*/, true /*purge_v8_memory_enabled*/);
-  // Create unsafe shared memory region to write metrics in reporter.
-  base::UnsafeSharedMemoryRegion shm =
-      base::UnsafeSharedMemoryRegion::Create(sizeof(OomInterventionMetrics));
-  CrashMemoryMetricsReporterImpl::Instance().shared_metrics_mapping_ =
-      shm.Map();
-  EXPECT_TRUE(CrashMemoryMetricsReporterImpl::Instance()
-                  .shared_metrics_mapping_.IsValid());
-
-  intervention_->Check(nullptr);
-  OomInterventionMetrics* metrics = static_cast<OomInterventionMetrics*>(
-      CrashMemoryMetricsReporterImpl::Instance()
-          .shared_metrics_mapping_.memory());
-  EXPECT_EQ(expected_private_footprint_kb,
-            metrics->current_private_footprint_kb);
-  EXPECT_EQ(expected_swap_kb, metrics->current_swap_kb);
-  EXPECT_EQ(expected_vm_size_kb, metrics->current_vm_size_kb);
+  EXPECT_TRUE(intervention_->mock_memory_usage_monitor()->HasObserver(
+      intervention_.get()));
 }
 
 // TODO(yuzus): Once OOPIF unit test infrastructure is ready, add a test case
 // with OOPIF enabled.
 TEST_F(OomInterventionImplTest, V1DetectionAdsNavigation) {
-  OomInterventionMetrics mock_metrics = {};
-  mock_metrics.current_blink_usage_kb = (kTestBlinkThreshold / 1024) - 1;
-  mock_metrics.current_private_footprint_kb = (kTestPMFThreshold / 1024) - 1;
-  mock_metrics.current_swap_kb = (kTestSwapThreshold / 1024) - 1;
+  MemoryUsage usage;
+  usage.v8_bytes = 0;
+  usage.blink_gc_bytes = 0;
   // Set value more than the threshold to trigger intervention.
-  mock_metrics.current_vm_size_kb = (kTestVmSizeThreshold / 1024) + 1;
-  intervention_->SetMetrics(mock_metrics);
+  usage.partition_alloc_bytes = kTestBlinkThreshold + 1024;
+  usage.private_footprint_bytes = 0;
+  usage.swap_bytes = 0;
+  usage.vm_size_bytes = 0;
+  intervention_->mock_memory_usage_monitor()->SetMockMemoryUsage(usage);
 
   WebViewImpl* web_view = web_view_helper_.InitializeAndLoad("about:blank");
   Page* page = web_view->MainFrameImpl()->GetFrame()->GetPage();
@@ -308,13 +286,15 @@
 }
 
 TEST_F(OomInterventionImplTest, V2DetectionV8PurgeMemory) {
-  OomInterventionMetrics mock_metrics = {};
-  mock_metrics.current_blink_usage_kb = (kTestBlinkThreshold / 1024) - 1;
-  mock_metrics.current_private_footprint_kb = (kTestPMFThreshold / 1024) - 1;
-  mock_metrics.current_swap_kb = (kTestSwapThreshold / 1024) - 1;
+  MemoryUsage usage;
+  usage.v8_bytes = 0;
+  usage.blink_gc_bytes = 0;
+  usage.partition_alloc_bytes = 0;
+  usage.private_footprint_bytes = 0;
+  usage.swap_bytes = 0;
   // Set value more than the threshold to trigger intervention.
-  mock_metrics.current_vm_size_kb = (kTestVmSizeThreshold / 1024) + 1;
-  intervention_->SetMetrics(mock_metrics);
+  usage.vm_size_bytes = kTestVmSizeThreshold + 1024;
+  intervention_->mock_memory_usage_monitor()->SetMockMemoryUsage(usage);
 
   WebViewImpl* web_view = web_view_helper_.InitializeAndLoad("about:blank");
   Page* page = web_view->MainFrameImpl()->GetFrame()->GetPage();
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index 9002c20..41989d7d 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -1804,6 +1804,7 @@
     "dom/document_statistics_collector_test.cc",
     "dom/document_test.cc",
     "dom/dom_implementation_test.cc",
+    "dom/dom_node_ids_test.cc",
     "dom/element_test.cc",
     "dom/element_visibility_observer_test.cc",
     "dom/events/event_path_test.cc",
diff --git a/third_party/blink/renderer/core/animation/compositor_animations_test.cc b/third_party/blink/renderer/core/animation/compositor_animations_test.cc
index d27d062c..3c9c76fa 100644
--- a/third_party/blink/renderer/core/animation/compositor_animations_test.cc
+++ b/third_party/blink/renderer/core/animation/compositor_animations_test.cc
@@ -465,7 +465,7 @@
 
   void BeginFrame() {
     helper_.GetWebView()->MainFrameWidget()->BeginFrame(
-        WTF::CurrentTimeTicks());
+        WTF::CurrentTimeTicks(), false /* record_main_frame_metrics */);
   }
 
   void ForceFullCompositingUpdate() {
diff --git a/third_party/blink/renderer/core/css/style_engine.cc b/third_party/blink/renderer/core/css/style_engine.cc
index dc148fc0..71b8964 100644
--- a/third_party/blink/renderer/core/css/style_engine.cc
+++ b/third_party/blink/renderer/core/css/style_engine.cc
@@ -1527,7 +1527,7 @@
 
 bool StyleEngine::UpdateRemUnits(const ComputedStyle* old_root_style,
                                  const ComputedStyle* new_root_style) {
-  if (!UsesRemUnits())
+  if (!new_root_style || !UsesRemUnits())
     return false;
   if (!old_root_style ||
       old_root_style->FontSize() != new_root_style->FontSize()) {
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_context.cc b/third_party/blink/renderer/core/display_lock/display_lock_context.cc
index 0fe7c25..bc4689e7 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_context.cc
+++ b/third_party/blink/renderer/core/display_lock/display_lock_context.cc
@@ -163,12 +163,9 @@
   if (state_ == kUnlocked)
     return GetRejectedPromise(script_state);
 
-  // If we have a resolver, we must be committing already, just return the same
-  // promise.
-  if (commit_resolver_) {
-    DCHECK(state_ == kCommitting) << state_;
+  // If we're already committing then return the promise.
+  if (state_ == kCommitting)
     return commit_resolver_->Promise();
-  }
 
   // Now that we've explicitly been requested to commit, we have cancel the
   // timeout task.
@@ -179,12 +176,42 @@
   // together will still wait until the lifecycle is clean before resolving any
   // of the promises.
   DCHECK_NE(state_, kCommitting);
-  commit_resolver_ = ScriptPromiseResolver::Create(script_state);
+  // We might already have a resolver if we called updateAndCommit() before
+  // this.
+  if (!commit_resolver_)
+    commit_resolver_ = ScriptPromiseResolver::Create(script_state);
   auto promise = commit_resolver_->Promise();
   StartCommit();
   return promise;
 }
 
+ScriptPromise DisplayLockContext::updateAndCommit(ScriptState* script_state) {
+  // Reject if we're unlocked.
+  if (state_ == kUnlocked)
+    return GetRejectedPromise(script_state);
+
+  // If we're in a state where a co-operative update doesn't make sense (e.g. we
+  // haven't acquired the lock, or we're already sync committing), then do
+  // whatever commit() would do.
+  if (state_ == kPendingAcquire || state_ == kCommitting ||
+      !element_->isConnected()) {
+    return commit(script_state);
+  }
+
+  // If we have a commit resolver already, return it.
+  if (commit_resolver_) {
+    // We must be in a second call to updateAndCommit(), meaning that we're in
+    // the kUpdating state with a commit_resolver_.
+    DCHECK_EQ(state_, kUpdating);
+    return commit_resolver_->Promise();
+  }
+
+  CancelTimeoutTask();
+  commit_resolver_ = ScriptPromiseResolver::Create(script_state);
+  StartUpdateIfNeeded();
+  return commit_resolver_->Promise();
+}
+
 void DisplayLockContext::FinishUpdateResolver(ResolverState state) {
   FinishResolver(&update_resolver_, state);
 }
@@ -240,10 +267,11 @@
   // unexpected behavior. By rejecting the promise, the behavior can be detected
   // by script.
   if (!ElementSupportsDisplayLocking()) {
+    bool should_stay_locked = state_ == kUpdating && !commit_resolver_;
     FinishUpdateResolver(kReject);
     FinishCommitResolver(kReject);
     FinishAcquireResolver(kReject);
-    state_ = state_ == kUpdating ? kLocked : kUnlocked;
+    state_ = should_stay_locked ? kLocked : kUnlocked;
     return;
   }
 
@@ -300,16 +328,17 @@
 }
 
 void DisplayLockContext::DidAttachLayoutTree() {
-  if (state_ == kUnlocked)
+  if (state_ >= kUnlocked)
     return;
 
   // Note that although we checked at style recalc time that the element has
   // "contain: style layout", it might not actually apply the containment at the
   // layout object level. This confirms that containment should apply.
   if (!ElementSupportsDisplayLocking()) {
+    bool should_stay_locked = state_ == kUpdating && !commit_resolver_;
     FinishUpdateResolver(kReject);
     FinishCommitResolver(kReject);
-    state_ = state_ == kUpdating ? kLocked : kUnlocked;
+    state_ = should_stay_locked ? kLocked : kUnlocked;
   }
 }
 
@@ -546,7 +575,13 @@
   if (!element_ || !element_->isConnected()) {
     FinishUpdateResolver(kReject);
     update_budget_.reset();
-    state_ = kLocked;
+
+    if (commit_resolver_) {
+      FinishCommitResolver(kReject);
+      state_ = kUnlocked;
+    } else {
+      state_ = kLocked;
+    }
     return;
   }
 
@@ -563,6 +598,17 @@
   FinishUpdateResolver(kResolve);
   update_budget_.reset();
   state_ = kLocked;
+
+  if (commit_resolver_) {
+    // Schedule a commit to run. Note that we can't call StartCommit directly
+    // here, since we're in the lifecycle updates right now and the code that
+    // runs after may depend on having clean layout state, which StartCommit
+    // might dirty.
+    GetExecutionContext()
+        ->GetTaskRunner(TaskType::kMiscPlatformAPI)
+        ->PostTask(FROM_HERE, WTF::Bind(&DisplayLockContext::StartCommit,
+                                        WrapWeakPersistent(this)));
+  }
 }
 
 void DisplayLockContext::ScheduleAnimation() {
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_context.h b/third_party/blink/renderer/core/display_lock/display_lock_context.h
index 3144eda..a5c0d38 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_context.h
+++ b/third_party/blink/renderer/core/display_lock/display_lock_context.h
@@ -95,6 +95,7 @@
   ScriptPromise acquire(ScriptState*, DisplayLockOptions*);
   ScriptPromise update(ScriptState*);
   ScriptPromise commit(ScriptState*);
+  ScriptPromise updateAndCommit(ScriptState*);
 
   // Lifecycle observation / state functions.
   bool ShouldStyle() const;
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_context.idl b/third_party/blink/renderer/core/display_lock/display_lock_context.idl
index 2e34af83b..592208e 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_context.idl
+++ b/third_party/blink/renderer/core/display_lock/display_lock_context.idl
@@ -16,4 +16,8 @@
   // necessary lifecycle phases.
   // Returns a promise that resolves when the commit is finished.
   [CallWith=ScriptState] Promise<any> commit();
+
+  // Causes co-operative updates to happen, followed by a commit.
+  // Returns a promise that resolves when the commit is finished.
+  [CallWith=ScriptState] Promise<any> updateAndCommit();
 };
diff --git a/third_party/blink/renderer/core/display_lock/strict_yielding_display_lock_budget.h b/third_party/blink/renderer/core/display_lock/strict_yielding_display_lock_budget.h
index da7512a..77c6ca1f 100644
--- a/third_party/blink/renderer/core/display_lock/strict_yielding_display_lock_budget.h
+++ b/third_party/blink/renderer/core/display_lock/strict_yielding_display_lock_budget.h
@@ -34,7 +34,7 @@
 
  protected:
   base::Optional<Phase> last_completed_phase_;
-  bool completed_new_phase_this_cycle_;
+  bool completed_new_phase_this_cycle_ = false;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/dom_node_ids.cc b/third_party/blink/renderer/core/dom/dom_node_ids.cc
index cad3cc1..18c9844d 100644
--- a/third_party/blink/renderer/core/dom/dom_node_ids.cc
+++ b/third_party/blink/renderer/core/dom/dom_node_ids.cc
@@ -12,12 +12,15 @@
 
 // static
 DOMNodeId DOMNodeIds::IdForNode(Node* node) {
-  return WeakIdentifierMap<Node, DOMNodeId>::Identifier(node);
+  return node ? WeakIdentifierMap<Node, DOMNodeId>::Identifier(node)
+              : kInvalidDOMNodeId;
 }
 
 // static
 Node* DOMNodeIds::NodeForId(DOMNodeId id) {
-  return WeakIdentifierMap<Node, DOMNodeId>::Lookup(id);
+  return id == kInvalidDOMNodeId
+             ? nullptr
+             : WeakIdentifierMap<Node, DOMNodeId>::Lookup(id);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/dom_node_ids_test.cc b/third_party/blink/renderer/core/dom/dom_node_ids_test.cc
new file mode 100644
index 0000000..d8038d99
--- /dev/null
+++ b/third_party/blink/renderer/core/dom/dom_node_ids_test.cc
@@ -0,0 +1,60 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/dom/dom_node_ids.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/dom/element.h"
+#include "third_party/blink/renderer/core/editing/testing/editing_test_base.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
+
+namespace blink {
+
+using DOMNodeIdsTest = EditingTestBase;
+
+TEST_F(DOMNodeIdsTest, NonNull) {
+  SetBodyContent("<div id='a'></div><div id='b'></div>");
+  Node* a = GetDocument().getElementById("a");
+  Node* b = GetDocument().getElementById("b");
+
+  DOMNodeId id_a = DOMNodeIds::IdForNode(a);
+  EXPECT_NE(kInvalidDOMNodeId, id_a);
+  EXPECT_EQ(id_a, DOMNodeIds::IdForNode(a));
+  EXPECT_EQ(a, DOMNodeIds::NodeForId(id_a));
+
+  DOMNodeId id_b = DOMNodeIds::IdForNode(b);
+  EXPECT_NE(kInvalidDOMNodeId, id_b);
+  EXPECT_NE(id_a, id_b);
+  EXPECT_EQ(id_b, DOMNodeIds::IdForNode(b));
+  EXPECT_EQ(b, DOMNodeIds::NodeForId(id_b));
+
+  EXPECT_EQ(id_a, DOMNodeIds::IdForNode(a));
+  EXPECT_EQ(a, DOMNodeIds::NodeForId(id_a));
+}
+
+TEST_F(DOMNodeIdsTest, DeletedNode) {
+  SetBodyContent("<div id='a'></div>");
+  Node* a = GetDocument().getElementById("a");
+  DOMNodeId id_a = DOMNodeIds::IdForNode(a);
+
+  a->remove();
+  ThreadState::Current()->CollectGarbage(
+      BlinkGC::kNoHeapPointersOnStack, BlinkGC::kAtomicMarking,
+      BlinkGC::kEagerSweeping, BlinkGC::GCReason::kForcedGC);
+  EXPECT_EQ(nullptr, DOMNodeIds::NodeForId(id_a));
+}
+
+TEST_F(DOMNodeIdsTest, UnusedID) {
+  SetBodyContent("<div id='a'></div>");
+  Node* a = GetDocument().getElementById("a");
+  DOMNodeId id_a = DOMNodeIds::IdForNode(a);
+  EXPECT_EQ(nullptr, DOMNodeIds::NodeForId(id_a + 1));
+}
+
+TEST_F(DOMNodeIdsTest, Null) {
+  EXPECT_EQ(kInvalidDOMNodeId, DOMNodeIds::IdForNode(nullptr));
+  EXPECT_EQ(nullptr, DOMNodeIds::NodeForId(kInvalidDOMNodeId));
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/exported/web_page_popup_impl.cc b/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
index 8b71a1fc..d7167cb 100644
--- a/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
@@ -47,6 +47,7 @@
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/frame/local_frame_client.h"
+#include "third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h"
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/frame/settings.h"
 #include "third_party/blink/renderer/core/frame/visual_viewport.h"
@@ -383,7 +384,8 @@
   page_->Animator().SetSuppressFrameRequestsWorkaroundFor704763Only(
       suppress_frame_requests);
 }
-void WebPagePopupImpl::BeginFrame(base::TimeTicks last_frame_time) {
+
+void WebPagePopupImpl::BeginFrame(base::TimeTicks last_frame_time, bool) {
   if (!page_)
     return;
   // FIXME: This should use lastFrameTimeMonotonic but doing so
diff --git a/third_party/blink/renderer/core/exported/web_page_popup_impl.h b/third_party/blink/renderer/core/exported/web_page_popup_impl.h
index f8b1ec81..0ddae22 100644
--- a/third_party/blink/renderer/core/exported/web_page_popup_impl.h
+++ b/third_party/blink/renderer/core/exported/web_page_popup_impl.h
@@ -83,9 +83,10 @@
   // WebWidget functions
   void SetLayerTreeView(WebLayerTreeView*) override;
   void SetSuppressFrameRequestsWorkaroundFor704763Only(bool) final;
-  void BeginFrame(base::TimeTicks last_frame_time) override;
+  void BeginFrame(base::TimeTicks last_frame_time,
+                  bool record_main_frame_metrics) override;
   void UpdateLifecycle(LifecycleUpdate requested_update,
-                       LifecycleUpdateReason reason /* Not used */) override;
+                       LifecycleUpdateReason reason) override;
   void UpdateAllLifecyclePhasesAndCompositeForTesting(bool do_raster) override;
   void WillCloseLayerTreeView() override;
   void PaintContent(cc::PaintCanvas*, const WebRect&) override;
@@ -136,6 +137,7 @@
   WebLayerTreeView* layer_tree_view_ = nullptr;
   scoped_refptr<cc::Layer> root_layer_;
   std::unique_ptr<CompositorAnimationHost> animation_host_;
+  base::TimeTicks raf_aligned_input_start_time_;
   bool is_accelerated_compositing_active_ = false;
 
   friend class WebPagePopup;
diff --git a/third_party/blink/renderer/core/exported/web_plugin_container_impl.cc b/third_party/blink/renderer/core/exported/web_plugin_container_impl.cc
index 68128c69..e0ad521 100644
--- a/third_party/blink/renderer/core/exported/web_plugin_container_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_plugin_container_impl.cc
@@ -59,6 +59,7 @@
 #include "third_party/blink/renderer/core/clipboard/data_object.h"
 #include "third_party/blink/renderer/core/clipboard/data_transfer.h"
 #include "third_party/blink/renderer/core/clipboard/system_clipboard.h"
+#include "third_party/blink/renderer/core/dom/dom_node_ids.h"
 #include "third_party/blink/renderer/core/dom/user_gesture_indicator.h"
 #include "third_party/blink/renderer/core/events/drag_event.h"
 #include "third_party/blink/renderer/core/events/gesture_event.h"
@@ -368,8 +369,10 @@
 
   if (layer_)
     GraphicsLayer::UnregisterContentsLayer(layer_);
-  if (new_layer)
+  if (new_layer) {
     GraphicsLayer::RegisterContentsLayer(new_layer);
+    new_layer->set_owner_node_id(DOMNodeIds::IdForNode(element_.Get()));
+  }
 
   layer_ = new_layer;
   prevent_contents_opaque_changes_ = prevent_contents_opaque_changes;
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index 4263bf1..8bafa25 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -98,6 +98,7 @@
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/frame/local_frame_client.h"
+#include "third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h"
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/frame/page_scale_constraints_set.h"
 #include "third_party/blink/renderer/core/frame/remote_frame.h"
@@ -1500,7 +1501,8 @@
   AsView().page->Animator().SetSuppressFrameRequestsWorkaroundFor704763Only(
       suppress_frame_requests);
 }
-void WebViewImpl::BeginFrame(base::TimeTicks last_frame_time) {
+void WebViewImpl::BeginFrame(base::TimeTicks last_frame_time,
+                             bool record_main_frame_metrics) {
   TRACE_EVENT1("blink", "WebViewImpl::beginFrame", "frameTime",
                last_frame_time);
   DCHECK(!last_frame_time.is_null());
@@ -1515,15 +1517,46 @@
 
   DocumentLifecycle::AllowThrottlingScope throttling_scope(
       MainFrameImpl()->GetFrame()->GetDocument()->Lifecycle());
+
+  base::Optional<LocalFrameUkmAggregator::ScopedUkmHierarchicalTimer> ukm_timer;
+  if (record_main_frame_metrics) {
+    ukm_timer.emplace(MainFrameImpl()
+                          ->GetFrame()
+                          ->View()
+                          ->EnsureUkmAggregator()
+                          .GetScopedTimer(LocalFrameUkmAggregator::kAnimate));
+  }
   PageWidgetDelegate::Animate(*AsView().page, last_frame_time);
 }
 
+void WebViewImpl::BeginRafAlignedInput() {
+  raf_aligned_input_start_time_ = CurrentTimeTicks();
+}
+
+void WebViewImpl::EndRafAlignedInput() {
+  if (MainFrameImpl()) {
+    MainFrameImpl()->GetFrame()->View()->EnsureUkmAggregator().RecordSample(
+        LocalFrameUkmAggregator::kHandleInputEvents,
+        raf_aligned_input_start_time_, CurrentTimeTicks());
+  }
+}
+
+void WebViewImpl::RecordStartOfFrameMetrics() {
+  if (!MainFrameImpl())
+    return;
+
+  MainFrameImpl()->GetFrame()->View()->EnsureUkmAggregator().BeginMainFrame();
+}
+
 void WebViewImpl::RecordEndOfFrameMetrics(base::TimeTicks frame_begin_time) {
   if (!MainFrameImpl())
     return;
 
-  MainFrameImpl()->GetFrame()->View()->RecordEndOfFrameMetrics(
-      frame_begin_time);
+  MainFrameImpl()
+      ->GetFrame()
+      ->View()
+      ->EnsureUkmAggregator()
+      .RecordEndOfFrameMetrics(frame_begin_time, CurrentTimeTicks());
 }
 
 void WebViewImpl::UpdateLifecycle(LifecycleUpdate requested_update,
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.h b/third_party/blink/renderer/core/exported/web_view_impl.h
index c83d975..931dbf6 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.h
+++ b/third_party/blink/renderer/core/exported/web_view_impl.h
@@ -427,7 +427,11 @@
   void DidEnterFullscreen() override;
   void DidExitFullscreen() override;
   void SetSuppressFrameRequestsWorkaroundFor704763Only(bool) override;
-  void BeginFrame(base::TimeTicks last_frame_time) override;
+  void BeginFrame(base::TimeTicks last_frame_time,
+                  bool record_main_frame_metrics) override;
+  void BeginRafAlignedInput() override;
+  void EndRafAlignedInput() override;
+  void RecordStartOfFrameMetrics() override;
   void RecordEndOfFrameMetrics(base::TimeTicks frame_begin_time) override;
   void UpdateLifecycle(LifecycleUpdate requested_update,
                        LifecycleUpdateReason reason) override;
@@ -697,6 +701,8 @@
       scoped_defer_main_frame_update_;
 
   Persistent<ResizeViewportAnchor> resize_viewport_anchor_;
+
+  base::TimeTicks raf_aligned_input_start_time_;
 };
 
 // We have no ways to check if the specified WebView is an instance of
diff --git a/third_party/blink/renderer/core/frame/ad_tracker_test.cc b/third_party/blink/renderer/core/frame/ad_tracker_test.cc
index 617ca7c1..56558d4 100644
--- a/third_party/blink/renderer/core/frame/ad_tracker_test.cc
+++ b/third_party/blink/renderer/core/frame/ad_tracker_test.cc
@@ -381,7 +381,8 @@
 
   EXPECT_TRUE(IsKnownAdScript(&GetDocument(), kAdUrl));
   EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kAdUrl));
-  EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kVanillaUrl));
+  Frame* child_frame = GetDocument().GetFrame()->Tree().FirstChild();
+  EXPECT_TRUE(ToLocalFrame(child_frame)->IsAdSubframe());
 }
 
 // A script tagged as an ad in one frame shouldn't cause it to be considered
diff --git a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h
index 5cb8e27..2d3be7e 100644
--- a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h
+++ b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h
@@ -132,6 +132,8 @@
     kStyleAndLayout,
     kForcedStyleAndLayout,
     kScrollingCoordinator,
+    kHandleInputEvents,
+    kAnimate,
     kCount
   };
 
@@ -149,7 +151,9 @@
                            "PrePaint",
                            "StyleAndLayout",
                            "ForcedStyleAndLayout",
-                           "ScrollingCoordinator"};
+                           "ScrollingCoordinator",
+                           "HandleInputEvents",
+                           "Animate"};
     return *strings;
   }
 
@@ -202,6 +206,8 @@
 
   void BeginMainFrame();
 
+  bool InMainFrame() { return in_main_frame_update_; }
+
  private:
   struct AbsoluteMetricRecord {
     String worst_case_metric_name;
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index 7dc3264..53b5e89 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -1997,11 +1997,6 @@
       DocumentLifecycle::LifecycleUpdateReason::kOther);
 }
 
-void LocalFrameView::RecordEndOfFrameMetrics(base::TimeTicks frame_begin_time) {
-  LocalFrameUkmAggregator& ukm_aggregator = EnsureUkmAggregator();
-  ukm_aggregator.RecordEndOfFrameMetrics(frame_begin_time, CurrentTimeTicks());
-}
-
 void LocalFrameView::ScheduleVisualUpdateForPaintInvalidationIfNeeded() {
   LocalFrame& local_frame_root = GetFrame().LocalFrameRoot();
   if (local_frame_root.View()->current_update_lifecycle_phases_target_state_ <
@@ -2145,9 +2140,6 @@
     return Lifecycle().GetState() == target_state;
   }
 
-  if (reason == DocumentLifecycle::LifecycleUpdateReason::kBeginMainFrame)
-    EnsureUkmAggregator().BeginMainFrame();
-
   for (auto& observer : lifecycle_observers_)
     observer->WillStartLifecycleUpdate();
 
@@ -2746,6 +2738,8 @@
   paint_artifact_compositor_->Update(
       paint_controller_->GetPaintArtifactShared(), composited_element_ids,
       viewport_properties, settings);
+
+  probe::layerTreePainted(&GetFrame());
 }
 
 std::unique_ptr<JSONObject> LocalFrameView::CompositedLayersAsJSON(
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.h b/third_party/blink/renderer/core/frame/local_frame_view.h
index fb5bd13d6..9fd61ff7 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.h
+++ b/third_party/blink/renderer/core/frame/local_frame_view.h
@@ -369,9 +369,6 @@
   // desired state.
   bool UpdateLifecycleToLayoutClean();
 
-  // Record any UMA and UKM metrics that depend on the end of a main frame.
-  void RecordEndOfFrameMetrics(base::TimeTicks frame_begin_time);
-
   void ScheduleVisualUpdateForPaintInvalidationIfNeeded();
 
   bool InvalidateViewportConstrainedObjects();
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
index f927f69..fd5bc6e2 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
@@ -60,6 +60,7 @@
 #include "third_party/blink/renderer/core/exported/web_remote_frame_impl.h"
 #include "third_party/blink/renderer/core/exported/web_view_impl.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h"
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/frame/remote_frame.h"
 #include "third_party/blink/renderer/core/frame/settings.h"
@@ -284,7 +285,8 @@
   GetPage()->Animator().SetSuppressFrameRequestsWorkaroundFor704763Only(
       suppress_frame_requests);
 }
-void WebFrameWidgetImpl::BeginFrame(base::TimeTicks last_frame_time) {
+void WebFrameWidgetImpl::BeginFrame(base::TimeTicks last_frame_time,
+                                    bool record_main_frame_metrics) {
   TRACE_EVENT1("blink", "WebFrameWidgetImpl::beginFrame", "frameTime",
                last_frame_time);
   DCHECK(!last_frame_time.is_null());
@@ -294,19 +296,48 @@
 
   DocumentLifecycle::AllowThrottlingScope throttling_scope(
       LocalRootImpl()->GetFrame()->GetDocument()->Lifecycle());
-  PageWidgetDelegate::Animate(*GetPage(), last_frame_time);
+  if (record_main_frame_metrics) {
+    SCOPED_UMA_AND_UKM_TIMER(
+        LocalRootImpl()->GetFrame()->View()->EnsureUkmAggregator(),
+        LocalFrameUkmAggregator::kAnimate);
+    PageWidgetDelegate::Animate(*GetPage(), last_frame_time);
+  } else {
+    PageWidgetDelegate::Animate(*GetPage(), last_frame_time);
+  }
   // Animate can cause the local frame to detach.
   if (LocalRootImpl())
     GetPage()->GetValidationMessageClient().LayoutOverlay();
 }
 
+void WebFrameWidgetImpl::BeginRafAlignedInput() {
+  raf_aligned_input_start_time_ = CurrentTimeTicks();
+}
+
+void WebFrameWidgetImpl::EndRafAlignedInput() {
+  if (LocalRootImpl()) {
+    LocalRootImpl()->GetFrame()->View()->EnsureUkmAggregator().RecordSample(
+        LocalFrameUkmAggregator::kHandleInputEvents,
+        raf_aligned_input_start_time_, CurrentTimeTicks());
+  }
+}
+
+void WebFrameWidgetImpl::RecordStartOfFrameMetrics() {
+  if (!LocalRootImpl())
+    return;
+
+  LocalRootImpl()->GetFrame()->View()->EnsureUkmAggregator().BeginMainFrame();
+}
+
 void WebFrameWidgetImpl::RecordEndOfFrameMetrics(
     base::TimeTicks frame_begin_time) {
   if (!LocalRootImpl())
     return;
 
-  LocalRootImpl()->GetFrame()->View()->RecordEndOfFrameMetrics(
-      frame_begin_time);
+  LocalRootImpl()
+      ->GetFrame()
+      ->View()
+      ->EnsureUkmAggregator()
+      .RecordEndOfFrameMetrics(frame_begin_time, CurrentTimeTicks());
 }
 
 void WebFrameWidgetImpl::UpdateLifecycle(LifecycleUpdate requested_update,
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
index 622592b..283b04a 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
@@ -84,7 +84,11 @@
   void DidEnterFullscreen() override;
   void DidExitFullscreen() override;
   void SetSuppressFrameRequestsWorkaroundFor704763Only(bool) final;
-  void BeginFrame(base::TimeTicks last_frame_time) override;
+  void BeginFrame(base::TimeTicks last_frame_time,
+                  bool record_main_frame_metrics) override;
+  void BeginRafAlignedInput() override;
+  void EndRafAlignedInput() override;
+  void RecordStartOfFrameMetrics() override;
   void RecordEndOfFrameMetrics(base::TimeTicks) override;
   void UpdateLifecycle(LifecycleUpdate requested_update,
                        LifecycleUpdateReason reason) override;
@@ -204,6 +208,7 @@
   scoped_refptr<cc::Layer> root_layer_;
   GraphicsLayer* root_graphics_layer_;
   std::unique_ptr<CompositorAnimationHost> animation_host_;
+  base::TimeTicks raf_aligned_input_start_time_;
   bool is_accelerated_compositing_active_;
   bool layer_tree_view_closed_;
 
diff --git a/third_party/blink/renderer/core/frame/web_view_frame_widget.cc b/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
index 8831aaf..4e358507 100644
--- a/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
+++ b/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
@@ -56,8 +56,21 @@
   web_view_->SetSuppressFrameRequestsWorkaroundFor704763Only(
       suppress_frame_requests);
 }
-void WebViewFrameWidget::BeginFrame(base::TimeTicks last_frame_time) {
-  web_view_->BeginFrame(last_frame_time);
+void WebViewFrameWidget::BeginFrame(base::TimeTicks last_frame_time,
+                                    bool record_main_frame_metrics) {
+  web_view_->BeginFrame(last_frame_time, record_main_frame_metrics);
+}
+
+void WebViewFrameWidget::BeginRafAlignedInput() {
+  web_view_->BeginRafAlignedInput();
+}
+
+void WebViewFrameWidget::EndRafAlignedInput() {
+  web_view_->EndRafAlignedInput();
+}
+
+void WebViewFrameWidget::RecordStartOfFrameMetrics() {
+  web_view_->RecordStartOfFrameMetrics();
 }
 
 void WebViewFrameWidget::RecordEndOfFrameMetrics(
diff --git a/third_party/blink/renderer/core/frame/web_view_frame_widget.h b/third_party/blink/renderer/core/frame/web_view_frame_widget.h
index fdffbc9..6c8832ed 100644
--- a/third_party/blink/renderer/core/frame/web_view_frame_widget.h
+++ b/third_party/blink/renderer/core/frame/web_view_frame_widget.h
@@ -48,7 +48,11 @@
   void DidEnterFullscreen() override;
   void DidExitFullscreen() override;
   void SetSuppressFrameRequestsWorkaroundFor704763Only(bool) final;
-  void BeginFrame(base::TimeTicks last_frame_time) override;
+  void BeginFrame(base::TimeTicks last_frame_time,
+                  bool record_main_frame_metrics) override;
+  void BeginRafAlignedInput() override;
+  void EndRafAlignedInput() override;
+  void RecordStartOfFrameMetrics() override;
   void RecordEndOfFrameMetrics(base::TimeTicks frame_begin_time) override;
   void UpdateLifecycle(LifecycleUpdate requested_update,
                        LifecycleUpdateReason reason) override;
diff --git a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
index af63c781..bcd91590 100644
--- a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
+++ b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
@@ -44,6 +44,7 @@
 #include "third_party/blink/renderer/core/css/css_font_selector.h"
 #include "third_party/blink/renderer/core/css/style_engine.h"
 #include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/dom/dom_node_ids.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/dom/element_traversal.h"
 #include "third_party/blink/renderer/core/dom/node_computed_style.h"
@@ -1528,6 +1529,9 @@
   if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled() &&
       GetLayoutObject() && GetLayoutObject()->HasLayer())
     GetLayoutBoxModelObject()->Layer()->SetNeedsRepaint();
+
+  if (auto* layer = ContentsCcLayer())
+    layer->set_owner_node_id(DOMNodeIds::IdForNode(this));
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/media/html_media_element.cc b/third_party/blink/renderer/core/html/media/html_media_element.cc
index fe485736..b45129b6 100644
--- a/third_party/blink/renderer/core/html/media/html_media_element.cc
+++ b/third_party/blink/renderer/core/html/media/html_media_element.cc
@@ -51,6 +51,7 @@
 #include "third_party/blink/renderer/core/css/media_list.h"
 #include "third_party/blink/renderer/core/dom/attribute.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
+#include "third_party/blink/renderer/core/dom/dom_node_ids.h"
 #include "third_party/blink/renderer/core/dom/element_traversal.h"
 #include "third_party/blink/renderer/core/dom/element_visibility_observer.h"
 #include "third_party/blink/renderer/core/dom/events/event.h"
@@ -3962,8 +3963,10 @@
   if (cc_layer_)
     GraphicsLayer::UnregisterContentsLayer(cc_layer_);
   cc_layer_ = cc_layer;
-  if (cc_layer_)
+  if (cc_layer_) {
     GraphicsLayer::RegisterContentsLayer(cc_layer_);
+    cc_layer_->set_owner_node_id(DOMNodeIds::IdForNode(this));
+  }
 }
 
 void HTMLMediaElement::MediaSourceOpened(WebMediaSource* web_media_source) {
diff --git a/third_party/blink/renderer/core/html/parser/preload_request.cc b/third_party/blink/renderer/core/html/parser/preload_request.cc
index 889dbb73..8cea788 100644
--- a/third_party/blink/renderer/core/html/parser/preload_request.cc
+++ b/third_party/blink/renderer/core/html/parser/preload_request.cc
@@ -43,8 +43,8 @@
   if (referrer_source_ == kBaseUrlIsReferrer)
     resource_request.SetReferrerString(base_url_.StrippedForUseAsReferrer());
 
-  resource_request.SetRequestContext(ResourceFetcher::DetermineRequestContext(
-      resource_type_, is_image_set_, false));
+  resource_request.SetRequestContext(
+      ResourceFetcher::DetermineRequestContext(resource_type_, is_image_set_));
 
   resource_request.SetFetchImportanceMode(importance_);
 
diff --git a/third_party/blink/renderer/core/inspector/inspector_layer_tree_agent.cc b/third_party/blink/renderer/core/inspector/inspector_layer_tree_agent.cc
index d7f42bc..7e20a15 100644
--- a/third_party/blink/renderer/core/inspector/inspector_layer_tree_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_layer_tree_agent.cc
@@ -268,9 +268,17 @@
 Response InspectorLayerTreeAgent::enable() {
   instrumenting_agents_->addInspectorLayerTreeAgent(this);
   Document* document = inspected_frames_->Root()->GetDocument();
-  if (document &&
-      document->Lifecycle().GetState() >= DocumentLifecycle::kCompositingClean)
+  if (!document)
+    return Response::Error("The root frame doesn't have document");
+
+  if (RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled() ||
+      RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
+    if (document->Lifecycle().GetState() >= DocumentLifecycle::kPaintClean)
+      LayerTreePainted();
+  } else if (document->Lifecycle().GetState() >=
+             DocumentLifecycle::kCompositingClean) {
     LayerTreeDidChange();
+  }
   return Response::OK();
 }
 
@@ -281,12 +289,15 @@
 }
 
 void InspectorLayerTreeAgent::LayerTreeDidChange() {
+  DCHECK(!RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled() &&
+         !RuntimeEnabledFeatures::CompositeAfterPaintEnabled());
   GetFrontend()->layerTreeDidChange(BuildLayerTree());
 }
 
 void InspectorLayerTreeAgent::DidPaint(const cc::Layer* layer,
-                                       GraphicsContext&,
                                        const LayoutRect& rect) {
+  DCHECK(!RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled() &&
+         !RuntimeEnabledFeatures::CompositeAfterPaintEnabled());
   if (suppress_layer_paint_events_)
     return;
 
@@ -304,6 +315,21 @@
   GetFrontend()->layerPainted(IdForLayer(layer), std::move(dom_rect));
 }
 
+void InspectorLayerTreeAgent::LayerTreePainted() {
+  DCHECK(RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled() ||
+         RuntimeEnabledFeatures::CompositeAfterPaintEnabled());
+
+  GetFrontend()->layerTreeDidChange(BuildLayerTree());
+
+  for (const auto& layer :
+       inspected_frames_->Root()->View()->RootCcLayer()->children()) {
+    if (!layer->update_rect().IsEmpty()) {
+      GetFrontend()->layerPainted(IdForLayer(layer.get()),
+                                  BuildObjectForRect(layer->update_rect()));
+    }
+  }
+}
+
 std::unique_ptr<Array<protocol::LayerTree::Layer>>
 InspectorLayerTreeAgent::BuildLayerTree() {
   const auto* root_layer = RootLayer();
diff --git a/third_party/blink/renderer/core/inspector/inspector_layer_tree_agent.h b/third_party/blink/renderer/core/inspector/inspector_layer_tree_agent.h
index 6346595..5229321 100644
--- a/third_party/blink/renderer/core/inspector/inspector_layer_tree_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_layer_tree_agent.h
@@ -44,7 +44,6 @@
 
 namespace blink {
 
-class GraphicsContext;
 class InspectedFrames;
 class LayoutRect;
 class PictureSnapshot;
@@ -72,7 +71,8 @@
 
   // Called from InspectorInstrumentation
   void LayerTreeDidChange();
-  void DidPaint(const cc::Layer*, GraphicsContext&, const LayoutRect&);
+  void DidPaint(const cc::Layer*, const LayoutRect&);
+  void LayerTreePainted();
 
   // Called from the front-end.
   protocol::Response enable() override;
diff --git a/third_party/blink/renderer/core/inspector/inspector_network_agent.cc b/third_party/blink/renderer/core/inspector/inspector_network_agent.cc
index cbfc3b68..765fb6e 100644
--- a/third_party/blink/renderer/core/inspector/inspector_network_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_network_agent.cc
@@ -747,14 +747,7 @@
     const FetchInitiatorInfo& initiator_info,
     InspectorPageAgent::ResourceType type) {
   String loader_id = IdentifiersFactory::LoaderId(loader);
-  // DocumentLoader doesn't have main resource set at the point, so RequestId()
-  // won't properly detect main resource. Workaround this by checking the
-  // frame type and manually setting request id to loader id.
   String request_id = IdentifiersFactory::RequestId(loader, identifier);
-  bool is_navigation =
-      request.GetFrameType() != network::mojom::RequestContextFrameType::kNone;
-  if (is_navigation)
-    request_id = loader_id;
   NetworkResourcesData::ResourceData const* data =
       resources_data_->Data(request_id);
   // Support for POST request redirect
@@ -775,9 +768,6 @@
     type = pending_request_type_;
   resources_data_->SetResourceType(request_id, type);
 
-  if (is_navigation)
-    return;
-
   String frame_id = loader && loader->GetFrame()
                         ? IdentifiersFactory::FrameId(loader->GetFrame())
                         : "";
@@ -827,6 +817,29 @@
   pending_request_ = nullptr;
 }
 
+void InspectorNetworkAgent::WillSendNavigationRequest(
+    ExecutionContext* execution_context,
+    unsigned long identifier,
+    DocumentLoader* loader,
+    const KURL& url,
+    const AtomicString& http_method,
+    EncodedFormData* http_body) {
+  String loader_id = IdentifiersFactory::LoaderId(loader);
+  String request_id = loader_id;
+  NetworkResourcesData::ResourceData const* data =
+      resources_data_->Data(request_id);
+  // Support for POST request redirect.
+  scoped_refptr<EncodedFormData> post_data;
+  if (data)
+    post_data = data->PostData();
+  else if (http_body)
+    post_data = http_body->DeepCopy();
+  resources_data_->ResourceCreated(execution_context, request_id, loader_id,
+                                   url, post_data);
+  resources_data_->SetResourceType(request_id,
+                                   InspectorPageAgent::kDocumentResource);
+}
+
 void InspectorNetworkAgent::WillSendRequest(
     ExecutionContext* execution_context,
     unsigned long identifier,
@@ -839,10 +852,6 @@
   if (initiator_info.name == fetch_initiator_type_names::kInternal)
     return;
 
-  if (initiator_info.name == fetch_initiator_type_names::kDocument &&
-      loader->HasSubstituteData())
-    return;
-
   if (!extra_request_headers_.IsEmpty()) {
     for (const WTF::String& key : extra_request_headers_.Keys()) {
       const WTF::String& value = extra_request_headers_.Get(key);
@@ -918,9 +927,6 @@
       saved_type == InspectorPageAgent::kEventSourceResource) {
     type = saved_type;
   }
-  if (type == InspectorPageAgent::kDocumentResource && loader &&
-      loader->HasSubstituteData())
-    return;
 
   // Resources are added to NetworkResourcesData as a WeakMember here and
   // removed in willDestroyResource() called in the prefinalizer of Resource.
diff --git a/third_party/blink/renderer/core/inspector/inspector_network_agent.h b/third_party/blink/renderer/core/inspector/inspector_network_agent.h
index e37e634..6cc00cd 100644
--- a/third_party/blink/renderer/core/inspector/inspector_network_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_network_agent.h
@@ -101,6 +101,12 @@
                        const ResourceResponse& redirect_response,
                        const FetchInitiatorInfo&,
                        ResourceType);
+  void WillSendNavigationRequest(ExecutionContext*,
+                                 unsigned long identifier,
+                                 DocumentLoader*,
+                                 const KURL&,
+                                 const AtomicString& http_method,
+                                 EncodedFormData* http_body);
   void MarkResourceAsCached(DocumentLoader*, unsigned long identifier);
   void DidReceiveResourceResponse(unsigned long identifier,
                                   DocumentLoader*,
diff --git a/third_party/blink/renderer/core/inspector/inspector_page_agent.cc b/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
index fb2982364f..1e07683 100644
--- a/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
@@ -203,8 +203,7 @@
   return type == ResourceType::kCSSStyleSheet ||
          type == ResourceType::kXSLStyleSheet ||
          type == ResourceType::kScript || type == ResourceType::kRaw ||
-         type == ResourceType::kImportResource ||
-         type == ResourceType::kMainResource;
+         type == ResourceType::kImportResource;
 }
 
 static std::unique_ptr<TextResourceDecoder> CreateResourceTextDecoder(
@@ -430,8 +429,6 @@
     case blink::ResourceType::kScript:
       return InspectorPageAgent::kScriptResource;
     case blink::ResourceType::kImportResource:
-    // Fall through.
-    case blink::ResourceType::kMainResource:
       return InspectorPageAgent::kDocumentResource;
     default:
       break;
diff --git a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
index 0069c74c..11cc66c 100644
--- a/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_trace_events.cc
@@ -116,6 +116,20 @@
       inspector_send_request_event::Data(loader, identifier, frame, request));
 }
 
+void InspectorTraceEvents::WillSendNavigationRequest(
+    ExecutionContext*,
+    unsigned long identifier,
+    DocumentLoader* loader,
+    const KURL& url,
+    const AtomicString& http_method,
+    EncodedFormData*) {
+  LocalFrame* frame = loader ? loader->GetFrame() : nullptr;
+  TRACE_EVENT_INSTANT1("devtools.timeline", "ResourceSendRequest",
+                       TRACE_EVENT_SCOPE_THREAD, "data",
+                       inspector_send_navigation_request_event::Data(
+                           loader, identifier, frame, url, http_method));
+}
+
 void InspectorTraceEvents::DidReceiveResourceResponse(
     unsigned long identifier,
     DocumentLoader* loader,
@@ -759,6 +773,25 @@
   return value;
 }
 
+std::unique_ptr<TracedValue> inspector_send_navigation_request_event::Data(
+    DocumentLoader* loader,
+    unsigned long identifier,
+    LocalFrame* frame,
+    const KURL& url,
+    const AtomicString& http_method) {
+  std::unique_ptr<TracedValue> value = TracedValue::Create();
+  value->SetString("requestId", IdentifiersFactory::LoaderId(loader));
+  value->SetString("frame", IdentifiersFactory::FrameId(frame));
+  value->SetString("url", url.GetString());
+  value->SetString("requestMethod", http_method);
+  const char* priority =
+      ResourcePriorityString(ResourceLoadPriority::kVeryHigh);
+  if (priority)
+    value->SetString("priority", priority);
+  SetCallStack(value.get());
+  return value;
+}
+
 namespace {
 void RecordTiming(const ResourceLoadTiming& timing, TracedValue* value) {
   value->SetDouble("requestTime", TimeTicksInSeconds(timing.RequestTime()));
diff --git a/third_party/blink/renderer/core/inspector/inspector_trace_events.h b/third_party/blink/renderer/core/inspector/inspector_trace_events.h
index 92a8ae8..691c2a5 100644
--- a/third_party/blink/renderer/core/inspector/inspector_trace_events.h
+++ b/third_party/blink/renderer/core/inspector/inspector_trace_events.h
@@ -43,6 +43,7 @@
 class Document;
 class DocumentLoader;
 class Element;
+class EncodedFormData;
 class Event;
 class ExecutionContext;
 struct FetchInitiatorInfo;
@@ -89,6 +90,12 @@
                        const ResourceResponse& redirect_response,
                        const FetchInitiatorInfo&,
                        ResourceType);
+  void WillSendNavigationRequest(ExecutionContext*,
+                                 unsigned long identifier,
+                                 DocumentLoader*,
+                                 const KURL&,
+                                 const AtomicString& http_method,
+                                 EncodedFormData*);
   void DidReceiveResourceResponse(unsigned long identifier,
                                   DocumentLoader*,
                                   const ResourceResponse&,
@@ -275,6 +282,14 @@
                                   const ResourceRequest&);
 }
 
+namespace inspector_send_navigation_request_event {
+std::unique_ptr<TracedValue> Data(DocumentLoader*,
+                                  unsigned long identifier,
+                                  LocalFrame*,
+                                  const KURL&,
+                                  const AtomicString& http_method);
+}
+
 namespace inspector_receive_response_event {
 std::unique_ptr<TracedValue> Data(DocumentLoader*,
                                   unsigned long identifier,
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 54653b22..a377c5e5 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -1550,6 +1550,10 @@
   return name.ToString();
 }
 
+DOMNodeId LayoutObject::OwnerNodeId() const {
+  return GetNode() ? DOMNodeIds::IdForNode(GetNode()) : kInvalidDOMNodeId;
+}
+
 LayoutRect LayoutObject::FragmentsVisualRectBoundingBox() const {
   if (!fragment_.NextFragment())
     return fragment_.VisualRect();
diff --git a/third_party/blink/renderer/core/layout/layout_object.h b/third_party/blink/renderer/core/layout/layout_object.h
index 721856e3..6128199 100644
--- a/third_party/blink/renderer/core/layout/layout_object.h
+++ b/third_party/blink/renderer/core/layout/layout_object.h
@@ -266,6 +266,8 @@
         .SetPartialInvalidationVisualRect(LayoutRect());
   }
 
+  DOMNodeId OwnerNodeId() const final;
+
  public:
   LayoutRect PartialInvalidationVisualRect() const final {
     return FirstFragment().PartialInvalidationVisualRect();
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.cc
index 821393f..846f7b76 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_caret_navigator.cc
@@ -33,26 +33,11 @@
   return GetData().IsBidiEnabled();
 }
 
-const NGInlineItem& NGCaretNavigator::GetItem(unsigned index) const {
-  const auto& items = GetData().items;
-  const NGInlineItem* item =
-      std::lower_bound(items.begin(), items.end(), index,
-                       [](const NGInlineItem& item, unsigned index) {
-                         if (item.StartOffset() > index)
-                           return false;
-                         return item.EndOffset() <= index;
-                       });
-  DCHECK_NE(item, items.end());
-  DCHECK_LE(item->StartOffset(), index);
-  DCHECK_LT(index, item->EndOffset());
-  return *item;
-}
-
 UBiDiLevel NGCaretNavigator::BidiLevelAt(unsigned index) const {
   DCHECK_LT(index, GetText().length());
   if (!IsBidiEnabled())
     return 0;
-  return GetItem(index).BidiLevel();
+  return GetData().FindItemForTextOffset(index).BidiLevel();
 }
 
 TextDirection NGCaretNavigator::TextDirectionAt(unsigned index) const {
@@ -73,6 +58,7 @@
 NGCaretNavigator::CaretPositionFromTextContentOffsetAndAffinity(
     unsigned offset,
     TextAffinity affinity) const {
+  DCHECK_LE(offset, GetText().length());
   // Callers sometimes pass in (0, upstream) or (length, downstream), which
   // originate from legacy callers. Make sure they are fixed up.
   // TODO(xiaochengh): Catch and eliminate such callers.
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.cc
index a22d01eb..a054069 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.cc
@@ -275,4 +275,20 @@
   is_end_collapsible_newline_ = is_newline;
 }
 
+const NGInlineItem& NGInlineItemsData::FindItemForTextOffset(
+    unsigned offset) const {
+  DCHECK_LT(offset, text_content.length());
+  const NGInlineItem* item =
+      std::lower_bound(items.begin(), items.end(), offset,
+                       [](const NGInlineItem& item, unsigned offset) {
+                         if (item.StartOffset() > offset)
+                           return false;
+                         return item.EndOffset() <= offset;
+                       });
+  DCHECK_NE(item, items.end());
+  DCHECK_LE(item->StartOffset(), offset);
+  DCHECK_LT(offset, item->EndOffset());
+  return *item;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h
index 7604cd2..c74ae29 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_item.h
@@ -224,6 +224,11 @@
   void AssertEndOffset(unsigned index, unsigned offset) const {
     items[index].AssertEndOffset(offset);
   }
+
+  // Returns the non-zero-length inline item whose |StartOffset() <= offset| and
+  // |EndOffset() > offset|, namely, contains the character at |offset|.
+  // Note: This function is not a trivial getter, but does a binary search.
+  const NGInlineItem& FindItemForTextOffset(unsigned offset) const;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/loader/base_fetch_context.cc b/third_party/blink/renderer/core/loader/base_fetch_context.cc
index 040374a..d97fcb0a 100644
--- a/third_party/blink/renderer/core/loader/base_fetch_context.cc
+++ b/third_party/blink/renderer/core/loader/base_fetch_context.cc
@@ -92,41 +92,36 @@
 
 }  // namespace
 
-void BaseFetchContext::AddAdditionalRequestHeaders(ResourceRequest& request,
-                                                   FetchResourceType type) {
+void BaseFetchContext::AddAdditionalRequestHeaders(ResourceRequest& request) {
   const FetchClientSettingsObject& fetch_client_settings_object =
       GetResourceFetcherProperties().GetFetchClientSettingsObject();
-  bool is_main_resource = type == kFetchMainResource;
-  if (!is_main_resource) {
-    // TODO(domfarolino): we can probably *just set* the HTTP `Referer` here
-    // no matter what now.
-    if (!request.DidSetHTTPReferrer()) {
-      String referrer_to_use = request.ReferrerString();
-      network::mojom::ReferrerPolicy referrer_policy_to_use =
-          request.GetReferrerPolicy();
+  // TODO(domfarolino): we can probably *just set* the HTTP `Referer` here
+  // no matter what now.
+  if (!request.DidSetHTTPReferrer()) {
+    String referrer_to_use = request.ReferrerString();
+    network::mojom::ReferrerPolicy referrer_policy_to_use =
+        request.GetReferrerPolicy();
 
-      if (referrer_to_use == Referrer::ClientReferrerString())
-        referrer_to_use = fetch_client_settings_object.GetOutgoingReferrer();
+    if (referrer_to_use == Referrer::ClientReferrerString())
+      referrer_to_use = fetch_client_settings_object.GetOutgoingReferrer();
 
-      if (referrer_policy_to_use == network::mojom::ReferrerPolicy::kDefault) {
-        referrer_policy_to_use =
-            fetch_client_settings_object.GetReferrerPolicy();
-      }
-
-      // TODO(domfarolino): Stop storing ResourceRequest's referrer as a header
-      // and store it elsewhere. See https://crbug.com/850813.
-      request.SetHTTPReferrer(SecurityPolicy::GenerateReferrer(
-          referrer_policy_to_use, request.Url(), referrer_to_use));
-      request.SetHTTPOriginIfNeeded(
-          fetch_client_settings_object.GetSecurityOrigin());
-    } else {
-      CHECK_EQ(SecurityPolicy::GenerateReferrer(request.GetReferrerPolicy(),
-                                                request.Url(),
-                                                request.HttpReferrer())
-                   .referrer,
-               request.HttpReferrer());
-      request.SetHTTPOriginToMatchReferrerIfNeeded();
+    if (referrer_policy_to_use == network::mojom::ReferrerPolicy::kDefault) {
+      referrer_policy_to_use = fetch_client_settings_object.GetReferrerPolicy();
     }
+
+    // TODO(domfarolino): Stop storing ResourceRequest's referrer as a header
+    // and store it elsewhere. See https://crbug.com/850813.
+    request.SetHTTPReferrer(SecurityPolicy::GenerateReferrer(
+        referrer_policy_to_use, request.Url(), referrer_to_use));
+    request.SetHTTPOriginIfNeeded(
+        fetch_client_settings_object.GetSecurityOrigin());
+  } else {
+    CHECK_EQ(
+        SecurityPolicy::GenerateReferrer(request.GetReferrerPolicy(),
+                                         request.Url(), request.HttpReferrer())
+            .referrer,
+        request.HttpReferrer());
+    request.SetHTTPOriginToMatchReferrerIfNeeded();
   }
 
   auto address_space = GetAddressSpace();
@@ -194,9 +189,8 @@
   SubresourceFilter* filter = GetSubresourceFilter();
 
   // We do not need main document tagging currently so skipping main resources.
-  if (filter && type != ResourceType::kMainResource) {
+  if (filter)
     return filter->IsAdResource(resource_url, request_context);
-  }
 
   return false;
 }
@@ -333,8 +327,7 @@
 
   // SVG Images have unique security rules that prevent all subresource requests
   // except for data urls.
-  if (type != ResourceType::kMainResource && IsSVGImageChromeClient() &&
-      !url.ProtocolIsData())
+  if (IsSVGImageChromeClient() && !url.ProtocolIsData())
     return ResourceRequestBlockedReason::kOrigin;
 
   network::mojom::RequestContextFrameType frame_type =
@@ -386,8 +379,7 @@
 
   // Let the client have the final say into whether or not the load should
   // proceed.
-  if (GetSubresourceFilter() && type != ResourceType::kMainResource &&
-      type != ResourceType::kImportResource) {
+  if (GetSubresourceFilter() && type != ResourceType::kImportResource) {
     if (!GetSubresourceFilter()->AllowLoad(url, request_context,
                                            reporting_policy)) {
       return ResourceRequestBlockedReason::kSubresourceFilter;
diff --git a/third_party/blink/renderer/core/loader/base_fetch_context.h b/third_party/blink/renderer/core/loader/base_fetch_context.h
index ed614f9..07497fa 100644
--- a/third_party/blink/renderer/core/loader/base_fetch_context.h
+++ b/third_party/blink/renderer/core/loader/base_fetch_context.h
@@ -30,8 +30,7 @@
 // Frame. This class provides basic default implementation for some methods.
 class CORE_EXPORT BaseFetchContext : public FetchContext {
  public:
-  void AddAdditionalRequestHeaders(ResourceRequest&,
-                                   FetchResourceType) override;
+  void AddAdditionalRequestHeaders(ResourceRequest&) override;
   base::Optional<ResourceRequestBlockedReason> CanRequest(
       ResourceType,
       const ResourceRequest&,
diff --git a/third_party/blink/renderer/core/loader/base_fetch_context_test.cc b/third_party/blink/renderer/core/loader/base_fetch_context_test.cc
index b09e1c1..01f04b8 100644
--- a/third_party/blink/renderer/core/loader/base_fetch_context_test.cc
+++ b/third_party/blink/renderer/core/loader/base_fetch_context_test.cc
@@ -168,14 +168,8 @@
     ScopedCorsRFC1918ForTest cors_rfc1918(false);
     for (const auto& test : cases) {
       SCOPED_TRACE(test.url);
-      ResourceRequest main_request(test.url);
-      fetch_context_->AddAdditionalRequestHeaders(main_request,
-                                                  kFetchMainResource);
-      EXPECT_FALSE(main_request.IsExternalRequest());
-
       ResourceRequest sub_request(test.url);
-      fetch_context_->AddAdditionalRequestHeaders(sub_request,
-                                                  kFetchSubresource);
+      fetch_context_->AddAdditionalRequestHeaders(sub_request);
       EXPECT_FALSE(sub_request.IsExternalRequest());
     }
   }
@@ -184,14 +178,8 @@
     ScopedCorsRFC1918ForTest cors_rfc1918(true);
     for (const auto& test : cases) {
       SCOPED_TRACE(test.url);
-      ResourceRequest main_request(test.url);
-      fetch_context_->AddAdditionalRequestHeaders(main_request,
-                                                  kFetchMainResource);
-      EXPECT_EQ(test.is_external_expectation, main_request.IsExternalRequest());
-
       ResourceRequest sub_request(test.url);
-      fetch_context_->AddAdditionalRequestHeaders(sub_request,
-                                                  kFetchSubresource);
+      fetch_context_->AddAdditionalRequestHeaders(sub_request);
       EXPECT_EQ(test.is_external_expectation, sub_request.IsExternalRequest());
     }
   }
@@ -220,14 +208,8 @@
     ScopedCorsRFC1918ForTest cors_rfc1918(false);
     for (const auto& test : cases) {
       SCOPED_TRACE(test.url);
-      ResourceRequest main_request(test.url);
-      fetch_context_->AddAdditionalRequestHeaders(main_request,
-                                                  kFetchMainResource);
-      EXPECT_FALSE(main_request.IsExternalRequest());
-
       ResourceRequest sub_request(test.url);
-      fetch_context_->AddAdditionalRequestHeaders(sub_request,
-                                                  kFetchSubresource);
+      fetch_context_->AddAdditionalRequestHeaders(sub_request);
       EXPECT_FALSE(sub_request.IsExternalRequest());
     }
   }
@@ -236,14 +218,8 @@
     ScopedCorsRFC1918ForTest cors_rfc1918(true);
     for (const auto& test : cases) {
       SCOPED_TRACE(test.url);
-      ResourceRequest main_request(test.url);
-      fetch_context_->AddAdditionalRequestHeaders(main_request,
-                                                  kFetchMainResource);
-      EXPECT_EQ(test.is_external_expectation, main_request.IsExternalRequest());
-
       ResourceRequest sub_request(test.url);
-      fetch_context_->AddAdditionalRequestHeaders(sub_request,
-                                                  kFetchSubresource);
+      fetch_context_->AddAdditionalRequestHeaders(sub_request);
       EXPECT_EQ(test.is_external_expectation, sub_request.IsExternalRequest());
     }
   }
@@ -271,14 +247,8 @@
   {
     ScopedCorsRFC1918ForTest cors_rfc1918(false);
     for (const auto& test : cases) {
-      ResourceRequest main_request(test.url);
-      fetch_context_->AddAdditionalRequestHeaders(main_request,
-                                                  kFetchMainResource);
-      EXPECT_FALSE(main_request.IsExternalRequest());
-
       ResourceRequest sub_request(test.url);
-      fetch_context_->AddAdditionalRequestHeaders(sub_request,
-                                                  kFetchSubresource);
+      fetch_context_->AddAdditionalRequestHeaders(sub_request);
       EXPECT_FALSE(sub_request.IsExternalRequest());
     }
   }
@@ -286,14 +256,8 @@
   {
     ScopedCorsRFC1918ForTest cors_rfc1918(true);
     for (const auto& test : cases) {
-      ResourceRequest main_request(test.url);
-      fetch_context_->AddAdditionalRequestHeaders(main_request,
-                                                  kFetchMainResource);
-      EXPECT_EQ(test.is_external_expectation, main_request.IsExternalRequest());
-
       ResourceRequest sub_request(test.url);
-      fetch_context_->AddAdditionalRequestHeaders(sub_request,
-                                                  kFetchSubresource);
+      fetch_context_->AddAdditionalRequestHeaders(sub_request);
       EXPECT_EQ(test.is_external_expectation, sub_request.IsExternalRequest());
     }
   }
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index f9d060e..665324f 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -79,6 +79,7 @@
 #include "third_party/blink/renderer/core/timing/window_performance.h"
 #include "third_party/blink/renderer/platform/bindings/microtask.h"
 #include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
+#include "third_party/blink/renderer/platform/bindings/v8_dom_activity_logger.h"
 #include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
 #include "third_party/blink/renderer/platform/loader/cors/cors.h"
 #include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h"
@@ -936,12 +937,13 @@
 void DocumentLoader::SetDefersLoading(bool defers) {
   defers_loading_ = defers;
   Fetcher()->SetDefersLoading(defers);
-  if (body_loader_)
+  if (body_loader_) {
     body_loader_->SetDefersLoading(defers);
-  if (defers_loading_)
-    virtual_time_pauser_.UnpauseVirtualTime();
-  else
-    virtual_time_pauser_.PauseVirtualTime();
+    if (defers_loading_)
+      virtual_time_pauser_.UnpauseVirtualTime();
+    else
+      virtual_time_pauser_.PauseVirtualTime();
+  }
 }
 
 void DocumentLoader::DetachFromFrame(bool flush_microtask_queue) {
@@ -1106,8 +1108,7 @@
   main_resource_identifier_ = CreateUniqueIdentifier();
 
   navigation_timing_info_ = ResourceTimingInfo::Create(
-      fetch_initiator_type_names::kDocument, GetTiming().NavigationStart(),
-      true /* is_main_resource */);
+      fetch_initiator_type_names::kDocument, GetTiming().NavigationStart());
   navigation_timing_info_->SetInitialURL(url_);
   report_timing_info_to_parent_ = ShouldReportTimingInfoToParent();
 
@@ -1123,24 +1124,23 @@
   // Many parties are interested in resource loading, so we will notify
   // them through various DispatchXXX methods on FrameFetchContext.
 
-  // TODO(dgozman): get rid of fake request and initiator info, we only use them
-  // for DispatchWillSendRequest.
-  ResourceRequest fake_request;
-  fake_request.SetURL(url_);
-  fake_request.SetPriority(WebURLRequest::Priority::kVeryHigh);
-  fake_request.SetHTTPBody(http_body_);
-  fake_request.SetFrameType(
-      frame_->IsMainFrame() ? network::mojom::RequestContextFrameType::kTopLevel
-                            : network::mojom::RequestContextFrameType::kNested);
-  fake_request.SetRequestContext(mojom::RequestContextType::HYPERLINK);
-  FetchInitiatorInfo initiator_info;
-  initiator_info.name = fetch_initiator_type_names::kDocument;
-  fetcher_->Context().RecordLoadingActivity(
-      fake_request, ResourceType::kMainResource,
-      fetch_initiator_type_names::kDocument);
-  fetcher_->Context().DispatchWillSendRequest(
-      main_resource_identifier_, fake_request, ResourceResponse(),
-      ResourceType::kMainResource, initiator_info);
+  if (!fetcher_->Archive()) {
+    V8DOMActivityLogger* activity_logger =
+        V8DOMActivityLogger::CurrentActivityLoggerIfIsolatedWorld();
+    if (activity_logger) {
+      Vector<String> argv;
+      argv.push_back("Main resource");
+      argv.push_back(url_.GetString());
+      activity_logger->LogEvent("blinkRequestResource", argv.size(),
+                                argv.data());
+    }
+  }
+
+  GetFrameLoader().Progress().WillStartLoading(main_resource_identifier_,
+                                               ResourceLoadPriority::kVeryHigh);
+  probe::willSendNavigationRequest(GetFrame()->GetDocument(),
+                                   main_resource_identifier_, this, url_,
+                                   http_method_, http_body_.get());
 
   for (size_t i = 0; i < params_->redirects.size(); ++i) {
     WebNavigationParams::RedirectInfo& redirect = params_->redirects[i];
@@ -1148,7 +1148,6 @@
     AtomicString new_http_method = redirect.new_http_method;
     if (http_method_ != new_http_method) {
       http_body_ = nullptr;
-      fake_request.SetHTTPBody(nullptr);
       http_method_ = new_http_method;
     }
     if (redirect.new_referrer.IsEmpty()) {
@@ -1160,16 +1159,22 @@
     http_content_type_ = g_null_atom;
     // TODO(dgozman): check whether clearing origin policy is intended behavior.
     origin_policy_ = String();
-    fake_request.SetURL(url_);
+    probe::willSendNavigationRequest(GetFrame()->GetDocument(),
+                                     main_resource_identifier_, this, url_,
+                                     http_method_, http_body_.get());
     ResourceResponse redirect_response =
         redirect.redirect_response.ToResourceResponse();
-    fetcher_->Context().DispatchWillSendRequest(
-        main_resource_identifier_, fake_request, redirect_response,
-        ResourceType::kMainResource, initiator_info);
     navigation_timing_info_->AddRedirect(redirect_response, url_);
     HandleRedirect(redirect_response.CurrentRequestUrl());
   }
 
+  // TODO(dgozman): get rid of fake request, we only use it for
+  // DispatchDidReceiveResponse.
+  ResourceRequest fake_request;
+  fake_request.SetFrameType(
+      frame_->IsMainFrame() ? network::mojom::RequestContextFrameType::kTopLevel
+                            : network::mojom::RequestContextFrameType::kNested);
+  fake_request.SetRequestContext(mojom::RequestContextType::HYPERLINK);
   fetcher_->Context().DispatchDidReceiveResponse(
       main_resource_identifier_, fake_request, final_response, nullptr,
       FetchContext::ResourceResponseType::kNotFromMemoryCache);
diff --git a/third_party/blink/renderer/core/loader/document_loader.h b/third_party/blink/renderer/core/loader/document_loader.h
index 6b84959..f2a9e82 100644
--- a/third_party/blink/renderer/core/loader/document_loader.h
+++ b/third_party/blink/renderer/core/loader/document_loader.h
@@ -157,7 +157,6 @@
     return state_ >= kCommitted && !data_received_;
   }
 
-  bool HasSubstituteData() const { return has_substitute_data_; }
   void FillNavigationParamsForErrorPage(WebNavigationParams*);
 
   // Without PlzNavigate, this is only false for a narrow window during
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.cc b/third_party/blink/renderer/core/loader/frame_fetch_context.cc
index bc3130e..629ee8a 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context.cc
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context.cc
@@ -122,19 +122,15 @@
     "AllowClientHintsToThirdParty", base::FEATURE_DISABLED_BY_DEFAULT};
 #endif
 
-enum class RequestMethod { kIsPost, kIsNotPost };
-enum class RequestType { kIsConditional, kIsNotConditional };
-enum class MainResourceType { kIsMainResource, kIsNotMainResource };
-
 void MaybeRecordCTPolicyComplianceUseCounter(
     LocalFrame* frame,
-    ResourceType resource_type,
+    bool is_main_resource,
     ResourceResponse::CTPolicyCompliance compliance,
     DocumentLoader* loader) {
   if (compliance != ResourceResponse::kCTPolicyDoesNotComply)
     return;
   // Exclude main-frame navigation requests; those are tracked elsewhere.
-  if (!frame->Tree().Parent() && resource_type == ResourceType::kMainResource)
+  if (!frame->Tree().Parent() && is_main_resource)
     return;
   if (loader) {
     loader->GetUseCounter().Count(
@@ -146,66 +142,21 @@
   }
 }
 
-void RecordLegacySymantecCertUseCounter(LocalFrame* frame,
-                                        ResourceType resource_type) {
-  // Main resources are counted in DocumentLoader.
-  if (resource_type == ResourceType::kMainResource) {
-    return;
-  }
-  UseCounter::Count(frame, WebFeature::kLegacySymantecCertInSubresource);
-}
-
-// Determines FetchCacheMode for a main resource, or FetchCacheMode that is
-// corresponding to WebFrameLoadType.
-// TODO(toyoshim): Probably, we should split WebFrameLoadType to FetchCacheMode
-// conversion logic into a separate function.
-mojom::FetchCacheMode DetermineCacheMode(RequestMethod method,
-                                         RequestType request_type,
-                                         MainResourceType resource_type,
-                                         WebFrameLoadType load_type) {
-  switch (load_type) {
-    case WebFrameLoadType::kStandard:
-    case WebFrameLoadType::kReplaceCurrentItem:
-      return (request_type == RequestType::kIsConditional ||
-              method == RequestMethod::kIsPost)
-                 ? mojom::FetchCacheMode::kValidateCache
-                 : mojom::FetchCacheMode::kDefault;
-    case WebFrameLoadType::kBackForward:
-      // Mutates the policy for POST requests to avoid form resubmission.
-      return method == RequestMethod::kIsPost
-                 ? mojom::FetchCacheMode::kOnlyIfCached
-                 : mojom::FetchCacheMode::kForceCache;
-    case WebFrameLoadType::kReload:
-      return resource_type == MainResourceType::kIsMainResource
-                 ? mojom::FetchCacheMode::kValidateCache
-                 : mojom::FetchCacheMode::kDefault;
-    case WebFrameLoadType::kReloadBypassingCache:
-      return mojom::FetchCacheMode::kBypassCache;
-  }
-  NOTREACHED();
-  return mojom::FetchCacheMode::kDefault;
-}
-
 // Determines FetchCacheMode for |frame|. This FetchCacheMode should be a base
 // policy to consider one of each resource belonging to the frame, and should
 // not count resource specific conditions in.
-// TODO(toyoshim): Remove |resourceType| to realize the design described above.
-// See also comments in resourceRequestCachePolicy().
-mojom::FetchCacheMode DetermineFrameCacheMode(Frame* frame,
-                                              MainResourceType resource_type) {
+mojom::FetchCacheMode DetermineFrameCacheMode(Frame* frame) {
   if (!frame)
     return mojom::FetchCacheMode::kDefault;
   if (!frame->IsLocalFrame())
-    return DetermineFrameCacheMode(frame->Tree().Parent(), resource_type);
+    return DetermineFrameCacheMode(frame->Tree().Parent());
 
   // Does not propagate cache policy for subresources after the load event.
   // TODO(toyoshim): We should be able to remove following parents' policy check
   // if each frame has a relevant WebFrameLoadType for reload and history
   // navigations.
-  if (resource_type == MainResourceType::kIsNotMainResource &&
-      ToLocalFrame(frame)->GetDocument()->LoadEventFinished()) {
+  if (ToLocalFrame(frame)->GetDocument()->LoadEventFinished())
     return mojom::FetchCacheMode::kDefault;
-  }
 
   // Respects BypassingCache rather than parent's policy.
   WebFrameLoadType load_type =
@@ -215,15 +166,25 @@
 
   // Respects parent's policy if it has a special one.
   mojom::FetchCacheMode parent_cache_mode =
-      DetermineFrameCacheMode(frame->Tree().Parent(), resource_type);
+      DetermineFrameCacheMode(frame->Tree().Parent());
   if (parent_cache_mode != mojom::FetchCacheMode::kDefault)
     return parent_cache_mode;
 
-  // Otherwise, follows WebFrameLoadType. Use kIsNotPost, kIsNotConditional, and
-  // kIsNotMainResource to obtain a representative policy for the frame.
-  return DetermineCacheMode(RequestMethod::kIsNotPost,
-                            RequestType::kIsNotConditional,
-                            MainResourceType::kIsNotMainResource, load_type);
+  // Otherwise, follows WebFrameLoadType.
+  switch (load_type) {
+    case WebFrameLoadType::kStandard:
+    case WebFrameLoadType::kReplaceCurrentItem:
+      return mojom::FetchCacheMode::kDefault;
+    case WebFrameLoadType::kBackForward:
+      // Mutates the policy for POST requests to avoid form resubmission.
+      return mojom::FetchCacheMode::kForceCache;
+    case WebFrameLoadType::kReload:
+      return mojom::FetchCacheMode::kDefault;
+    case WebFrameLoadType::kReloadBypassingCache:
+      return mojom::FetchCacheMode::kBypassCache;
+  }
+  NOTREACHED();
+  return mojom::FetchCacheMode::kDefault;
 }
 
 }  // namespace
@@ -371,9 +332,8 @@
   return GetFrame()->Client();
 }
 
-void FrameFetchContext::AddAdditionalRequestHeaders(ResourceRequest& request,
-                                                    FetchResourceType type) {
-  BaseFetchContext::AddAdditionalRequestHeaders(request, type);
+void FrameFetchContext::AddAdditionalRequestHeaders(ResourceRequest& request) {
+  BaseFetchContext::AddAdditionalRequestHeaders(request);
 
   // The remaining modifications are only necessary for HTTP and HTTPS.
   if (!request.Url().IsEmpty() && !request.Url().ProtocolIsInHTTPFamily())
@@ -426,27 +386,7 @@
     return mojom::FetchCacheMode::kDefault;
 
   DCHECK(GetFrame());
-  if (type == ResourceType::kMainResource) {
-    const auto cache_mode = DetermineCacheMode(
-        request.HttpMethod() == http_names::kPOST ? RequestMethod::kIsPost
-                                                  : RequestMethod::kIsNotPost,
-        request.IsConditional() ? RequestType::kIsConditional
-                                : RequestType::kIsNotConditional,
-        MainResourceType::kIsMainResource, MasterDocumentLoader()->LoadType());
-    // Follows the parent frame's policy.
-    // TODO(toyoshim): Probably, WebFrameLoadType for each frame should have a
-    // right type for reload or history navigations, and should not need to
-    // check parent's frame policy here. Once it has a right WebFrameLoadType,
-    // we can remove Resource::Type argument from determineFrameCacheMode.
-    // See also crbug.com/332602.
-    if (cache_mode != mojom::FetchCacheMode::kDefault)
-      return cache_mode;
-    return DetermineFrameCacheMode(GetFrame()->Tree().Parent(),
-                                   MainResourceType::kIsMainResource);
-  }
-
-  const auto cache_mode =
-      DetermineFrameCacheMode(GetFrame(), MainResourceType::kIsNotMainResource);
+  const auto cache_mode = DetermineFrameCacheMode(GetFrame());
 
   // TODO(toyoshim): Revisit to consider if this clause can be merged to
   // determineWebCachePolicy or determineFrameCacheMode.
@@ -556,16 +496,15 @@
   if (IsDetached())
     return;
 
-  // Note: resource can be null for navigations.
-  ResourceType resource_type =
-      resource ? resource->GetType() : ResourceType::kMainResource;
+  // Note: resource is null if and only if this is a navigation response.
+  bool is_main_resource = !resource;
 
   if (GetSubresourceFilter() && resource &&
       resource->GetResourceRequest().IsAdResource()) {
     GetSubresourceFilter()->ReportAdRequestId(response.RequestId());
   }
 
-  MaybeRecordCTPolicyComplianceUseCounter(GetFrame(), resource_type,
+  MaybeRecordCTPolicyComplianceUseCounter(GetFrame(), is_main_resource,
                                           response.GetCTPolicyCompliance(),
                                           MasterDocumentLoader());
 
@@ -594,19 +533,13 @@
     // haven't committed yet, and we cannot load resources, only preconnect.
     resource_loading_policy = PreloadHelper::kDoNotLoadResources;
   }
-  // Client hints preferences should be persisted only from responses that were
-  // served by the same host as the host of the document-level origin.
-  KURL frame_url = Url();
-  if (frame_url == NullURL())
-    frame_url = GetDocumentLoader()->Url();
 
   // The accept-ch-lifetime header is honored only on the navigation responses.
   // Further, the navigation response should be from a top level frame (i.e.,
   // main frame) or the origin of the response should match the origin of the
   // top level frame.
-  if (resource_type == ResourceType::kMainResource &&
-      (GetResourceFetcherProperties().IsMainFrame() ||
-       IsFirstPartyOrigin(response.CurrentRequestUrl()))) {
+  if (is_main_resource && (GetResourceFetcherProperties().IsMainFrame() ||
+                           IsFirstPartyOrigin(response.CurrentRequestUrl()))) {
     ParseAndPersistClientHints(response);
   }
 
@@ -623,13 +556,17 @@
   }
 
   if (response.IsLegacySymantecCert()) {
-    RecordLegacySymantecCertUseCounter(GetFrame(), resource_type);
+    if (!is_main_resource) {
+      // Main resources are counted in DocumentLoader.
+      UseCounter::Count(GetFrame(),
+                        WebFeature::kLegacySymantecCertInSubresource);
+    }
     GetLocalFrameClient()->ReportLegacySymantecCert(
         response.CurrentRequestUrl(), false /* did_fail */);
   }
 
   if (response.IsLegacyTLSVersion()) {
-    if (resource_type != ResourceType::kMainResource) {
+    if (!is_main_resource) {
       // Main resources are counted in DocumentLoader.
       UseCounter::Count(GetFrame(), WebFeature::kLegacyTLSVersionInSubresource);
     }
@@ -793,7 +730,6 @@
     return;
 
   // Timing for main resource is handled in DocumentLoader.
-  DCHECK(!info.IsMainResource());
   // All other resources are reported to the corresponding Document.
   DOMWindowPerformance::performance(
       *frame_or_imported_document_->GetDocument()->domWindow())
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.h b/third_party/blink/renderer/core/loader/frame_fetch_context.h
index f5f84c41..7816b46 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context.h
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context.h
@@ -72,8 +72,7 @@
   explicit FrameFetchContext(const FrameOrImportedDocument&);
   ~FrameFetchContext() override = default;
 
-  void AddAdditionalRequestHeaders(ResourceRequest&,
-                                   FetchResourceType) override;
+  void AddAdditionalRequestHeaders(ResourceRequest&) override;
   base::Optional<ResourceRequestBlockedReason> CanRequest(
       ResourceType type,
       const ResourceRequest& resource_request,
@@ -97,7 +96,7 @@
       const ResourceResponse& redirect_response,
       ResourceType,
       const FetchInitiatorInfo& = FetchInitiatorInfo()) override;
-  // Resource* can be null for navigations.
+  // Resource* is null if and only if this is a navigation response.
   void DispatchDidReceiveResponse(unsigned long identifier,
                                   const ResourceRequest&,
                                   const ResourceResponse&,
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc b/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc
index f87adc15..c1a25b5 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc
@@ -837,102 +837,6 @@
       GetHeaderValue("https://www.example.com/1.gif", "ect").Ascii().length());
 }
 
-TEST_F(FrameFetchContextTest, MainResourceCachePolicy) {
-  // Default case
-  ResourceRequest request("http://www.example.com");
-  EXPECT_EQ(
-      mojom::FetchCacheMode::kDefault,
-      GetFetchContext()->ResourceRequestCachePolicy(
-          request, ResourceType::kMainResource, FetchParameters::kNoDefer));
-
-  // Post
-  ResourceRequest post_request("http://www.example.com");
-  post_request.SetHTTPMethod(http_names::kPOST);
-  EXPECT_EQ(mojom::FetchCacheMode::kValidateCache,
-            GetFetchContext()->ResourceRequestCachePolicy(
-                post_request, ResourceType::kMainResource,
-                FetchParameters::kNoDefer));
-
-  // Re-post
-  document->Loader()->SetLoadType(WebFrameLoadType::kBackForward);
-  EXPECT_EQ(mojom::FetchCacheMode::kOnlyIfCached,
-            GetFetchContext()->ResourceRequestCachePolicy(
-                post_request, ResourceType::kMainResource,
-                FetchParameters::kNoDefer));
-
-  // WebFrameLoadType::kReload
-  document->Loader()->SetLoadType(WebFrameLoadType::kReload);
-  EXPECT_EQ(
-      mojom::FetchCacheMode::kValidateCache,
-      GetFetchContext()->ResourceRequestCachePolicy(
-          request, ResourceType::kMainResource, FetchParameters::kNoDefer));
-
-  // Conditional request
-  document->Loader()->SetLoadType(WebFrameLoadType::kStandard);
-  ResourceRequest conditional("http://www.example.com");
-  conditional.SetHTTPHeaderField(http_names::kIfModifiedSince, "foo");
-  EXPECT_EQ(
-      mojom::FetchCacheMode::kValidateCache,
-      GetFetchContext()->ResourceRequestCachePolicy(
-          conditional, ResourceType::kMainResource, FetchParameters::kNoDefer));
-
-  // WebFrameLoadType::kReloadBypassingCache
-  document->Loader()->SetLoadType(WebFrameLoadType::kReloadBypassingCache);
-  EXPECT_EQ(
-      mojom::FetchCacheMode::kBypassCache,
-      GetFetchContext()->ResourceRequestCachePolicy(
-          request, ResourceType::kMainResource, FetchParameters::kNoDefer));
-
-  // WebFrameLoadType::kReloadBypassingCache with a conditional request
-  document->Loader()->SetLoadType(WebFrameLoadType::kReloadBypassingCache);
-  EXPECT_EQ(
-      mojom::FetchCacheMode::kBypassCache,
-      GetFetchContext()->ResourceRequestCachePolicy(
-          conditional, ResourceType::kMainResource, FetchParameters::kNoDefer));
-
-  // WebFrameLoadType::kReloadBypassingCache with a post request
-  document->Loader()->SetLoadType(WebFrameLoadType::kReloadBypassingCache);
-  EXPECT_EQ(mojom::FetchCacheMode::kBypassCache,
-            GetFetchContext()->ResourceRequestCachePolicy(
-                post_request, ResourceType::kMainResource,
-                FetchParameters::kNoDefer));
-
-  // Set up a child frame
-  FrameFetchContext* child_fetch_context = CreateChildFrame();
-
-  // Child frame as part of back/forward
-  document->Loader()->SetLoadType(WebFrameLoadType::kBackForward);
-  EXPECT_EQ(
-      mojom::FetchCacheMode::kForceCache,
-      child_fetch_context->ResourceRequestCachePolicy(
-          request, ResourceType::kMainResource, FetchParameters::kNoDefer));
-
-  // Child frame as part of reload
-  document->Loader()->SetLoadType(WebFrameLoadType::kReload);
-  EXPECT_EQ(
-      mojom::FetchCacheMode::kDefault,
-      child_fetch_context->ResourceRequestCachePolicy(
-          request, ResourceType::kMainResource, FetchParameters::kNoDefer));
-
-  // Child frame as part of reload bypassing cache
-  document->Loader()->SetLoadType(WebFrameLoadType::kReloadBypassingCache);
-  EXPECT_EQ(
-      mojom::FetchCacheMode::kBypassCache,
-      child_fetch_context->ResourceRequestCachePolicy(
-          request, ResourceType::kMainResource, FetchParameters::kNoDefer));
-
-  // Per-frame bypassing reload, but parent load type is different.
-  // This is not the case users can trigger through user interfaces, but for
-  // checking code correctness and consistency.
-  document->Loader()->SetLoadType(WebFrameLoadType::kReload);
-  child_frame->Loader().GetDocumentLoader()->SetLoadType(
-      WebFrameLoadType::kReloadBypassingCache);
-  EXPECT_EQ(
-      mojom::FetchCacheMode::kBypassCache,
-      child_fetch_context->ResourceRequestCachePolicy(
-          request, ResourceType::kMainResource, FetchParameters::kNoDefer));
-}
-
 TEST_F(FrameFetchContextTest, SubResourceCachePolicy) {
   // Reset load event state: if the load event is finished, we ignore the
   // DocumentLoader load type.
@@ -1042,14 +946,12 @@
   RecreateFetchContext();
 
   ResourceRequest resource_request("http://www.example.com");
-  GetFetchContext()->AddAdditionalRequestHeaders(resource_request,
-                                                 kFetchMainResource);
+  GetFetchContext()->AddAdditionalRequestHeaders(resource_request);
   EXPECT_EQ("on", resource_request.HttpHeaderField("Save-Data"));
 
   // Subsequent call to addAdditionalRequestHeaders should not append to the
   // save-data header.
-  GetFetchContext()->AddAdditionalRequestHeaders(resource_request,
-                                                 kFetchMainResource);
+  GetFetchContext()->AddAdditionalRequestHeaders(resource_request);
   EXPECT_EQ("on", resource_request.HttpHeaderField("Save-Data"));
 }
 
@@ -1060,8 +962,7 @@
   RecreateFetchContext();
 
   ResourceRequest resource_request("http://www.example.com");
-  GetFetchContext()->AddAdditionalRequestHeaders(resource_request,
-                                                 kFetchMainResource);
+  GetFetchContext()->AddAdditionalRequestHeaders(resource_request);
   EXPECT_EQ(String(), resource_request.HttpHeaderField("Save-Data"));
 }
 
@@ -1071,28 +972,24 @@
   // Recreate the fetch context so that the updated save data settings are read.
   RecreateFetchContext();
   ResourceRequest resource_request("http://www.example.com");
-  GetFetchContext()->AddAdditionalRequestHeaders(resource_request,
-                                                 kFetchMainResource);
+  GetFetchContext()->AddAdditionalRequestHeaders(resource_request);
   EXPECT_EQ("on", resource_request.HttpHeaderField("Save-Data"));
 
   GetNetworkStateNotifier().SetSaveDataEnabledOverride(false);
   RecreateFetchContext();
   document->Loader()->SetLoadType(WebFrameLoadType::kReload);
-  GetFetchContext()->AddAdditionalRequestHeaders(resource_request,
-                                                 kFetchMainResource);
+  GetFetchContext()->AddAdditionalRequestHeaders(resource_request);
   EXPECT_EQ(String(), resource_request.HttpHeaderField("Save-Data"));
 
   GetNetworkStateNotifier().SetSaveDataEnabledOverride(true);
   RecreateFetchContext();
-  GetFetchContext()->AddAdditionalRequestHeaders(resource_request,
-                                                 kFetchMainResource);
+  GetFetchContext()->AddAdditionalRequestHeaders(resource_request);
   EXPECT_EQ("on", resource_request.HttpHeaderField("Save-Data"));
 
   GetNetworkStateNotifier().SetSaveDataEnabledOverride(false);
   RecreateFetchContext();
   document->Loader()->SetLoadType(WebFrameLoadType::kReload);
-  GetFetchContext()->AddAdditionalRequestHeaders(resource_request,
-                                                 kFetchMainResource);
+  GetFetchContext()->AddAdditionalRequestHeaders(resource_request);
   EXPECT_EQ(String(), resource_request.HttpHeaderField("Save-Data"));
 }
 
@@ -1202,7 +1099,7 @@
 
   dummy_page_holder = nullptr;
 
-  GetFetchContext()->AddAdditionalRequestHeaders(request, kFetchSubresource);
+  GetFetchContext()->AddAdditionalRequestHeaders(request);
 
   EXPECT_EQ(origin, request.HttpHeaderField(http_names::kOrigin));
   EXPECT_EQ(String(origin + "/"),
@@ -1311,8 +1208,6 @@
   EXPECT_FALSE(GetFetchContext()->ShouldLoadNewResource(ResourceType::kImage));
   EXPECT_FALSE(GetFetchContext()->ShouldLoadNewResource(ResourceType::kRaw));
   EXPECT_FALSE(GetFetchContext()->ShouldLoadNewResource(ResourceType::kScript));
-  EXPECT_FALSE(
-      GetFetchContext()->ShouldLoadNewResource(ResourceType::kMainResource));
 }
 
 TEST_F(FrameFetchContextTest, RecordLoadingActivityWhenDetached) {
@@ -1342,7 +1237,7 @@
 
 TEST_F(FrameFetchContextTest, AddResourceTimingWhenDetached) {
   scoped_refptr<ResourceTimingInfo> info =
-      ResourceTimingInfo::Create("type", TimeTicksFromSeconds(0.3), false);
+      ResourceTimingInfo::Create("type", TimeTicksFromSeconds(0.3));
 
   dummy_page_holder = nullptr;
 
@@ -1417,15 +1312,13 @@
   EXPECT_CALL(*client, GetPreviewsStateForFrame())
       .WillRepeatedly(testing::Return(WebURLRequest::kPreviewsOff));
   ResourceRequest resource_request("http://www.example.com/style.css");
-  GetFetchContext()->AddAdditionalRequestHeaders(resource_request,
-                                                 kFetchMainResource);
+  GetFetchContext()->AddAdditionalRequestHeaders(resource_request);
   EXPECT_EQ(g_null_atom, resource_request.HttpHeaderField("Intervention"));
 
   // Verify header is added if Lo-Fi is active.
   EXPECT_CALL(*client, GetPreviewsStateForFrame())
       .WillRepeatedly(testing::Return(WebURLRequest::kClientLoFiOn));
-  GetFetchContext()->AddAdditionalRequestHeaders(resource_request,
-                                                 kFetchSubresource);
+  GetFetchContext()->AddAdditionalRequestHeaders(resource_request);
   EXPECT_EQ(
       "<https://www.chromestatus.com/features/6072546726248448>; "
       "level=\"warning\"",
@@ -1435,8 +1328,7 @@
   ResourceRequest resource_request2("http://www.example.com/getad.js");
   resource_request2.SetHTTPHeaderField("Intervention",
                                        "<https://otherintervention.org>");
-  GetFetchContext()->AddAdditionalRequestHeaders(resource_request2,
-                                                 kFetchSubresource);
+  GetFetchContext()->AddAdditionalRequestHeaders(resource_request2);
   EXPECT_EQ(
       "<https://otherintervention.org>, "
       "<https://www.chromestatus.com/features/6072546726248448>; "
@@ -1451,15 +1343,13 @@
   EXPECT_CALL(*client, GetPreviewsStateForFrame())
       .WillRepeatedly(testing::Return(WebURLRequest::kPreviewsOff));
   ResourceRequest resource_request("http://www.example.com/style.css");
-  GetFetchContext()->AddAdditionalRequestHeaders(resource_request,
-                                                 kFetchMainResource);
+  GetFetchContext()->AddAdditionalRequestHeaders(resource_request);
   EXPECT_EQ(g_null_atom, resource_request.HttpHeaderField("Intervention"));
 
   // Verify header is added if NoScript is active.
   EXPECT_CALL(*client, GetPreviewsStateForFrame())
       .WillRepeatedly(testing::Return(WebURLRequest::kNoScriptOn));
-  GetFetchContext()->AddAdditionalRequestHeaders(resource_request,
-                                                 kFetchSubresource);
+  GetFetchContext()->AddAdditionalRequestHeaders(resource_request);
   EXPECT_EQ(
       "<https://www.chromestatus.com/features/4775088607985664>; "
       "level=\"warning\"",
@@ -1469,8 +1359,7 @@
   ResourceRequest resource_request2("http://www.example.com/getad.js");
   resource_request2.SetHTTPHeaderField("Intervention",
                                        "<https://otherintervention.org>");
-  GetFetchContext()->AddAdditionalRequestHeaders(resource_request2,
-                                                 kFetchSubresource);
+  GetFetchContext()->AddAdditionalRequestHeaders(resource_request2);
   EXPECT_EQ(
       "<https://otherintervention.org>, "
       "<https://www.chromestatus.com/features/4775088607985664>; "
diff --git a/third_party/blink/renderer/core/loader/frame_resource_fetcher_properties.cc b/third_party/blink/renderer/core/loader/frame_resource_fetcher_properties.cc
index 7174e5d..5222d7e 100644
--- a/third_party/blink/renderer/core/loader/frame_resource_fetcher_properties.cc
+++ b/third_party/blink/renderer/core/loader/frame_resource_fetcher_properties.cc
@@ -67,16 +67,6 @@
   return document && document->LoadEventFinished();
 }
 
-bool FrameResourceFetcherProperties::ShouldBlockLoadingMainResource() const {
-  DocumentLoader* document_loader =
-      frame_or_imported_document_->GetDocumentLoader();
-  if (!document_loader)
-    return false;
-
-  FrameLoader& loader = frame_or_imported_document_->GetFrame().Loader();
-  return document_loader != loader.GetProvisionalDocumentLoader();
-}
-
 bool FrameResourceFetcherProperties::ShouldBlockLoadingSubResource() const {
   DocumentLoader* document_loader =
       frame_or_imported_document_->GetDocumentLoader();
diff --git a/third_party/blink/renderer/core/loader/frame_resource_fetcher_properties.h b/third_party/blink/renderer/core/loader/frame_resource_fetcher_properties.h
index e941e43..0b70eb8 100644
--- a/third_party/blink/renderer/core/loader/frame_resource_fetcher_properties.h
+++ b/third_party/blink/renderer/core/loader/frame_resource_fetcher_properties.h
@@ -39,7 +39,6 @@
   bool IsPaused() const override;
   bool IsDetached() const override { return false; }
   bool IsLoadComplete() const override;
-  bool ShouldBlockLoadingMainResource() const override;
   bool ShouldBlockLoadingSubResource() const override;
 
  private:
diff --git a/third_party/blink/renderer/core/loader/preload_helper.cc b/third_party/blink/renderer/core/loader/preload_helper.cc
index 72d95c5c..3fd0fd5 100644
--- a/third_party/blink/renderer/core/loader/preload_helper.cc
+++ b/third_party/blink/renderer/core/loader/preload_helper.cc
@@ -253,7 +253,7 @@
   }
   ResourceRequest resource_request(url);
   resource_request.SetRequestContext(ResourceFetcher::DetermineRequestContext(
-      resource_type.value(), ResourceFetcher::kImageNotImageSet, false));
+      resource_type.value(), ResourceFetcher::kImageNotImageSet));
 
   resource_request.SetReferrerPolicy(params.referrer_policy);
 
diff --git a/third_party/blink/renderer/core/loader/worker_fetch_context.cc b/third_party/blink/renderer/core/loader/worker_fetch_context.cc
index 74fcefe..2d8bcac 100644
--- a/third_party/blink/renderer/core/loader/worker_fetch_context.cc
+++ b/third_party/blink/renderer/core/loader/worker_fetch_context.cc
@@ -235,9 +235,8 @@
   web_context_->WillSendRequest(webreq);
 }
 
-void WorkerFetchContext::AddAdditionalRequestHeaders(ResourceRequest& request,
-                                                     FetchResourceType type) {
-  BaseFetchContext::AddAdditionalRequestHeaders(request, type);
+void WorkerFetchContext::AddAdditionalRequestHeaders(ResourceRequest& request) {
+  BaseFetchContext::AddAdditionalRequestHeaders(request);
 
   // The remaining modifications are only necessary for HTTP and HTTPS.
   if (!request.Url().IsEmpty() && !request.Url().ProtocolIsInHTTPFamily())
diff --git a/third_party/blink/renderer/core/loader/worker_fetch_context.h b/third_party/blink/renderer/core/loader/worker_fetch_context.h
index ba2a6db..b169c88 100644
--- a/third_party/blink/renderer/core/loader/worker_fetch_context.h
+++ b/third_party/blink/renderer/core/loader/worker_fetch_context.h
@@ -74,8 +74,7 @@
   void PrepareRequest(ResourceRequest&,
                       WebScopedVirtualTimePauser&,
                       RedirectType) override;
-  void AddAdditionalRequestHeaders(ResourceRequest&,
-                                   FetchResourceType) override;
+  void AddAdditionalRequestHeaders(ResourceRequest&) override;
   void DispatchWillSendRequest(unsigned long,
                                ResourceRequest&,
                                const ResourceResponse&,
diff --git a/third_party/blink/renderer/core/loader/worker_resource_fetcher_properties.h b/third_party/blink/renderer/core/loader/worker_resource_fetcher_properties.h
index ac54710b7..eb400bb 100644
--- a/third_party/blink/renderer/core/loader/worker_resource_fetcher_properties.h
+++ b/third_party/blink/renderer/core/loader/worker_resource_fetcher_properties.h
@@ -44,7 +44,6 @@
   bool IsPaused() const override;
   bool IsDetached() const override { return false; }
   bool IsLoadComplete() const override { return false; }
-  bool ShouldBlockLoadingMainResource() const override { return false; }
   bool ShouldBlockLoadingSubResource() const override { return false; }
 
  private:
diff --git a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
index dc4c742..6860f86 100644
--- a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
+++ b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
@@ -3455,9 +3455,12 @@
   } else if (IsScrollableAreaLayer(graphics_layer)) {
     PaintScrollableArea(graphics_layer, context, interest_rect);
   }
-  probe::didPaint(owning_layer_.GetLayoutObject().GetFrame(),
-                  graphics_layer->CcLayer(), context,
-                  LayoutRect(interest_rect));
+
+  if (!RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled()) {
+    probe::didPaint(owning_layer_.GetLayoutObject().GetFrame(),
+                    graphics_layer->CcLayer(), LayoutRect(interest_rect));
+  }
+
 #if DCHECK_IS_ON()
   if (Page* page = GetLayoutObject().GetFrame()->GetPage())
     page->SetIsPainting(false);
diff --git a/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc b/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc
index 6111fe6..85774d7 100644
--- a/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc
+++ b/third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.cc
@@ -560,7 +560,7 @@
     AttachRootLayerViaChromeClient();
 
   // Inform the inspector that the layer tree has changed.
-  if (IsMainFrame())
+  if (IsMainFrame() && !RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled())
     probe::layerTreeDidChange(layout_view_.GetFrame());
 
   Lifecycle().AdvanceTo(DocumentLifecycle::kCompositingClean);
diff --git a/third_party/blink/renderer/core/paint/frame_painter.cc b/third_party/blink/renderer/core/paint/frame_painter.cc
index f2bc7e39..2a4b508 100644
--- a/third_party/blink/renderer/core/paint/frame_painter.cc
+++ b/third_party/blink/renderer/core/paint/frame_painter.cc
@@ -12,7 +12,6 @@
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_painter.h"
 #include "third_party/blink/renderer/core/paint/scrollbar_painter.h"
-#include "third_party/blink/renderer/core/probe/core_probes.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
 #include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h"
@@ -115,9 +114,6 @@
     GetMemoryCache()->UpdateFramePaintTimestamp();
     in_paint_contents_ = false;
   }
-
-  probe::didPaint(layout_view->GetFrame(), nullptr, context,
-                  LayoutRect(cull_rect.Rect()));
 }
 
 const LocalFrameView& FramePainter::GetFrameView() {
diff --git a/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.cc b/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.cc
index 4d210a13..82fa3393 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.cc
@@ -532,8 +532,9 @@
 
 NGPaintFragment::FragmentRange NGPaintFragment::InlineFragmentsFor(
     const LayoutObject* layout_object) {
-  DCHECK(layout_object && layout_object->IsInline() &&
-         !layout_object->IsFloatingOrOutOfFlowPositioned());
+  DCHECK(layout_object);
+  DCHECK(layout_object->IsInline());
+  DCHECK(!layout_object->IsFloatingOrOutOfFlowPositioned());
 
   if (layout_object->IsInLayoutNGInlineFormattingContext())
     return FragmentRange(layout_object->FirstInlineFragment());
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index 584f9390a..6fe8640 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -235,6 +235,10 @@
   return GetLayoutObject().DebugName();
 }
 
+DOMNodeId PaintLayer::OwnerNodeId() const {
+  return static_cast<const DisplayItemClient&>(GetLayoutObject()).OwnerNodeId();
+}
+
 LayoutRect PaintLayer::VisualRect() const {
   return layout_object_.FragmentsVisualRectBoundingBox();
 }
diff --git a/third_party/blink/renderer/core/paint/paint_layer.h b/third_party/blink/renderer/core/paint/paint_layer.h
index 939ba935..ac350c6 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.h
+++ b/third_party/blink/renderer/core/paint/paint_layer.h
@@ -231,6 +231,7 @@
 
   // DisplayItemClient methods
   String DebugName() const final;
+  DOMNodeId OwnerNodeId() const final;
   LayoutRect VisualRect() const final;
 
   LayoutBoxModelObject& GetLayoutObject() const { return layout_object_; }
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
index c5a561b..fb6fdabb0 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
@@ -2880,6 +2880,13 @@
          scrollable_area_->GetLayoutBox()->DebugName();
 }
 
+DOMNodeId
+PaintLayerScrollableArea::ScrollingBackgroundDisplayItemClient::OwnerNodeId()
+    const {
+  return static_cast<const DisplayItemClient*>(scrollable_area_->GetLayoutBox())
+      ->OwnerNodeId();
+}
+
 bool PaintLayerScrollableArea::ScrollingBackgroundDisplayItemClient::
     PaintedOutputOfObjectHasNoEffectRegardlessOfSize() const {
   return scrollable_area_->GetLayoutBox()
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h
index 225e030..25ba9d5 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h
@@ -691,7 +691,7 @@
   LayoutRect vertical_scrollbar_visual_rect_;
   LayoutRect scroll_corner_and_resizer_visual_rect_;
 
-  class ScrollingBackgroundDisplayItemClient : public DisplayItemClient {
+  class ScrollingBackgroundDisplayItemClient final : public DisplayItemClient {
     DISALLOW_NEW();
 
    public:
@@ -699,13 +699,14 @@
         const PaintLayerScrollableArea& scrollable_area)
         : scrollable_area_(&scrollable_area) {}
 
-    LayoutRect VisualRect() const override;
-    String DebugName() const override;
-    bool PaintedOutputOfObjectHasNoEffectRegardlessOfSize() const override;
-
     void Trace(Visitor* visitor) { visitor->Trace(scrollable_area_); }
 
    private:
+    LayoutRect VisualRect() const final;
+    String DebugName() const final;
+    DOMNodeId OwnerNodeId() const final;
+    bool PaintedOutputOfObjectHasNoEffectRegardlessOfSize() const final;
+
     Member<const PaintLayerScrollableArea> scrollable_area_;
   };
 
diff --git a/third_party/blink/renderer/core/probe/core_probes.json5 b/third_party/blink/renderer/core/probe/core_probes.json5
index 40ada2c..56e4adf 100644
--- a/third_party/blink/renderer/core/probe/core_probes.json5
+++ b/third_party/blink/renderer/core/probe/core_probes.json5
@@ -107,6 +107,7 @@
       probes: [
         "didPaint",
         "layerTreeDidChange",
+        "layerTreePainted",
       ]
     },
     InspectorLogAgent: {
@@ -145,6 +146,7 @@
         "willLoadXHR",
         "willSendEventSourceRequest",
         "willSendRequest",
+        "willSendNavigationRequest",
         "willSendWebSocketHandshakeRequest",
       ]
     },
@@ -207,6 +209,7 @@
         "frameStartedLoading",
         "paintTiming",
         "willSendRequest",
+        "willSendNavigationRequest",
       ]
     },
     DevToolsSession: {
diff --git a/third_party/blink/renderer/core/probe/core_probes.pidl b/third_party/blink/renderer/core/probe/core_probes.pidl
index a3e17b50..de8a05a 100644
--- a/third_party/blink/renderer/core/probe/core_probes.pidl
+++ b/third_party/blink/renderer/core/probe/core_probes.pidl
@@ -85,12 +85,14 @@
   void didFireWebGLWarning(Element*);
   void didFireWebGLErrorOrWarning(Element*, const String& message);
   void didResizeMainFrame(LocalFrame*);
-  void didPaint(LocalFrame*, const cc::Layer*, GraphicsContext&, const LayoutRect&);
+  /* This is for pre-BlinkGenPropertyTrees. TODO(wangxianzhu): Remove this function for BlinkGenPropertyTrees. */
+  void didPaint(LocalFrame*, const cc::Layer*, const LayoutRect&);
   void applyAcceptLanguageOverride(ExecutionContext*, String* acceptLanguage);
   void applyUserAgentOverride(CoreProbeSink*, String* userAgent);
   void didBlockRequest([Keep] ExecutionContext*, const ResourceRequest&, DocumentLoader*, const FetchInitiatorInfo&, ResourceRequestBlockedReason, ResourceType);
   void didChangeResourcePriority(LocalFrame*, DocumentLoader*, unsigned long identifier, ResourceLoadPriority loadPriority);
   void willSendRequest([Keep] ExecutionContext*, unsigned long identifier, DocumentLoader*, ResourceRequest&, const ResourceResponse& redirectResponse, const FetchInitiatorInfo&, ResourceType);
+  void willSendNavigationRequest([Keep] ExecutionContext*, unsigned long identifier, DocumentLoader*, const KURL&, const AtomicString& http_method, EncodedFormData*);
   void markResourceAsCached(LocalFrame*, DocumentLoader*, unsigned long identifier);
   void didReceiveResourceResponse(CoreProbeSink*, unsigned long identifier, DocumentLoader*, const ResourceResponse&, Resource*);
   void didReceiveData(CoreProbeSink*, unsigned long identifier, DocumentLoader*, const char* data, uint64_t dataLength);
@@ -130,7 +132,10 @@
   void didReceiveWebSocketMessageError(ExecutionContext*, unsigned long identifier, const String& errorMessage);
   void networkStateChanged([Keep] LocalFrame*, bool online);
   void updateApplicationCacheStatus([Keep] LocalFrame*);
+  /* This is for pre-BlinkGenPropertyTrees. TODO(wangxianzhu): Remove this function for BlinkGenPropertyTrees. */
   void layerTreeDidChange(LocalFrame*);
+  /* For BlinkGenPropertyTrees/CompositeAfterPaint. */
+  void layerTreePainted(LocalFrame*);
   void pseudoElementCreated([Keep] PseudoElement*);
   void pseudoElementDestroyed([Keep] PseudoElement*);
   void didCreateAnimation(Document*, unsigned);
diff --git a/third_party/blink/renderer/core/testing/sim/sim_compositor.cc b/third_party/blink/renderer/core/testing/sim/sim_compositor.cc
index 0affb2c7..e06c443 100644
--- a/third_party/blink/renderer/core/testing/sim/sim_compositor.cc
+++ b/third_party/blink/renderer/core/testing/sim/sim_compositor.cc
@@ -101,7 +101,7 @@
 void SimCompositor::BeginMainFrame(base::TimeTicks frame_time) {
   // There is no WebWidget like RenderWidget would have..? So go right to the
   // WebViewImpl.
-  web_view_->MainFrameWidget()->BeginFrame(last_frame_time_);
+  web_view_->MainFrameWidget()->BeginFrame(last_frame_time_, false);
   web_view_->MainFrameWidget()->UpdateAllLifecyclePhases(
       WebWidget::LifecycleUpdateReason::kTest);
   *paint_commands_ = PaintFrame();
diff --git a/third_party/blink/renderer/devtools/front_end/timeline/TimelineUIUtils.js b/third_party/blink/renderer/devtools/front_end/timeline/TimelineUIUtils.js
index 5334c9e..3993199 100644
--- a/third_party/blink/renderer/devtools/front_end/timeline/TimelineUIUtils.js
+++ b/third_party/blink/renderer/devtools/front_end/timeline/TimelineUIUtils.js
@@ -109,6 +109,16 @@
         new Timeline.TimelineRecordStyle(Common.UIString('Evaluate Module'), categories['scripting']);
     eventStyles[recordTypes.ParseScriptOnBackground] =
         new Timeline.TimelineRecordStyle(Common.UIString('Parse Script'), categories['scripting']);
+    eventStyles[recordTypes.WasmStreamFromResponseCallback] =
+        new Timeline.TimelineRecordStyle(Common.UIString(ls`Streaming Wasm Response`), categories['scripting']);
+    eventStyles[recordTypes.WasmCompiledModule] =
+        new Timeline.TimelineRecordStyle(Common.UIString(ls`Compiled Wasm Module`), categories['scripting']);
+    eventStyles[recordTypes.WasmCachedModule] =
+        new Timeline.TimelineRecordStyle(Common.UIString(ls`Cached Wasm Module`), categories['scripting']);
+    eventStyles[recordTypes.WasmModuleCacheHit] =
+        new Timeline.TimelineRecordStyle(Common.UIString(ls`Wasm Module Cache Hit`), categories['scripting']);
+    eventStyles[recordTypes.WasmModuleCacheInvalid] =
+        new Timeline.TimelineRecordStyle(Common.UIString(ls`Wasm Module Cache Invalid`), categories['scripting']);
     eventStyles[recordTypes.FrameStartedLoading] =
         new Timeline.TimelineRecordStyle(ls`Frame Started Loading`, categories['loading'], true);
     eventStyles[recordTypes.MarkLoad] =
@@ -560,6 +570,14 @@
           detailsText = Bindings.displayNameForURL(url) + ':' + (eventData['lineNumber'] + 1);
         break;
       }
+      case recordType.WasmCompiledModule:
+      case recordType.WasmModuleCacheHit: {
+        const url = event.args['url'];
+        if (url)
+          detailsText = Bindings.displayNameForURL(url);
+        break;
+      }
+
       case recordType.ParseScriptOnBackground:
       case recordType.XHRReadyStateChange:
       case recordType.XHRLoad: {
@@ -666,6 +684,11 @@
       case recordType.Animation:
       case recordType.EmbedderCallback:
       case recordType.ParseHTML:
+      case recordType.WasmStreamFromResponseCallback:
+      case recordType.WasmCompiledModule:
+      case recordType.WasmModuleCacheHit:
+      case recordType.WasmCachedModule:
+      case recordType.WasmModuleCacheInvalid:
       case recordType.WebSocketCreate:
       case recordType.WebSocketSendHandshakeRequest:
       case recordType.WebSocketReceiveHandshakeResponse:
@@ -893,6 +916,23 @@
         if (url)
           contentHelper.appendLocationRow(ls`Script`, url, eventData['lineNumber'], eventData['columnNumber']);
         break;
+      case recordTypes.WasmStreamFromResponseCallback:
+      case recordTypes.WasmCompiledModule:
+      case recordTypes.WasmCachedModule:
+      case recordTypes.WasmModuleCacheHit:
+      case recordTypes.WasmModuleCacheInvalid:
+        if (eventData) {
+          url = event.args['url'];
+          if (url)
+            contentHelper.appendTextRow(ls`Url`, url);
+          const producedCachedSize = event.args['producedCachedSize'];
+          if (producedCachedSize)
+            contentHelper.appendTextRow(ls`Produced Cache Size`, producedCachedSize);
+          const consumedCachedSize = event.args['consumedCachedSize'];
+          if (consumedCachedSize)
+            contentHelper.appendTextRow(ls`Consumed Cache Size`, consumedCachedSize);
+        }
+        break;
       case recordTypes.Paint:
         const clip = eventData['clip'];
         contentHelper.appendTextRow(ls`Location`, ls`(${clip[0]}, ${clip[1]})`);
diff --git a/third_party/blink/renderer/devtools/front_end/timeline_model/TimelineModel.js b/third_party/blink/renderer/devtools/front_end/timeline_model/TimelineModel.js
index 1b0efac5..65809eba 100644
--- a/third_party/blink/renderer/devtools/front_end/timeline_model/TimelineModel.js
+++ b/third_party/blink/renderer/devtools/front_end/timeline_model/TimelineModel.js
@@ -1211,6 +1211,11 @@
   EvaluateScript: 'EvaluateScript',
   CompileModule: 'v8.compileModule',
   EvaluateModule: 'v8.evaluateModule',
+  WasmStreamFromResponseCallback: 'v8.wasm.streamFromResponseCallback',
+  WasmCompiledModule: 'v8.wasm.compiledModule',
+  WasmCachedModule: 'v8.wasm.cachedModule',
+  WasmModuleCacheHit: 'v8.wasm.moduleCacheHit',
+  WasmModuleCacheInvalid: 'v8.wasm.moduleCacheInvalid',
 
   FrameStartedLoading: 'FrameStartedLoading',
   CommitLoad: 'CommitLoad',
diff --git a/third_party/blink/renderer/modules/accessibility/ax_media_controls.cc b/third_party/blink/renderer/modules/accessibility/ax_media_controls.cc
index edcb448..3b5378c 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_media_controls.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_media_controls.cc
@@ -69,9 +69,7 @@
     case kMediaOverlayCastOnButton:
     case kMediaOverflowButton:
     case kMediaOverflowList:
-    case kMediaScrubbingMessage:
     case kMediaDisplayCutoutFullscreenButton:
-    case kMediaAnimatedArrowContainer:
       return MakeGarbageCollected<AccessibilityMediaControl>(layout_object,
                                                              ax_object_cache);
     // Removed as a part of the a11y tree rewrite https://crbug/836549.
@@ -124,8 +122,6 @@
     case kMediaTimelineContainer:
     case kMediaControlsPanel:
     case kMediaOverflowList:
-    case kMediaScrubbingMessage:
-    case kMediaAnimatedArrowContainer:
       return QueryString(WebLocalizedString::kAXMediaDefault);
     case kMediaDisplayCutoutFullscreenButton:
       return QueryString(
@@ -161,8 +157,6 @@
     case kMediaTimelineContainer:
     case kMediaControlsPanel:
     case kMediaOverflowList:
-    case kMediaScrubbingMessage:
-    case kMediaAnimatedArrowContainer:
       return QueryString(WebLocalizedString::kAXMediaDefault);
     case kMediaSlider:
     // Removed as a part of the a11y tree rewrite https://crbug/836549.
@@ -201,8 +195,6 @@
 
     case kMediaControlsPanel:
     case kMediaSliderThumb:
-    case kMediaScrubbingMessage:
-    case kMediaAnimatedArrowContainer:
       return ax::mojom::Role::kUnknown;
 
     case kMediaSlider:
diff --git a/third_party/blink/renderer/modules/media_controls/elements/media_control_animated_arrow_container_element.cc b/third_party/blink/renderer/modules/media_controls/elements/media_control_animated_arrow_container_element.cc
index da595e2a..02e5a74e 100644
--- a/third_party/blink/renderer/modules/media_controls/elements/media_control_animated_arrow_container_element.cc
+++ b/third_party/blink/renderer/modules/media_controls/elements/media_control_animated_arrow_container_element.cc
@@ -72,7 +72,7 @@
 
 MediaControlAnimatedArrowContainerElement::
     MediaControlAnimatedArrowContainerElement(MediaControlsImpl& media_controls)
-    : MediaControlDivElement(media_controls, kMediaAnimatedArrowContainer),
+    : MediaControlDivElement(media_controls, kMediaIgnore),
       left_jump_arrow_(nullptr),
       right_jump_arrow_(nullptr) {
   EnsureUserAgentShadowRoot();
diff --git a/third_party/blink/renderer/modules/media_controls/elements/media_control_element_type.h b/third_party/blink/renderer/modules/media_controls/elements/media_control_element_type.h
index da1cb2e8..8b743ea 100644
--- a/third_party/blink/renderer/modules/media_controls/elements/media_control_element_type.h
+++ b/third_party/blink/renderer/modules/media_controls/elements/media_control_element_type.h
@@ -19,9 +19,7 @@
   kMediaOverlayCastOnButton,
   kMediaOverflowButton,
   kMediaOverflowList,
-  kMediaScrubbingMessage,
   kMediaDisplayCutoutFullscreenButton,
-  kMediaAnimatedArrowContainer,
   kMediaIgnore
 };
 
diff --git a/third_party/blink/renderer/modules/media_controls/elements/media_control_scrubbing_message_element.cc b/third_party/blink/renderer/modules/media_controls/elements/media_control_scrubbing_message_element.cc
index aa7722e..4bd0dd5 100644
--- a/third_party/blink/renderer/modules/media_controls/elements/media_control_scrubbing_message_element.cc
+++ b/third_party/blink/renderer/modules/media_controls/elements/media_control_scrubbing_message_element.cc
@@ -16,7 +16,7 @@
 
 MediaControlScrubbingMessageElement::MediaControlScrubbingMessageElement(
     MediaControlsImpl& media_controls)
-    : MediaControlDivElement(media_controls, kMediaScrubbingMessage) {
+    : MediaControlDivElement(media_controls, kMediaIgnore) {
   SetShadowPseudoId(AtomicString("-internal-media-controls-scrubbing-message"));
   CreateUserAgentShadowRoot();
   SetIsWanted(false);
diff --git a/third_party/blink/renderer/modules/service_worker/fetch_event.cc b/third_party/blink/renderer/modules/service_worker/fetch_event.cc
index 38630ba..75bcb24 100644
--- a/third_party/blink/renderer/modules/service_worker/fetch_event.cc
+++ b/third_party/blink/renderer/modules/service_worker/fetch_event.cc
@@ -195,8 +195,7 @@
   // According to the Resource Timing spec, the initiator type of
   // navigation preload request is "navigation".
   scoped_refptr<ResourceTimingInfo> info = ResourceTimingInfo::Create(
-      "navigation", resource_response.GetResourceLoadTiming()->RequestTime(),
-      false /* is_main_resource */);
+      "navigation", resource_response.GetResourceLoadTiming()->RequestTime());
   info->SetNegativeAllowed(true);
   info->SetLoadFinishTime(completion_time);
   info->SetInitialURL(request_->url());
diff --git a/third_party/blink/renderer/modules/service_worker/service_worker_container.cc b/third_party/blink/renderer/modules/service_worker/service_worker_container.cc
index d943705..84ecf71 100644
--- a/third_party/blink/renderer/modules/service_worker/service_worker_container.cc
+++ b/third_party/blink/renderer/modules/service_worker/service_worker_container.cc
@@ -494,7 +494,7 @@
     event = MessageEvent::CreateError(
         GetExecutionContext()->GetSecurityOrigin()->ToString(), source);
   }
-  DispatchEvent(*event);
+  EnqueueEvent(*event, TaskType::kServiceWorkerClientMessage);
 }
 
 void ServiceWorkerContainer::CountFeature(mojom::WebFeature feature) {
diff --git a/third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.cc b/third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.cc
index 0f6eea3..566aab93 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.cc
@@ -138,7 +138,7 @@
 
     // Render by reading directly from the buffer.
     if (!RenderFromBuffer(output_bus, quantum_frame_offset,
-                          buffer_frames_to_process)) {
+                          buffer_frames_to_process, start_time_offset)) {
       output_bus->Zero();
       return;
     }
@@ -177,7 +177,8 @@
 bool AudioBufferSourceHandler::RenderFromBuffer(
     AudioBus* bus,
     unsigned destination_frame_offset,
-    uint32_t number_of_frames) {
+    uint32_t number_of_frames,
+    double start_time_offset) {
   DCHECK(Context()->IsAudioThread());
 
   // Basic sanity checking
@@ -273,6 +274,20 @@
   // Get local copy.
   double virtual_read_index = virtual_read_index_;
 
+  // We should never start the the source before the start time, so
+  // start_time_offset should always be negative or 0.
+  DCHECK_LE(start_time_offset, 0);
+
+  // Adjust the read index by the start_time_offset (compensated by the playback
+  // rate) because we always start output on a frame boundary with interpolation
+  // if necessary.
+  if (start_time_offset < 0) {
+    if (computed_playback_rate != 0) {
+      virtual_read_index +=
+          std::abs(start_time_offset * computed_playback_rate);
+    }
+  }
+
   // Render loop - reading from the source buffer to the destination using
   // linear interpolation.
   int frames_to_process = number_of_frames;
@@ -293,6 +308,7 @@
     unsigned read_index = static_cast<unsigned>(virtual_read_index);
     unsigned delta_frames = static_cast<unsigned>(virtual_delta_frames);
     end_frame = static_cast<unsigned>(virtual_end_frame);
+
     while (frames_to_process > 0) {
       int frames_to_end = end_frame - read_index;
       int frames_this_time = std::min(frames_to_process, frames_to_end);
diff --git a/third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.h b/third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.h
index 19bd49c..d9cd745 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.h
+++ b/third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.h
@@ -108,10 +108,25 @@
                    bool is_duration_given,
                    ExceptionState&);
 
-  // Returns true on success.
-  bool RenderFromBuffer(AudioBus*,
+  // Render audio directly from the buffer to the audio bus. Returns true on
+  // success, i.e., audio was written to the output bus because all the internal
+  // checks passed.
+  //
+  //   output_bus -
+  //     AudioBus where the rendered audio goes.
+  //   destination_frame_offset -
+  //     Index into the output bus where the first frame should be written.
+  //   number_of_frames -
+  //     Maximum number of frames to process; this can be less that a render
+  //     quantum.
+  //   start_time_offset -
+  //     Actual start time relative to the |destination_frame_offset|.  This
+  //     should be the \sart_time_offset| value returned by
+  //     |UpdateSchedulingInfo|.
+  bool RenderFromBuffer(AudioBus* output_bus,
                         unsigned destination_frame_offset,
-                        uint32_t number_of_frames);
+                        uint32_t number_of_frames,
+                        double start_time_offset);
 
   // Render silence starting from "index" frame in AudioBus.
   inline bool RenderSilenceAndFinishIfNotLooping(AudioBus*,
diff --git a/third_party/blink/renderer/modules/webaudio/audio_scheduled_source_node.cc b/third_party/blink/renderer/modules/webaudio/audio_scheduled_source_node.cc
index 767cd03..54d9875 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_scheduled_source_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_scheduled_source_node.cc
@@ -83,12 +83,22 @@
   // endFrame              : End frame for this source.
   size_t quantum_start_frame = Context()->CurrentSampleFrame();
   size_t quantum_end_frame = quantum_start_frame + quantum_frame_size;
-  size_t start_frame =
-      audio_utilities::TimeToSampleFrame(start_time_, sample_rate);
-  size_t end_frame =
-      end_time_ == kUnknownTime
-          ? 0
-          : audio_utilities::TimeToSampleFrame(end_time_, sample_rate);
+
+  // Round up if the start_time isn't on a frame boundary so we don't start too
+  // early.
+  size_t start_frame = audio_utilities::TimeToSampleFrame(
+      start_time_, sample_rate, audio_utilities::kRoundUp);
+  size_t end_frame = 0;
+
+  if (end_time_ == kUnknownTime) {
+    end_frame = 0;
+  } else {
+    // The end frame is the end time rounded up because it is an exclusive upper
+    // bound of the end time.  We also need to take care to handle huge end
+    // times and clamp the corresponding frame to the largest size_t value.
+    end_frame = audio_utilities::TimeToSampleFrame(end_time_, sample_rate,
+                                                   audio_utilities::kRoundUp);
+  }
 
   // If we know the end time and it's already passed, then don't bother doing
   // any more rendering this cycle.
@@ -125,6 +135,7 @@
 
   if (!non_silent_frames_to_process) {
     // Output silence.
+    DCHECK_LE(start_frame_offset, 0);
     output_bus->Zero();
     return std::make_tuple(quantum_frame_offset, non_silent_frames_to_process,
                            start_frame_offset);
@@ -147,11 +158,13 @@
     size_t zero_start_frame = end_frame - quantum_start_frame;
     size_t frames_to_zero = quantum_frame_size - zero_start_frame;
 
+    DCHECK_LT(zero_start_frame, quantum_frame_size);
+    DCHECK_LE(frames_to_zero, quantum_frame_size);
+    DCHECK_LE(zero_start_frame + frames_to_zero, quantum_frame_size);
+
     bool is_safe = zero_start_frame < quantum_frame_size &&
                    frames_to_zero <= quantum_frame_size &&
                    zero_start_frame + frames_to_zero <= quantum_frame_size;
-    DCHECK(is_safe);
-
     if (is_safe) {
       if (frames_to_zero > non_silent_frames_to_process)
         non_silent_frames_to_process = 0;
@@ -166,6 +179,7 @@
     Finish();
   }
 
+  DCHECK_LE(start_frame_offset, 0);
   return std::make_tuple(quantum_frame_offset, non_silent_frames_to_process,
                          start_frame_offset);
 }
diff --git a/third_party/blink/renderer/platform/audio/audio_utilities.cc b/third_party/blink/renderer/platform/audio/audio_utilities.cc
index f15d376..14d96f75a 100644
--- a/third_party/blink/renderer/platform/audio/audio_utilities.cc
+++ b/third_party/blink/renderer/platform/audio/audio_utilities.cc
@@ -66,9 +66,25 @@
   return 1 - exp(-1 / (sample_rate * time_constant));
 }
 
-size_t TimeToSampleFrame(double time, double sample_rate) {
+size_t TimeToSampleFrame(double time,
+                         double sample_rate,
+                         enum SampleFrameRounding rounding_mode) {
   DCHECK_GE(time, 0);
-  double frame = round(time * sample_rate);
+  double frame;
+
+  switch (rounding_mode) {
+    case kRoundToNearest:
+      frame = round(time * sample_rate);
+      break;
+    case kRoundDown:
+      frame = floor(time * sample_rate);
+      break;
+    case kRoundUp:
+      frame = ceil(time * sample_rate);
+      break;
+    default:
+      NOTREACHED();
+  }
 
   // Just return the largest possible size_t value if necessary.
   if (frame >= std::numeric_limits<size_t>::max()) {
diff --git a/third_party/blink/renderer/platform/audio/audio_utilities.h b/third_party/blink/renderer/platform/audio/audio_utilities.h
index 94cdb94b..2d17f6bd 100644
--- a/third_party/blink/renderer/platform/audio/audio_utilities.h
+++ b/third_party/blink/renderer/platform/audio/audio_utilities.h
@@ -32,6 +32,16 @@
 namespace blink {
 namespace audio_utilities {
 
+// How to do rounding when converting time to sample frame.
+enum SampleFrameRounding {
+  // Round to nearest integer
+  kRoundToNearest,
+  // Round down
+  kRoundDown,
+  // Round up
+  kRoundUp
+};
+
 // Rendering quantum size.  This is how many frames are processed at a time for
 // each node in the audio graph.
 static const unsigned kRenderQuantumFrames = 128;
@@ -48,7 +58,10 @@
                                                          double sample_rate);
 
 // Convert the time to a sample frame at the given sample rate.
-PLATFORM_EXPORT size_t TimeToSampleFrame(double time, double sample_rate);
+PLATFORM_EXPORT size_t
+TimeToSampleFrame(double time,
+                  double sample_rate,
+                  enum SampleFrameRounding rounding = kRoundToNearest);
 
 // Check that |sampleRate| is a valid rate for AudioBuffers.
 PLATFORM_EXPORT bool IsValidAudioBufferSampleRate(float sample_rate);
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
index 4d20e8d..973f8bd 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
@@ -386,6 +386,11 @@
   rect_known_to_be_opaque = FloatRect();
 }
 
+const PaintChunk& PaintArtifactCompositor::PendingLayer::FirstPaintChunk(
+    const PaintArtifact& paint_artifact) const {
+  return paint_artifact.PaintChunks()[paint_chunk_indices[0]];
+}
+
 static bool IsNonCompositingAncestorOf(
     const TransformPaintPropertyNode* unaliased_ancestor,
     const TransformPaintPropertyNode* node) {
@@ -884,10 +889,12 @@
     layer->SetEffectTreeIndex(effect_id);
     bool backface_hidden = property_state.Transform()->IsBackfaceHidden();
     layer->SetDoubleSided(!backface_hidden);
-    // TODO(wangxianzhu): cc::PropertyTreeBuilder has a more sophisticated
-    // condition for this. Do we need to do the same here?
     layer->SetShouldCheckBackfaceVisibility(backface_hidden);
 
+    layer->set_owner_node_id(
+        pending_layer.FirstPaintChunk(*paint_artifact).id.client.OwnerNodeId());
+    // TODO(wangxianzhu): cc_picture_layer_->set_compositing_reasons(...);
+
     InsertAncestorElementIds(property_state.Effect(), composited_element_ids);
     InsertAncestorElementIds(transform, composited_element_ids);
     if (layer->scrollable())
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
index 26a8f15..e38c90f9 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h
@@ -155,6 +155,8 @@
     // to the chunks as Skia commands.
     void Upcast(const PropertyTreeState&);
 
+    const PaintChunk& FirstPaintChunk(const PaintArtifact&) const;
+
     FloatRect bounds;
     Vector<wtf_size_t> paint_chunk_indices;
     FloatRect rect_known_to_be_opaque;
diff --git a/third_party/blink/renderer/platform/graphics/graphics_layer.cc b/third_party/blink/renderer/platform/graphics/graphics_layer.cc
index 75bfb51..60d859d9 100644
--- a/third_party/blink/renderer/platform/graphics/graphics_layer.cc
+++ b/third_party/blink/renderer/platform/graphics/graphics_layer.cc
@@ -914,6 +914,7 @@
             .TakePaintImage();
     if (!image_layer_) {
       image_layer_ = cc::PictureImageLayer::Create();
+      image_layer_->set_owner_node_id(CcLayer()->owner_node_id());
       RegisterContentsLayer(image_layer_.get());
     }
     image_layer_->SetImage(std::move(paint_image), matrix,
diff --git a/third_party/blink/renderer/platform/graphics/paint/display_item_client.h b/third_party/blink/renderer/platform/graphics/paint/display_item_client.h
index 27a43f1..4342ad0 100644
--- a/third_party/blink/renderer/platform/graphics/paint/display_item_client.h
+++ b/third_party/blink/renderer/platform/graphics/paint/display_item_client.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_DISPLAY_ITEM_CLIENT_H_
 
 #include "third_party/blink/renderer/platform/geometry/layout_rect.h"
+#include "third_party/blink/renderer/platform/graphics/dom_node_id.h"
 #include "third_party/blink/renderer/platform/graphics/paint_invalidation_reason.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/blink/renderer/platform/wtf/assertions.h"
@@ -41,6 +42,12 @@
 
   virtual String DebugName() const = 0;
 
+  // Needed for paint chunk clients only. Returns the id of the DOM node
+  // associated with this DisplayItemClient, or kInvalidDOMNodeId if there is no
+  // associated DOM node or this DisplayItemClient is never used as a paint
+  // chunk client.
+  virtual DOMNodeId OwnerNodeId() const { return kInvalidDOMNodeId; }
+
   // The visual rect of this DisplayItemClient. For SPv1, it's in the object
   // space of the object that owns the GraphicsLayer, i.e. offset by
   // GraphicsLayer::OffsetFromLayoutObjectWithSubpixelAccumulation().
diff --git a/third_party/blink/renderer/platform/graphics/paint/foreign_layer_display_item.cc b/third_party/blink/renderer/platform/graphics/paint/foreign_layer_display_item.cc
index 5e57ee5..7fdeabc 100644
--- a/third_party/blink/renderer/platform/graphics/paint/foreign_layer_display_item.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/foreign_layer_display_item.cc
@@ -24,6 +24,8 @@
 
   String DebugName() const final { return "ForeignLayer"; }
 
+  DOMNodeId OwnerNodeId() const final { return layer_->owner_node_id(); }
+
   LayoutRect VisualRect() const final {
     const auto& offset = layer_->offset_to_transform_parent();
     return LayoutRect(LayoutPoint(offset.x(), offset.y()),
diff --git a/third_party/blink/renderer/platform/loader/fetch/fetch_context.cc b/third_party/blink/renderer/platform/loader/fetch/fetch_context.cc
index d874c6a0..16a3a17 100644
--- a/third_party/blink/renderer/platform/loader/fetch/fetch_context.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/fetch_context.cc
@@ -81,8 +81,7 @@
                                                      ResourceLoadPriority,
                                                      int) {}
 
-void FetchContext::AddAdditionalRequestHeaders(ResourceRequest&,
-                                               FetchResourceType) {}
+void FetchContext::AddAdditionalRequestHeaders(ResourceRequest&) {}
 
 mojom::FetchCacheMode FetchContext::ResourceRequestCachePolicy(
     const ResourceRequest&,
@@ -129,8 +128,6 @@
                                    bool) {}
 
 bool FetchContext::ShouldLoadNewResource(ResourceType type) const {
-  if (type == ResourceType::kMainResource)
-    return !GetResourceFetcherProperties().ShouldBlockLoadingMainResource();
   return !GetResourceFetcherProperties().ShouldBlockLoadingSubResource();
 }
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/fetch_context.h b/third_party/blink/renderer/platform/loader/fetch/fetch_context.h
index 4d084287..cce2e96 100644
--- a/third_party/blink/renderer/platform/loader/fetch/fetch_context.h
+++ b/third_party/blink/renderer/platform/loader/fetch/fetch_context.h
@@ -71,8 +71,6 @@
 class ResourceTimingInfo;
 class WebScopedVirtualTimePauser;
 
-enum FetchResourceType { kFetchMainResource, kFetchSubresource };
-
 // The FetchContext is an interface for performing context specific processing
 // in response to events in the ResourceFetcher. The ResourceFetcher or its job
 // class, ResourceLoader, may call the methods on a FetchContext.
@@ -98,7 +96,7 @@
 
   virtual void Trace(blink::Visitor*);
 
-  virtual void AddAdditionalRequestHeaders(ResourceRequest&, FetchResourceType);
+  virtual void AddAdditionalRequestHeaders(ResourceRequest&);
 
   const ResourceFetcherProperties& GetResourceFetcherProperties() const;
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/memory_cache_correctness_test.cc b/third_party/blink/renderer/platform/loader/fetch/memory_cache_correctness_test.cc
index 04a6bdf5..9b1a1dc 100644
--- a/third_party/blink/renderer/platform/loader/fetch/memory_cache_correctness_test.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/memory_cache_correctness_test.cc
@@ -119,7 +119,6 @@
     MockFetchContext* context = MakeGarbageCollected<MockFetchContext>();
     auto* properties =
         MakeGarbageCollected<TestResourceFetcherProperties>(security_origin_);
-    properties->SetShouldBlockLoadingMainResource(true);
     properties->SetShouldBlockLoadingSubResource(true);
     fetcher_ = MakeGarbageCollected<ResourceFetcher>(
         ResourceFetcherInit(*properties, context,
diff --git a/third_party/blink/renderer/platform/loader/fetch/null_resource_fetcher_properties.h b/third_party/blink/renderer/platform/loader/fetch/null_resource_fetcher_properties.h
index 7f6867a..7a2d62b 100644
--- a/third_party/blink/renderer/platform/loader/fetch/null_resource_fetcher_properties.h
+++ b/third_party/blink/renderer/platform/loader/fetch/null_resource_fetcher_properties.h
@@ -38,7 +38,6 @@
   bool IsPaused() const override { return false; }
   bool IsDetached() const override { return true; }
   bool IsLoadComplete() const override { return true; }
-  bool ShouldBlockLoadingMainResource() const override { return true; }
   bool ShouldBlockLoadingSubResource() const override { return true; }
 
  private:
diff --git a/third_party/blink/renderer/platform/loader/fetch/preload_key.h b/third_party/blink/renderer/platform/loader/fetch/preload_key.h
index 3340d39..04bf568 100644
--- a/third_party/blink/renderer/platform/loader/fetch/preload_key.h
+++ b/third_party/blink/renderer/platform/loader/fetch/preload_key.h
@@ -46,7 +46,7 @@
   }
 
   KURL url;
-  ResourceType type = ResourceType::kMainResource;
+  ResourceType type = ResourceType::kImage;
 };
 
 }  // namespace blink
@@ -61,6 +61,8 @@
 template <>
 struct HashTraits<blink::PreloadKey>
     : public SimpleClassHashTraits<blink::PreloadKey> {
+  static const bool kEmptyValueIsZero = false;
+
   static bool IsDeletedValue(const blink::PreloadKey& value) {
     return HashTraits<blink::KURL>::IsDeletedValue(value.url);
   }
diff --git a/third_party/blink/renderer/platform/loader/fetch/raw_resource.cc b/third_party/blink/renderer/platform/loader/fetch/raw_resource.cc
index f41bfb1f..658ed0c4 100644
--- a/third_party/blink/renderer/platform/loader/fetch/raw_resource.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/raw_resource.cc
@@ -171,12 +171,6 @@
     c->RedirectBlocked();
 }
 
-SourceKeyedCachedMetadataHandler* RawResource::InlineScriptCacheHandler() {
-  DCHECK_EQ(ResourceType::kMainResource, GetType());
-  return static_cast<SourceKeyedCachedMetadataHandler*>(
-      Resource::CacheHandler());
-}
-
 SingleCachedMetadataHandler* RawResource::ScriptCacheHandler() {
   DCHECK_EQ(ResourceType::kRaw, GetType());
   return static_cast<SingleCachedMetadataHandler*>(Resource::CacheHandler());
@@ -210,12 +204,7 @@
 
 CachedMetadataHandler* RawResource::CreateCachedMetadataHandler(
     std::unique_ptr<CachedMetadataSender> send_callback) {
-  if (GetType() == ResourceType::kMainResource) {
-    // This is a document resource; create a cache handler that can handle
-    // multiple inline scripts.
-    return MakeGarbageCollected<SourceKeyedCachedMetadataHandler>(
-        Encoding(), std::move(send_callback));
-  } else if (GetType() == ResourceType::kRaw) {
+  if (GetType() == ResourceType::kRaw) {
     // This is a resource of indeterminate type, e.g. a fetched WebAssembly
     // module; create a cache handler that can store a single metadata entry.
     return MakeGarbageCollected<ScriptCachedMetadataHandler>(
@@ -228,13 +217,7 @@
                                               size_t size) {
   Resource::SetSerializedCachedMetadata(data, size);
 
-  if (GetType() == ResourceType::kMainResource) {
-    SourceKeyedCachedMetadataHandler* cache_handler =
-        InlineScriptCacheHandler();
-    if (cache_handler) {
-      cache_handler->SetSerializedCachedMetadata(data, size);
-    }
-  } else if (GetType() == ResourceType::kRaw) {
+  if (GetType() == ResourceType::kRaw) {
     ScriptCachedMetadataHandler* cache_handler =
         static_cast<ScriptCachedMetadataHandler*>(Resource::CacheHandler());
     if (cache_handler) {
diff --git a/third_party/blink/renderer/platform/loader/fetch/raw_resource.h b/third_party/blink/renderer/platform/loader/fetch/raw_resource.h
index ed590d0..820f14e 100644
--- a/third_party/blink/renderer/platform/loader/fetch/raw_resource.h
+++ b/third_party/blink/renderer/platform/loader/fetch/raw_resource.h
@@ -38,7 +38,6 @@
 class FetchParameters;
 class RawResourceClient;
 class ResourceFetcher;
-class SourceKeyedCachedMetadataHandler;
 
 class PLATFORM_EXPORT RawResource final : public Resource {
  public:
@@ -86,12 +85,6 @@
 
   void SetSerializedCachedMetadata(const uint8_t*, size_t) override;
 
-  // Used for code caching of scripts with source code inline in the HTML.
-  // Returns a cache handler which can store multiple cache metadata entries,
-  // keyed by the source code of the script. This is valid only if type is
-  // kMainResource.
-  SourceKeyedCachedMetadataHandler* InlineScriptCacheHandler();
-
   // Used for code caching of fetched code resources. Returns a cache handler
   // which can only store a single cache metadata entry. This is valid only if
   // type is kRaw.
@@ -145,9 +138,9 @@
 // TODO(yhirano): Recover #if ENABLE_SECURITY_ASSERT when we stop adding
 // RawResources to MemoryCache.
 inline bool IsRawResource(ResourceType type) {
-  return type == ResourceType::kMainResource || type == ResourceType::kRaw ||
-         type == ResourceType::kTextTrack || type == ResourceType::kAudio ||
-         type == ResourceType::kVideo || type == ResourceType::kManifest ||
+  return type == ResourceType::kRaw || type == ResourceType::kTextTrack ||
+         type == ResourceType::kAudio || type == ResourceType::kVideo ||
+         type == ResourceType::kManifest ||
          type == ResourceType::kImportResource;
 }
 inline bool IsRawResource(const Resource& resource) {
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource.cc b/third_party/blink/renderer/platform/loader/fetch/resource.cc
index a20c89be..af9a3700 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource.cc
@@ -1150,8 +1150,6 @@
     ResourceType type,
     const AtomicString& fetch_initiator_name) {
   switch (type) {
-    case ResourceType::kMainResource:
-      return "Main resource";
     case ResourceType::kImage:
       return "Image";
     case ResourceType::kCSSStyleSheet:
@@ -1191,9 +1189,8 @@
   DCHECK(
       // Cacheable WebAssembly modules are fetched, so raw resource type.
       resource_type == ResourceType::kRaw ||
-      // Cacheable Javascript is a script or a document resource.
+      // Cacheable Javascript is a script resource.
       resource_type == ResourceType::kScript ||
-      resource_type == ResourceType::kMainResource ||
       // Also accept mock resources for testing.
       resource_type == ResourceType::kMock);
   return ToCodeCacheType(resource_type);
@@ -1205,7 +1202,6 @@
 
 bool Resource::IsLoadEventBlockingResourceType() const {
   switch (type_) {
-    case ResourceType::kMainResource:
     case ResourceType::kImage:
     case ResourceType::kCSSStyleSheet:
     case ResourceType::kScript:
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource.h b/third_party/blink/renderer/platform/loader/fetch/resource.h
index 7874407..07e2364 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource.h
@@ -70,8 +70,8 @@
 // |ResourceType| enum values are used in UMAs, so do not change the values of
 // existing types. When adding a new type, append it at the end.
 enum class ResourceType : uint8_t {
-  kMainResource,
-  kImage,
+  // We do not have kMainResource anymore, which used to have zero value.
+  kImage = 1,
   kCSSStyleSheet,
   kScript,
   kFont,
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
index 208a7f2..99e8d05 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
@@ -103,7 +103,6 @@
     DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, Image)          \
     DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, ImportResource) \
     DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, LinkPrefetch)   \
-    DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, MainResource)   \
     DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, Manifest)       \
     DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, Audio)          \
     DEFINE_SINGLE_RESOURCE_HISTOGRAM(prefix, Video)          \
@@ -117,7 +116,6 @@
 
 ResourceLoadPriority TypeToPriority(ResourceType type) {
   switch (type) {
-    case ResourceType::kMainResource:
     case ResourceType::kCSSStyleSheet:
     case ResourceType::kFont:
       // Also parser-blocking scripts (set explicitly in loadPriority)
@@ -335,17 +333,10 @@
 
 mojom::RequestContextType ResourceFetcher::DetermineRequestContext(
     ResourceType type,
-    IsImageSet is_image_set,
-    bool is_main_frame) {
+    IsImageSet is_image_set) {
   DCHECK((is_image_set == kImageNotImageSet) ||
          (type == ResourceType::kImage && is_image_set == kImageIsImageSet));
   switch (type) {
-    case ResourceType::kMainResource:
-      if (!is_main_frame)
-        return mojom::RequestContextType::IFRAME;
-      // FIXME: Change this to a context frame type (once we introduce them):
-      // http://fetch.spec.whatwg.org/#concept-request-context-frame-type
-      return mojom::RequestContextType::HYPERLINK;
     case ResourceType::kXSLStyleSheet:
       DCHECK(RuntimeEnabledFeatures::XSLTEnabled());
       FALLTHROUGH;
@@ -440,10 +431,6 @@
   bool IsLoadComplete() const override {
     return properties_ ? properties_->IsLoadComplete() : load_complete_;
   }
-  bool ShouldBlockLoadingMainResource() const override {
-    // Returns true when detached in order to preserve the existing behavior.
-    return properties_ ? properties_->ShouldBlockLoadingMainResource() : true;
-  }
   bool ShouldBlockLoadingSubResource() const override {
     // Returns true when detached in order to preserve the existing behavior.
     return properties_ ? properties_->ShouldBlockLoadingSubResource() : true;
@@ -547,8 +534,7 @@
     // Resources loaded from memory cache should be reported the first time
     // they're used.
     scoped_refptr<ResourceTimingInfo> info = ResourceTimingInfo::Create(
-        params.Options().initiator_info.name, CurrentTimeTicks(),
-        resource->GetType() == ResourceType::kMainResource);
+        params.Options().initiator_info.name, CurrentTimeTicks());
     // TODO(yoav): GetInitialUrlForResourceTiming() is only needed until
     // Out-of-Blink CORS lands: https://crbug.com/736308
     info->SetInitialURL(
@@ -776,8 +762,8 @@
   }
   if (resource_request.GetRequestContext() ==
       mojom::RequestContextType::UNSPECIFIED) {
-    resource_request.SetRequestContext(DetermineRequestContext(
-        resource_type, kImageNotImageSet, properties_->IsMainFrame()));
+    resource_request.SetRequestContext(
+        DetermineRequestContext(resource_type, kImageNotImageSet));
   }
   if (resource_type == ResourceType::kLinkPrefetch)
     resource_request.SetHTTPHeaderField(http_names::kPurpose, "prefetch");
@@ -813,10 +799,7 @@
       resource_request.HttpMethod() == http_names::kGET &&
       !IsRawResource(resource_type) && !params.IsStaleRevalidation());
 
-  Context().AddAdditionalRequestHeaders(
-      resource_request, (resource_type == ResourceType::kMainResource)
-                            ? kFetchMainResource
-                            : kFetchSubresource);
+  Context().AddAdditionalRequestHeaders(resource_request);
 
   network_instrumentation::ResourcePrioritySet(identifier,
                                                resource_request.Priority());
@@ -1125,11 +1108,8 @@
   if (fetch_initiator == fetch_initiator_type_names::kInternal)
     return;
 
-  if (resource->GetType() == ResourceType::kMainResource)
-    return;
-
-  scoped_refptr<ResourceTimingInfo> info = ResourceTimingInfo::Create(
-      fetch_initiator, CurrentTimeTicks(), false /* is_main_resource */);
+  scoped_refptr<ResourceTimingInfo> info =
+      ResourceTimingInfo::Create(fetch_initiator, CurrentTimeTicks());
 
   if (resource->IsCacheValidator()) {
     const AtomicString& timing_allow_origin =
@@ -1713,7 +1693,7 @@
       // original request.
       scoped_refptr<ResourceTimingInfo> preflight_info =
           ResourceTimingInfo::Create(info->InitiatorType(),
-                                     timing_info.start_time, false);
+                                     timing_info.start_time);
       preflight_info->SetInitialURL(info->InitialURL());
       preflight_info->SetLoadFinishTime(timing_info.finish_time);
       preflight_info->AddFinalTransferSize(timing_info.transfer_size);
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h
index e0ce7e9..f74b2d44 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h
@@ -211,8 +211,9 @@
 
   enum IsImageSet { kImageNotImageSet, kImageIsImageSet };
 
-  WARN_UNUSED_RESULT static mojom::RequestContextType
-  DetermineRequestContext(ResourceType, IsImageSet, bool is_main_frame);
+  WARN_UNUSED_RESULT static mojom::RequestContextType DetermineRequestContext(
+      ResourceType,
+      IsImageSet);
 
   void UpdateAllImageResourcePriorities();
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher_properties.h b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher_properties.h
index dafb16d..e860655b9 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher_properties.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher_properties.h
@@ -66,9 +66,6 @@
   // Returns whether the main resource for this global context is loaded.
   virtual bool IsLoadComplete() const = 0;
 
-  // Returns whether we should disallow a main resource loading.
-  virtual bool ShouldBlockLoadingMainResource() const = 0;
-
   // Returns whether we should disallow a sub resource loading.
   virtual bool ShouldBlockLoadingSubResource() const = 0;
 };
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher_test.cc b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher_test.cc
index 3a23c15..76a8f17 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher_test.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher_test.cc
@@ -230,11 +230,10 @@
 
 TEST_F(ResourceFetcherTest, ResourceTimingInfo) {
   auto info = ResourceTimingInfo::Create(fetch_initiator_type_names::kDocument,
-                                         CurrentTimeTicks(),
-                                         true /* is_main_resource */);
+                                         CurrentTimeTicks());
   info->AddFinalTransferSize(5);
   EXPECT_EQ(info->TransferSize(), 5);
-  ResourceResponse redirect_response(KURL("https://example.com"));
+  ResourceResponse redirect_response(KURL("https://example.com/original"));
   redirect_response.SetHTTPStatusCode(200);
   redirect_response.SetEncodedDataLength(7);
   info->AddRedirect(redirect_response, KURL("https://example.com/redirect"));
@@ -1004,7 +1003,6 @@
   EXPECT_FALSE(properties.IsPaused());
   EXPECT_FALSE(properties.IsDetached());
   EXPECT_FALSE(properties.IsLoadComplete());
-  EXPECT_FALSE(properties.ShouldBlockLoadingMainResource());
   EXPECT_FALSE(properties.ShouldBlockLoadingSubResource());
 
   fetcher->ClearContext();
@@ -1022,7 +1020,6 @@
   EXPECT_FALSE(properties.IsPaused());
   EXPECT_TRUE(properties.IsDetached());
   EXPECT_FALSE(properties.IsLoadComplete());
-  EXPECT_TRUE(properties.ShouldBlockLoadingMainResource());
   EXPECT_TRUE(properties.ShouldBlockLoadingSubResource());
 }
 
@@ -1050,7 +1047,6 @@
   original_properties.SetServiceWorkerId(133);
   original_properties.SetIsPaused(true);
   original_properties.SetIsLoadComplete(true);
-  original_properties.SetShouldBlockLoadingMainResource(true);
   original_properties.SetShouldBlockLoadingSubResource(true);
 
   const auto& client_settings_object =
@@ -1063,7 +1059,6 @@
   EXPECT_TRUE(properties.IsPaused());
   EXPECT_FALSE(properties.IsDetached());
   EXPECT_TRUE(properties.IsLoadComplete());
-  EXPECT_TRUE(properties.ShouldBlockLoadingMainResource());
   EXPECT_TRUE(properties.ShouldBlockLoadingSubResource());
 
   fetcher->ClearContext();
@@ -1081,7 +1076,6 @@
   EXPECT_TRUE(properties.IsPaused());
   EXPECT_TRUE(properties.IsDetached());
   EXPECT_TRUE(properties.IsLoadComplete());
-  EXPECT_TRUE(properties.ShouldBlockLoadingMainResource());
   EXPECT_TRUE(properties.ShouldBlockLoadingSubResource());
 }
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler_test.cc b/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler_test.cc
index 6a546a44..9cbe68a 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler_test.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler_test.cc
@@ -68,7 +68,6 @@
   void SetUp() override {
     DCHECK(RuntimeEnabledFeatures::ResourceLoadSchedulerEnabled());
     auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
-    properties->SetShouldBlockLoadingMainResource(true);
     properties->SetShouldBlockLoadingSubResource(true);
     auto* context = MakeGarbageCollected<MockFetchContext>();
     MakeGarbageCollected<ResourceFetcher>(
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
index 5ff90f1..fe5f7740 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
@@ -335,12 +335,6 @@
       !RuntimeEnabledFeatures::WasmCodeCacheEnabled())
     return false;
 
-  // TODO(crbug.com/867347): Enable fetching of code caches on non-main threads
-  // once code cache has its own mojo interface. Currently it is using
-  // RenderMessageFilter that is not available on non-main threads.
-  if (!IsMainThread())
-    return false;
-
   const ResourceRequest& request = resource_->GetResourceRequest();
   if (!request.Url().ProtocolIsInHTTPFamily())
     return false;
@@ -352,9 +346,9 @@
     return false;
   if (request.DownloadToBlob())
     return false;
-  // Javascript resources have type kScript or kMainResource (for inline
-  // scripts). WebAssembly module resources have type kRaw. Note that we
-  // always perform a code fetch for all of these resources because:
+  // Javascript resources have type kScript. WebAssembly module resources
+  // have type kRaw. Note that we always perform a code fetch for all of
+  // these resources because:
   //
   // * It is not easy to distinguish WebAssembly modules from other raw
   //   resources
@@ -366,7 +360,6 @@
   // no browser process disk IO since the cache index is in memory and the
   // resource key should not be present.
   return resource_->GetType() == ResourceType::kScript ||
-         resource_->GetType() == ResourceType::kMainResource ||
          resource_->GetType() == ResourceType::kRaw;
 }
 
@@ -384,9 +377,6 @@
   // stoppable. We also disable throttling and stopping for non-http[s]
   // requests.
   if (resource_->Options().synchronous_policy == kRequestSynchronously ||
-      (request.GetFrameType() ==
-           network::mojom::RequestContextFrameType::kTopLevel &&
-       resource_->GetType() == ResourceType::kMainResource) ||
       !request.Url().ProtocolIsInHTTPFamily()) {
     throttle_option =
         ResourceLoadScheduler::ThrottleOption::kCanNotBeStoppedOrThrottled;
@@ -1105,10 +1095,6 @@
     return;
   DCHECK_GE(response_out.ToResourceResponse().EncodedBodyLength(), 0);
 
-  // TODO(crbug.com/867347): Enable fetching of code caches synchronously
-  // once code cache has its own mojo interface. Currently it is using
-  // RenderMessageFilter that is not available on non-main threads.
-
   // Follow the async case convention of not calling DidReceiveData or
   // appending data to m_resource if the response body is empty. Copying the
   // empty buffer is a noop in most cases, but is destructive in the case of
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h b/third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h
index ce12f13..541ef06 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h
@@ -49,12 +49,10 @@
 
  public:
   static scoped_refptr<ResourceTimingInfo> Create(const AtomicString& type,
-                                                  const TimeTicks time,
-                                                  bool is_main_resource) {
-    return base::AdoptRef(new ResourceTimingInfo(type, time, is_main_resource));
+                                                  const TimeTicks time) {
+    return base::AdoptRef(new ResourceTimingInfo(type, time));
   }
   TimeTicks InitialTime() const { return initial_time_; }
-  bool IsMainResource() const { return is_main_resource_; }
 
   const AtomicString& InitiatorType() const { return type_; }
 
@@ -100,10 +98,8 @@
   bool NegativeAllowed() const { return negative_allowed_; }
 
  private:
-  ResourceTimingInfo(const AtomicString& type,
-                     const TimeTicks time,
-                     bool is_main_resource)
-      : type_(type), initial_time_(time), is_main_resource_(is_main_resource) {}
+  ResourceTimingInfo(const AtomicString& type, const TimeTicks time)
+      : type_(type), initial_time_(time) {}
 
   AtomicString type_;
   AtomicString original_timing_allow_origin_;
@@ -113,7 +109,6 @@
   ResourceResponse final_response_;
   Vector<ResourceResponse> redirect_chain_;
   long long transfer_size_ = 0;
-  bool is_main_resource_;
   bool has_cross_origin_redirect_ = false;
   bool negative_allowed_ = false;
 };
diff --git a/third_party/blink/renderer/platform/loader/testing/test_resource_fetcher_properties.h b/third_party/blink/renderer/platform/loader/testing/test_resource_fetcher_properties.h
index 5e6360b..7e94739 100644
--- a/third_party/blink/renderer/platform/loader/testing/test_resource_fetcher_properties.h
+++ b/third_party/blink/renderer/platform/loader/testing/test_resource_fetcher_properties.h
@@ -42,9 +42,6 @@
   bool IsPaused() const override { return paused_; }
   bool IsDetached() const override { return false; }
   bool IsLoadComplete() const override { return load_complete_; }
-  bool ShouldBlockLoadingMainResource() const override {
-    return should_block_loading_main_resource_;
-  }
   bool ShouldBlockLoadingSubResource() const override {
     return should_block_loading_sub_resource_;
   }
@@ -56,9 +53,6 @@
   void SetServiceWorkerId(int64_t id) { service_worker_id_ = id; }
   void SetIsPaused(bool value) { paused_ = value; }
   void SetIsLoadComplete(bool value) { load_complete_ = value; }
-  void SetShouldBlockLoadingMainResource(bool value) {
-    should_block_loading_main_resource_ = value;
-  }
   void SetShouldBlockLoadingSubResource(bool value) {
     should_block_loading_sub_resource_ = value;
   }
@@ -71,7 +65,6 @@
   int64_t service_worker_id_ = 0;
   bool paused_ = false;
   bool load_complete_ = false;
-  bool should_block_loading_main_resource_ = false;
   bool should_block_loading_sub_resource_ = false;
 };
 
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc
index 5c2bc18..c515a10 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc
@@ -435,6 +435,7 @@
     // PostedMessage can be used for navigation, so we shouldn't defer it
     // when expecting a user gesture.
     case TaskType::kPostedMessage:
+    case TaskType::kServiceWorkerClientMessage:
     case TaskType::kWorkerAnimation:
     // UserInteraction tasks should be run even when expecting a user gesture.
     case TaskType::kUserInteraction:
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/task_type_names.cc b/third_party/blink/renderer/platform/scheduler/main_thread/task_type_names.cc
index 39530c12..a4cd8be 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/task_type_names.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/task_type_names.cc
@@ -73,6 +73,8 @@
       return "BackgroundFetch";
     case TaskType::kPermission:
       return "Permission";
+    case TaskType::kServiceWorkerClientMessage:
+      return "ServiceWorkerClientMessage";
     case TaskType::kInternalDefault:
       return "InternalDefault";
     case TaskType::kInternalLoading:
diff --git a/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler.cc b/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler.cc
index b4dfcaa..251719bf 100644
--- a/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler.cc
+++ b/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler.cc
@@ -193,6 +193,7 @@
     case TaskType::kExperimentalWebSchedulingUserInteraction:
     case TaskType::kExperimentalWebSchedulingBestEffort:
     case TaskType::kInternalTranslation:
+    case TaskType::kServiceWorkerClientMessage:
     case TaskType::kCount:
       NOTREACHED();
       break;
diff --git a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint
index a710eef..6ae9a59 100644
--- a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint
+++ b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint
@@ -2,7 +2,6 @@
 # See: https://docs.google.com/document/d/1QCM912Dr6u38DqyQqd7pxQxDy8FFOoWMMDq7uAXqKdA/view
 # We are focused on fast/, compositing/, and svg/ with all other directories skipped (for now).
 Bug(none) editing/input [ Skip ]
-Bug(none) http/tests/devtools/layers/ [ Skip ]
 Bug(none) http/tests/devtools/tracing/ [ Skip ]
 Bug(none) printing/ [ Skip ]
 Bug(none) scrollbars/ [ Skip ]
@@ -182,8 +181,6 @@
 Bug(none) fragmentation/outline-crossing-columns.html [ Crash ]
 Bug(none) fullscreen/compositor-touch-hit-rects-fullscreen-video-controls.html [ Failure ]
 Bug(none) http/tests/misc/slow-loading-mask.html [ Failure ]
-Bug(none) inspector-protocol/layers/paint-profiler.js [ Failure ]
-Bug(none) inspector-protocol/layers/get-layers.js [ Timeout ]
 Bug(none) paint/pagination/pagination-change-clip-crash.html [ Failure ]
 
 # Less invalidations or different invalidations without pixel failures.
@@ -304,7 +301,6 @@
 Bug(none) fast/borders/overflow-hidden-border-radius-force-backing-store.html [ Failure ]
 
 # virtual/threaded variants of sub-directories and tests already skipped or marked as failing above.
-Bug(none) virtual/threaded/http/tests/devtools/tracing/ [ Skip ]
 Bug(none) virtual/threaded/fast/events/pinch/gesture-pinch-zoom-scroll-bubble.html [ Timeout ]
 Bug(none) virtual/threaded/fast/events/pinch/pinch-zoom-pan-within-zoomed-viewport.html [ Failure ]
 Bug(none) virtual/threaded/fast/events/pinch/scroll-visual-viewport-send-boundary-events.html [ Timeout ]
@@ -445,3 +441,11 @@
 Bug(none) css3/filters/blur-filter-page-scroll-self.html [ Failure ]
 
 crbug.com/921729 paint/invalidation/compositing/composited-layer-move.html [ Failure ]
+
+# Crash during PictureLayer::GetPicture() when DisplayItemList is finished twice.
+Bug(none) http/tests/devtools/layers/layer-canvas-log.js [ Crash ]
+Bug(none) http/tests/devtools/layers/layer-replay-scale.js [ Crash ]
+# Missing compositing reasons
+Bug(none) http/tests/devtools/layers/layer-compositing-reasons.js [ Failure ]
+# Missing WheelEventHandler
+Bug(none) http/tests/devtools/layers/layer-scroll-rects-get.js [ Failure ]
diff --git a/third_party/blink/web_tests/SlowTests b/third_party/blink/web_tests/SlowTests
index e55b134..93233d7 100644
--- a/third_party/blink/web_tests/SlowTests
+++ b/third_party/blink/web_tests/SlowTests
@@ -92,8 +92,10 @@
 crbug.com/420008 virtual/threaded/http/tests/devtools/tracing/ [ Slow ]
 crbug.com/902685 http/tests/devtools/isolated-code-cache/same-origin-test.js [ Slow ]
 crbug.com/902685 http/tests/devtools/isolated-code-cache/cross-origin-test.js [ Slow ]
+crbug.com/902685 http/tests/devtools/wasm-isolated-code-cache/wasm-cache-test.js [ Slow ]
 crbug.com/902685 virtual/site-isolated-code-cache/http/tests/devtools/isolated-code-cache/same-origin-test.js [ Slow ]
 crbug.com/902685 virtual/site-isolated-code-cache/http/tests/devtools/isolated-code-cache/cross-origin-test.js [ Slow ]
+crbug.com/902685 virtual/wasm-site-isolated-code-cache/http/tests/devtools/wasm-isolated-code-cache/wasm-cache-test.js [ Slow ]
 crbug.com/902685 virtual/not-site-per-process/http/tests/devtools/isolated-code-cache/same-origin-test.js [ Slow ]
 crbug.com/902685 virtual/not-site-per-process/http/tests/devtools/isolated-code-cache/cross-origin-test.js [ Slow ]
 
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index c5a9bbbe..cb10f09 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -375,7 +375,8 @@
 Bug(none) virtual/stable/compositing/overflow/clip-escaping-reverse-order-should-not-crash.html [ Failure ]
 Bug(none) virtual/stable/compositing/overflow/nested-border-radius-clipping.html [ Failure ]
 
-# These tests are no longer applicable with layer list mode (BGPT). Move to NeverFixTests later.
+# These tests are no longer applicable with layer list mode (BGPT).
+# TODO(wangxianzhu): Remove them when we remove non-BGPT code.
 Bug(none) http/tests/devtools/layers/layer-sticky-position-constraint-get.js [ Skip ]
 Bug(none) inspector-protocol/layers/get-layers.js [ Skip ]
 
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 3b92d6c..a6d90ec 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -778,6 +778,13 @@
              "--site-per-process"]
   },
   {
+    "prefix": "wasm-site-isolated-code-cache",
+    "base": "http/tests/devtools/wasm-isolated-code-cache",
+    "args": ["--enable-features=IsolatedCodeCache,WasmCodeCache",
+             "--disable-features=WebAssemblyBaseline",
+             "--site-per-process"]
+  },
+  {
     "prefix": "not-site-per-process",
     "base": "http/tests/devtools/isolated-code-cache",
     "args": ["--disable-site-isolation-trials"]
diff --git a/third_party/blink/web_tests/display-lock/lock-after-append/acquire-immediate-update-and-commit-expected.html b/third_party/blink/web_tests/display-lock/lock-after-append/acquire-immediate-update-and-commit-expected.html
new file mode 100644
index 0000000..9801e1a
--- /dev/null
+++ b/third_party/blink/web_tests/display-lock/lock-after-append/acquire-immediate-update-and-commit-expected.html
@@ -0,0 +1,18 @@
+<!doctype HTML>
+
+<style>
+#container {
+  contain: style layout;
+  width: 150px;
+  height: 150px;
+  background: lightblue;
+}
+#child {
+  width: 50px;
+  height: 50px;
+  background: lightgreen;
+}
+</style>
+
+<div id="log">PASS</div>
+<div id="container"><div id="child"></div></div>
diff --git a/third_party/blink/web_tests/display-lock/lock-after-append/acquire-immediate-update-and-commit.html b/third_party/blink/web_tests/display-lock/lock-after-append/acquire-immediate-update-and-commit.html
new file mode 100644
index 0000000..ccff550
--- /dev/null
+++ b/third_party/blink/web_tests/display-lock/lock-after-append/acquire-immediate-update-and-commit.html
@@ -0,0 +1,50 @@
+<!doctype HTML>
+
+<!--
+Runs an acquire, and calls updateAndCommit without waiting for the acquire promise.
+-->
+
+<style>
+#container {
+  contain: style layout;
+  width: 150px;
+  height: 150px;
+  background: lightblue;
+}
+#child {
+  width: 50px;
+  height: 50px;
+  background: lightgreen;
+}
+</style>
+
+<div id="log"></div>
+<div id="container"></div>
+
+<script>
+// TODO(vmpstr): In WPT this needs to be replaced with reftest-wait.
+if (window.testRunner)
+  window.testRunner.waitUntilDone();
+
+function finishTest(status_string) {
+  if (document.getElementById("log").innerHTML === "")
+    document.getElementById("log").innerHTML = status_string;
+  if (window.testRunner)
+    window.testRunner.notifyDone();
+}
+
+function runTest() {
+  let container = document.getElementById("container");
+  container.displayLock.acquire({ timeout: Infinity });
+
+  let child = document.createElement("div");
+  child.id = "child";
+  container.appendChild(child);
+
+  container.displayLock.updateAndCommit().then(
+    () => { finishTest("PASS"); },
+    () => { finishTest("FAIL"); });
+}
+
+window.onload = runTest;
+</script>
diff --git a/third_party/blink/web_tests/display-lock/lock-before-append/acquire-update-and-commit-expected.html b/third_party/blink/web_tests/display-lock/lock-before-append/acquire-update-and-commit-expected.html
new file mode 100644
index 0000000..9801e1a
--- /dev/null
+++ b/third_party/blink/web_tests/display-lock/lock-before-append/acquire-update-and-commit-expected.html
@@ -0,0 +1,18 @@
+<!doctype HTML>
+
+<style>
+#container {
+  contain: style layout;
+  width: 150px;
+  height: 150px;
+  background: lightblue;
+}
+#child {
+  width: 50px;
+  height: 50px;
+  background: lightgreen;
+}
+</style>
+
+<div id="log">PASS</div>
+<div id="container"><div id="child"></div></div>
diff --git a/third_party/blink/web_tests/display-lock/lock-before-append/acquire-update-and-commit.html b/third_party/blink/web_tests/display-lock/lock-before-append/acquire-update-and-commit.html
new file mode 100644
index 0000000..2ff1ffd
--- /dev/null
+++ b/third_party/blink/web_tests/display-lock/lock-before-append/acquire-update-and-commit.html
@@ -0,0 +1,52 @@
+<!doctype HTML>
+
+<!--
+Runs an acquire, appends a child, and calls updateAndCommit.
+-->
+
+<style>
+#container {
+  contain: style layout;
+  width: 150px;
+  height: 150px;
+  background: lightblue;
+}
+#child {
+  width: 50px;
+  height: 50px;
+  background: lightgreen;
+}
+</style>
+
+<div id="log"></div>
+
+<script>
+// TODO(vmpstr): In WPT this needs to be replaced with reftest-wait.
+if (window.testRunner)
+  window.testRunner.waitUntilDone();
+
+function finishTest(status_string) {
+  if (document.getElementById("log").innerHTML === "")
+    document.getElementById("log").innerHTML = status_string;
+  if (window.testRunner)
+    window.testRunner.notifyDone();
+}
+
+function runTest() {
+  let container = document.createElement("div");
+  container.displayLock.acquire({ timeout: Infinity }).then(() => {
+    let child = document.createElement("div");
+    child.id = "child";
+    container.appendChild(child);
+
+    container.id = "container";
+    document.body.appendChild(container);
+
+    container.displayLock.updateAndCommit().then(
+      () => { finishTest("PASS"); },
+      () => { finishTest("FAIL"); });
+  });
+}
+
+window.onload = runTest;
+</script>
diff --git a/third_party/blink/web_tests/display-lock/lock-before-append/commit-followed-by-update-and-commit-expected.html b/third_party/blink/web_tests/display-lock/lock-before-append/commit-followed-by-update-and-commit-expected.html
new file mode 100644
index 0000000..9801e1a
--- /dev/null
+++ b/third_party/blink/web_tests/display-lock/lock-before-append/commit-followed-by-update-and-commit-expected.html
@@ -0,0 +1,18 @@
+<!doctype HTML>
+
+<style>
+#container {
+  contain: style layout;
+  width: 150px;
+  height: 150px;
+  background: lightblue;
+}
+#child {
+  width: 50px;
+  height: 50px;
+  background: lightgreen;
+}
+</style>
+
+<div id="log">PASS</div>
+<div id="container"><div id="child"></div></div>
diff --git a/third_party/blink/web_tests/display-lock/lock-before-append/commit-followed-by-update-and-commit.html b/third_party/blink/web_tests/display-lock/lock-before-append/commit-followed-by-update-and-commit.html
new file mode 100644
index 0000000..ca6f5084
--- /dev/null
+++ b/third_party/blink/web_tests/display-lock/lock-before-append/commit-followed-by-update-and-commit.html
@@ -0,0 +1,48 @@
+<!doctype HTML>
+
+<style>
+#container {
+  contain: style layout;
+  width: 150px;
+  height: 150px;
+  background: lightblue;
+}
+#child {
+  width: 50px;
+  height: 50px;
+  background: lightgreen;
+}
+</style>
+
+<div id="log"></div>
+
+<script>
+// TODO(vmpstr): In WPT this needs to be replaced with reftest-wait.
+if (window.testRunner)
+  window.testRunner.waitUntilDone();
+
+function finishTest(status_string) {
+  if (document.getElementById("log").innerHTML === "")
+    document.getElementById("log").innerHTML = status_string;
+  if (window.testRunner)
+    window.testRunner.notifyDone();
+}
+
+function runTest() {
+  let container = document.createElement("div");
+  container.displayLock.acquire({ timeout: Infinity }).then(() => {
+    let child = document.createElement("div");
+    child.id = "child";
+    container.appendChild(child);
+
+    container.id = "container";
+    document.body.appendChild(container);
+
+    let first_promise = container.displayLock.commit();
+    let second_promise = container.displayLock.updateAndCommit();
+    Promise.all([first_promise, second_promise]).then(() => finishTest("PASS"));
+  });
+}
+
+window.onload = runTest;
+</script>
diff --git a/third_party/blink/web_tests/display-lock/lock-before-append/multiple-update-and-commit-expected.html b/third_party/blink/web_tests/display-lock/lock-before-append/multiple-update-and-commit-expected.html
new file mode 100644
index 0000000..9801e1a
--- /dev/null
+++ b/third_party/blink/web_tests/display-lock/lock-before-append/multiple-update-and-commit-expected.html
@@ -0,0 +1,18 @@
+<!doctype HTML>
+
+<style>
+#container {
+  contain: style layout;
+  width: 150px;
+  height: 150px;
+  background: lightblue;
+}
+#child {
+  width: 50px;
+  height: 50px;
+  background: lightgreen;
+}
+</style>
+
+<div id="log">PASS</div>
+<div id="container"><div id="child"></div></div>
diff --git a/third_party/blink/web_tests/display-lock/lock-before-append/multiple-update-and-commit.html b/third_party/blink/web_tests/display-lock/lock-before-append/multiple-update-and-commit.html
new file mode 100644
index 0000000..f881f209
--- /dev/null
+++ b/third_party/blink/web_tests/display-lock/lock-before-append/multiple-update-and-commit.html
@@ -0,0 +1,50 @@
+<!doctype HTML>
+
+<style>
+#container {
+  contain: style layout;
+  width: 150px;
+  height: 150px;
+  background: lightblue;
+}
+#child {
+  width: 50px;
+  height: 50px;
+  background: lightgreen;
+}
+</style>
+
+<div id="log"></div>
+
+<script>
+// TODO(vmpstr): In WPT this needs to be replaced with reftest-wait.
+if (window.testRunner)
+  window.testRunner.waitUntilDone();
+
+function finishTest(status_string) {
+  if (document.getElementById("log").innerHTML === "")
+    document.getElementById("log").innerHTML = status_string;
+  if (window.testRunner)
+    window.testRunner.notifyDone();
+}
+
+function runTest() {
+  let container = document.createElement("div");
+  container.displayLock.acquire({ timeout: Infinity }).then(() => {
+    let child = document.createElement("div");
+    child.id = "child";
+    container.appendChild(child);
+
+    container.id = "container";
+    document.body.appendChild(container);
+
+    let promises = []
+    for (let i = 0; i < 10; ++i) {
+      promises.push(container.displayLock.updateAndCommit());
+    }
+    Promise.all(promises).then(() => finishTest("PASS"));
+  });
+}
+
+window.onload = runTest;
+</script>
diff --git a/third_party/blink/web_tests/display-lock/lock-before-append/update-and-commit-expected.html b/third_party/blink/web_tests/display-lock/lock-before-append/update-and-commit-expected.html
new file mode 100644
index 0000000..a1a74ba
--- /dev/null
+++ b/third_party/blink/web_tests/display-lock/lock-before-append/update-and-commit-expected.html
@@ -0,0 +1,3 @@
+<!doctype HTML>
+
+<div id="log">PASS</div>
diff --git a/third_party/blink/web_tests/display-lock/lock-before-append/update-and-commit-followed-by-commit-expected.html b/third_party/blink/web_tests/display-lock/lock-before-append/update-and-commit-followed-by-commit-expected.html
new file mode 100644
index 0000000..9801e1a
--- /dev/null
+++ b/third_party/blink/web_tests/display-lock/lock-before-append/update-and-commit-followed-by-commit-expected.html
@@ -0,0 +1,18 @@
+<!doctype HTML>
+
+<style>
+#container {
+  contain: style layout;
+  width: 150px;
+  height: 150px;
+  background: lightblue;
+}
+#child {
+  width: 50px;
+  height: 50px;
+  background: lightgreen;
+}
+</style>
+
+<div id="log">PASS</div>
+<div id="container"><div id="child"></div></div>
diff --git a/third_party/blink/web_tests/display-lock/lock-before-append/update-and-commit-followed-by-commit.html b/third_party/blink/web_tests/display-lock/lock-before-append/update-and-commit-followed-by-commit.html
new file mode 100644
index 0000000..9e56578f
--- /dev/null
+++ b/third_party/blink/web_tests/display-lock/lock-before-append/update-and-commit-followed-by-commit.html
@@ -0,0 +1,48 @@
+<!doctype HTML>
+
+<style>
+#container {
+  contain: style layout;
+  width: 150px;
+  height: 150px;
+  background: lightblue;
+}
+#child {
+  width: 50px;
+  height: 50px;
+  background: lightgreen;
+}
+</style>
+
+<div id="log"></div>
+
+<script>
+// TODO(vmpstr): In WPT this needs to be replaced with reftest-wait.
+if (window.testRunner)
+  window.testRunner.waitUntilDone();
+
+function finishTest(status_string) {
+  if (document.getElementById("log").innerHTML === "")
+    document.getElementById("log").innerHTML = status_string;
+  if (window.testRunner)
+    window.testRunner.notifyDone();
+}
+
+function runTest() {
+  let container = document.createElement("div");
+  container.displayLock.acquire({ timeout: Infinity }).then(() => {
+    let child = document.createElement("div");
+    child.id = "child";
+    container.appendChild(child);
+
+    container.id = "container";
+    document.body.appendChild(container);
+
+    let first_promise = container.displayLock.updateAndCommit();
+    let second_promise = container.displayLock.commit();
+    Promise.all([first_promise, second_promise]).then(() => finishTest("PASS"));
+  });
+}
+
+window.onload = runTest;
+</script>
diff --git a/third_party/blink/web_tests/display-lock/lock-before-append/update-and-commit-followed-by-update-expected.html b/third_party/blink/web_tests/display-lock/lock-before-append/update-and-commit-followed-by-update-expected.html
new file mode 100644
index 0000000..9801e1a
--- /dev/null
+++ b/third_party/blink/web_tests/display-lock/lock-before-append/update-and-commit-followed-by-update-expected.html
@@ -0,0 +1,18 @@
+<!doctype HTML>
+
+<style>
+#container {
+  contain: style layout;
+  width: 150px;
+  height: 150px;
+  background: lightblue;
+}
+#child {
+  width: 50px;
+  height: 50px;
+  background: lightgreen;
+}
+</style>
+
+<div id="log">PASS</div>
+<div id="container"><div id="child"></div></div>
diff --git a/third_party/blink/web_tests/display-lock/lock-before-append/update-and-commit-followed-by-update.html b/third_party/blink/web_tests/display-lock/lock-before-append/update-and-commit-followed-by-update.html
new file mode 100644
index 0000000..f0d2b63
--- /dev/null
+++ b/third_party/blink/web_tests/display-lock/lock-before-append/update-and-commit-followed-by-update.html
@@ -0,0 +1,48 @@
+<!doctype HTML>
+
+<style>
+#container {
+  contain: style layout;
+  width: 150px;
+  height: 150px;
+  background: lightblue;
+}
+#child {
+  width: 50px;
+  height: 50px;
+  background: lightgreen;
+}
+</style>
+
+<div id="log"></div>
+
+<script>
+// TODO(vmpstr): In WPT this needs to be replaced with reftest-wait.
+if (window.testRunner)
+  window.testRunner.waitUntilDone();
+
+function finishTest(status_string) {
+  if (document.getElementById("log").innerHTML === "")
+    document.getElementById("log").innerHTML = status_string;
+  if (window.testRunner)
+    window.testRunner.notifyDone();
+}
+
+function runTest() {
+  let container = document.createElement("div");
+  container.displayLock.acquire({ timeout: Infinity }).then(() => {
+    let child = document.createElement("div");
+    child.id = "child";
+    container.appendChild(child);
+
+    container.id = "container";
+    document.body.appendChild(container);
+
+    let first_promise = container.displayLock.updateAndCommit();
+    let second_promise = container.displayLock.update();
+    Promise.all([first_promise, second_promise]).then(() => finishTest("PASS"));
+  });
+}
+
+window.onload = runTest;
+</script>
diff --git a/third_party/blink/web_tests/display-lock/lock-before-append/update-and-commit.html b/third_party/blink/web_tests/display-lock/lock-before-append/update-and-commit.html
new file mode 100644
index 0000000..0ea2539
--- /dev/null
+++ b/third_party/blink/web_tests/display-lock/lock-before-append/update-and-commit.html
@@ -0,0 +1,39 @@
+<!doctype HTML>
+
+<!--
+Runs an updateAndCommit() without acquiring, which fails.
+-->
+
+<style>
+#container {
+  contain: style layout;
+  width: 150px;
+  height: 150px;
+  background: lightblue;
+}
+</style>
+
+<div id="log"></div>
+
+<script>
+// TODO(vmpstr): In WPT this needs to be replaced with reftest-wait.
+if (window.testRunner)
+  window.testRunner.waitUntilDone();
+
+function finishTest(status_string) {
+  if (document.getElementById("log").innerHTML === "")
+    document.getElementById("log").innerHTML = status_string;
+  if (window.testRunner)
+    window.testRunner.notifyDone();
+}
+
+function runTest() {
+  let container = document.createElement("div");
+  container.id = "container";
+  container.displayLock.updateAndCommit().then(
+    () => { finishTest("FAIL"); },
+    () => { finishTest("PASS"); });
+}
+
+window.onload = runTest;
+</script>
diff --git a/third_party/blink/web_tests/display-lock/lock-before-append/update-followed-by-update-and-commit-expected.html b/third_party/blink/web_tests/display-lock/lock-before-append/update-followed-by-update-and-commit-expected.html
new file mode 100644
index 0000000..9801e1a
--- /dev/null
+++ b/third_party/blink/web_tests/display-lock/lock-before-append/update-followed-by-update-and-commit-expected.html
@@ -0,0 +1,18 @@
+<!doctype HTML>
+
+<style>
+#container {
+  contain: style layout;
+  width: 150px;
+  height: 150px;
+  background: lightblue;
+}
+#child {
+  width: 50px;
+  height: 50px;
+  background: lightgreen;
+}
+</style>
+
+<div id="log">PASS</div>
+<div id="container"><div id="child"></div></div>
diff --git a/third_party/blink/web_tests/display-lock/lock-before-append/update-followed-by-update-and-commit.html b/third_party/blink/web_tests/display-lock/lock-before-append/update-followed-by-update-and-commit.html
new file mode 100644
index 0000000..0e40582
--- /dev/null
+++ b/third_party/blink/web_tests/display-lock/lock-before-append/update-followed-by-update-and-commit.html
@@ -0,0 +1,48 @@
+<!doctype HTML>
+
+<style>
+#container {
+  contain: style layout;
+  width: 150px;
+  height: 150px;
+  background: lightblue;
+}
+#child {
+  width: 50px;
+  height: 50px;
+  background: lightgreen;
+}
+</style>
+
+<div id="log"></div>
+
+<script>
+// TODO(vmpstr): In WPT this needs to be replaced with reftest-wait.
+if (window.testRunner)
+  window.testRunner.waitUntilDone();
+
+function finishTest(status_string) {
+  if (document.getElementById("log").innerHTML === "")
+    document.getElementById("log").innerHTML = status_string;
+  if (window.testRunner)
+    window.testRunner.notifyDone();
+}
+
+function runTest() {
+  let container = document.createElement("div");
+  container.displayLock.acquire({ timeout: Infinity }).then(() => {
+    let child = document.createElement("div");
+    child.id = "child";
+    container.appendChild(child);
+
+    container.id = "container";
+    document.body.appendChild(container);
+
+    let first_promise = container.displayLock.update();
+    let second_promise = container.displayLock.updateAndCommit();
+    Promise.all([first_promise, second_promise]).then(() => finishTest("PASS"));
+  });
+}
+
+window.onload = runTest;
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/webaudio/resources/biquad-testing.js b/third_party/blink/web_tests/external/wpt/webaudio/resources/biquad-testing.js
index 7a0b6e6c..7f90a1f 100644
--- a/third_party/blink/web_tests/external/wpt/webaudio/resources/biquad-testing.js
+++ b/third_party/blink/web_tests/external/wpt/webaudio/resources/biquad-testing.js
@@ -5,15 +5,16 @@
 let renderedBuffer;
 let renderedData;
 
-let sampleRate = 44100.0;
+// Use a power of two to eliminate round-off in converting frame to time
+let sampleRate = 32768;
 let pulseLengthFrames = .1 * sampleRate;
 
 // Maximum allowed error for the test to succeed.  Experimentally determined.
 let maxAllowedError = 5.9e-8;
 
-// This must be large enough so that the filtered result is
-// essentially zero.  See comments for createTestAndRun.
-let timeStep = .1;
+// This must be large enough so that the filtered result is essentially zero.
+// See comments for createTestAndRun.  This must be a whole number of frames.
+let timeStep = Math.ceil(.1 * sampleRate) / sampleRate;
 
 // Maximum number of filters we can process (mostly for setting the
 // render length correctly.)
diff --git a/third_party/blink/web_tests/external/wpt/webaudio/resources/distance-model-testing.js b/third_party/blink/web_tests/external/wpt/webaudio/resources/distance-model-testing.js
index 1b9adde..f8a6cf9 100644
--- a/third_party/blink/web_tests/external/wpt/webaudio/resources/distance-model-testing.js
+++ b/third_party/blink/web_tests/external/wpt/webaudio/resources/distance-model-testing.js
@@ -1,10 +1,13 @@
-let sampleRate = 44100.0;
+// Use a power of two to eliminate round-off when converting frames to time and
+// vice versa.
+let sampleRate = 32768;
 
 // How many panner nodes to create for the test.
 let nodesToCreate = 100;
 
-// Time step when each panner node starts.
-let timeStep = 0.001;
+// Time step when each panner node starts.  Make sure it starts on a frame
+// boundary.
+let timeStep = Math.floor(0.001 * sampleRate) / sampleRate;
 
 // Make sure we render long enough to get all of our nodes.
 let renderLengthSeconds = timeStep * (nodesToCreate + 1);
@@ -134,7 +137,7 @@
   // The max allowed error between the actual gain and the expected
   // value.  This is determined experimentally.  Set to 0 to see
   // what the actual errors are.
-  let maxAllowedError = 3.3e-6;
+  let maxAllowedError = 2.2720e-6;
 
   let success = true;
 
diff --git a/third_party/blink/web_tests/external/wpt/webaudio/resources/note-grain-on-testing.js b/third_party/blink/web_tests/external/wpt/webaudio/resources/note-grain-on-testing.js
index 1e941897..ad06316 100644
--- a/third_party/blink/web_tests/external/wpt/webaudio/resources/note-grain-on-testing.js
+++ b/third_party/blink/web_tests/external/wpt/webaudio/resources/note-grain-on-testing.js
@@ -1,17 +1,23 @@
-let sampleRate = 44100.0;
+// Use a power of two to eliminate round-off converting from frames to time.
+let sampleRate = 32768;
 
 // How many grains to play.
 let numberOfTests = 100;
 
-// Duration of each grain to be played
-let duration = 0.01;
+// Duration of each grain to be played.  Make a whole number of frames
+let duration = Math.floor(0.01 * sampleRate) / sampleRate;
+
+// A little extra bit of silence between grain boundaries.  Must be a whole
+// number of frames.
+let grainGap = Math.floor(0.005 * sampleRate) / sampleRate;
 
 // Time step between the start of each grain.  We need to add a little
 // bit of silence so we can detect grain boundaries
-let timeStep = duration + .005;
+let timeStep = duration + grainGap;
 
-// Time step between the start for each grain.
-let grainOffsetStep = 0.001;
+// Time step between the start for each grain.  Must be a whole number of
+// frames.
+let grainOffsetStep = Math.floor(0.001 * sampleRate) / sampleRate;
 
 // How long to render to cover all of the grains.
 let renderTime = (numberOfTests + 1) * timeStep;
diff --git a/third_party/blink/web_tests/external/wpt/webaudio/resources/panner-model-testing.js b/third_party/blink/web_tests/external/wpt/webaudio/resources/panner-model-testing.js
index 662fb1d6..4df3e17 100644
--- a/third_party/blink/web_tests/external/wpt/webaudio/resources/panner-model-testing.js
+++ b/third_party/blink/web_tests/external/wpt/webaudio/resources/panner-model-testing.js
@@ -1,9 +1,12 @@
-let sampleRate = 44100.0;
+// Use a power of two to eliminate round-off when converting frames to time and
+// vice versa.
+let sampleRate = 32768;
 
 let numberOfChannels = 1;
 
-// Time step when each panner node starts.
-let timeStep = 0.001;
+// Time step when each panner node starts.  Make sure it starts on a frame
+// boundary.
+let timeStep = Math.floor(0.001 * sampleRate) / sampleRate;
 
 // Length of the impulse signal.
 let pulseLengthFrames = Math.round(timeStep * sampleRate);
@@ -114,7 +117,7 @@
   // The max error we allow between the rendered impulse and the
   // expected value.  This value is experimentally determined.  Set
   // to 0 to make the test fail to see what the actual error is.
-  let maxAllowedError = 1.3e-6;
+  let maxAllowedError = 1.1597e-6;
 
   let success = true;
 
diff --git a/third_party/blink/web_tests/external/wpt/webaudio/resources/stereopanner-testing.js b/third_party/blink/web_tests/external/wpt/webaudio/resources/stereopanner-testing.js
index d6238a9c..2778493 100644
--- a/third_party/blink/web_tests/external/wpt/webaudio/resources/stereopanner-testing.js
+++ b/third_party/blink/web_tests/external/wpt/webaudio/resources/stereopanner-testing.js
@@ -3,10 +3,12 @@
   // Constants
   let PI_OVER_TWO = Math.PI * 0.5;
 
-  let gSampleRate = 44100;
+  // Use a power of two to eliminate any round-off when converting frames to
+  // time.
+  let gSampleRate = 32768;
 
-  // Time step when each panner node starts.
-  let gTimeStep = 0.001;
+  // Time step when each panner node starts.  Make sure this is on a frame boundary.
+  let gTimeStep = Math.floor(0.001 * gSampleRate) / gSampleRate;
 
   // How many panner nodes to create for the test
   let gNodesToCreate = 100;
@@ -77,7 +79,7 @@
     // The max error we allow between the rendered impulse and the
     // expected value.  This value is experimentally determined.  Set
     // to 0 to make the test fail to see what the actual error is.
-    this.maxAllowedError = 1.3e-6;
+    this.maxAllowedError = 9.8015e-8;
 
     // Max (absolute) error and the index of the maxima for the left
     // and right channels.
diff --git a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audiobuffersourcenode-interface/audiobuffersource-playbackrate-zero.html b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audiobuffersourcenode-interface/audiobuffersource-playbackrate-zero.html
index 58ee49e..5624054e 100644
--- a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audiobuffersourcenode-interface/audiobuffersource-playbackrate-zero.html
+++ b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audiobuffersourcenode-interface/audiobuffersource-playbackrate-zero.html
@@ -74,6 +74,42 @@
             .then(() => task.done());
       });
 
+      audit.define('subsample start with playback rate 0', (task, should) => {
+        let context = new OfflineAudioContext(1, renderLength, sampleRate);
+        let rampBuffer = new AudioBuffer(
+            {length: renderLength, sampleRate: context.sampleRate});
+        let data = new Float32Array(renderLength);
+        let startValue = 5;
+        for (let k = 0; k < data.length; ++k) {
+          data[k] = k + startValue;
+        }
+        rampBuffer.copyToChannel(data, 0);
+
+        let src = new AudioBufferSourceNode(
+            context, {buffer: rampBuffer, playbackRate: 0});
+
+        src.connect(context.destination);
+
+        // Purposely start the source between frame boundaries
+        let startFrame = 27.3;
+        src.start(startFrame / context.sampleRate);
+
+        context.startRendering()
+            .then(audioBuffer => {
+              let actualStartFrame = Math.ceil(startFrame);
+              let audio = audioBuffer.getChannelData(0);
+
+              should(
+                  audio.slice(0, actualStartFrame),
+                  `output[0:${actualStartFrame - 1}]`)
+                  .beConstantValueOf(0);
+              should(
+                  audio.slice(actualStartFrame), `output[${actualStartFrame}:]`)
+                  .beConstantValueOf(startValue);
+            })
+            .then(() => task.done());
+      });
+
       audit.run();
     </script>
   </body>
diff --git a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audiobuffersourcenode-interface/resources/sub-sample-scheduling.html b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audiobuffersourcenode-interface/resources/sub-sample-scheduling.html
new file mode 100644
index 0000000..27ac098
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audiobuffersourcenode-interface/resources/sub-sample-scheduling.html
@@ -0,0 +1,423 @@
+<!doctype html>
+<html>
+  <head>
+    <title>
+      Test Sub-Sample Accurate Scheduling for ABSN
+    </title>
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="/webaudio/resources/audit-util.js"></script>
+    <script src="/webaudio/resources/audit.js"></script>
+  </head>
+  <body>
+    <script>
+      // Power of two so there's no roundoff converting from integer frames to
+      // time.
+      let sampleRate = 32768;
+
+      let audit = Audit.createTaskRunner();
+
+      audit.define('sub-sample accurate start', (task, should) => {
+        // There are two channels, one for each source.  Only need to render
+        // quanta for this test.
+        let context = new OfflineAudioContext(
+            {numberOfChannels: 2, length: 8192, sampleRate: sampleRate});
+        let merger = new ChannelMergerNode(
+            context, {numberOfInputs: context.destination.channelCount});
+
+        merger.connect(context.destination);
+
+        // Use a simple linear ramp for the sources with integer steps starting
+        // at 1 to make it easy to verify and test that have sub-sample accurate
+        // start.  Ramp starts at 1 so we can easily tell when the source
+        // starts.
+        let rampBuffer = new AudioBuffer(
+            {length: context.length, sampleRate: context.sampleRate});
+        let r = rampBuffer.getChannelData(0);
+        for (let k = 0; k < r.length; ++k) {
+          r[k] = k + 1;
+        }
+
+        const src0 = new AudioBufferSourceNode(context, {buffer: rampBuffer});
+        const src1 = new AudioBufferSourceNode(context, {buffer: rampBuffer});
+
+        // Frame where sources should start. This is pretty arbitrary, but one
+        // should be close to an integer and the other should be close to the
+        // next integer.  We do this to catch the case where rounding of the
+        // start frame is being done.  Rounding is incorrect.
+        const startFrame = 33;
+        const startFrame0 = startFrame + 0.1;
+        const startFrame1 = startFrame + 0.9;
+
+        src0.connect(merger, 0, 0);
+        src1.connect(merger, 0, 1);
+
+        src0.start(startFrame0 / context.sampleRate);
+        src1.start(startFrame1 / context.sampleRate);
+
+        context.startRendering()
+            .then(audioBuffer => {
+              const output0 = audioBuffer.getChannelData(0);
+              const output1 = audioBuffer.getChannelData(1);
+
+              // Compute the expected output by interpolating the ramp buffer of
+              // the sources if they started at the given frame.
+              const ramp = rampBuffer.getChannelData(0);
+              const expected0 = interpolateRamp(ramp, startFrame0);
+              const expected1 = interpolateRamp(ramp, startFrame1);
+
+              // Verify output0 has the correct values
+
+              // For information only
+              should(startFrame0, 'src0 start frame').beEqualTo(startFrame0);
+
+              // Output must be zero before the source start frame, and it must
+              // be interpolated correctly after the start frame.  The
+              // absoluteThreshold below is currently set for Chrome which does
+              // linear interpolation.  This needs to be updated eventually if
+              // other browsers do not user interpolation. 
+              should(
+                  output0.slice(0, startFrame + 1), `output0[0:${startFrame}]`)
+                  .beConstantValueOf(0);
+              should(
+                  output0.slice(startFrame + 1, expected0.length),
+                  `output0[${startFrame + 1}:${expected0.length - 1}]`)
+                  .beCloseToArray(
+                      expected0.slice(startFrame + 1), {absoluteThreshold: 0});
+
+              // Verify output1 has the correct values.  Same approach as for
+              // output0.
+              should(startFrame1, 'src1 start frame').beEqualTo(startFrame1);
+
+              should(
+                  output1.slice(0, startFrame + 1), `output1[0:${startFrame}]`)
+                  .beConstantValueOf(0);
+              should(
+                  output1.slice(startFrame + 1, expected1.length),
+                  `output1[${startFrame + 1}:${expected1.length - 1}]`)
+                  .beCloseToArray(
+                      expected1.slice(startFrame + 1), {absoluteThreshold: 0});
+            })
+            .then(() => task.done());
+      });
+
+      audit.define('sub-sample accurate stop', (task, should) => {
+        // There are threes channesl, one for each source.  Only need to render
+        // quanta for this test.
+        let context = new OfflineAudioContext(
+            {numberOfChannels: 3, length: 128, sampleRate: sampleRate});
+        let merger = new ChannelMergerNode(
+            context, {numberOfInputs: context.destination.channelCount});
+
+        merger.connect(context.destination);
+
+        // The source can be as simple constant for this test.
+        let buffer = new AudioBuffer(
+            {length: context.length, sampleRate: context.sampleRate});
+        buffer.getChannelData(0).fill(1);
+
+        const src0 = new AudioBufferSourceNode(context, {buffer: buffer});
+        const src1 = new AudioBufferSourceNode(context, {buffer: buffer});
+        const src2 = new AudioBufferSourceNode(context, {buffer: buffer});
+
+        // Frame where sources should start. This is pretty arbitrary, but one
+        // should be an integer, one should be close to an integer and the other
+        // should be close to the next integer.  This is to catch the case where
+        // rounding is used for the end frame.  Rounding is incorrect.
+        const endFrame = 33;
+        const endFrame1 = endFrame + 0.1;
+        const endFrame2 = endFrame + 0.9;
+
+        src0.connect(merger, 0, 0);
+        src1.connect(merger, 0, 1);
+        src2.connect(merger, 0, 2);
+
+        src0.start(0);
+        src1.start(0);
+        src2.start(0);
+        src0.stop(endFrame / context.sampleRate);
+        src1.stop(endFrame1 / context.sampleRate);
+        src2.stop(endFrame2 / context.sampleRate);
+
+        context.startRendering()
+          .then(audioBuffer => {
+            let actual0 = audioBuffer.getChannelData(0);
+            let actual1 = audioBuffer.getChannelData(1);
+            let actual2 = audioBuffer.getChannelData(2);
+
+            // Just verify that we stopped at the right time.
+
+            // This is case where the end frame is an integer.  Since the first
+            // output ends on an exact frame, the output must be zero at that
+            // frame number.  We print the end frame for information only; it
+            // makes interpretation of the rest easier.
+            should(endFrame - 1, 'src0 end frame')
+              .beEqualTo(endFrame - 1);
+            should(actual0[endFrame - 1], `output0[${endFrame - 1}]`)
+              .notBeEqualTo(0);
+            should(actual0.slice(endFrame),
+                   `output0[${endFrame}:]`)
+              .beConstantValueOf(0);
+
+            // The case where the end frame is just a little above an integer.
+            // The output must not be zero just before the end and must be zero
+            // after.
+            should(endFrame1, 'src1 end frame')
+              .beEqualTo(endFrame1);
+            should(actual1[endFrame], `output1[${endFrame}]`)
+              .notBeEqualTo(0);
+            should(actual1.slice(endFrame + 1),
+                   `output1[${endFrame + 1}:]`)
+              .beConstantValueOf(0);
+
+            // The case where the end frame is just a little below an integer.
+            // The output must not be zero just before the end and must be zero
+            // after.
+            should(endFrame2, 'src2 end frame')
+              .beEqualTo(endFrame2);
+            should(actual2[endFrame], `output2[${endFrame}]`)
+              .notBeEqualTo(0);
+            should(actual2.slice(endFrame + 1),
+                   `output2[${endFrame + 1}:]`)
+              .beConstantValueOf(0);
+          })
+          .then(() => task.done());
+      });
+
+      audit.define('sub-sample-grain', (task, should) => {
+        let context = new OfflineAudioContext(
+            {numberOfChannels: 2, length: 128, sampleRate: sampleRate});
+
+        let merger = new ChannelMergerNode(
+            context, {numberOfInputs: context.destination.channelCount});
+
+        merger.connect(context.destination);
+
+        // The source can be as simple constant for this test.
+        let buffer = new AudioBuffer(
+            {length: context.length, sampleRate: context.sampleRate});
+        buffer.getChannelData(0).fill(1);
+
+        let src0 = new AudioBufferSourceNode(context, {buffer: buffer});
+        let src1 = new AudioBufferSourceNode(context, {buffer: buffer});
+
+        src0.connect(merger, 0, 0);
+        src1.connect(merger, 0, 1);
+
+        // Start a short grain.
+        const src0StartGrain = 3.1;
+        const src0EndGrain = 37.2;
+        src0.start(
+            src0StartGrain / context.sampleRate, 0,
+            (src0EndGrain - src0StartGrain) / context.sampleRate);
+
+        const src1StartGrain = 5.8;
+        const src1EndGrain = 43.9;
+        src1.start(
+            src1StartGrain / context.sampleRate, 0,
+            (src1EndGrain - src1StartGrain) / context.sampleRate);
+
+        context.startRendering()
+            .then(audioBuffer => {
+              let output0 = audioBuffer.getChannelData(0);
+              let output1 = audioBuffer.getChannelData(1);
+
+              let expected = new Float32Array(context.length);
+
+              // Compute the expected output for output0 and verify the actual
+              // output matches.
+              expected.fill(1);
+              for (let k = 0; k <= Math.floor(src0StartGrain); ++k) {
+                expected[k] = 0;
+              }
+              for (let k = Math.ceil(src0EndGrain); k < expected.length; ++k) {
+                expected[k] = 0;
+              }
+
+              verifyGrain(should, output0, {
+                startGrain: src0StartGrain,
+                endGrain: src0EndGrain,
+                sourceName: 'src0',
+                outputName: 'output0'
+              });
+
+              verifyGrain(should, output1, {
+                startGrain: src1StartGrain,
+                endGrain: src1EndGrain,
+                sourceName: 'src1',
+                outputName: 'output1'
+              });
+            })
+            .then(() => task.done());
+      });
+
+      audit.define(
+          'sub-sample accurate start with playbackRate', (task, should) => {
+            // There are two channels, one for each source.  Only need to render
+            // quanta for this test.
+            let context = new OfflineAudioContext(
+                {numberOfChannels: 2, length: 8192, sampleRate: sampleRate});
+            let merger = new ChannelMergerNode(
+                context, {numberOfInputs: context.destination.channelCount});
+
+            merger.connect(context.destination);
+
+            // Use a simple linear ramp for the sources with integer steps
+            // starting at 1 to make it easy to verify and test that have
+            // sub-sample accurate start.  Ramp starts at 1 so we can easily
+            // tell when the source starts.
+            let buffer = new AudioBuffer(
+                {length: context.length, sampleRate: context.sampleRate});
+            let r = buffer.getChannelData(0);
+            for (let k = 0; k < r.length; ++k) {
+              r[k] = k + 1;
+            }
+
+            // Two sources with different playback rates
+            const src0 = new AudioBufferSourceNode(
+                context, {buffer: buffer, playbackRate: .25});
+            const src1 = new AudioBufferSourceNode(
+                context, {buffer: buffer, playbackRate: 4});
+
+            // Frame where sources start.  Pretty arbitrary but should not be an
+            // integer.
+            const startFrame = 17.8;
+
+            src0.connect(merger, 0, 0);
+            src1.connect(merger, 0, 1);
+
+            src0.start(startFrame / context.sampleRate);
+            src1.start(startFrame / context.sampleRate);
+
+            context.startRendering()
+                .then(audioBuffer => {
+                  const output0 = audioBuffer.getChannelData(0);
+                  const output1 = audioBuffer.getChannelData(1);
+
+                  const frameBefore = Math.floor(startFrame);
+                  const frameAfter = frameBefore + 1;
+
+                  // Informative message so we know what the following output
+                  // indices really mean.
+                  should(startFrame, 'Source start frame')
+                      .beEqualTo(startFrame);
+
+                  // Verify the output
+
+                  // With a startFrame of 17.8, the first output is at frame 18,
+                  // but the actual start is at 17.8.  So we would interpolate
+                  // the output 0.2 fraction of the way between 17.8 and 18, for
+                  // an output of 1.2 for our ramp.  But the playback rate is
+                  // 0.25, so we're really only 1/4 as far along as we think so
+                  // the output is .2*0.25 of the way between 1 and 2 or 1.05.
+
+                  const ramp0 = buffer.getChannelData(0)[0];
+                  const ramp1 = buffer.getChannelData(0)[1];
+
+                  const src0Output = ramp0 +
+                      (ramp1 - ramp0) * (frameAfter - startFrame) *
+                          src0.playbackRate.value;
+
+                  let playbackMessage =
+                      `With playbackRate ${src0.playbackRate.value}:`;
+
+                  should(
+                      output0[frameBefore],
+                      `${playbackMessage} output0[${frameBefore}]`)
+                      .beEqualTo(0);
+                  should(
+                      output0[frameAfter],
+                      `${playbackMessage} output0[${frameAfter}]`)
+                      .beCloseTo(src0Output, {threshold: 4.542e-8});
+
+                  const src1Output = ramp0 +
+                      (ramp1 - ramp0) * (frameAfter - startFrame) *
+                          src1.playbackRate.value;
+
+                  playbackMessage =
+                      `With playbackRate ${src1.playbackRate.value}:`;
+
+                  should(
+                      output1[frameBefore],
+                      `${playbackMessage} output1[${frameBefore}]`)
+                      .beEqualTo(0);
+                  should(
+                      output1[frameAfter],
+                      `${playbackMessage} output1[${frameAfter}]`)
+                      .beCloseTo(src1Output, {threshold: 4.542e-8});
+                })
+                .then(() => task.done());
+          });
+
+      audit.run();
+
+      // Given an input ramp in |rampBuffer|, interpolate the signal assuming
+      // this ramp is used for an ABSN that starts at frame |startFrame|, which
+      // is not necessarily an integer.  For simplicity we just use linear
+      // interpolation here.  The interpolation is not part of the spec but
+      // this should be pretty close to whatever interpolation is being done.
+      function interpolateRamp(rampBuffer, startFrame) {
+        // |start| is the last zero sample before the ABSN actually starts.
+        const start = Math.floor(startFrame);
+        // One less than the rampBuffer because we can't linearly interpolate
+        // the last frame.
+        let result = new Float32Array(rampBuffer.length - 1);
+
+        for (let k = 0; k <= start; ++k) {
+          result[k] = 0;
+        }
+
+        // Now start linear interpolation.
+        let frame = startFrame;
+        let index = 1;
+        for (let k = start + 1; k < result.length; ++k) {
+          let s0 = rampBuffer[index];
+          let s1 = rampBuffer[index - 1];
+          let delta = frame - k;
+          let s = s1 - delta * (s0 - s1);
+          result[k] = s;
+          ++frame;
+          ++index;
+        }
+
+        return result;
+      }
+
+      function verifyGrain(should, output, options) {
+        let {startGrain, endGrain, sourceName, outputName} = options;
+        let expected = new Float32Array(output.length);
+        // Compute the expected output for output and verify the actual
+        // output matches.
+        expected.fill(1);
+        for (let k = 0; k <= Math.floor(startGrain); ++k) {
+          expected[k] = 0;
+        }
+        for (let k = Math.ceil(endGrain); k < expected.length; ++k) {
+          expected[k] = 0;
+        }
+
+        should(startGrain, `${sourceName} grain start`).beEqualTo(startGrain);
+        should(endGrain - startGrain, `${sourceName} grain duration`)
+            .beEqualTo(endGrain - startGrain);
+        should(endGrain, `${sourceName} grain end`).beEqualTo(endGrain);
+        should(output, outputName).beEqualToArray(expected);
+        should(
+            output[Math.floor(startGrain)],
+            `${outputName}[${Math.floor(startGrain)}]`)
+            .beEqualTo(0);
+        should(
+            output[1 + Math.floor(startGrain)],
+            `${outputName}[${1 + Math.floor(startGrain)}]`)
+            .notBeEqualTo(0);
+        should(
+            output[Math.floor(endGrain)],
+            `${outputName}[${Math.floor(endGrain)}]`)
+            .notBeEqualTo(0);
+        should(
+            output[1 + Math.floor(endGrain)],
+            `${outputName}[${1 + Math.floor(endGrain)}]`)
+            .beEqualTo(0);
+      }
+    </script>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audionode-interface/audionode-channel-rules.html b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audionode-interface/audionode-channel-rules.html
index a1c3273..9067e68 100644
--- a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audionode-interface/audionode-channel-rules.html
+++ b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audionode-interface/audionode-channel-rules.html
@@ -14,7 +14,8 @@
     <script id="layout-test-code">
       let audit = Audit.createTaskRunner();
       let context = 0;
-      let sampleRate = 44100;
+      // Use a power of two to eliminate round-off converting frames to time.
+      let sampleRate = 32768;
       let renderNumberOfChannels = 8;
       let singleTestFrameLength = 8;
       let testBuffers;
diff --git a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-lowpass.html b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-lowpass.html
index d20786e..69dc85a 100644
--- a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-lowpass.html
+++ b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-biquadfilternode-interface/biquad-lowpass.html
@@ -34,7 +34,7 @@
 
             createTestAndRun(context, 'lowpass', {
               should: should,
-              threshold: 9.7869e-8,
+              threshold: 4.6943e-8,
               filterParameters: filterParameters
             }).then(task.done.bind(task));
           });
diff --git a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-gainnode-interface/gain.html b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-gainnode-interface/gain.html
index cff609de..c1ee024 100644
--- a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-gainnode-interface/gain.html
+++ b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-gainnode-interface/gain.html
@@ -18,11 +18,19 @@
 
       let audit = Audit.createTaskRunner();
 
-      let sampleRate = 44100.0;
-      let bufferDurationSeconds = 0.125;
+      // Use a power of two to eliminate any round-off when converting frame to
+      // time.
+      let sampleRate = 32768;
+      // Make sure the buffer duration and spacing are all exact frame lengths
+      // so that the note spacing is also on frame boundaries to eliminate
+      // sub-sample accurate start of a ABSN.
+      let bufferDurationSeconds = Math.floor(0.125 * sampleRate) / sampleRate;
       let numberOfNotes = 11;
-      let noteSpacing = bufferDurationSeconds +
-          0.020;  // leave 20ms of silence between each "note"
+      // Leave about 20ms of silence, being sure this is an exact frame
+      // duration.
+      let noteSilence = Math.floor(0.020 * sampleRate) / sampleRate;
+      let noteSpacing = bufferDurationSeconds + noteSilence;
+          
       let lengthInSeconds = numberOfNotes * noteSpacing;
 
       let context = 0;
@@ -131,18 +139,18 @@
                   // Verify the channels are clsoe to the reference.
                   should(actual0, 'Left output from gain node')
                       .beCloseToArray(
-                          reference0, {relativeThreshold: 1.1908e-7});
+                          reference0, {relativeThreshold: 1.1877e-7});
                   should(actual1, 'Right output from gain node')
                       .beCloseToArray(
-                          reference1, {relativeThreshold: 1.1908e-7});
+                          reference1, {relativeThreshold: 1.1877e-7});
 
                   // Test the SNR too for both channels.
                   let snr0 = 10 * Math.log10(computeSNR(actual0, reference0));
                   let snr1 = 10 * Math.log10(computeSNR(actual1, reference1));
                   should(snr0, 'Left SNR (in dB)')
-                      .beGreaterThanOrEqualTo(148.69);
+                      .beGreaterThanOrEqualTo(148.71);
                   should(snr1, 'Right SNR (in dB)')
-                      .beGreaterThanOrEqualTo(148.69);
+                      .beGreaterThanOrEqualTo(148.71);
                 })
                 .then(() => task.done());
             ;
diff --git a/third_party/blink/web_tests/fast/css/rem-display-none-crash.html b/third_party/blink/web_tests/fast/css/rem-display-none-crash.html
new file mode 100644
index 0000000..8b9cd6a9
--- /dev/null
+++ b/third_party/blink/web_tests/fast/css/rem-display-none-crash.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<style>
+  :root { font-size: 10px }
+  div { font-size: 2rem }
+</style>
+<div id="rem">2rem</div>
+<script>
+  test(() => {
+    document.documentElement.offsetTop;
+    document.documentElement.style.display = "none";
+      assert_equals(getComputedStyle(rem).fontSize, "20px");
+  }, "Root element set to display:none should not crash updating rem units.");
+</script>
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/http/tests/devtools/layers/layer-tree-model-expected.txt b/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/http/tests/devtools/layers/layer-tree-model-expected.txt
new file mode 100644
index 0000000..fdc330c
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/http/tests/devtools/layers/layer-tree-model-expected.txt
@@ -0,0 +1,26 @@
+Tests general layer tree model functionality
+Initial layer tree
+<invalid node id> 0x0 (0)
+    #document (1)
+    div#a 200x200 (1)
+    div#b1 100x150 (1)
+    div#b2 110x140 (1)
+    div#c 90x100 (1)
+    div#b3 110x140 (1)
+Updated layer tree
+<invalid node id> 0x0 (0)
+    #document (1)
+    div#a 200x200 (1)
+    div#b2 110x140 (1)
+    div#c 90x100
+    div#b1 100x150
+    div#b4 88x77
+Updated layer geometry
+<invalid node id> 0x0 (0)
+    #document (1)
+    div#a 200x200 (1)
+    div#b2 110x140 (1)
+    div#c 90x80
+    div#b1 100x150
+    div#b4 88x77
+
diff --git a/third_party/blink/web_tests/http/tests/devtools/layers/layer-tree-model-expected.txt b/third_party/blink/web_tests/http/tests/devtools/layers/layer-tree-model-expected.txt
index defb215..0f7cb832 100644
--- a/third_party/blink/web_tests/http/tests/devtools/layers/layer-tree-model-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/layers/layer-tree-model-expected.txt
@@ -25,10 +25,10 @@
     #document 200x200 (1)
     div#subframe1 80x80 (1)
     div#a 200x200 (1)
-    div#b1 100x150 (1)
     div#b2 110x140 (1)
     div#c 90x100 (1)
-    div#b3 110x140 (1)
+    div#b1 100x150
+    div#b4 88x77
 Updated layer geometry
 <invalid node id> 0x0 (0)
     <invalid node id> (1)
@@ -43,5 +43,5 @@
     div#b2 110x140 (1)
     div#c 90x80 (1)
     div#b1 100x150
-    div#b4 0x0
+    div#b4 88x77
 
diff --git a/third_party/blink/web_tests/http/tests/devtools/layers/layer-tree-model.js b/third_party/blink/web_tests/http/tests/devtools/layers/layer-tree-model.js
index b45a87e6..c48489f 100644
--- a/third_party/blink/web_tests/http/tests/devtools/layers/layer-tree-model.js
+++ b/third_party/blink/web_tests/http/tests/devtools/layers/layer-tree-model.js
@@ -10,10 +10,11 @@
       .layer {
           transform: translateZ(10px);
           opacity: 0.8;
+          background: blue;
       }
       #frame {
-        width: 200px;
-        height: 200px;
+          width: 200px;
+          height: 200px;
       }
       </style>
       <div id="a" style="width: 200px; height: 200px" class="layer">
@@ -34,6 +35,8 @@
           var b4 = document.createElement("div");
           b4.id = "b4";
           b4.className = "layer";
+          b4.style.width = "77px";
+          b4.style.height = "88px";
           document.getElementById("a").appendChild(b4);
       }
 
diff --git a/third_party/blink/web_tests/http/tests/devtools/layers/no-overlay-layers.js b/third_party/blink/web_tests/http/tests/devtools/layers/no-overlay-layers.js
index eed66a14..78b1647 100644
--- a/third_party/blink/web_tests/http/tests/devtools/layers/no-overlay-layers.js
+++ b/third_party/blink/web_tests/http/tests/devtools/layers/no-overlay-layers.js
@@ -11,6 +11,7 @@
       .layer {
           transform: translateZ(10px);
           opacity: 0.8;
+          background: blue;
       }
       </style>
       <div id="a" style="width: 200px; height: 200px" class="layer">
diff --git a/third_party/blink/web_tests/http/tests/devtools/layers/resources/compositing-reasons.html b/third_party/blink/web_tests/http/tests/devtools/layers/resources/compositing-reasons.html
index 26102d18c..d67c7080 100644
--- a/third_party/blink/web_tests/http/tests/devtools/layers/resources/compositing-reasons.html
+++ b/third_party/blink/web_tests/http/tests/devtools/layers/resources/compositing-reasons.html
@@ -10,6 +10,12 @@
     50%  { rotate: 180deg; }
     100%  { rotate: 360deg; }
 }
+div {
+    /* to avoid empty layers */
+    min-width: 100px;
+    min-height: 20px;
+    background: blue;
+}
 </style>
 <script type="application/x-javascript">
   if (window.internals)
diff --git a/third_party/blink/web_tests/http/tests/devtools/wasm-isolated-code-cache/wasm-cache-test-expected.txt b/third_party/blink/web_tests/http/tests/devtools/wasm-isolated-code-cache/wasm-cache-test-expected.txt
new file mode 100644
index 0000000..4b10d7a
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/devtools/wasm-isolated-code-cache/wasm-cache-test-expected.txt
@@ -0,0 +1,88 @@
+Tests V8 code cache for WebAssembly resources.
+
+---First navigation - produce and consume code cache ------
+
+v8.wasm.streamFromResponseCallback Properties:
+{
+    data : {
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.streamFromResponseCallback"
+}
+v8.wasm.streamFromResponseCallback Properties:
+{
+    data : {
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.streamFromResponseCallback"
+}
+v8.wasm.streamFromResponseCallback Properties:
+{
+    data : {
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.streamFromResponseCallback"
+}
+v8.wasm.streamFromResponseCallback Properties:
+{
+    data : {
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.streamFromResponseCallback"
+}
+v8.wasm.streamFromResponseCallback Properties:
+{
+    data : {
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.streamFromResponseCallback"
+}
+
+--- Second navigation - from a different origin ------
+
+v8.wasm.streamFromResponseCallback Properties:
+{
+    data : {
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.streamFromResponseCallback"
+}
+v8.wasm.streamFromResponseCallback Properties:
+{
+    data : {
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.streamFromResponseCallback"
+}
+v8.wasm.streamFromResponseCallback Properties:
+{
+    data : {
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.streamFromResponseCallback"
+}
+v8.wasm.streamFromResponseCallback Properties:
+{
+    data : {
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.streamFromResponseCallback"
+}
+v8.wasm.streamFromResponseCallback Properties:
+{
+    data : {
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.streamFromResponseCallback"
+}
+
diff --git a/third_party/blink/web_tests/http/tests/devtools/wasm-isolated-code-cache/wasm-cache-test.js b/third_party/blink/web_tests/http/tests/devtools/wasm-isolated-code-cache/wasm-cache-test.js
new file mode 100644
index 0000000..8d90680
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/devtools/wasm-isolated-code-cache/wasm-cache-test.js
@@ -0,0 +1,85 @@
+// 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.
+
+(async function() {
+  TestRunner.addResult(`Tests V8 code cache for WebAssembly resources.\n`);
+  await TestRunner.loadModule('performance_test_runner');
+  await TestRunner.showPanel('timeline');
+
+  // Clear browser cache to avoid any existing entries for the fetched
+  // scripts in the cache.
+  SDK.multitargetNetworkManager.clearBrowserCache();
+
+  function runTests() {
+    // Loads a WASM module that is smaller than the threshold twice. It should
+    // compile and not be cached.
+    function loadSmallWasmModule(iframe_window) {
+      const url = 'http://127.0.0.1:8000/wasm/resources/load-wasm.php?name=small.wasm&cors'
+      return iframe_window.instantiateModule(url)
+        .then(() => iframe_window.instantiateModule(url));
+    }
+    // Loads a WASM module that is larger than the caching threshold. It
+    // should be cached the first run and bypass compilation the second.
+    function loadLargeWasmModule(iframe_window) {
+      const url = 'http://127.0.0.1:8000/wasm/resources/load-wasm.php?name=large.wasm&cors'
+      return iframe_window.instantiateModule(url)
+        .then(() => iframe_window.instantiateModule(url));
+    }
+    // Load the same large WASM module with a different URL. It should miss
+    // the cache, compile and be cached.
+    function loadOtherLargeWasmModule(iframe_window) {
+      const url = 'http://localhost:8000/wasm/resources/load-wasm.php?name=large.wasm&cors'
+      return iframe_window.instantiateModule(url);
+    }
+
+    let script = document.createElement('script');
+    script.type = 'module';
+    script.text = 'window.finishTest()';
+    document.body.appendChild(script);
+
+    const frameId = 'frame_id';
+    const iframe_window = document.getElementById(frameId).contentWindow;
+
+    // These functions must be called in this order.
+    return loadSmallWasmModule(iframe_window)
+      .then(() => loadLargeWasmModule(iframe_window))
+      .then(() => loadOtherLargeWasmModule(iframe_window));
+  }
+
+  await TestRunner.evaluateInPagePromise(runTests.toString());
+
+  TestRunner.addResult(
+      '---First navigation - produce and consume code cache ------\n');
+
+  // Create a same origin iframe.
+  const scope = 'http://127.0.0.1:8000/wasm/resources/wasm-cache-iframe.html';
+  await TestRunner.addIframe(scope, {id: 'frame_id'});
+
+  await PerformanceTestRunner.invokeAsyncWithTimeline('runTests');
+
+  const events = new Set([
+    TimelineModel.TimelineModel.RecordType.WasmStreamFromResponseCallback,
+    TimelineModel.TimelineModel.RecordType.WasmCompiledModule,
+    TimelineModel.TimelineModel.RecordType.WasmCachedModule,
+    TimelineModel.TimelineModel.RecordType.WasmModuleCacheHit,
+    TimelineModel.TimelineModel.RecordType.WasmModuleCacheInvalid]);
+  const tracingModel = PerformanceTestRunner.tracingModel();
+
+  tracingModel.sortedProcesses().forEach(p => p.sortedThreads().forEach(t =>
+      t.events().filter(event => events.has(event.name)).forEach(PerformanceTestRunner.printTraceEventProperties)));
+
+  // Second navigation
+  TestRunner.addResult(
+      '\n--- Second navigation - from a different origin ------\n');
+
+  const other_scope = 'http://localhost:8000/wasm/resources/wasm-cache-iframe.html';
+  await TestRunner.addIframe(other_scope, {id: 'frame_id'});
+
+  await PerformanceTestRunner.invokeAsyncWithTimeline('runTests');
+
+  tracingModel.sortedProcesses().forEach(p => p.sortedThreads().forEach(t =>
+      t.events().filter(event => events.has(event.name)).forEach(PerformanceTestRunner.printTraceEventProperties)));
+
+  TestRunner.completeTest();
+})();
diff --git a/third_party/blink/web_tests/http/tests/wasm/resources/large.wasm b/third_party/blink/web_tests/http/tests/wasm/resources/large.wasm
new file mode 100644
index 0000000..fe5e206
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/wasm/resources/large.wasm
Binary files differ
diff --git a/third_party/blink/web_tests/http/tests/wasm/resources/load-wasm.php b/third_party/blink/web_tests/http/tests/wasm/resources/load-wasm.php
index 9b1ac01..14967b94 100644
--- a/third_party/blink/web_tests/http/tests/wasm/resources/load-wasm.php
+++ b/third_party/blink/web_tests/http/tests/wasm/resources/load-wasm.php
@@ -4,7 +4,16 @@
     if (isset($_GET['name'])) {
        $fileName = $_GET['name'];
     }
+    $last_modified = "Tue, 18 Dec 2018 23:15:53 GMT";
+    if (isset($_GET['last-modified'])) {
+       $last_modified = $_GET['last-modified'];
+    }
 
     header("Content-Type: application/wasm");
+    header("Last-Modified: ".$last_modified);
+
+    if (isset($_GET['cors'])) {
+      header("Access-Control-Allow-Origin: *");
+    }
     require($fileName);
 ?>
diff --git a/third_party/blink/web_tests/http/tests/wasm/resources/small.wasm b/third_party/blink/web_tests/http/tests/wasm/resources/small.wasm
new file mode 100644
index 0000000..f8b2262
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/wasm/resources/small.wasm
Binary files differ
diff --git a/third_party/blink/web_tests/http/tests/wasm/resources/wasm-cache-iframe.html b/third_party/blink/web_tests/http/tests/wasm/resources/wasm-cache-iframe.html
new file mode 100644
index 0000000..8a62a7aa
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/wasm/resources/wasm-cache-iframe.html
@@ -0,0 +1,10 @@
+<script src="../../../resources/testharness.js"></script>
+<script>
+// Instantiate a WASM test module and make sure it works.
+function instantiateModule(url)
+{
+  return WebAssembly.instantiateStreaming(fetch(url)).then(
+  	({module, instance}) => assert_equals(instance.exports.exported_func(), 42));
+}
+</script>
+
diff --git a/third_party/WebKit/LayoutTests/media/controls/accessibility-volume-slider.html b/third_party/blink/web_tests/media/controls/accessibility-volume-slider.html
similarity index 100%
rename from third_party/WebKit/LayoutTests/media/controls/accessibility-volume-slider.html
rename to third_party/blink/web_tests/media/controls/accessibility-volume-slider.html
diff --git a/third_party/blink/web_tests/virtual/wasm-site-isolated-code-cache/http/tests/devtools/wasm-isolated-code-cache/README.txt b/third_party/blink/web_tests/virtual/wasm-site-isolated-code-cache/http/tests/devtools/wasm-isolated-code-cache/README.txt
new file mode 100644
index 0000000..4ebf844c
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/wasm-site-isolated-code-cache/http/tests/devtools/wasm-isolated-code-cache/README.txt
@@ -0,0 +1,4 @@
+# This suite runs the tests in http/tests/devtools/wasm-isolated-code-cache with
+# --enable-features=IsolatedCodeCache,WasmCodeCache --site-per-process.
+# This feature is required for security to enforce site isolation on V8
+# code caches. Tracking bug: crbug.com/812168
diff --git a/third_party/blink/web_tests/virtual/wasm-site-isolated-code-cache/http/tests/devtools/wasm-isolated-code-cache/wasm-cache-test-expected.txt b/third_party/blink/web_tests/virtual/wasm-site-isolated-code-cache/http/tests/devtools/wasm-isolated-code-cache/wasm-cache-test-expected.txt
new file mode 100644
index 0000000..c0d97ff
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/wasm-site-isolated-code-cache/http/tests/devtools/wasm-isolated-code-cache/wasm-cache-test-expected.txt
@@ -0,0 +1,216 @@
+Tests V8 code cache for WebAssembly resources.
+
+---First navigation - produce and consume code cache ------
+
+v8.wasm.streamFromResponseCallback Properties:
+{
+    data : {
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.streamFromResponseCallback"
+}
+v8.wasm.compiledModule Properties:
+{
+    data : {
+        url : http://127.0.0.1:8000/wasm/resources/load-wasm.php?name=small.wasm&cors
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.compiledModule"
+}
+v8.wasm.streamFromResponseCallback Properties:
+{
+    data : {
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.streamFromResponseCallback"
+}
+v8.wasm.compiledModule Properties:
+{
+    data : {
+        url : http://127.0.0.1:8000/wasm/resources/load-wasm.php?name=small.wasm&cors
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.compiledModule"
+}
+v8.wasm.streamFromResponseCallback Properties:
+{
+    data : {
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.streamFromResponseCallback"
+}
+v8.wasm.compiledModule Properties:
+{
+    data : {
+        url : http://127.0.0.1:8000/wasm/resources/load-wasm.php?name=large.wasm&cors
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.compiledModule"
+}
+v8.wasm.cachedModule Properties:
+{
+    data : {
+        producedCacheSize : <number>
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.cachedModule"
+}
+v8.wasm.streamFromResponseCallback Properties:
+{
+    data : {
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.streamFromResponseCallback"
+}
+v8.wasm.moduleCacheHit Properties:
+{
+    data : {
+        consumedCacheSize : <number>
+        url : http://127.0.0.1:8000/wasm/resources/load-wasm.php?name=large.wasm&cors
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.moduleCacheHit"
+}
+v8.wasm.streamFromResponseCallback Properties:
+{
+    data : {
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.streamFromResponseCallback"
+}
+v8.wasm.compiledModule Properties:
+{
+    data : {
+        url : http://localhost:8000/wasm/resources/load-wasm.php?name=large.wasm&cors
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.compiledModule"
+}
+v8.wasm.cachedModule Properties:
+{
+    data : {
+        producedCacheSize : <number>
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.cachedModule"
+}
+
+--- Second navigation - from a different origin ------
+
+v8.wasm.streamFromResponseCallback Properties:
+{
+    data : {
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.streamFromResponseCallback"
+}
+v8.wasm.compiledModule Properties:
+{
+    data : {
+        url : http://127.0.0.1:8000/wasm/resources/load-wasm.php?name=small.wasm&cors
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.compiledModule"
+}
+v8.wasm.streamFromResponseCallback Properties:
+{
+    data : {
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.streamFromResponseCallback"
+}
+v8.wasm.compiledModule Properties:
+{
+    data : {
+        url : http://127.0.0.1:8000/wasm/resources/load-wasm.php?name=small.wasm&cors
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.compiledModule"
+}
+v8.wasm.streamFromResponseCallback Properties:
+{
+    data : {
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.streamFromResponseCallback"
+}
+v8.wasm.compiledModule Properties:
+{
+    data : {
+        url : http://127.0.0.1:8000/wasm/resources/load-wasm.php?name=large.wasm&cors
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.compiledModule"
+}
+v8.wasm.cachedModule Properties:
+{
+    data : {
+        producedCacheSize : <number>
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.cachedModule"
+}
+v8.wasm.streamFromResponseCallback Properties:
+{
+    data : {
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.streamFromResponseCallback"
+}
+v8.wasm.moduleCacheHit Properties:
+{
+    data : {
+        consumedCacheSize : <number>
+        url : http://127.0.0.1:8000/wasm/resources/load-wasm.php?name=large.wasm&cors
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.moduleCacheHit"
+}
+v8.wasm.streamFromResponseCallback Properties:
+{
+    data : {
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.streamFromResponseCallback"
+}
+v8.wasm.compiledModule Properties:
+{
+    data : {
+        url : http://localhost:8000/wasm/resources/load-wasm.php?name=large.wasm&cors
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.compiledModule"
+}
+v8.wasm.cachedModule Properties:
+{
+    data : {
+        producedCacheSize : <number>
+    }
+    endTime : <number>
+    startTime : <number>
+    type : "v8.wasm.cachedModule"
+}
+
diff --git a/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-loop-comprehensive.html b/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-loop-comprehensive.html
index ab332e7..476df826 100644
--- a/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-loop-comprehensive.html
+++ b/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-loop-comprehensive.html
@@ -224,7 +224,7 @@
 
       ];
 
-      let sampleRate = 44100;
+      let sampleRate = 32768;
       let buffer;
       let bufferFrameLength = 8;
       let testSpacingFrames = 32;
diff --git a/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-loop-points.html b/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-loop-points.html
index d50c6fc6a..d63eff8 100644
--- a/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-loop-points.html
+++ b/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-loop-points.html
@@ -11,16 +11,22 @@
     <script src="../../resources/testharnessreport.js"></script>
     <script src="../resources/audit-util.js"></script>
     <script src="../resources/audit.js"></script>
+    <script src="../resources/audio-file-utils.js"></script>
   </head>
   <body>
     <script id="layout-test-code">
       let audit = Audit.createTaskRunner();
 
-      let sampleRate = 44100.0;
+      // Use power of two to eliminate round-off in computing frames from time
+      // and vice versa. 
+      let sampleRate = 32768;
       let numberOfNotes = 60;  // play over a 5 octave range
-      let noteDuration = 0.025;
-      let noteSpacing =
-          noteDuration + 0.005;  // leave 5ms of silence between each "note"
+      // Make sure noteDuration, noteSilence, and noteSpacing are exactly whole
+      // frames.
+      let noteDuration = Math.floor(0.025 * sampleRate) / sampleRate;
+      // Leave about 5ms of silence between each "note"
+      let noteSilence = Math.floor(0.005 * sampleRate) / sampleRate;
+      let noteSpacing = noteDuration + noteSilence;
       let lengthInSeconds = numberOfNotes * noteSpacing;
 
       let context = 0;
@@ -139,7 +145,7 @@
                   // in ULP. This is experimentally determined. Assuming that
                   // the reference file is a 16-bit wav file, the max values in
                   // the wave file are +/- 32768.
-                  let maxUlp = 0.9999;
+                  let maxUlp = 0;
                   let threshold = maxUlp / 32768;
 
                   for (let k = 0; k < renderedAudio.numberOfChannels; ++k) {
@@ -150,6 +156,11 @@
                             expectedAudio.getChannelData(k),
                             {absoluteThreshold: threshold});
                   }
+
+                  const filename = 'audiobuffersource-loop-points-actual.wav';
+                  if (downloadAudioBuffer(renderedAudio, filename, true)) {
+                    should(true, 'Saved reference file').message(filename, '');
+                  }
                 })
                 .then(() => task.done());
           });
diff --git a/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-playbackrate.html b/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-playbackrate.html
index a0084ae..b3b531d 100644
--- a/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-playbackrate.html
+++ b/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-playbackrate.html
@@ -16,22 +16,22 @@
       // Any sample rate mutiple of 128 is valid for this test, but here it uses
       // 48000Hz because it is a commonly used number that happens to be
       // multiple of 128.
-      let sampleRate = 48000;
+      let sampleRate = 32768;
 
       // The test iterates over 60 pitches starting from 36. (MIDI pitch of C2)
       let fundamentalPitch = 36;
       let numberOfPitches = 60;
 
-      let noteDuration = 0.025;
+      let noteDuration = Math.floor(0.025 * sampleRate) / sampleRate;
       let totalDuration = noteDuration * numberOfPitches;
 
       // Test constraints for each octave.
       let testConstraints = [
-        {thresholdSNR: 103.8508, thresholdDiffULP: 0.3028},
-        {thresholdSNR: 103.8657, thresholdDiffULP: 0.3029},
-        {thresholdSNR: 103.8141, thresholdDiffULP: 0.3047},
-        {thresholdSNR: 103.6818, thresholdDiffULP: 0.3262},
-        {thresholdSNR: 103.1514, thresholdDiffULP: 0.3946}
+        {thresholdSNR: 97.215, thresholdDiffULP: 0.6446},
+        {thresholdSNR: 97.212, thresholdDiffULP: 0.6446},
+        {thresholdSNR: 97.217, thresholdDiffULP: 0.6446},
+        {thresholdSNR: 97.196, thresholdDiffULP: 0.6485},
+        {thresholdSNR: 97.074, thresholdDiffULP: 0.6915}
       ];
 
       function pitchToFrequency(midiPitch) {
diff --git a/third_party/blink/web_tests/webaudio/AudioBufferSource/resources/audiobuffersource-loop-points-expected.wav b/third_party/blink/web_tests/webaudio/AudioBufferSource/resources/audiobuffersource-loop-points-expected.wav
index 7f7d8646..9a5e0440e 100644
--- a/third_party/blink/web_tests/webaudio/AudioBufferSource/resources/audiobuffersource-loop-points-expected.wav
+++ b/third_party/blink/web_tests/webaudio/AudioBufferSource/resources/audiobuffersource-loop-points-expected.wav
Binary files differ
diff --git a/third_party/closure_compiler/externs/automation.js b/third_party/closure_compiler/externs/automation.js
index 801b17a..53dc7d7 100644
--- a/third_party/closure_compiler/externs/automation.js
+++ b/third_party/closure_compiler/externs/automation.js
@@ -1189,6 +1189,12 @@
 chrome.automation.AutomationNode.prototype.language;
 
 /**
+ * The detected language code for this subtree.
+ * @type {(string|undefined)}
+ */
+chrome.automation.AutomationNode.prototype.detectedLanguage;
+
+/**
  * Indicates the availability and type of interactive popup element true - the popup is a menu menu - the popup is a menu listbox - the popup is a listbox tree - the popup is a tree grid - the popup is a grid dialog - the popup is a dialog
  * @type {(string|undefined)}
  * @see https://developer.chrome.com/extensions/automation#type-hasPopup
diff --git a/third_party/closure_compiler/externs/chrome_extensions.js b/third_party/closure_compiler/externs/chrome_extensions.js
index 6039cc2..8800ee7a 100644
--- a/third_party/closure_compiler/externs/chrome_extensions.js
+++ b/third_party/closure_compiler/externs/chrome_extensions.js
@@ -10343,6 +10343,193 @@
 
 /**
  * @const
+ * @see https://cs.chromium.org/chromium/src/extensions/common/api/bluetooth_private.idl
+ */
+chrome.bluetoothPrivate = {};
+
+
+
+/** @constructor */
+chrome.bluetoothPrivate.PairingEvent = function() {};
+
+
+/** @type {string} */
+chrome.bluetoothPrivate.PairingEvent.prototype.pairing;
+
+
+/** @type {!chrome.bluetooth.Device} */
+chrome.bluetoothPrivate.PairingEvent.prototype.device;
+
+
+/** @type {string|undefined} */
+chrome.bluetoothPrivate.PairingEvent.prototype.pincode;
+
+
+/** @type {number|undefined} */
+chrome.bluetoothPrivate.PairingEvent.prototype.passkey;
+
+
+/** @type {number|undefined} */
+chrome.bluetoothPrivate.PairingEvent.prototype.enteredKey;
+
+
+/**
+ * @typedef {{
+ *   name: (string|undefined),
+ *   powered: (boolean|undefined),
+ *   discoverable: (boolean|undefined)
+ * }}
+ */
+chrome.bluetoothPrivate.NewAdapterState;
+
+
+/**
+ * @typedef {{
+ *   device: !chrome.bluetooth.Device,
+ *   response: (string|undefined),
+ *   pincode: (string|undefined),
+ *   passkey: (number|undefined),
+ *   enteredKey: (number|undefined)
+ * }}
+ */
+chrome.bluetoothPrivate.SetPairingResponseOptions;
+
+
+/**
+ * @param {!chrome.bluetoothPrivate.NewAdapterState} adapterState
+ * @param {function()} callback
+ * @return {undefined}
+ */
+chrome.bluetoothPrivate.setAdapterState = function(adapterState, callback) {};
+
+
+/**
+ * @param {!chrome.bluetoothPrivate.SetPairingResponseOptions} options
+ * @param {function()} callback
+ * @return {undefined}
+ */
+chrome.bluetoothPrivate.setPairingResponse = function(options, callback) {};
+
+
+/**
+ * @param {string} deviceAddress
+ * @param {function():void=} callback
+ */
+chrome.bluetoothPrivate.disconnectAll = function(deviceAddress, callback) {};
+
+
+/**
+ * @param {string} deviceAddress
+ * @param {function():void=} callback
+ * @return {undefined}
+ */
+chrome.bluetoothPrivate.forgetDevice = function(deviceAddress, callback) {};
+
+
+/**
+ * @typedef {{
+ *   transport: (!chrome.bluetoothPrivate.TransportType|undefined),
+ *   uuids: ((string|!Array<string>)|undefined),
+ *   rssi: (number|undefined),
+ *   pathloss: (number|undefined)
+ * }}
+ */
+chrome.bluetoothPrivate.DiscoveryFilter;
+
+
+/**
+ * Set or clear discovery filter.
+ * @param {!chrome.bluetoothPrivate.DiscoveryFilter} discoveryFilter
+ * @param {function():void=} callback
+ */
+chrome.bluetoothPrivate.setDiscoveryFilter = function(
+    discoveryFilter, callback) {};
+
+
+/**
+ * Event whose listeners take a PairingEvent parameter.
+ * @interface
+ * @extends {ChromeBaseEvent<function(!chrome.bluetoothPrivate.PairingEvent)>}
+ */
+chrome.bluetoothPrivate.PairingEventEvent = function() {};
+
+
+/** @type {!chrome.bluetoothPrivate.PairingEventEvent} */
+chrome.bluetoothPrivate.onPairing;
+
+
+/**
+ * @param {string} deviceAddress
+ * @param {function(number, string): void=} opt_callback
+ */
+chrome.bluetoothPrivate.pair = function(deviceAddress, opt_callback) {};
+
+
+/**
+ * @enum {string}
+ * @see https://developer.chrome.com/extensions/bluetoothPrivate#type-PairingResponse
+ */
+chrome.bluetoothPrivate.PairingResponse = {
+  CONFIRM: '',
+  REJECT: '',
+  CANCEL: '',
+};
+
+
+/**
+ * @enum {string}
+ * @see https://developer.chrome.com/extensions/bluetoothPrivate#type-PairingEventType
+ */
+chrome.bluetoothPrivate.PairingEventType = {
+  REQUEST_PINCODE: '',
+  DISPLAY_PINCODE: '',
+  REQUEST_PASSKEY: '',
+  DISPLAY_PASSKEY: '',
+  KEYS_ENTERED: '',
+  CONFIRM_PASSKEY: '',
+  REQUEST_AUTHORIZATION: '',
+  COMPLETE: '',
+};
+
+
+/**
+ * @enum {string}
+ * @see https://developer.chrome.com/extensions/bluetoothPrivate#type-ConnectResultType
+ */
+chrome.bluetoothPrivate.ConnectResultType = {
+  ALREADY_CONNECTED: '',
+  ATTRIBUTE_LENGTH_INVALID: '',
+  AUTH_CANCELED: '',
+  AUTH_FAILED: '',
+  AUTH_REJECTED: '',
+  AUTH_TIMEOUT: '',
+  CONNECTION_CONGESTED: '',
+  FAILED: '',
+  IN_PROGRESS: '',
+  INSUFFICIENT_ENCRYPTION: '',
+  OFFSET_INVALID: '',
+  READ_NOT_PERMITTED: '',
+  REQUEST_NOT_SUPPORTED: '',
+  SUCCESS: '',
+  UNKNOWN_ERROR: '',
+  UNSUPPORTED_DEVICE: '',
+  WRITE_NOT_PERMITTED: '',
+};
+
+
+/**
+ * @enum {string}
+ * @see https://developer.chrome.com/extensions/bluetoothPrivate#type-TransportType
+ */
+chrome.bluetoothPrivate.TransportType = {
+  LE: '',
+  BREDR: '',
+  DUAL: '',
+};
+
+
+/**
+ * @const
  * @see http://goo.gl/XmVdHm
  */
 chrome.inlineInstallPrivate = {};
diff --git a/third_party/closure_compiler/externs/pending.js b/third_party/closure_compiler/externs/pending.js
index f09f3948..d567fa8 100644
--- a/third_party/closure_compiler/externs/pending.js
+++ b/third_party/closure_compiler/externs/pending.js
@@ -46,6 +46,34 @@
 }
 
 /**
+ * TODO(dstockwell): Remove this once it is added to Closure Compiler itself.
+ * @see https://drafts.fxtf.org/geometry/#DOMMatrix
+ */
+class DOMMatrix {
+  /**
+   * @param {number} x
+   * @param {number} y
+   */
+  translateSelf(x, y) {}
+  /**
+   * @param {number} x
+   * @param {number} y
+   * @param {number} z
+   */
+  rotateSelf(x, y, z) {}
+  /**
+   * @param {number} x
+   * @param {number} y
+   */
+  scaleSelf(x, y) {}
+  /**
+   * @param {{x: number, y: number}} point
+   * @return {{x: number, y: number}}
+   */
+  transformPoint(point) {}
+}
+
+/**
  * @see https://wicg.github.io/ResizeObserver/#resizeobserverentry
  * @typedef {{contentRect: DOMRectReadOnly,
  *            target: Element}}
diff --git a/third_party/fuchsia-sdk/BUILD.gn b/third_party/fuchsia-sdk/BUILD.gn
index f9618f3..9a6dfad 100644
--- a/third_party/fuchsia-sdk/BUILD.gn
+++ b/third_party/fuchsia-sdk/BUILD.gn
@@ -4,6 +4,7 @@
 
 assert(is_fuchsia)
 
+import("//build/toolchain/toolchain.gni")
 import("fuchsia_sdk_pkg.gni")
 
 config("sdk_lib_dirs_config") {
@@ -11,6 +12,29 @@
   lib_dirs = [ "sdk/arch/${target_cpu}/lib" ]
 }
 
+# Copy the loader to place it at the expected path in the final package.
+copy("sysroot_dist_libs") {
+  sources = [
+    "sdk/arch/${target_cpu}/sysroot/dist/lib/ld.so.1",
+  ]
+
+  outputs = [
+    "${root_out_dir}${shlib_subdir}/{{source_file_part}}",
+  ]
+}
+
+# This adds the runtime deps for //build/config/compiler:runtime_library
+# as that is a config target and thus cannot include data_deps.
+group("runtime_library") {
+  data_deps = [
+    ":sysroot_dist_libs",
+
+    # This is used directly from //build/config/fuchsia:compiler and thus
+    # also needs to be included by default.
+    "sdk:fdio_dist_libs",
+  ]
+}
+
 copy("vulkan_base_configs") {
   sources = [
     "sdk/pkg/vulkan_layers/data/vulkan/explicit_layer.d/VkLayer_image_pipe_swapchain.json",
@@ -21,21 +45,21 @@
   ]
 }
 
-copy("vulkan_base_libs") {
+copy("vulkan_image_pipe") {
   sources = [
     "sdk/arch/${target_cpu}/dist/libVkLayer_image_pipe_swapchain.so",
-    "sdk/arch/${target_cpu}/dist/libvulkan.so",
   ]
 
   outputs = [
-    "${root_out_dir}/{{source_file_part}}",
+    "${root_out_dir}${shlib_subdir}/{{source_file_part}}",
   ]
 }
 
 group("vulkan_base") {
   data_deps = [
     ":vulkan_base_configs",
-    ":vulkan_base_libs",
+    ":vulkan_image_pipe",
+    "sdk:vulkan_dist_libs",
   ]
 }
 
@@ -64,7 +88,7 @@
   ]
 
   outputs = [
-    "${root_out_dir}/{{source_file_part}}",
+    "${root_out_dir}${shlib_subdir}/{{source_file_part}}",
   ]
 }
 
diff --git a/third_party/fuchsia-sdk/LICENSE b/third_party/fuchsia-sdk/LICENSE
new file mode 100644
index 0000000..6d6ddbcc
--- /dev/null
+++ b/third_party/fuchsia-sdk/LICENSE
@@ -0,0 +1,27 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/third_party/fuchsia-sdk/fuchsia_sdk_pkg.gni b/third_party/fuchsia-sdk/fuchsia_sdk_pkg.gni
index 371e622e..32c8c89 100644
--- a/third_party/fuchsia-sdk/fuchsia_sdk_pkg.gni
+++ b/third_party/fuchsia-sdk/fuchsia_sdk_pkg.gni
@@ -5,6 +5,7 @@
 assert(is_fuchsia)
 
 import("//build/config/fuchsia/fidl_library.gni")
+import("//build/toolchain/toolchain.gni")
 
 # Templates for Fuchsia SDK packages.
 
@@ -48,19 +49,38 @@
 #                  is not specified explicitly.
 #   sources      - List of sources relative to sdk/pkg/${name}.
 #   deps         - List of dependencies.
-#   libs         - List of precompiled libraries.
+#   shared_libs  - List of precompiled shared libraries.
+#   static_libs  - List of precompiled static libraries.
 template("fuchsia_sdk_pkg") {
   config("${target_name}_config") {
     forward_variables_from(invoker, [ "include_dirs" ])
     visibility = [ ":${invoker.target_name}" ]
   }
 
+  if (defined(invoker.shared_libs)) {
+    if (defined(invoker.sdk_dist_dir)) {
+      sdk_dist_dir = invoker.sdk_dist_dir
+    } else {
+      sdk_dist_dir = "arch/${target_cpu}/dist"
+    }
+
+    copy("${target_name}_dist_libs") {
+      sources = []
+      foreach(lib, invoker.shared_libs) {
+        sources += [ "${sdk_dist_dir}/lib${lib}.so" ]
+      }
+
+      outputs = [
+        "${root_out_dir}${shlib_subdir}/{{source_file_part}}",
+      ]
+    }
+  }
+
   static_library(target_name) {
     forward_variables_from(invoker,
                            [
                              "data",
                              "deps",
-                             "libs",
                              "public_deps",
                              "sources",
                              "testonly",
@@ -69,8 +89,14 @@
 
     public_configs = [ ":${invoker.target_name}_config" ]
 
-    if (defined(libs)) {
+    if (defined(invoker.shared_libs)) {
       configs += [ "//third_party/fuchsia-sdk:sdk_lib_dirs_config" ]
+      libs = invoker.shared_libs
+      data_deps = [
+        ":${target_name}_dist_libs",
+      ]
+    } else if (defined(invoker.static_libs)) {
+      libs = invoker.static_libs
     }
   }
 }
diff --git a/third_party/fuchsia-sdk/gen_build_defs.py b/third_party/fuchsia-sdk/gen_build_defs.py
index a6389545..ec501c7 100755
--- a/third_party/fuchsia-sdk/gen_build_defs.py
+++ b/third_party/fuchsia-sdk/gen_build_defs.py
@@ -121,9 +121,13 @@
   converted = ConvertCommonFields(json)
   converted['type'] = 'fuchsia_sdk_pkg'
   converted['sources'] = json['headers']
-  converted['libs'] = [json['name']]
   converted['include_dirs'] = [json['root'] + '/include']
 
+  if json['format'] == 'shared':
+    converted['shared_libs'] = [json['name']]
+  else:
+    converted['static_libs'] = [json['name']]
+
   return converted
 
 def ConvertCcSourceLibrary(json):
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 0b2dd12b..f3f27b0 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -2200,6 +2200,8 @@
 </action>
 
 <action name="Arc.SmartTextSelection.Request">
+  <owner>linben@chromium.org</owner>
+  <owner>djacobo@chromium.org</owner>
   <owner>jorgegil@google.com</owner>
   <description>
     Recorded when a request to the ARC++ container to generate Smart Text
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index a761b4b..baa83fc 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -8260,6 +8260,8 @@
   <int value="44" label="USB guard"/>
   <int value="45" label="Background fetch"/>
   <int value="46" label="Intent picker display"/>
+  <int value="47" label="Serial guard"/>
+  <int value="48" label="Serial permission data"/>
 </enum>
 
 <enum name="ContentTypeParseableResult">
@@ -10992,6 +10994,7 @@
   <int value="16" label="Squid"/>
   <int value="17" label="Web Store"/>
   <int value="18" label="YouTube"/>
+  <int value="19" label="Screensaver"/>
 </enum>
 
 <enum name="DemoModeIdleLogoutWarningEvent">
@@ -12702,6 +12705,26 @@
   <int value="319" label="pyo"/>
   <int value="320" label="desktop"/>
   <int value="321" label="cpi"/>
+  <int value="322" label="jpg"/>
+  <int value="323" label="jpeg"/>
+  <int value="324" label="mp3"/>
+  <int value="325" label="mp4"/>
+  <int value="326" label="png"/>
+  <int value="327" label="xls"/>
+  <int value="328" label="doc"/>
+  <int value="329" label="pptx"/>
+  <int value="330" label="csv"/>
+  <int value="331" label="ica"/>
+  <int value="332" label="ppt"/>
+  <int value="333" label="gif"/>
+  <int value="334" label="txt"/>
+  <int value="335" label="package"/>
+  <int value="336" label="tif"/>
+  <int value="337" label="rtf"/>
+  <int value="338" label="webp"/>
+  <int value="339" label="mkv"/>
+  <int value="340" label="wav"/>
+  <int value="341" label="mov"/>
 </enum>
 
 <enum name="DownloadItem.DangerType">
@@ -43702,6 +43725,7 @@
   <int value="11" label="INVITATION_AVAILABLE"/>
   <int value="12" label="INVITATION_ACCEPTED"/>
   <int value="13" label="INVITATION_REJECTED"/>
+  <int value="14" label="MANAGE_BUTTON_CLICKED"/>
 </enum>
 
 <enum name="PrintPreviewPrintDocumentTypeBuckets">
@@ -46883,6 +46907,28 @@
   <int value="317" label="OXT"/>
   <int value="318" label="PYD"/>
   <int value="319" label="PYO"/>
+  <int value="320" label="DESKTOP"/>
+  <int value="321" label="CPI"/>
+  <int value="322" label="JPG"/>
+  <int value="323" label="JPEG"/>
+  <int value="324" label="MP3"/>
+  <int value="325" label="MP4"/>
+  <int value="326" label="PNG"/>
+  <int value="327" label="XLS"/>
+  <int value="328" label="DOC"/>
+  <int value="329" label="PPTX"/>
+  <int value="330" label="CSV"/>
+  <int value="331" label="ICA"/>
+  <int value="332" label="PPT"/>
+  <int value="333" label="GIF"/>
+  <int value="334" label="TXT"/>
+  <int value="335" label="PACKAGE"/>
+  <int value="336" label="TIF"/>
+  <int value="337" label="RTF"/>
+  <int value="338" label="WEBP"/>
+  <int value="339" label="MKV"/>
+  <int value="340" label="WAV"/>
+  <int value="341" label="MOV"/>
 </enum>
 
 <enum name="SBClientDownloadIsSignedBinary">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index feaf30b..6bcc0de 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -9705,6 +9705,16 @@
   <summary>Records whenever a Blimp tab toggles visibility.</summary>
 </histogram>
 
+<histogram name="Blink.Animate.UpdateTime" units="microseconds">
+  <owner>paint-dev@chromium.org</owner>
+  <summary>
+    Time spent processing main frame animations during a main frame update.
+
+    This histogram does not record metrics on machines with low-resolution
+    clocks.
+  </summary>
+</histogram>
+
 <histogram name="Blink.Binding.CreateV8ContextForMainFrame"
     units="microseconds">
   <owner>peria@chromium.org</owner>
@@ -10650,6 +10660,16 @@
   </summary>
 </histogram>
 
+<histogram name="Blink.HandleInputEvents.UpdateTime" units="microseconds">
+  <owner>paint-dev@chromium.org</owner>
+  <summary>
+    Time spent processing rAF-aligned input during a main frame update.
+
+    This histogram does not record metrics on machines with low-resolution
+    clocks.
+  </summary>
+</histogram>
+
 <histogram name="Blink.ImageDecoders.Jpeg.Area" units="pixels">
   <owner>andrescj@chromium.org</owner>
   <summary>
@@ -57109,17 +57129,20 @@
 </histogram>
 
 <histogram name="Net.BidirectionalStream.ReceivedBytes" units="bytes">
-  <owner>xunjieli@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <owner>pauljensen@chromium.org</owner>
   <summary>Number of bytes received over this stream.</summary>
 </histogram>
 
 <histogram name="Net.BidirectionalStream.SentBytes" units="bytes">
-  <owner>xunjieli@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <owner>pauljensen@chromium.org</owner>
   <summary>Number of bytes sent over this stream.</summary>
 </histogram>
 
 <histogram name="Net.BidirectionalStream.TimeToReadEnd" units="ms">
-  <owner>xunjieli@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <owner>pauljensen@chromium.org</owner>
   <summary>
     How long it takes from starting the request to reading the end of the
     response.
@@ -57127,7 +57150,8 @@
 </histogram>
 
 <histogram name="Net.BidirectionalStream.TimeToReadStart" units="ms">
-  <owner>xunjieli@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <owner>pauljensen@chromium.org</owner>
   <summary>
     How long it takes from starting the request to reading the start of the
     response.
@@ -57135,14 +57159,16 @@
 </histogram>
 
 <histogram name="Net.BidirectionalStream.TimeToSendEnd" units="ms">
-  <owner>xunjieli@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <owner>pauljensen@chromium.org</owner>
   <summary>
     How long it takes from starting the request to when the last byte is sent.
   </summary>
 </histogram>
 
 <histogram name="Net.BidirectionalStream.TimeToSendStart" units="ms">
-  <owner>xunjieli@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <owner>pauljensen@chromium.org</owner>
   <summary>
     How long it takes from starting the request to when we can start sending
     data.
@@ -57995,7 +58021,8 @@
 </histogram>
 
 <histogram name="Net.ContentDecodingFailed2" enum="NetFilterType2">
-  <owner>xunjieli@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <owner>pauljensen@chromium.org</owner>
   <summary>
     For each CONTENT_DECODING_FAILED, record the filter that failed.
   </summary>
@@ -58005,7 +58032,8 @@
   <obsolete>
     Obsoleted in favor of Net.ContentDecodingFailed2 above.
   </obsolete>
-  <owner>xunjieli@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <owner>pauljensen@chromium.org</owner>
   <summary>
     For each CONTENT_DECODING_FAILED, record the filter that failed.
   </summary>
@@ -60305,7 +60333,8 @@
   <obsolete>
     Deprecated as of 1/2018.
   </obsolete>
-  <owner>xunjieli@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <owner>pauljensen@chromium.org</owner>
   <summary>Reports the next state that the Alternative Job is in.</summary>
 </histogram>
 
@@ -60314,7 +60343,8 @@
   <obsolete>
     Deprecated as of 1/2018.
   </obsolete>
-  <owner>xunjieli@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <owner>pauljensen@chromium.org</owner>
   <summary>Reports the state that the Alternative Job is in.</summary>
 </histogram>
 
@@ -60323,7 +60353,8 @@
   <obsolete>
     Deprecated as of 1/2018.
   </obsolete>
-  <owner>xunjieli@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <owner>pauljensen@chromium.org</owner>
   <summary>Reports the next state that the Main Job is in.</summary>
 </histogram>
 
@@ -60332,7 +60363,8 @@
   <obsolete>
     Deprecated as of 1/2018.
   </obsolete>
-  <owner>xunjieli@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <owner>pauljensen@chromium.org</owner>
   <summary>Reports the state that the Main Job is in.</summary>
 </histogram>
 
@@ -60691,7 +60723,8 @@
 
 <histogram name="Net.NetworkConfigWatcherMac.SCDynamicStore.Create"
     enum="SCStatusCode">
-  <owner>xunjieli@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <owner>pauljensen@chromium.org</owner>
   <summary>
     This is logged when SCDynamicStoreCreate fails in
     network_config_watcher_mac.cc.
@@ -60701,7 +60734,8 @@
 <histogram
     name="Net.NetworkConfigWatcherMac.SCDynamicStore.Create.RunLoopSource"
     enum="SCStatusCode">
-  <owner>xunjieli@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <owner>pauljensen@chromium.org</owner>
   <summary>
     This is logged when SCDynamicStoreCreateRunLoopSource fails in
     network_config_watcher_mac.cc.
@@ -60710,7 +60744,8 @@
 
 <histogram name="Net.NetworkConfigWatcherMac.SCDynamicStore.NumRetry"
     units="retries">
-  <owner>xunjieli@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <owner>pauljensen@chromium.org</owner>
   <summary>
     This is logged when retrying SCDynamicStore has stopped after either
     receiving a success or hitting the maximum number of retries in
@@ -63547,7 +63582,8 @@
   <obsolete>
     Removed from Chromium as of 2018/7.
   </obsolete>
-  <owner>xunjieli@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <owner>pauljensen@chromium.org</owner>
   <summary>
     The largest number of outstanding requests that are handled by the resource
     dispatcher host for a single process.
@@ -63559,7 +63595,8 @@
   <obsolete>
     Removed from Chromium as of 2018/7.
   </obsolete>
-  <owner>xunjieli@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <owner>pauljensen@chromium.org</owner>
   <summary>
     The largest number of outstanding requests that are handled by the resource
     dispatcher host across all processes.
@@ -63714,7 +63751,8 @@
   <obsolete>
     Deprecated as of 9/2017.
   </obsolete>
-  <owner>xunjieli@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <owner>pauljensen@chromium.org</owner>
   <summary>
     Whether an idle socket is reused, timed out, or closed to make room for new
     sockets.
@@ -63736,7 +63774,8 @@
   <obsolete>
     Deprecated as of 11/2016.
   </obsolete>
-  <owner>xunjieli@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <owner>pauljensen@chromium.org</owner>
   <summary>
     Number of milliseconds an idle socket saved in connection establishment
     because it is reused.
@@ -136168,7 +136207,8 @@
 
 <histogram_suffixes name="Net.BidirectionalStreamExperiment" separator=".">
   <suffix name="HTTP2" label="Bidirectional stream that use HTTP2 protocol"/>
-  <owner>xunjieli@chromium.org</owner>
+  <owner>mef@chromium.org</owner>
+  <owner>pauljensen@chromium.org</owner>
   <suffix name="QUIC" label="Bidirectional streams that use QUIC protocol"/>
   <affected-histogram name="Net.BidirectionalStream.ReceivedBytes"/>
   <affected-histogram name="Net.BidirectionalStream.SentBytes"/>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 807e80c9..12bb1fec 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -457,6 +457,11 @@
       ith type was detected. Multiple types are possible.
     </summary>
   </metric>
+  <metric name="HasUpiVpaField">
+    <summary>
+      True for forms containing a UPI/VPA field.
+    </summary>
+  </metric>
   <metric name="IsForCreditCard">
     <summary>
       True for credit card forms, false for address/profile forms. See
@@ -654,6 +659,39 @@
     compositing, and layout update times. The metrics are recorded once per 30
     seconds and at the destruction of the local frame root.
   </summary>
+  <metric name="Animate.Average">
+    <summary>
+      The average time taken for main frame animation in microseconds in the
+      event period.
+    </summary>
+    <aggregation>
+      <history>
+        <index fields="profile.country"/>
+        <statistics>
+          <quantiles type="std-percentiles"/>
+        </statistics>
+      </history>
+    </aggregation>
+  </metric>
+  <metric name="Animate.AverageRatio">
+    <summary>
+      The average over frames within the sample window of the ratio of time
+      taken for main frame animation to the total time for the main frame. An
+      int in the range [0,100].
+    </summary>
+  </metric>
+  <metric name="Animate.WorstCase">
+    <summary>
+      The longest single time taken for main frame animation in microseconds in
+      the event period.
+    </summary>
+  </metric>
+  <metric name="Animate.WorstCaseRatio">
+    <summary>
+      The highest proportion of a frame ever used for main frame animation
+      within the sample window. An int in the range [0,100].
+    </summary>
+  </metric>
   <metric name="Compositing.Average">
     <summary>
       The average time taken by the compositing phase in microseconds in the
@@ -755,6 +793,40 @@
       frame. An int in the range [0,100].
     </summary>
   </metric>
+  <metric name="HandleInputEvents.Average">
+    <summary>
+      The average time taken to process rAF-aligned input for the main frame in
+      microseconds in the event period.
+    </summary>
+    <aggregation>
+      <history>
+        <index fields="profile.country"/>
+        <statistics>
+          <quantiles type="std-percentiles"/>
+        </statistics>
+      </history>
+    </aggregation>
+  </metric>
+  <metric name="HandleInputEvents.AverageRatio">
+    <summary>
+      The average over frames within the sample window of the ratio of time
+      taken to process rAF-aligned input for the main frame to the total time
+      for the main frame. An int in the range [0,100].
+    </summary>
+  </metric>
+  <metric name="HandleInputEvents.WorstCase">
+    <summary>
+      The longest single time taken to process rAF-aligned input for the main
+      frame in microseconds in the event period.
+    </summary>
+  </metric>
+  <metric name="HandleInputEvents.WorstCaseRatio">
+    <summary>
+      The highest proportion of a frame ever taken to process rAF-aligned input
+      for the main frame to the total time for the main frame. An int in the
+      range [0,100].
+    </summary>
+  </metric>
   <metric name="IntersectionObservation.Average">
     <summary>
       The average time taken to compute IntersectionObserver observations in
diff --git a/tools/perf/benchmark.csv b/tools/perf/benchmark.csv
index a47990f..f5721c6 100644
--- a/tools/perf/benchmark.csv
+++ b/tools/perf/benchmark.csv
@@ -26,7 +26,7 @@
 load_library_perf_tests,"xhwang@chromium.org, crouleau@chromium.org",Internals>Media>Encrypted,,
 loading.desktop,"kouhei@chromium.org, ksakamoto@chromium.org",,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",,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,background,beginning_to_end,busyjs,cns,h264,is_4k,is_50fps,mp3,mse,opus,pcm,seek,src,video_only,vorbis,vp8,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,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_perftests,"crouleau@chromium.org, dalecurtis@chromium.org",Internals>Media,,
 memory.desktop,erikchen@chromium.org,,,
diff --git a/tools/perf/core/perf_data_generator.py b/tools/perf/core/perf_data_generator.py
index 544a19f..968d173 100755
--- a/tools/perf/core/perf_data_generator.py
+++ b/tools/perf/core/perf_data_generator.py
@@ -931,27 +931,20 @@
 
   return test_args
 
+
 def generate_non_telemetry_args(test_name):
   # --gtest-benchmark-name so the benchmark name is consistent with the test
   # step's name. This is not always the same as the test binary's name (see
   # crbug.com/870692).
-  # --non-telemetry tells run_performance_tests.py that this test needs
-  #   to be executed differently
-  # --migrated-test tells run_performance_test_wrapper that this has
-  #   non-telemetry test has been migrated to the new recipe.
   return [
     '--gtest-benchmark-name', test_name,
-    '--non-telemetry=true',
-    '--migrated-test=true'
   ]
 
+
 def generate_performance_test(tester_config, test):
   isolate_name = test['isolate']
 
-  # Check to see if the name is different than the isolate
-  test_suite = isolate_name
-  if test.get('test_suite', False):
-    test_suite = test['test_suite']
+  test_suite = test.get('test_suite', isolate_name)
 
   if test.get('telemetry', True):
     test_args = generate_telemetry_args(tester_config)
diff --git a/tools/perf/core/perf_data_generator_unittest.py b/tools/perf/core/perf_data_generator_unittest.py
index 9925042..2e6b27e2 100644
--- a/tools/perf/core/perf_data_generator_unittest.py
+++ b/tools/perf/core/perf_data_generator_unittest.py
@@ -72,8 +72,7 @@
     expected_generated_test = {
         'override_compile_targets': ['angle_perftest'],
         'isolate_name': 'angle_perftest',
-        'args': ['--gtest-benchmark-name', 'angle_perftest',
-                 '--non-telemetry=true', '--migrated-test=true'],
+        'args': ['--gtest-benchmark-name', 'angle_perftest'],
         'trigger_script': {
           'args': [
             '--multiple-dimension-script-verbose',
@@ -121,7 +120,8 @@
         'override_compile_targets': ['performance_test_suite'],
         'isolate_name': 'performance_test_suite',
         'args': ['-v', '--browser=android-webview', '--upload-results',
-                 '--webview-embedder-apk=../../out/Release/apks/SystemWebViewShell.apk',
+                 '--webview-embedder-apk=../../out/Release'
+                 '/apks/SystemWebViewShell.apk',
                  '--run-ref-build',
                  '--test-shard-map-filename=shard_map.json'],
         'trigger_script': {
diff --git a/tools/perf/page_sets/media_cases.py b/tools/perf/page_sets/media_cases.py
index 37993cf..5f213b8 100644
--- a/tools/perf/page_sets/media_cases.py
+++ b/tools/perf/page_sets/media_cases.py
@@ -24,6 +24,7 @@
     'vorbis',
     'opus',
     # Video codecs.
+    'av1',
     'h264',
     'vp8',
     'vp9',
@@ -204,7 +205,24 @@
       raise RuntimeError(action_runner.EvaluateJavaScript('window.__testError'))
 
 
-def _GetMediaPages(page_set):
+def _GetDesktopOnlyMediaPages(page_set):
+  return [
+        _BeginningToEndPlayPage(
+          url=_URL_BASE + 'video.html?src=tulip0.av1.mp4',
+          page_set=page_set,
+          tags=['av1', 'video_only']),
+        _SeekPage(
+          url=_URL_BASE + 'video.html?src=tulip0.av1.mp4&seek',
+          page_set=page_set,
+          tags=['av1', 'video_only', 'seek']),
+        _MSEPage(
+          url=_URL_BASE + 'mse.html?media=tulip0.av1.mp4',
+          page_set=page_set,
+          tags=['av1', 'video_only']),
+        ]
+
+
+def _GetCrossPlatformMediaPages(page_set):
   return [
       _BeginningToEndPlayPage(
           url=_URL_BASE + 'video.html?src=crowd.ogg&type=audio',
@@ -349,7 +367,8 @@
   many other media-specific and generic metrics.
   """
   def _BuildPages(self):
-    return iter(_GetMediaPages(self))
+    return iter(
+        _GetCrossPlatformMediaPages(self) + _GetDesktopOnlyMediaPages(self))
 
 
 class MediaCasesMobileStorySet(_MediaCasesStorySet):
@@ -361,7 +380,7 @@
   devices.
   """
   def _BuildPages(self):
-    for page in _GetMediaPages(self):
+    for page in _GetCrossPlatformMediaPages(self):
       if 'is_4k' in page.tags or 'is_50fps' in page.tags:
         continue
       yield page
diff --git a/tools/perf/page_sets/media_cases/mse.js b/tools/perf/page_sets/media_cases/mse.js
index 3d16fba5..6a097f8 100644
--- a/tools/perf/page_sets/media_cases/mse.js
+++ b/tools/perf/page_sets/media_cases/mse.js
@@ -12,6 +12,7 @@
   const MEDIA_MIMES = {
     "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\"",
   };
   const testParams = {}
 
diff --git a/tools/perf/page_sets/media_cases/tulip0.av1.mp4.sha1 b/tools/perf/page_sets/media_cases/tulip0.av1.mp4.sha1
new file mode 100644
index 0000000..8936683
--- /dev/null
+++ b/tools/perf/page_sets/media_cases/tulip0.av1.mp4.sha1
@@ -0,0 +1 @@
+5f93fb72701a3924b2ba719f5a1a4ae49e678fc6
\ No newline at end of file
diff --git a/ui/aura/mus/embed_root.cc b/ui/aura/mus/embed_root.cc
index 32fa6b5..e0262c58 100644
--- a/ui/aura/mus/embed_root.cc
+++ b/ui/aura/mus/embed_root.cc
@@ -55,22 +55,35 @@
   }
 
   void FocusWindowImpl(Window* window) {
-    Window* previously_focused_window = focused_window_;
+    Window* lost_focus = focused_window_;
 
-    if (previously_focused_window)
-      previously_focused_window->RemoveObserver(this);
+    if (lost_focus)
+      lost_focus->RemoveObserver(this);
     focused_window_ = window;
     if (focused_window_)
       focused_window_->AddObserver(this);
 
     WindowTracker window_tracker;
-    if (previously_focused_window)
-      window_tracker.Add(previously_focused_window);
+    if (lost_focus)
+      window_tracker.Add(lost_focus);
+
     for (auto& observer : observers_) {
       observer.OnWindowFocused(
-          focused_window_, window_tracker.Contains(previously_focused_window)
-                               ? previously_focused_window
-                               : nullptr);
+          focused_window_,
+          window_tracker.Contains(lost_focus) ? lost_focus : nullptr);
+    }
+    if (window_tracker.Contains(lost_focus)) {
+      client::FocusChangeObserver* observer =
+          client::GetFocusChangeObserver(lost_focus);
+      if (observer)
+        observer->OnWindowFocused(focused_window_, lost_focus);
+    }
+    client::FocusChangeObserver* observer =
+        client::GetFocusChangeObserver(focused_window_);
+    if (observer) {
+      observer->OnWindowFocused(
+          focused_window_,
+          window_tracker.Contains(lost_focus) ? lost_focus : nullptr);
     }
   }
 
diff --git a/ui/compositor/compositor.cc b/ui/compositor/compositor.cc
index 74e8d5b78..b4639d8 100644
--- a/ui/compositor/compositor.cc
+++ b/ui/compositor/compositor.cc
@@ -562,7 +562,7 @@
     SendDamagedRectsRecursive(child);
 }
 
-void Compositor::UpdateLayerTreeHost(bool record_main_frame_metrics) {
+void Compositor::UpdateLayerTreeHost() {
   if (!root_layer())
     return;
   SendDamagedRectsRecursive(root_layer());
diff --git a/ui/compositor/compositor.h b/ui/compositor/compositor.h
index 313431f..99f547c 100644
--- a/ui/compositor/compositor.h
+++ b/ui/compositor/compositor.h
@@ -370,7 +370,7 @@
   void BeginMainFrame(const viz::BeginFrameArgs& args) override;
   void BeginMainFrameNotExpectedSoon() override;
   void BeginMainFrameNotExpectedUntil(base::TimeTicks time) override;
-  void UpdateLayerTreeHost(bool record_main_frame_metrics) override;
+  void UpdateLayerTreeHost() override;
   void ApplyViewportChanges(const cc::ApplyViewportChangesArgs& args) override {
   }
   void RecordWheelAndTouchScrollingCount(bool has_scrolled_by_wheel,
@@ -391,6 +391,7 @@
   void DidPresentCompositorFrame(
       uint32_t frame_token,
       const gfx::PresentationFeedback& feedback) override;
+  void RecordStartOfFrameMetrics() override {}
   void RecordEndOfFrameMetrics(base::TimeTicks frame_begin_time) override {}
   void DidGenerateLocalSurfaceIdAllocation(
       const viz::LocalSurfaceIdAllocation& allocation) override;
diff --git a/ui/display/display_switches.cc b/ui/display/display_switches.cc
index 40b7fa1..9e2d1f0 100644
--- a/ui/display/display_switches.cc
+++ b/ui/display/display_switches.cc
@@ -2,17 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "build/build_config.h"
 #include "ui/display/display_switches.h"
+#include "build/build_config.h"
 
 namespace switches {
 
 // TODO(rjkroege): Some of these have an "ash" prefix. When ChromeOS startup
 // scripts have been updated, the leading "ash" prefix should be removed.
 
-// Disables mirroring across multiple displays.
-const char kDisableMultiMirroring[] = "disable-multi-mirroring";
-
 // Enables software based mirroring.
 const char kEnableSoftwareMirroring[] = "ash-enable-software-mirroring";
 
diff --git a/ui/display/display_switches.h b/ui/display/display_switches.h
index 0079182..70fac73 100644
--- a/ui/display/display_switches.h
+++ b/ui/display/display_switches.h
@@ -13,7 +13,6 @@
 namespace switches {
 
 // Keep sorted.
-DISPLAY_EXPORT extern const char kDisableMultiMirroring[];
 DISPLAY_EXPORT extern const char kEnableSoftwareMirroring[];
 DISPLAY_EXPORT extern const char kEnsureForcedColorProfile[];
 DISPLAY_EXPORT extern const char kForceDeviceScaleFactor[];
diff --git a/ui/display/manager/display_change_observer.cc b/ui/display/manager/display_change_observer.cc
index e99d15e4..73657edf 100644
--- a/ui/display/manager/display_change_observer.cc
+++ b/ui/display/manager/display_change_observer.cc
@@ -43,11 +43,8 @@
 // Update the list of zoom levels whenever a new device scale factor is added
 // here. See zoom level list in /ui/display/manager/display_util.cc
 const DeviceScaleFactorDPIThreshold kThresholdTableForInternal[] = {
-    {270.0f, 2.25f},
-    {220.0f, 2.0f},
-    {180.0f, 1.6f},
-    {150.0f, 1.25f},
-    {0.0f, 1.0f},
+    {270.0f, 2.25f}, {220.0f, 2.0f}, {180.0f, 1.6f},
+    {150.0f, 1.25f}, {0.0f, 1.0f},
 };
 
 }  // namespace
@@ -137,14 +134,6 @@
   UpdateInternalDisplay(display_states);
   if (display_states.size() == 1)
     return MULTIPLE_DISPLAY_STATE_SINGLE;
-  if (!display_manager_->is_multi_mirroring_enabled() &&
-      display_states.size() > 2) {
-    // TODO(weidongg/774795): Remove this condition when multi-mirroring is
-    // enabled by default.
-    // When multi-mirroring is disabled, mirroring across 3+ displays are not
-    // supported, so default to EXTENDED.
-    return MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED;
-  }
   DisplayIdList list =
       GenerateDisplayIdList(display_states.begin(), display_states.end(),
                             [](const DisplaySnapshot* display_state) {
diff --git a/ui/display/manager/display_configurator.cc b/ui/display/manager/display_configurator.cc
index 4d184bb..a114d43 100644
--- a/ui/display/manager/display_configurator.cc
+++ b/ui/display/manager/display_configurator.cc
@@ -298,12 +298,8 @@
         return false;
       }
 
-      bool can_set_mirror_mode =
-          configurator_->is_multi_mirroring_enabled_
-              ? (states.size() > 1 &&
-                 (num_on_displays == 0 || num_on_displays > 1))
-              : (states.size() == 2 &&
-                 (num_on_displays == 0 || num_on_displays == 2));
+      const bool can_set_mirror_mode =
+          states.size() > 1 && num_on_displays != 1;
       if (!can_set_mirror_mode) {
         LOG(WARNING) << "Ignoring request to enter mirrored mode with "
                      << states.size() << " connected display(s) and "
@@ -379,9 +375,8 @@
     const DisplaySnapshot& display) const {
   gfx::Size size;
   const DisplayMode* selected_mode = nullptr;
-  if (GetStateController() &&
-      GetStateController()->GetResolutionForDisplayId(display.display_id(),
-                                                      &size)) {
+  if (GetStateController() && GetStateController()->GetResolutionForDisplayId(
+                                  display.display_id(), &size)) {
     selected_mode = FindDisplayModeMatchingSize(display, size);
   }
 
@@ -540,9 +535,6 @@
       display_control_changing_(false),
       displays_suspended_(false),
       layout_manager_(new DisplayLayoutManagerImpl(this)),
-      is_multi_mirroring_enabled_(
-          !base::CommandLine::ForCurrentProcess()->HasSwitch(
-              ::switches::kDisableMultiMirroring)),
       weak_ptr_factory_(this) {}
 
 DisplayConfigurator::~DisplayConfigurator() {
@@ -1061,9 +1053,10 @@
     // This gives a chance to wait for all displays to be added and detected
     // before configuration is performed, so we won't immediately resize the
     // desktops and the windows on it to fit on a single display.
-    configure_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(
-                                          kResumeConfigureMultiDisplayDelayMs),
-                           this, &DisplayConfigurator::ConfigureDisplays);
+    configure_timer_.Start(
+        FROM_HERE,
+        base::TimeDelta::FromMilliseconds(kResumeConfigureMultiDisplayDelayMs),
+        this, &DisplayConfigurator::ConfigureDisplays);
   }
 
   // TODO(crbug.com/794831): Solve the issue of mirror mode on display resume.
diff --git a/ui/display/manager/display_configurator.h b/ui/display/manager/display_configurator.h
index 3e55655..5e69a3c3 100644
--- a/ui/display/manager/display_configurator.h
+++ b/ui/display/manager/display_configurator.h
@@ -302,16 +302,12 @@
   // Returns the requested power state if set or the default power state.
   chromeos::DisplayPowerState GetRequestedPowerState() const;
 
-  void set_is_multi_mirroring_enabled_for_test(bool enabled) {
-    is_multi_mirroring_enabled_ = enabled;
-  }
-
   void reset_requested_power_state_for_test() {
     requested_power_state_ = base::nullopt;
   }
 
-  base::Optional<chromeos::DisplayPowerState>
-  GetRequestedPowerStateForTest() const {
+  base::Optional<chromeos::DisplayPowerState> GetRequestedPowerStateForTest()
+      const {
     return requested_power_state_;
   }
 
@@ -478,8 +474,6 @@
 
   std::unique_ptr<UpdateDisplayConfigurationTask> configuration_task_;
 
-  bool is_multi_mirroring_enabled_;
-
   // This must be the last variable.
   base::WeakPtrFactory<DisplayConfigurator> weak_ptr_factory_;
 
diff --git a/ui/display/manager/display_configurator_unittest.cc b/ui/display/manager/display_configurator_unittest.cc
index 699fe63..76b5c6fa 100644
--- a/ui/display/manager/display_configurator_unittest.cc
+++ b/ui/display/manager/display_configurator_unittest.cc
@@ -699,11 +699,10 @@
   configurator_.SuspendDisplays(config_waiter_.on_configuration_callback());
   EXPECT_EQ(kNoDelay, config_waiter_.Wait());
   EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
-  EXPECT_EQ(
-      JoinActions(
-          GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
-          nullptr),
-      log_->GetActionsAndClear());
+  EXPECT_EQ(JoinActions(
+                GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
+                nullptr),
+            log_->GetActionsAndClear());
 
   // No resume delay in single display mode.
   config_waiter_.Reset();
@@ -724,11 +723,10 @@
                                 config_waiter_.on_configuration_callback());
   EXPECT_EQ(kNoDelay, config_waiter_.Wait());
   EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
-  EXPECT_EQ(
-      JoinActions(
-          GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
-          nullptr),
-      log_->GetActionsAndClear());
+  EXPECT_EQ(JoinActions(
+                GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
+                nullptr),
+            log_->GetActionsAndClear());
 
   config_waiter_.Reset();
   configurator_.SuspendDisplays(config_waiter_.on_configuration_callback());
@@ -938,7 +936,6 @@
   EXPECT_EQ(outputs_[1]->current_mode(), cached[1]->current_mode());
 }
 
-
 TEST_F(DisplayConfiguratorTest, ContentProtection) {
   Init(false);
   configurator_.ForceInitialConfigure();
@@ -1017,11 +1014,10 @@
   configurator_.SuspendDisplays(config_waiter_.on_configuration_callback());
   EXPECT_EQ(kNoDelay, config_waiter_.Wait());
   EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
-  EXPECT_EQ(
-      JoinActions(
-          GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
-          nullptr),
-      log_->GetActionsAndClear());
+  EXPECT_EQ(JoinActions(
+                GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
+                nullptr),
+            log_->GetActionsAndClear());
 
   // The configuration timer should not be started when the displays
   // are suspended.
@@ -1074,11 +1070,10 @@
   configurator_.SuspendDisplays(config_waiter_.on_configuration_callback());
   EXPECT_EQ(kNoDelay, config_waiter_.Wait());
   EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
-  EXPECT_EQ(
-      JoinActions(
-          GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
-          nullptr),
-      log_->GetActionsAndClear());
+  EXPECT_EQ(JoinActions(
+                GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
+                nullptr),
+            log_->GetActionsAndClear());
   EXPECT_FALSE(test_api_.TriggerConfigureTimeout());
   EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
 
@@ -1424,11 +1419,10 @@
       base::BindOnce(&DisplayConfiguratorTest::OnDisplayControlUpdated,
                      base::Unretained(this)));
   EXPECT_EQ(CALLBACK_SUCCESS, PopDisplayControlResult());
-  EXPECT_EQ(
-      JoinActions(
-          GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
-          kRelinquishDisplayControl, nullptr),
-      log_->GetActionsAndClear());
+  EXPECT_EQ(JoinActions(
+                GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
+                kRelinquishDisplayControl, nullptr),
+            log_->GetActionsAndClear());
   configurator_.TakeControl(
       base::BindOnce(&DisplayConfiguratorTest::OnDisplayControlUpdated,
                      base::Unretained(this)));
@@ -1799,10 +1793,7 @@
   DisplayConfiguratorMultiMirroringTest() = default;
   ~DisplayConfiguratorMultiMirroringTest() override = default;
 
-  void SetUp() override {
-    configurator_.set_is_multi_mirroring_enabled_for_test(true);
-    DisplayConfiguratorTest::SetUp();
-  }
+  void SetUp() override { DisplayConfiguratorTest::SetUp(); }
 
   // Test that setting mirror mode with current outputs, all displays are set to
   // expected mirror mode.
diff --git a/ui/display/manager/display_manager.cc b/ui/display/manager/display_manager.cc
index 43d520a6..045f5e5 100644
--- a/ui/display/manager/display_manager.cc
+++ b/ui/display/manager/display_manager.cc
@@ -320,9 +320,6 @@
 DisplayManager::DisplayManager(std::unique_ptr<Screen> screen)
     : screen_(std::move(screen)),
       layout_store_(new DisplayLayoutStore),
-      is_multi_mirroring_enabled_(
-          !base::CommandLine::ForCurrentProcess()->HasSwitch(
-              ::switches::kDisableMultiMirroring)),
       weak_ptr_factory_(this) {
 #if defined(OS_CHROMEOS)
   configure_displays_ = chromeos::IsRunningAsSystemCompositor();
@@ -412,13 +409,6 @@
   DisplayIdList display_id_list = CreateDisplayIdList(active_display_list_);
 
   if (IsInSoftwareMirrorMode()) {
-    if (!is_multi_mirroring_enabled_) {
-      CHECK_EQ(2u, num_connected_displays());
-      // This comment is to make it easy to distinguish the crash
-      // between two checks.
-      CHECK_EQ(1u, active_display_list_.size());
-    }
-
     DisplayIdList software_mirroring_display_id_list =
         CreateDisplayIdList(software_mirroring_display_list_);
     display_id_list.insert(display_id_list.end(),
@@ -983,8 +973,9 @@
       // the layout.
       // Using display.bounds() and display.work_area() would fail most of the
       // time.
-      if (force_bounds_changed_ || (current_display_info.bounds_in_native() !=
-                                    new_display_info.bounds_in_native()) ||
+      if (force_bounds_changed_ ||
+          (current_display_info.bounds_in_native() !=
+           new_display_info.bounds_in_native()) ||
           (current_display_info.GetOverscanInsetsInPixel() !=
            new_display_info.GetOverscanInsetsInPixel()) ||
           current_display.size() != new_display.size()) {
@@ -1339,10 +1330,8 @@
 void DisplayManager::SetMirrorMode(
     MirrorMode mode,
     const base::Optional<MixedMirrorModeParams>& mixed_params) {
-  if ((is_multi_mirroring_enabled_ && num_connected_displays() < 2) ||
-      (!is_multi_mirroring_enabled_ && num_connected_displays() != 2)) {
+  if (num_connected_displays() < 2)
     return;
-  }
 
   if (mode == MirrorMode::kMixed) {
     // Set mixed mirror mode parameters. This will be used to do two things:
@@ -1713,10 +1702,8 @@
   // mirrored.
   switch (multi_display_mode_) {
     case MIRRORING: {
-      if ((is_multi_mirroring_enabled_ && display_info_list->size() < 2) ||
-          (!is_multi_mirroring_enabled_ && display_info_list->size() != 2)) {
+      if (display_info_list->size() < 2)
         return;
-      }
 
       std::set<int64_t> destination_ids;
       int64_t source_id = kInvalidDisplayId;
diff --git a/ui/display/manager/display_manager.h b/ui/display/manager/display_manager.h
index 9efea193..aa03457 100644
--- a/ui/display/manager/display_manager.h
+++ b/ui/display/manager/display_manager.h
@@ -41,7 +41,7 @@
 namespace gfx {
 class Insets;
 class Rect;
-}
+}  // namespace gfx
 
 namespace display {
 class DisplayLayoutStore;
@@ -139,10 +139,6 @@
   }
 #endif
 
-  bool is_multi_mirroring_enabled() const {
-    return is_multi_mirroring_enabled_;
-  }
-
   const UnifiedDesktopLayoutMatrix& current_unified_desktop_matrix() const {
     return current_unified_desktop_matrix_;
   }
@@ -680,9 +676,6 @@
   base::CancelableCallback<void()> on_display_zoom_modify_timeout_;
 #endif
 
-  // Whether mirroring across multiple displays is enabled.
-  bool is_multi_mirroring_enabled_;
-
   base::WeakPtrFactory<DisplayManager> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(DisplayManager);
diff --git a/ui/file_manager/file_manager/background/js/file_operation_util.js b/ui/file_manager/file_manager/background/js/file_operation_util.js
index 2ee3c90..c4f7b6db 100644
--- a/ui/file_manager/file_manager/background/js/file_operation_util.js
+++ b/ui/file_manager/file_manager/background/js/file_operation_util.js
@@ -1097,12 +1097,13 @@
 fileOperationUtil.MoveTask.processEntry_ = function(
     sourceEntry, destinationEntry, entryChangedCallback, successCallback,
     errorCallback) {
+  const destination =
+      /** @type{!DirectoryEntry} */ (
+          assert(util.unwrapEntry(destinationEntry)));
   fileOperationUtil.deduplicatePath(
-      destinationEntry,
-      sourceEntry.name,
-      function(destinationName) {
+      destination, sourceEntry.name, function(destinationName) {
         sourceEntry.moveTo(
-            destinationEntry, destinationName,
+            destination, destinationName,
             function(movedEntry) {
               entryChangedCallback(util.EntryChangedKind.CREATED, movedEntry);
               entryChangedCallback(util.EntryChangedKind.DELETED, sourceEntry);
@@ -1112,8 +1113,7 @@
               errorCallback(new fileOperationUtil.Error(
                   util.FileOperationErrorType.FILESYSTEM_ERROR, error));
             });
-      },
-      errorCallback);
+      }, errorCallback);
 };
 
 /**
diff --git a/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js b/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
index 80642ca16..c76a84b 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
@@ -763,11 +763,14 @@
   item.dirEntry_ = modelItem.entry;
   item.parentTree_ = tree;
 
+  const icon = queryRequiredElement('.icon', item);
   if (window.IN_TEST && item.entry && item.entry.volumeInfo) {
     item.setAttribute(
         'volume-type-for-testing', item.entry.volumeInfo.volumeType);
+    // TODO(crbug.com/880130) Remove volume-type-icon from here once
+    // MyFilesVolume flag is removed.
+    icon.setAttribute('volume-type-icon', rootType);
   }
-  const icon = queryRequiredElement('.icon', item);
   icon.classList.add('item-icon');
   icon.setAttribute('root-type-icon', rootType);
   return item;
diff --git a/ui/file_manager/integration_tests/file_manager/quick_view.js b/ui/file_manager/integration_tests/file_manager/quick_view.js
index d026247..e7c503cd 100644
--- a/ui/file_manager/integration_tests/file_manager/quick_view.js
+++ b/ui/file_manager/integration_tests/file_manager/quick_view.js
@@ -659,6 +659,140 @@
 };
 
 /**
+ * Tests opening Quick View with multiple files and using the up/down arrow
+ * keys to select and view their content.
+ */
+testcase.openQuickViewKeyboardUpDownChangesView = async function() {
+  const caller = getCaller();
+
+  /**
+   * The text <webview> resides in the #quick-view shadow DOM, as a child of
+   * the #dialog element.
+   */
+  const webView = ['#quick-view', '#dialog[open] webview.text-content'];
+
+  // Open Files app on Downloads containing two text files.
+  const files = [ENTRIES.hello, ENTRIES.tallText];
+  const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS, files, []);
+
+  // Open the last file in Quick View.
+  await openQuickView(appId, ENTRIES.tallText.nameText);
+
+  // Wait for the Quick View <webview> to load and display its content.
+  function checkWebViewTextLoaded(elements) {
+    let haveElements = Array.isArray(elements) && elements.length === 1;
+    if (haveElements) {
+      haveElements = elements[0].styles.display.includes('block');
+    }
+    if (!haveElements || !elements[0].attributes.src) {
+      return pending(caller, 'Waiting for <webview> to load.');
+    }
+    return;
+  }
+  await repeatUntil(async () => {
+    return checkWebViewTextLoaded(await remoteCall.callRemoteTestUtil(
+        'deepQueryAllElements', appId, [webView, ['display']]));
+  });
+
+  // Press the down arrow key to select the next file.
+  const downArrow = ['#quick-view', 'ArrowDown', false, false, false];
+  chrome.test.assertTrue(
+      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, downArrow));
+
+  // Wait until the <webview> displays that file's content.
+  await repeatUntil(async () => {
+    const getTextContent = 'window.document.body.textContent';
+    const text = await remoteCall.callRemoteTestUtil(
+        'deepExecuteScriptInWebView', appId, [webView, getTextContent]);
+    if (!text || !text[0].includes('This is a sample file')) {
+      return pending(caller, 'Waiting for <webview> content.');
+    }
+  });
+
+  // Press the up arrow key to select the previous file.
+  const upArrow = ['#quick-view', 'ArrowUp', false, false, false];
+  chrome.test.assertTrue(
+      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, upArrow));
+
+  // Wait until the <webview> displays that file's content.
+  await repeatUntil(async () => {
+    const getTextContent = 'window.document.body.textContent';
+    const text = await remoteCall.callRemoteTestUtil(
+        'deepExecuteScriptInWebView', appId, [webView, getTextContent]);
+    if (!text || !text[0].includes('42 tall text')) {
+      return pending(caller, 'Waiting for <webview> content.');
+    }
+  });
+};
+
+/**
+ * Tests opening Quick View with multiple files and using the left/right arrow
+ * keys to select and view their content.
+ */
+testcase.openQuickViewKeyboardLeftRightChangesView = async function() {
+  const caller = getCaller();
+
+  /**
+   * The text <webview> resides in the #quick-view shadow DOM, as a child of
+   * the #dialog element.
+   */
+  const webView = ['#quick-view', '#dialog[open] webview.text-content'];
+
+  // Open Files app on Downloads containing two text files.
+  const files = [ENTRIES.hello, ENTRIES.tallText];
+  const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS, files, []);
+
+  // Open the last file in Quick View.
+  await openQuickView(appId, ENTRIES.tallText.nameText);
+
+  // Wait for the Quick View <webview> to load and display its content.
+  function checkWebViewTextLoaded(elements) {
+    let haveElements = Array.isArray(elements) && elements.length === 1;
+    if (haveElements) {
+      haveElements = elements[0].styles.display.includes('block');
+    }
+    if (!haveElements || !elements[0].attributes.src) {
+      return pending(caller, 'Waiting for <webview> to load.');
+    }
+    return;
+  }
+  await repeatUntil(async () => {
+    return checkWebViewTextLoaded(await remoteCall.callRemoteTestUtil(
+        'deepQueryAllElements', appId, [webView, ['display']]));
+  });
+
+  // Press the right arrow key to select the next file item.
+  const rightArrow = ['#quick-view', 'ArrowRight', false, false, false];
+  chrome.test.assertTrue(
+      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, rightArrow));
+
+  // Wait until the <webview> displays that file's content.
+  await repeatUntil(async () => {
+    const getTextContent = 'window.document.body.textContent';
+    const text = await remoteCall.callRemoteTestUtil(
+        'deepExecuteScriptInWebView', appId, [webView, getTextContent]);
+    if (!text || !text[0].includes('This is a sample file')) {
+      return pending(caller, 'Waiting for <webview> content.');
+    }
+  });
+
+  // Press the left arrow key to select the previous file item.
+  const leftArrow = ['#quick-view', 'ArrowLeft', false, false, false];
+  chrome.test.assertTrue(
+      await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, leftArrow));
+
+  // Wait until the <webview> displays that file's content.
+  await repeatUntil(async () => {
+    const getTextContent = 'window.document.body.textContent';
+    const text = await remoteCall.callRemoteTestUtil(
+        'deepExecuteScriptInWebView', appId, [webView, getTextContent]);
+    if (!text || !text[0].includes('42 tall text')) {
+      return pending(caller, 'Waiting for <webview> content.');
+    }
+  });
+};
+
+/**
  * Tests close/open metadata info via Enter key.
  */
 testcase.pressEnterOnInfoBoxToOpenClose = async function() {
diff --git a/ui/file_manager/integration_tests/file_manager/transfer.js b/ui/file_manager/integration_tests/file_manager/transfer.js
index 9ee771d2..979d2a7 100644
--- a/ui/file_manager/integration_tests/file_manager/transfer.js
+++ b/ui/file_manager/integration_tests/file_manager/transfer.js
@@ -171,7 +171,7 @@
       'selectFile', appId, [transferInfo.fileToTransfer.nameText]));
 
   // Copy the file.
-  let transferCommand = transferInfo.isMove ? 'move' : 'copy';
+  let transferCommand = transferInfo.isMove ? 'cut' : 'copy';
   chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
       'execCommand', appId, [transferCommand]));
 
@@ -181,8 +181,8 @@
       appId, [transferInfo.destination.volumeName]));
 
   // Wait for the expected files to appear in the file list.
-  await remoteCall.waitForFiles(appId, dstContents);
-
+  await remoteCall.waitForFiles(
+      appId, dstContents, {ignoreFileSize: true, ignoreLastModifiedTime: true});
   // Paste the file.
   chrome.test.assertTrue(
       await remoteCall.callRemoteTestUtil('execCommand', appId, ['paste']));
@@ -204,7 +204,8 @@
       transferInfo.source.volumeName == 'drive_shared_with_me' ||
       transferInfo.source.volumeName == 'drive_offline' ||
       transferInfo.destination.volumeName == 'drive_shared_with_me' ||
-      transferInfo.destination.volumeName == 'drive_offline';
+      transferInfo.destination.volumeName == 'drive_offline' ||
+      transferInfo.destination.volumeName == 'my_files';
 
   // If we expected the transfer to succeed, add the pasted file to the list
   // of expected rows.
@@ -262,8 +263,37 @@
     isTeamDrive: true,
     initialEntries: TEAM_DRIVE_ENTRY_SET
   }),
-});
 
+  my_files: new TransferLocationInfo({
+    volumeName: 'my_files',
+    initialEntries: [
+      new TestEntryInfo({
+        type: EntryType.DIRECTORY,
+        targetPath: 'Play files',
+        nameText: 'Play files',
+        lastModifiedTime: 'Jan 1, 1980, 11:59 PM',
+        sizeText: '--',
+        typeText: 'Folder'
+      }),
+      new TestEntryInfo({
+        type: EntryType.DIRECTORY,
+        targetPath: 'Downloads',
+        nameText: 'Downloads',
+        lastModifiedTime: 'Jan 1, 1980, 11:59 PM',
+        sizeText: '--',
+        typeText: 'Folder'
+      }),
+      new TestEntryInfo({
+        type: EntryType.DIRECTORY,
+        targetPath: 'Linux files',
+        nameText: 'Linux files',
+        lastModifiedTime: '...',
+        sizeText: '--',
+        typeText: 'Folder'
+      }),
+    ]
+  }),
+});
 
 /**
  * Tests copying from Drive to Downloads.
@@ -277,6 +307,30 @@
 };
 
 /**
+ * Tests moving files from MyFiles/Downloads to MyFiles crbug.com/925175.
+ */
+testcase.transferFromDownloadsToMyFilesMove = function() {
+  return transferBetweenVolumes(new TransferInfo({
+    fileToTransfer: ENTRIES.hello,
+    source: TRANSFER_LOCATIONS.downloads,
+    destination: TRANSFER_LOCATIONS.my_files,
+    isMove: true,
+  }));
+};
+
+/**
+ * Tests copying files from MyFiles/Downloads to MyFiles crbug.com/925175.
+ */
+testcase.transferFromDownloadsToMyFiles = function() {
+  return transferBetweenVolumes(new TransferInfo({
+    fileToTransfer: ENTRIES.hello,
+    source: TRANSFER_LOCATIONS.downloads,
+    destination: TRANSFER_LOCATIONS.my_files,
+    isMove: false,
+  }));
+};
+
+/**
  * Tests copying from Downloads to Drive.
  */
 testcase.transferFromDownloadsToDrive = function() {
diff --git a/ui/file_manager/integration_tests/remote_call.js b/ui/file_manager/integration_tests/remote_call.js
index 902d93ce..0e6a89e 100644
--- a/ui/file_manager/integration_tests/remote_call.js
+++ b/ui/file_manager/integration_tests/remote_call.js
@@ -398,13 +398,18 @@
         expected.sort();
       }
       for (var i = 0; i < Math.min(files.length, expected.length); i++) {
+        // Change the value received from the UI to match when comparing.
         if (options.ignoreFileSize) {
-          files[i][1] = '';
-          expected[i][1] = '';
+          files[i][1] = expected[i][1];
         }
         if (options.ignoreLastModifiedTime) {
-          files[i][3] = '';
-          expected[i][3] = '';
+          if (expected[i].length < 4) {
+            // expected sometimes doesn't include the modified time at all, so
+            // just remove from the data from UI.
+            files[i].splice(3, 1);
+          } else {
+            files[i][3] = expected[i][3];
+          }
         }
       }
       if (!chrome.test.checkDeepEq(expected, files)) {
diff --git a/ui/file_manager/video_player/js/video_player_native_controls.js b/ui/file_manager/video_player/js/video_player_native_controls.js
index bb92bda..70eb6443b 100644
--- a/ui/file_manager/video_player/js/video_player_native_controls.js
+++ b/ui/file_manager/video_player/js/video_player_native_controls.js
@@ -39,17 +39,123 @@
 
   this.videoElement_.addEventListener('pause', this.onPause_.bind(this));
 
+  this.preparePlayList_();
+  this.addKeyControls_();
+};
+
+/**
+ * 10 seconds should be skipped when J/L key is pressed.
+ */
+NativeControlsVideoPlayer.PROGRESS_MAX_SECONDS_TO_SKIP = 10;
+
+/**
+ * 20% of duration should be skipped when the video is too short to skip 10
+ * seconds.
+ */
+NativeControlsVideoPlayer.PROGRESS_MAX_RATIO_TO_SKIP = 0.2;
+
+/**
+ * Attach arrow box for previous/next track to document and set
+ * 'multiple' attribute if user opens more than 1 videos.
+ */
+NativeControlsVideoPlayer.prototype.preparePlayList_ = function() {
   let videoPlayerElement = getRequiredElement('video-player');
-  if (videos.length > 1) {
+  if (this.videos_.length > 1) {
     videoPlayerElement.setAttribute('multiple', true);
   } else {
     videoPlayerElement.removeAttribute('multiple');
   }
 
   let arrowRight = queryRequiredElement('.arrow-box .arrow.right');
-  arrowRight.addEventListener('click', this.advance_.wrap(this, 1));
+  arrowRight.addEventListener(
+      'click', this.advance_.wrap(this, true /* next track */));
   let arrowLeft = queryRequiredElement('.arrow-box .arrow.left');
-  arrowLeft.addEventListener('click', this.advance_.wrap(this, 0));
+  arrowLeft.addEventListener(
+      'click', this.advance_.wrap(this, false /* previous track */));
+};
+
+/**
+ * Add keyboard controls to document.
+ */
+NativeControlsVideoPlayer.prototype.addKeyControls_ = function() {
+  document.addEventListener('keydown', function(e) {
+    switch (util.getKeyModifiers(e) + e.key) {
+      // Handle debug shortcut keys.
+      case 'Ctrl-Shift-I':  // Ctrl+Shift+I
+        chrome.fileManagerPrivate.openInspector('normal');
+        break;
+      case 'Ctrl-Shift-J':  // Ctrl+Shift+J
+        chrome.fileManagerPrivate.openInspector('console');
+        break;
+      case 'Ctrl-Shift-C':  // Ctrl+Shift+C
+        chrome.fileManagerPrivate.openInspector('element');
+        break;
+      case 'Ctrl-Shift-B':  // Ctrl+Shift+B
+        chrome.fileManagerPrivate.openInspector('background');
+        break;
+
+      case 'k':
+      case 'MediaPlayPause':
+        this.togglePlayState_();
+        break;
+      case 'Escape':
+        util.toggleFullScreen(
+            chrome.app.window.current(),
+            false);  // Leave the full screen mode.
+        break;
+      case 'MediaTrackNext':
+        this.advance_(true /* next track */);
+        break;
+      case 'MediaTrackPrevious':
+        this.advance_(false /* previous track */);
+        break;
+      case 'l':
+        this.skip_(true /* forward */);
+        break;
+      case 'j':
+        this.skip_(false /* backward */);
+        break;
+      case 'BrowserBack':
+        chrome.app.window.current().close();
+        break;
+      case 'MediaStop':
+        // TODO: Define "Stop" behavior.
+        break;
+    }
+  }.wrap(this));
+};
+
+/**
+ * Skips forward/backward.
+ * @param {boolean} forward Whether to skip forward or backward.
+ * @private
+ */
+NativeControlsVideoPlayer.prototype.skip_ = function(forward) {
+  let secondsToSkip = Math.min(
+      NativeControlsVideoPlayer.PROGRESS_MAX_SECONDS_TO_SKIP,
+      this.videoElement_.duration *
+          NativeControlsVideoPlayer.PROGRESS_MAX_RATIO_TO_SKIP);
+
+  if (!forward) {
+    secondsToSkip *= -1;
+  }
+
+  this.videoElement_.currentTime = Math.max(
+      Math.min(
+          this.videoElement_.currentTime + secondsToSkip,
+          this.videoElement_.duration),
+      0);
+};
+
+/**
+ * Toggle play/pause.
+ */
+NativeControlsVideoPlayer.prototype.togglePlayState_ = function() {
+  if (this.videoElement_.paused) {
+    this.videoElement_.play();
+  } else {
+    this.videoElement_.pause();
+  }
 };
 
 /**
@@ -106,6 +212,7 @@
       Math.max(0, Math.round(oldTop - (newHeight - oldHeight) / 2));
   appWindow.show();
 
+  this.videoElement_.focus();
   this.videoElement_.play();
 };
 
diff --git a/ui/gl/android/android_surface_control_compat.cc b/ui/gl/android/android_surface_control_compat.cc
index 26c981c..6bfaba1 100644
--- a/ui/gl/android/android_surface_control_compat.cc
+++ b/ui/gl/android/android_surface_control_compat.cc
@@ -7,16 +7,21 @@
 #include <dlfcn.h>
 
 #include "base/android/build_info.h"
+#include "base/bind.h"
 #include "base/memory/ptr_util.h"
 #include "base/no_destructor.h"
 
 extern "C" {
+typedef struct ASurfaceTransactionStats ASurfaceTransactionStats;
+typedef void (*ASurfaceTransaction_OnComplete)(void* context,
+                                               ASurfaceTransactionStats* stats);
+
 // ASurface
 using pASurfaceControl_createFromWindow =
     ASurfaceControl* (*)(ANativeWindow* parent, const char* name);
 using pASurfaceControl_create = ASurfaceControl* (*)(ASurfaceControl* parent,
                                                      const char* name);
-using pASurfaceControl_destroy = void (*)(ASurfaceControl*);
+using pASurfaceControl_release = void (*)(ASurfaceControl*);
 
 // ASurfaceTransaction enums
 enum {
@@ -61,6 +66,18 @@
              ASurfaceControl* surface,
              const ARect rects[],
              uint32_t count);
+
+// ASurfaceTransactionStats
+using pASurfaceTransactionStats_getPresentFenceFd =
+    int (*)(ASurfaceTransactionStats* stats);
+using pASurfaceTransactionStats_getASurfaceControls =
+    void (*)(ASurfaceTransactionStats* stats,
+             ASurfaceControl*** surface_controls,
+             size_t* size);
+using pASurfaceTransactionStats_releaseASurfaceControls =
+    void (*)(ASurfaceControl** surface_controls);
+using pASurfaceTransactionStats_getPreviousReleaseFenceFd =
+    int (*)(ASurfaceTransactionStats* stats, ASurfaceControl* surface_control);
 }
 
 namespace gl {
@@ -92,7 +109,7 @@
 
     LOAD_FUNCTION(main_dl_handle, ASurfaceControl_createFromWindow);
     LOAD_FUNCTION(main_dl_handle, ASurfaceControl_create);
-    LOAD_FUNCTION(main_dl_handle, ASurfaceControl_destroy);
+    LOAD_FUNCTION(main_dl_handle, ASurfaceControl_release);
 
     LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_create);
     LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_delete);
@@ -104,6 +121,13 @@
     LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_setGeometry);
     LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_setBufferTransparency);
     LOAD_FUNCTION(main_dl_handle, ASurfaceTransaction_setDamageRegion);
+
+    LOAD_FUNCTION(main_dl_handle, ASurfaceTransactionStats_getPresentFenceFd);
+    LOAD_FUNCTION(main_dl_handle, ASurfaceTransactionStats_getASurfaceControls);
+    LOAD_FUNCTION(main_dl_handle,
+                  ASurfaceTransactionStats_releaseASurfaceControls);
+    LOAD_FUNCTION(main_dl_handle,
+                  ASurfaceTransactionStats_getPreviousReleaseFenceFd);
   }
 
   ~SurfaceControlMethods() = default;
@@ -112,7 +136,7 @@
   // Surface methods.
   pASurfaceControl_createFromWindow ASurfaceControl_createFromWindowFn;
   pASurfaceControl_create ASurfaceControl_createFn;
-  pASurfaceControl_destroy ASurfaceControl_destroyFn;
+  pASurfaceControl_release ASurfaceControl_releaseFn;
 
   // Transaction methods.
   pASurfaceTransaction_create ASurfaceTransaction_createFn;
@@ -126,6 +150,16 @@
   pASurfaceTransaction_setBufferTransparency
       ASurfaceTransaction_setBufferTransparencyFn;
   pASurfaceTransaction_setDamageRegion ASurfaceTransaction_setDamageRegionFn;
+
+  // TransactionStats methods.
+  pASurfaceTransactionStats_getPresentFenceFd
+      ASurfaceTransactionStats_getPresentFenceFdFn;
+  pASurfaceTransactionStats_getASurfaceControls
+      ASurfaceTransactionStats_getASurfaceControlsFn;
+  pASurfaceTransactionStats_releaseASurfaceControls
+      ASurfaceTransactionStats_releaseASurfaceControlsFn;
+  pASurfaceTransactionStats_getPreviousReleaseFenceFd
+      ASurfaceTransactionStats_getPreviousReleaseFenceFdFn;
 };
 
 ARect RectToARect(const gfx::Rect& rect) {
@@ -153,6 +187,56 @@
   NOTREACHED();
   return ANATIVEWINDOW_TRANSFORM_IDENTITY;
 }
+
+SurfaceControl::TransactionStats ToTransactionStats(
+    ASurfaceTransactionStats* stats) {
+  SurfaceControl::TransactionStats transaction_stats;
+  transaction_stats.present_fence = base::ScopedFD(
+      SurfaceControlMethods::Get().ASurfaceTransactionStats_getPresentFenceFdFn(
+          stats));
+
+  ASurfaceControl** surface_controls = nullptr;
+  size_t size = 0u;
+  SurfaceControlMethods::Get().ASurfaceTransactionStats_getASurfaceControlsFn(
+      stats, &surface_controls, &size);
+  transaction_stats.surface_stats.resize(size);
+  for (size_t i = 0u; i < size; ++i) {
+    transaction_stats.surface_stats[i].surface = surface_controls[i];
+    int fence_fd = SurfaceControlMethods::Get()
+                       .ASurfaceTransactionStats_getPreviousReleaseFenceFdFn(
+                           stats, surface_controls[i]);
+    if (fence_fd != -1) {
+      transaction_stats.surface_stats[i].fence = base::ScopedFD(fence_fd);
+    }
+  }
+  SurfaceControlMethods::Get()
+      .ASurfaceTransactionStats_releaseASurfaceControlsFn(surface_controls);
+
+  return transaction_stats;
+}
+
+struct TransactionAckCtx {
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner;
+  SurfaceControl::Transaction::OnCompleteCb callback;
+};
+
+// Note that the framework API states that this callback can be dispatched on
+// any thread (in practice it should be the binder thread).
+void OnTransactionCompletedOnAnyThread(void* context,
+                                       ASurfaceTransactionStats* stats) {
+  auto* ack_ctx = static_cast<TransactionAckCtx*>(context);
+  auto transaction_stats = ToTransactionStats(stats);
+
+  if (ack_ctx->task_runner) {
+    ack_ctx->task_runner->PostTask(
+        FROM_HERE, base::BindOnce(std::move(ack_ctx->callback),
+                                  std::move(transaction_stats)));
+  } else {
+    std::move(ack_ctx->callback).Run(std::move(transaction_stats));
+  }
+
+  delete ack_ctx;
+}
 };
 
 // static
@@ -185,22 +269,23 @@
 
 SurfaceControl::Surface::~Surface() {
   if (surface_)
-    SurfaceControlMethods::Get().ASurfaceControl_destroyFn(surface_);
+    SurfaceControlMethods::Get().ASurfaceControl_releaseFn(surface_);
 }
 
-SurfaceControl::Surface::Surface(Surface&& other) {
-  surface_ = other.surface_;
-  other.surface_ = nullptr;
-}
+SurfaceControl::SurfaceStats::SurfaceStats() = default;
+SurfaceControl::SurfaceStats::~SurfaceStats() = default;
 
-SurfaceControl::Surface& SurfaceControl::Surface::operator=(Surface&& other) {
-  if (surface_)
-    SurfaceControlMethods::Get().ASurfaceControl_destroyFn(surface_);
+SurfaceControl::SurfaceStats::SurfaceStats(SurfaceStats&& other) = default;
+SurfaceControl::SurfaceStats& SurfaceControl::SurfaceStats::operator=(
+    SurfaceStats&& other) = default;
 
-  surface_ = other.surface_;
-  other.surface_ = nullptr;
-  return *this;
-}
+SurfaceControl::TransactionStats::TransactionStats() = default;
+SurfaceControl::TransactionStats::~TransactionStats() = default;
+
+SurfaceControl::TransactionStats::TransactionStats(TransactionStats&& other) =
+    default;
+SurfaceControl::TransactionStats& SurfaceControl::TransactionStats::operator=(
+    TransactionStats&& other) = default;
 
 SurfaceControl::Transaction::Transaction() {
   transaction_ = SurfaceControlMethods::Get().ASurfaceTransaction_createFn();
@@ -254,11 +339,15 @@
       transaction_, surface.surface(), &a_rect, 1u);
 }
 
-void SurfaceControl::Transaction::SetOnCompleteFunc(
-    ASurfaceTransaction_OnComplete func,
-    void* ctx) {
-  SurfaceControlMethods::Get().ASurfaceTransaction_setOnCompleteFn(transaction_,
-                                                                   ctx, func);
+void SurfaceControl::Transaction::SetOnCompleteCb(
+    OnCompleteCb cb,
+    scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
+  TransactionAckCtx* ack_ctx = new TransactionAckCtx;
+  ack_ctx->callback = std::move(cb);
+  ack_ctx->task_runner = std::move(task_runner);
+
+  SurfaceControlMethods::Get().ASurfaceTransaction_setOnCompleteFn(
+      transaction_, ack_ctx, &OnTransactionCompletedOnAnyThread);
 }
 
 void SurfaceControl::Transaction::Apply() {
diff --git a/ui/gl/android/android_surface_control_compat.h b/ui/gl/android/android_surface_control_compat.h
index 111f7d5..bec240d 100644
--- a/ui/gl/android/android_surface_control_compat.h
+++ b/ui/gl/android/android_surface_control_compat.h
@@ -11,6 +11,8 @@
 #include <android/native_window.h>
 
 #include "base/files/scoped_file.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/overlay_transform.h"
 #include "ui/gl/gl_export.h"
@@ -18,8 +20,6 @@
 extern "C" {
 typedef struct ASurfaceControl ASurfaceControl;
 typedef struct ASurfaceTransaction ASurfaceTransaction;
-typedef void (*ASurfaceTransaction_OnComplete)(void* context,
-                                               int32_t present_fence);
 }
 
 namespace gl {
@@ -28,20 +28,52 @@
  public:
   static bool IsSupported();
 
-  class GL_EXPORT Surface {
+  class GL_EXPORT Surface : public base::RefCounted<Surface> {
    public:
     Surface();
     Surface(const Surface& parent, const char* name);
     Surface(ANativeWindow* parent, const char* name);
-    ~Surface();
-
-    Surface(Surface&& other);
-    Surface& operator=(Surface&& other);
 
     ASurfaceControl* surface() const { return surface_; }
 
    private:
+    friend class base::RefCounted<Surface>;
+    ~Surface();
+
     ASurfaceControl* surface_ = nullptr;
+
+    DISALLOW_COPY_AND_ASSIGN(Surface);
+  };
+
+  struct GL_EXPORT SurfaceStats {
+    SurfaceStats();
+    ~SurfaceStats();
+
+    SurfaceStats(SurfaceStats&& other);
+    SurfaceStats& operator=(SurfaceStats&& other);
+
+    ASurfaceControl* surface = nullptr;
+
+    // The fence which is signaled when the reads for the previous buffer for
+    // the given |surface| are finished.
+    base::ScopedFD fence;
+  };
+
+  struct GL_EXPORT TransactionStats {
+   public:
+    TransactionStats();
+    ~TransactionStats();
+
+    TransactionStats(TransactionStats&& other);
+    TransactionStats& operator=(TransactionStats&& other);
+
+    // The fence which is signaled when this transaction is presented by the
+    // display.
+    base::ScopedFD present_fence;
+    std::vector<SurfaceStats> surface_stats;
+
+   private:
+    DISALLOW_COPY_AND_ASSIGN(TransactionStats);
   };
 
   class GL_EXPORT Transaction {
@@ -60,7 +92,16 @@
                      gfx::OverlayTransform transform);
     void SetOpaque(const Surface& surface, bool opaque);
     void SetDamageRect(const Surface& surface, const gfx::Rect& rect);
-    void SetOnCompleteFunc(ASurfaceTransaction_OnComplete func, void* ctx);
+
+    // Sets the callback which will be dispatched when the transaction is acked
+    // by the framework.
+    // |task_runner| provides an optional task runner on which the callback
+    // should be run.
+    using OnCompleteCb = base::OnceCallback<void(TransactionStats stats)>;
+    void SetOnCompleteCb(
+        OnCompleteCb cb,
+        scoped_refptr<base::SingleThreadTaskRunner> task_runner);
+
     void Apply();
 
    private:
diff --git a/ui/gl/gl_image_ahardwarebuffer.cc b/ui/gl/gl_image_ahardwarebuffer.cc
index 1f2b248..1d037ef96 100644
--- a/ui/gl/gl_image_ahardwarebuffer.cc
+++ b/ui/gl/gl_image_ahardwarebuffer.cc
@@ -3,14 +3,37 @@
 // found in the LICENSE file.
 
 #include "ui/gl/gl_image_ahardwarebuffer.h"
+
 #include "base/android/android_hardware_buffer_compat.h"
 #include "base/android/scoped_hardware_buffer_fence_sync.h"
-
 #include "ui/gl/gl_bindings.h"
+#include "ui/gl/gl_fence_android_native_fence_sync.h"
 
 namespace gl {
 namespace {
 
+class ScopedHardwareBufferFenceSyncImpl
+    : public base::android::ScopedHardwareBufferFenceSync {
+ public:
+  ScopedHardwareBufferFenceSyncImpl(
+      base::android::ScopedHardwareBufferHandle handle,
+      base::ScopedFD fence_fd)
+      : ScopedHardwareBufferFenceSync(std::move(handle), std::move(fence_fd)) {}
+  ~ScopedHardwareBufferFenceSyncImpl() override = default;
+
+  void SetReadFence(base::ScopedFD fence_fd) override {
+    // Insert a service wait for this fence to ensure any resource reuse is
+    // after it is signaled.
+    gfx::GpuFenceHandle handle;
+    handle.type = gfx::GpuFenceHandleType::kAndroidNativeFenceSync;
+    handle.native_fd =
+        base::FileDescriptor(fence_fd.release(), /*auto_close=*/true);
+    gfx::GpuFence gpu_fence(handle);
+    auto gl_fence = GLFence::CreateFromGpuFence(gpu_fence);
+    gl_fence->ServerWait();
+  }
+};
+
 uint32_t GetBufferFormat(const AHardwareBuffer* buffer) {
   AHardwareBuffer_Desc desc = {};
   base::AndroidHardwareBufferCompat::GetInstance().Describe(buffer, &desc);
@@ -86,7 +109,7 @@
 
 std::unique_ptr<base::android::ScopedHardwareBufferFenceSync>
 GLImageAHardwareBuffer::GetAHardwareBuffer() {
-  return std::make_unique<base::android::ScopedHardwareBufferFenceSync>(
+  return std::make_unique<ScopedHardwareBufferFenceSyncImpl>(
       base::android::ScopedHardwareBufferHandle::Create(handle_.get()),
       base::ScopedFD());
 }
diff --git a/ui/gl/gl_image_egl.cc b/ui/gl/gl_image_egl.cc
index e229679..3386eae 100644
--- a/ui/gl/gl_image_egl.cc
+++ b/ui/gl/gl_image_egl.cc
@@ -50,8 +50,7 @@
   DCHECK_EQ(BIND, ShouldBindOrCopy());
 
   glEGLImageTargetTexture2DOES(target, egl_image_);
-  DCHECK_EQ(static_cast<GLenum>(GL_NO_ERROR), glGetError());
-  return true;
+  return glGetError() == static_cast<GLenum>(GL_NO_ERROR);
 }
 
 }  // namespace gl
diff --git a/ui/gl/gl_surface_egl_surface_control.cc b/ui/gl/gl_surface_egl_surface_control.cc
index 4972591..e617b2a 100644
--- a/ui/gl/gl_surface_egl_surface_control.cc
+++ b/ui/gl/gl_surface_egl_surface_control.cc
@@ -9,7 +9,7 @@
 #include "base/bind.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "ui/gfx/geometry/rect_conversions.h"
-#include "ui/gl/gl_fence_android_native_fence_sync.h"
+#include "ui/gl/gl_context.h"
 #include "ui/gl/gl_image_ahardwarebuffer.h"
 
 namespace gl {
@@ -24,27 +24,12 @@
   return gfx::Size(desc.width, desc.height);
 }
 
-struct TransactionAckCtx {
-  scoped_refptr<base::SingleThreadTaskRunner> task_runner;
-  base::OnceCallback<void(int32_t)> callback;
-};
-
-// Note that the framework API states that this callback can be dispatched on
-// any thread (in practice it should be the binder thread), so we need to post
-// a task back to the GPU thread.
-void OnTransactionCompletedOnAnyThread(void* ctx, int32_t present_fence) {
-  auto* ack_ctx = static_cast<TransactionAckCtx*>(ctx);
-  ack_ctx->task_runner->PostTask(
-      FROM_HERE, base::BindOnce(std::move(ack_ctx->callback), present_fence));
-  delete ack_ctx;
-}
-
 }  // namespace
 
 GLSurfaceEGLSurfaceControl::GLSurfaceEGLSurfaceControl(
     ANativeWindow* window,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner)
-    : root_surface_(window, kRootSurfaceName),
+    : root_surface_(new SurfaceControl::Surface(window, kRootSurfaceName)),
       gpu_task_runner_(std::move(task_runner)),
       weak_factory_(this) {}
 
@@ -63,7 +48,7 @@
 void GLSurfaceEGLSurfaceControl::Destroy() {
   pending_transaction_.reset();
   surface_list_.clear();
-  root_surface_ = SurfaceControl::Surface();
+  root_surface_.reset();
 }
 
 bool GLSurfaceEGLSurfaceControl::Resize(const gfx::Size& size,
@@ -116,18 +101,11 @@
   current_frame_resources_.swap(pending_frame_resources_);
   pending_frame_resources_.clear();
 
-  // Set up the callback to be notified when the frame is presented by the
-  // framework. Note that it is assumed that all GPU/display work for this frame
-  // is finished when the callback is dispatched, and all resources from the
-  // previous frame can be reused.
-  TransactionAckCtx* ack_ctx = new TransactionAckCtx;
-  ack_ctx->task_runner = gpu_task_runner_;
-  ack_ctx->callback =
+  SurfaceControl::Transaction::OnCompleteCb callback =
       base::BindOnce(&GLSurfaceEGLSurfaceControl::OnTransactionAckOnGpuThread,
                      weak_factory_.GetWeakPtr(), completion_callback,
                      present_callback, std::move(resources_to_release));
-  pending_transaction_->SetOnCompleteFunc(&OnTransactionCompletedOnAnyThread,
-                                          ack_ctx);
+  pending_transaction_->SetOnCompleteCb(std::move(callback), gpu_task_runner_);
 
   pending_transaction_->Apply();
   pending_transaction_.reset();
@@ -142,6 +120,7 @@
 }
 
 bool GLSurfaceEGLSurfaceControl::OnMakeCurrent(GLContext* context) {
+  context_ = context;
   return true;
 }
 
@@ -159,14 +138,14 @@
   bool uninitialized = false;
   if (pending_surfaces_count_ == surface_list_.size()) {
     uninitialized = true;
-    surface_list_.emplace_back(root_surface_);
+    surface_list_.emplace_back(*root_surface_);
   }
   pending_surfaces_count_++;
   auto& surface_state = surface_list_.at(pending_surfaces_count_ - 1);
 
   if (uninitialized || surface_state.z_order != z_order) {
     surface_state.z_order = z_order;
-    pending_transaction_->SetZOrder(surface_state.surface, z_order);
+    pending_transaction_->SetZOrder(*surface_state.surface, z_order);
   }
 
   AHardwareBuffer* hardware_buffer = nullptr;
@@ -175,7 +154,13 @@
   if (scoped_hardware_buffer) {
     hardware_buffer = scoped_hardware_buffer->buffer();
     fence_fd = scoped_hardware_buffer->TakeFence();
-    pending_frame_resources_.push_back(std::move(scoped_hardware_buffer));
+
+    auto* a_surface = surface_state.surface->surface();
+    DCHECK_EQ(pending_frame_resources_.count(a_surface), 0u);
+
+    auto& resource_ref = pending_frame_resources_[a_surface];
+    resource_ref.surface = surface_state.surface;
+    resource_ref.scoped_buffer = std::move(scoped_hardware_buffer);
   }
 
   if (uninitialized || surface_state.hardware_buffer != hardware_buffer) {
@@ -188,7 +173,7 @@
       fence_fd = base::ScopedFD(fence_handle.native_fd.fd);
     }
 
-    pending_transaction_->SetBuffer(surface_state.surface,
+    pending_transaction_->SetBuffer(*surface_state.surface,
                                     surface_state.hardware_buffer,
                                     std::move(fence_fd));
   }
@@ -209,7 +194,7 @@
       surface_state.src = src;
       surface_state.dst = dst;
       surface_state.transform = transform;
-      pending_transaction_->SetGeometry(surface_state.surface, src, dst,
+      pending_transaction_->SetGeometry(*surface_state.surface, src, dst,
                                         transform);
     }
   }
@@ -217,7 +202,7 @@
   bool opaque = !enable_blend;
   if (uninitialized || surface_state.opaque != opaque) {
     surface_state.opaque = opaque;
-    pending_transaction_->SetOpaque(surface_state.surface, opaque);
+    pending_transaction_->SetOpaque(*surface_state.surface, opaque);
   }
 
   return true;
@@ -256,34 +241,32 @@
     SwapCompletionCallback completion_callback,
     PresentationCallback presentation_callback,
     ResourceRefs released_resources,
-    int32_t present_fence) {
+    SurfaceControl::TransactionStats transaction_stats) {
   DCHECK(gpu_task_runner_->BelongsToCurrentThread());
-
-  // Insert a service wait for this fence to ensure any resource reuse is after
-  // it is signaled.
-  gfx::GpuFenceHandle handle;
-  handle.type = gfx::GpuFenceHandleType::kAndroidNativeFenceSync;
-  handle.native_fd = base::FileDescriptor(present_fence, /*auto_close=*/true);
-  gfx::GpuFence gpu_fence(handle);
-  // TODO(khushalsagar): But what about vulkan?
-  auto gl_fence = GLFence::CreateFromGpuFence(gpu_fence);
-  gl_fence->ServerWait();
+  context_->MakeCurrent(this);
 
   // The presentation feedback callback must run after swap completion.
   completion_callback.Run(gfx::SwapResult::SWAP_ACK, nullptr);
 
-  // TODO(khushalsagar): Maintain a queue of fences so we poll to see if they
-  // are signaled every frame, and get a signal timestamp to feed into this
+  // TODO(khushalsagar): Maintain a queue of present fences so we poll to see if
+  // they are signaled every frame, and get a signal timestamp to feed into this
   // feedback.
   gfx::PresentationFeedback feedback(base::TimeTicks::Now(), base::TimeDelta(),
                                      0 /* flags */);
   presentation_callback.Run(feedback);
+
+  for (auto& surface_stat : transaction_stats.surface_stats) {
+    auto it = released_resources.find(surface_stat.surface);
+    DCHECK(it != released_resources.end());
+    if (surface_stat.fence.is_valid())
+      it->second.scoped_buffer->SetReadFence(std::move(surface_stat.fence));
+  }
   released_resources.clear();
 }
 
 GLSurfaceEGLSurfaceControl::SurfaceState::SurfaceState(
     const SurfaceControl::Surface& parent)
-    : surface(parent, kChildSurfaceName) {}
+    : surface(new SurfaceControl::Surface(parent, kChildSurfaceName)) {}
 
 GLSurfaceEGLSurfaceControl::SurfaceState::SurfaceState() = default;
 GLSurfaceEGLSurfaceControl::SurfaceState::SurfaceState(SurfaceState&& other) =
@@ -294,4 +277,12 @@
 
 GLSurfaceEGLSurfaceControl::SurfaceState::~SurfaceState() = default;
 
+GLSurfaceEGLSurfaceControl::ResourceRef::ResourceRef() = default;
+GLSurfaceEGLSurfaceControl::ResourceRef::~ResourceRef() = default;
+GLSurfaceEGLSurfaceControl::ResourceRef::ResourceRef(ResourceRef&& other) =
+    default;
+GLSurfaceEGLSurfaceControl::ResourceRef&
+GLSurfaceEGLSurfaceControl::ResourceRef::operator=(ResourceRef&& other) =
+    default;
+
 }  // namespace gl
diff --git a/ui/gl/gl_surface_egl_surface_control.h b/ui/gl/gl_surface_egl_surface_control.h
index 5542d504..ca7b9f08 100644
--- a/ui/gl/gl_surface_egl_surface_control.h
+++ b/ui/gl/gl_surface_egl_surface_control.h
@@ -9,6 +9,7 @@
 #include <memory>
 
 #include "base/android/scoped_hardware_buffer_handle.h"
+#include "base/containers/flat_map.h"
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
 #include "ui/gl/android/android_surface_control_compat.h"
@@ -25,7 +26,7 @@
 
 namespace gl {
 
-class GL_EXPORT GLSurfaceEGLSurfaceControl : public gl::GLSurfaceEGL {
+class GL_EXPORT GLSurfaceEGLSurfaceControl : public GLSurfaceEGL {
  public:
   explicit GLSurfaceEGLSurfaceControl(
       ANativeWindow* window,
@@ -33,7 +34,7 @@
 
   // GLSurface implementation.
   int GetBufferCount() const override;
-  bool Initialize(gl::GLSurfaceFormat format) override;
+  bool Initialize(GLSurfaceFormat format) override;
   void Destroy() override;
   bool Resize(const gfx::Size& size,
               float scale_factor,
@@ -50,10 +51,10 @@
       const SwapCompletionCallback& completion_callback,
       const PresentationCallback& presentation_callback) override;
   gfx::Size GetSize() override;
-  bool OnMakeCurrent(gl::GLContext* context) override;
+  bool OnMakeCurrent(GLContext* context) override;
   bool ScheduleOverlayPlane(int z_order,
                             gfx::OverlayTransform transform,
-                            gl::GLImage* image,
+                            GLImage* image,
                             const gfx::Rect& bounds_rect,
                             const gfx::RectF& crop_rect,
                             bool enable_blend,
@@ -85,11 +86,20 @@
     gfx::OverlayTransform transform = gfx::OVERLAY_TRANSFORM_NONE;
     bool opaque = true;
 
-    gl::SurfaceControl::Surface surface;
+    scoped_refptr<SurfaceControl::Surface> surface;
   };
 
-  using ResourceRefs = std::vector<
-      std::unique_ptr<base::android::ScopedHardwareBufferFenceSync>>;
+  struct ResourceRef {
+    ResourceRef();
+    ~ResourceRef();
+
+    ResourceRef(ResourceRef&& other);
+    ResourceRef& operator=(ResourceRef&& other);
+
+    scoped_refptr<SurfaceControl::Surface> surface;
+    std::unique_ptr<base::android::ScopedHardwareBufferFenceSync> scoped_buffer;
+  };
+  using ResourceRefs = base::flat_map<ASurfaceControl*, ResourceRef>;
 
   void CommitPendingTransaction(
       const SwapCompletionCallback& completion_callback,
@@ -97,13 +107,14 @@
 
   // Called on the |gpu_task_runner_| when a transaction is acked by the
   // framework.
-  void OnTransactionAckOnGpuThread(SwapCompletionCallback completion_callback,
-                                   PresentationCallback presentation_callback,
-                                   ResourceRefs released_resources,
-                                   int32_t present_fence);
+  void OnTransactionAckOnGpuThread(
+      SwapCompletionCallback completion_callback,
+      PresentationCallback presentation_callback,
+      ResourceRefs released_resources,
+      SurfaceControl::TransactionStats transaction_stats);
 
   // Holds the surface state changes made since the last call to SwapBuffers.
-  base::Optional<gl::SurfaceControl::Transaction> pending_transaction_;
+  base::Optional<SurfaceControl::Transaction> pending_transaction_;
 
   // The list of Surfaces and the corresponding state. The initial
   // |pending_surfaces_count_| surfaces in this list are surfaces with state
@@ -128,7 +139,10 @@
 
   // The root surface tied to the ANativeWindow that places the content of this
   // GLSurface in the java view tree.
-  gl::SurfaceControl::Surface root_surface_;
+  scoped_refptr<SurfaceControl::Surface> root_surface_;
+
+  // The last context made current with this surface.
+  scoped_refptr<GLContext> context_;
 
   scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner_;
   base::WeakPtrFactory<GLSurfaceEGLSurfaceControl> weak_factory_;
diff --git a/ui/keyboard/keyboard_controller.cc b/ui/keyboard/keyboard_controller.cc
index cb42418..ab92b68 100644
--- a/ui/keyboard/keyboard_controller.cc
+++ b/ui/keyboard/keyboard_controller.cc
@@ -274,6 +274,10 @@
 
   ActivateKeyboardInContainer(
       layout_delegate_->GetContainerForDefaultDisplay());
+
+  // Start preloading the virtual keyboard UI in the background, so that it
+  // shows up faster when needed.
+  LoadKeyboardWindowInBackground();
 }
 
 void KeyboardController::DisableKeyboard() {
diff --git a/ui/keyboard/keyboard_controller.h b/ui/keyboard/keyboard_controller.h
index b60125c..7ea3eed 100644
--- a/ui/keyboard/keyboard_controller.h
+++ b/ui/keyboard/keyboard_controller.h
@@ -87,6 +87,7 @@
   static bool HasInstance();
 
   // Enables the virtual keyboard with a specified |ui| and |delegate|.
+  // Immediately starts pre-loading the keyboard window in the background.
   // Disables and re-enables the keyboard if it is already enabled.
   void EnableKeyboard(std::unique_ptr<KeyboardUI> ui,
                       KeyboardLayoutDelegate* delegate);
@@ -177,10 +178,6 @@
   // lock the keyboard
   void ShowKeyboardInDisplay(const display::Display& display);
 
-  // Loads the keyboard window in the background, but does not display
-  // the keyboard.
-  void LoadKeyboardWindowInBackground();
-
   // Returns the bounds in screen for the visible portion of the keyboard. An
   // empty rectangle will get returned when the keyboard is hidden.
   const gfx::Rect& visual_bounds_in_screen() const {
@@ -322,6 +319,10 @@
   // keyboard if it is currently visible.
   void DeactivateKeyboard();
 
+  // Loads the keyboard window in the background, but does not display
+  // the keyboard.
+  void LoadKeyboardWindowInBackground();
+
   // Show virtual keyboard immediately with animation.
   void ShowKeyboardInternal(aura::Window* target_container);
   void PopulateKeyboardContent(aura::Window* target_container,
diff --git a/ui/keyboard/keyboard_controller_unittest.cc b/ui/keyboard/keyboard_controller_unittest.cc
index 5281d79..a8aaaa0 100644
--- a/ui/keyboard/keyboard_controller_unittest.cc
+++ b/ui/keyboard/keyboard_controller_unittest.cc
@@ -282,8 +282,6 @@
 TEST_F(KeyboardControllerTest, KeyboardSize) {
   root_window()->SetLayoutManager(new KeyboardLayoutManager(&controller()));
 
-  controller().LoadKeyboardWindowInBackground();
-
   // The keyboard window should not be visible.
   aura::Window* keyboard_window = controller().GetKeyboardWindow();
   EXPECT_FALSE(keyboard_window->IsVisible());
@@ -317,7 +315,6 @@
   ui::DummyTextInputClient no_input_client(ui::TEXT_INPUT_TYPE_NONE);
 
   base::RunLoop run_loop;
-  controller().LoadKeyboardWindowInBackground();
   aura::Window* keyboard_window = controller().GetKeyboardWindow();
   auto keyboard_container_observer =
       std::make_unique<KeyboardContainerObserver>(keyboard_window, &run_loop);
@@ -360,7 +357,6 @@
   ui::DummyTextInputClient no_input_client(ui::TEXT_INPUT_TYPE_NONE);
 
   base::RunLoop run_loop;
-  controller().LoadKeyboardWindowInBackground();
   aura::Window* keyboard_window = controller().GetKeyboardWindow();
   auto keyboard_container_observer =
       std::make_unique<KeyboardContainerObserver>(keyboard_window, &run_loop);
@@ -398,7 +394,6 @@
   ui::DummyTextInputClient no_input_client_1(ui::TEXT_INPUT_TYPE_NONE);
 
   base::RunLoop run_loop;
-  controller().LoadKeyboardWindowInBackground();
   aura::Window* keyboard_window = controller().GetKeyboardWindow();
   auto keyboard_container_observer =
       std::make_unique<KeyboardContainerObserver>(keyboard_window, &run_loop);
@@ -457,7 +452,6 @@
   ui::DummyTextInputClient no_input_client_1(ui::TEXT_INPUT_TYPE_NONE);
 
   base::RunLoop run_loop;
-  controller().LoadKeyboardWindowInBackground();
   aura::Window* keyboard_window = controller().GetKeyboardWindow();
   auto keyboard_container_observer =
       std::make_unique<KeyboardContainerObserver>(keyboard_window, &run_loop);
@@ -496,7 +490,6 @@
 
 // Tests that disabling the keyboard will get a corresponding event.
 TEST_F(KeyboardControllerTest, DisableKeyboard) {
-  controller().LoadKeyboardWindowInBackground();
   aura::Window* keyboard_window = controller().GetKeyboardWindow();
 
   ShowKeyboard();
@@ -508,7 +501,6 @@
 }
 
 TEST_F(KeyboardControllerTest, SetOccludedBoundsChangesFullscreenBounds) {
-  controller().LoadKeyboardWindowInBackground();
 
   // Keyboard is hidden, so SetContainerType should be synchronous.
   controller().SetContainerType(mojom::ContainerType::kFullscreen,
@@ -547,8 +539,6 @@
 
     KeyboardControllerTest::SetUp();
 
-    // Preload the keyboard contents so that we can set its bounds.
-    controller().LoadKeyboardWindowInBackground();
     // Wait for the keyboard contents to load.
     base::RunLoop().RunUntilIdle();
     keyboard_window()->SetBounds(root_window()->bounds());
@@ -711,7 +701,6 @@
                                            ui::TEXT_INPUT_MODE_NONE);
 
   base::RunLoop run_loop;
-  controller().LoadKeyboardWindowInBackground();
   aura::Window* keyboard_window = controller().GetKeyboardWindow();
   auto keyboard_container_observer =
       std::make_unique<KeyboardContainerObserver>(keyboard_window, &run_loop);
diff --git a/ui/keyboard/test/keyboard_test_util.cc b/ui/keyboard/test/keyboard_test_util.cc
index efeda891..73d71ba 100644
--- a/ui/keyboard/test/keyboard_test_util.cc
+++ b/ui/keyboard/test/keyboard_test_util.cc
@@ -48,6 +48,21 @@
 
 }  // namespace
 
+namespace test {
+
+bool WaitUntilLoaded() {
+  // In tests, the keyboard window is mocked out so it usually "loads" within a
+  // single RunUntilIdle call.
+  base::RunLoop run_loop;
+  while (KeyboardController::Get()->GetStateForTest() ==
+         KeyboardControllerState::LOADING_EXTENSION) {
+    run_loop.RunUntilIdle();
+  }
+  return true;
+}
+
+}  // namespace test
+
 bool WaitUntilShown() {
   // KeyboardController send a visibility update once the show animation
   // finishes.
diff --git a/ui/keyboard/test/keyboard_test_util.h b/ui/keyboard/test/keyboard_test_util.h
index 0cacd08..d85a9810 100644
--- a/ui/keyboard/test/keyboard_test_util.h
+++ b/ui/keyboard/test/keyboard_test_util.h
@@ -13,6 +13,14 @@
 
 namespace keyboard {
 
+// TODO(shend): Move other methods into test namespace.
+namespace test {
+
+// Waits until the keyboard window finishes loading.
+bool WaitUntilLoaded();
+
+}  // namespace test
+
 // Waits until the keyboard is fully shown, with no pending animations.
 bool WaitUntilShown();
 
diff --git a/ui/views/animation/ink_drop_impl_unittest.cc b/ui/views/animation/ink_drop_impl_unittest.cc
index dbe46ff2..e990c733 100644
--- a/ui/views/animation/ink_drop_impl_unittest.cc
+++ b/ui/views/animation/ink_drop_impl_unittest.cc
@@ -323,7 +323,7 @@
 typedef InkDropImplAutoHighlightTest InkDropImplCommonAutoHighlightTest;
 // Note: First argument is optional and intentionally left blank.
 // (it's a prefix for the generated test cases)
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ,
     InkDropImplCommonAutoHighlightTest,
     testing::Values(InkDropImpl::AutoHighlightMode::NONE,
@@ -371,9 +371,9 @@
 typedef InkDropImplAutoHighlightTest InkDropImplNoAutoHighlightTest;
 // Note: First argument is optional and intentionally left blank.
 // (it's a prefix for the generated test cases)
-INSTANTIATE_TEST_CASE_P(,
-                        InkDropImplNoAutoHighlightTest,
-                        testing::Values(InkDropImpl::AutoHighlightMode::NONE));
+INSTANTIATE_TEST_SUITE_P(,
+                         InkDropImplNoAutoHighlightTest,
+                         testing::Values(InkDropImpl::AutoHighlightMode::NONE));
 
 TEST_P(InkDropImplNoAutoHighlightTest, VisibleHighlightDuringRippleAnimations) {
   test_api()->SetShouldHighlight(true);
@@ -407,7 +407,7 @@
 typedef InkDropImplAutoHighlightTest InkDropImplHideAutoHighlightTest;
 // Note: First argument is optional and intentionally left blank.
 // (it's a prefix for the generated test cases)
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ,
     InkDropImplHideAutoHighlightTest,
     testing::Values(InkDropImpl::AutoHighlightMode::HIDE_ON_RIPPLE));
@@ -567,7 +567,7 @@
 typedef InkDropImplAutoHighlightTest InkDropImplShowAutoHighlightTest;
 // Note: First argument is optional and intentionally left blank.
 // (it's a prefix for the generated test cases)
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     ,
     InkDropImplShowAutoHighlightTest,
     testing::Values(InkDropImpl::AutoHighlightMode::SHOW_ON_RIPPLE));
diff --git a/ui/views/controls/image_view_unittest.cc b/ui/views/controls/image_view_unittest.cc
index 5e4d4a5..399ed9c 100644
--- a/ui/views/controls/image_view_unittest.cc
+++ b/ui/views/controls/image_view_unittest.cc
@@ -141,8 +141,8 @@
   EXPECT_EQ(image_view_bounds, image_view()->bounds());
 }
 
-INSTANTIATE_TEST_CASE_P(,
-                        ImageViewTest,
-                        ::testing::Values(Axis::kHorizontal, Axis::kVertical));
+INSTANTIATE_TEST_SUITE_P(,
+                         ImageViewTest,
+                         ::testing::Values(Axis::kHorizontal, Axis::kVertical));
 
 }  // namespace views
diff --git a/ui/views/controls/menu/menu_controller.cc b/ui/views/controls/menu/menu_controller.cc
index bda7ba1..5d3a78a 100644
--- a/ui/views/controls/menu/menu_controller.cc
+++ b/ui/views/controls/menu/menu_controller.cc
@@ -2528,6 +2528,10 @@
   if (item->GetSubmenu()->GetMenuItemCount() > 0)
     to_select = FindInitialSelectableMenuItem(item, INCREMENT_SELECTION_DOWN);
   if (to_select) {
+    // Selection is going from the ACTIONABLE to the SUBMENU region of the
+    // ACTIONABLE_SUBMENU, so highlight the SUBMENU area.
+    if (item->type_ == MenuItemView::ACTIONABLE_SUBMENU)
+      item->SetSelectionOfActionableSubmenu(true);
     SetSelection(to_select, SELECTION_UPDATE_IMMEDIATELY);
     return;
   }
diff --git a/ui/views/controls/scroll_view_unittest.cc b/ui/views/controls/scroll_view_unittest.cc
index 2da29295..41565d3 100644
--- a/ui/views/controls/scroll_view_unittest.cc
+++ b/ui/views/controls/scroll_view_unittest.cc
@@ -1725,12 +1725,12 @@
   EXPECT_EQ(gfx::ScrollOffset(0, 10), test_api.CurrentOffset());
 }
 
-INSTANTIATE_TEST_CASE_P(,
-                        WidgetScrollViewTestRTLAndLayers,
-                        ::testing::Values(UiConfig::kLtr,
-                                          UiConfig::kRtl,
-                                          UiConfig::kLtrWithLayers,
-                                          UiConfig::kRtlWithLayers),
-                        &UiConfigToString);
+INSTANTIATE_TEST_SUITE_P(,
+                         WidgetScrollViewTestRTLAndLayers,
+                         ::testing::Values(UiConfig::kLtr,
+                                           UiConfig::kRtl,
+                                           UiConfig::kLtrWithLayers,
+                                           UiConfig::kRtlWithLayers),
+                         &UiConfigToString);
 
 }  // namespace views
diff --git a/ui/views/focus/focus_manager_unittest.cc b/ui/views/focus/focus_manager_unittest.cc
index 91414af..0687a18 100644
--- a/ui/views/focus/focus_manager_unittest.cc
+++ b/ui/views/focus/focus_manager_unittest.cc
@@ -660,7 +660,7 @@
 
 // Instantiate the Boolean which is used to toggle RTL in
 // the parameterized tests.
-INSTANTIATE_TEST_CASE_P(, FocusManagerArrowKeyTraversalTest, testing::Bool());
+INSTANTIATE_TEST_SUITE_P(, FocusManagerArrowKeyTraversalTest, testing::Bool());
 
 }  // namespace
 
diff --git a/ui/views/mus/remote_view/BUILD.gn b/ui/views/mus/remote_view/BUILD.gn
index 8b33332..dd53a6e 100644
--- a/ui/views/mus/remote_view/BUILD.gn
+++ b/ui/views/mus/remote_view/BUILD.gn
@@ -56,6 +56,7 @@
     ":remote_view_provider",
     ":test_support",
     "//base",
+    "//base/test:test_support",
     "//testing/gtest",
     "//ui/aura",
     "//ui/aura:test_support",
diff --git a/ui/views/mus/remote_view/remote_view_provider_unittest.cc b/ui/views/mus/remote_view/remote_view_provider_unittest.cc
index 1d727f6..0ff8e8f3 100644
--- a/ui/views/mus/remote_view/remote_view_provider_unittest.cc
+++ b/ui/views/mus/remote_view/remote_view_provider_unittest.cc
@@ -10,7 +10,9 @@
 #include "base/bind_helpers.h"
 #include "base/macros.h"
 #include "base/run_loop.h"
+#include "base/test/bind_test_util.h"
 #include "base/unguessable_token.h"
+#include "ui/aura/client/focus_change_observer.h"
 #include "ui/aura/mus/window_mus.h"
 #include "ui/aura/test/aura_test_base.h"
 #include "ui/aura/test/mus/test_window_tree.h"
@@ -22,6 +24,30 @@
 
 namespace views {
 
+class TestFocusChangeObserver : public aura::client::FocusChangeObserver {
+ public:
+  explicit TestFocusChangeObserver(aura::Window* window) : window_(window) {
+    aura::client::SetFocusChangeObserver(window_, this);
+  }
+  ~TestFocusChangeObserver() override {
+    aura::client::SetFocusChangeObserver(window_, nullptr);
+  }
+
+  // aura::client::FocusChangeObserver:
+  void OnWindowFocused(aura::Window* gained_focus,
+                       aura::Window* lost_focus) override {
+    on_window_focused_called_ = true;
+  }
+
+  bool on_window_focused_called() const { return on_window_focused_called_; }
+
+ private:
+  aura::Window* const window_;
+  bool on_window_focused_called_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(TestFocusChangeObserver);
+};
+
 class RemoteViewProviderTest : public aura::test::AuraTestBase {
  public:
   RemoteViewProviderTest() = default;
@@ -55,13 +81,11 @@
   base::UnguessableToken GetEmbedToken() {
     base::RunLoop run_loop;
     base::UnguessableToken token;
-    provider_->GetEmbedToken(base::BindOnce(
-        [](base::RunLoop* run_loop, base::UnguessableToken* out_token,
-           const base::UnguessableToken& token) {
-          *out_token = token;
-          run_loop->Quit();
-        },
-        &run_loop, &token));
+    provider_->GetEmbedToken(
+        base::BindLambdaForTesting([&](const base::UnguessableToken& in_token) {
+          token = in_token;
+          run_loop.Quit();
+        }));
     run_loop.Run();
     return token;
   }
@@ -73,13 +97,10 @@
     base::RunLoop run_loop;
     aura::Window* embedder = nullptr;
     provider_->SetCallbacks(
-        base::BindRepeating(
-            [](base::RunLoop* run_loop, aura::Window** out_embedder,
-               aura::Window* embedder) {
-              *out_embedder = embedder;
-              run_loop->Quit();
-            },
-            &run_loop, &embedder),
+        base::BindLambdaForTesting([&](aura::Window* in_embedder) {
+          embedder = in_embedder;
+          run_loop.Quit();
+        }),
         base::DoNothing() /* OnUnembedCallback */);
     window_tree()->AddEmbedRootForToken(token);
     run_loop.Run();
@@ -102,8 +123,7 @@
     base::RunLoop run_loop;
     provider_->SetCallbacks(
         base::DoNothing() /* OnEmbedCallback */,
-        base::BindRepeating([](base::RunLoop* run_loop) { run_loop->Quit(); },
-                            &run_loop));
+        base::BindLambdaForTesting([&]() { run_loop.Quit(); }));
 
     const ws::Id embedder_window_id =
         aura::WindowMus::Get(embedder)->server_id();
@@ -191,4 +211,14 @@
   EXPECT_EQ(root_bounds.origin(), root_window->GetBoundsInScreen().origin());
 }
 
+TEST_F(RemoteViewProviderTest, FocusChangeObserver) {
+  SimulateEmbed();
+
+  TestFocusChangeObserver observer(embedded_.get());
+  ASSERT_FALSE(observer.on_window_focused_called());
+
+  embedded_->Focus();
+  EXPECT_TRUE(observer.on_window_focused_called());
+}
+
 }  // namespace views
diff --git a/ui/views/test/DEPS b/ui/views/test/DEPS
index 98b1137..2ba6d7d 100644
--- a/ui/views/test/DEPS
+++ b/ui/views/test/DEPS
@@ -7,6 +7,6 @@
     "+services/service_manager/background/background_service_manager.h",
     "+services/service_manager/public",
     "+services/ws/ime/test_ime_driver/manifest.h",
-    "+services/ws/test_ws/manifest.h",
+    "+services/ws/test_ws/test_manifest.h",
   ]
 }
diff --git a/ui/views/test/platform_test_helper_mus.cc b/ui/views/test/platform_test_helper_mus.cc
index 505067e..342c0b8b 100644
--- a/ui/views/test/platform_test_helper_mus.cc
+++ b/ui/views/test/platform_test_helper_mus.cc
@@ -16,7 +16,7 @@
 #include "services/service_manager/public/cpp/service_binding.h"
 #include "services/ws/ime/test_ime_driver/manifest.h"
 #include "services/ws/public/mojom/constants.mojom.h"
-#include "services/ws/test_ws/manifest.h"
+#include "services/ws/test_ws/test_manifest.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/aura/mus/window_tree_host_mus.h"
 #include "ui/aura/test/env_test_helper.h"
diff --git a/ui/views/widget/widget_unittest.cc b/ui/views/widget/widget_unittest.cc
index ca9e496..7e8621d 100644
--- a/ui/views/widget/widget_unittest.cc
+++ b/ui/views/widget/widget_unittest.cc
@@ -630,7 +630,7 @@
   InvokeWidgetMethods(&widget);
 }
 
-INSTANTIATE_TEST_CASE_P(
+INSTANTIATE_TEST_SUITE_P(
     PlatformWidgetWithDestroyedNativeViewTest,
     WidgetWithDestroyedNativeViewTest,
     ::testing::Values(ViewsTestBase::NativeWidgetType::kDefault,
diff --git a/ui/wm/core/accelerator_filter.cc b/ui/wm/core/accelerator_filter.cc
index 21f10d5..598b141 100644
--- a/ui/wm/core/accelerator_filter.cc
+++ b/ui/wm/core/accelerator_filter.cc
@@ -28,16 +28,29 @@
 AcceleratorFilter::~AcceleratorFilter() {
 }
 
+bool AcceleratorFilter::ShouldFilter(ui::KeyEvent* event) {
+  const ui::EventType type = event->type();
+  if (!event->target() ||
+      (type != ui::ET_KEY_PRESSED && type != ui::ET_KEY_RELEASED) ||
+      event->is_char() || !event->target() ||
+      // Key events with key code of VKEY_PROCESSKEY, usually created by virtual
+      // keyboard (like handwriting input), have no effect on accelerator and
+      // they may disturb the accelerator history. So filter them out. (see
+      // https://crbug.com/918317)
+      event->key_code() == ui::VKEY_PROCESSKEY) {
+    return true;
+  }
+
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // AcceleratorFilter, EventFilter implementation:
 
 void AcceleratorFilter::OnKeyEvent(ui::KeyEvent* event) {
-  const ui::EventType type = event->type();
   DCHECK(event->target());
-  if ((type != ui::ET_KEY_PRESSED && type != ui::ET_KEY_RELEASED) ||
-      event->is_char() || !event->target()) {
+  if (ShouldFilter(event))
     return;
-  }
 
   ui::Accelerator accelerator(*event);
   accelerator_history_->StoreCurrentAccelerator(accelerator);
diff --git a/ui/wm/core/accelerator_filter.h b/ui/wm/core/accelerator_filter.h
index 86bdf77e2..18f27645 100644
--- a/ui/wm/core/accelerator_filter.h
+++ b/ui/wm/core/accelerator_filter.h
@@ -28,6 +28,9 @@
                     ui::AcceleratorHistory* accelerator_history);
   ~AcceleratorFilter() override;
 
+  // If the return value is true, |event| should be filtered out.
+  static bool ShouldFilter(ui::KeyEvent* event);
+
   // Overridden from ui::EventHandler:
   void OnKeyEvent(ui::KeyEvent* event) override;
   void OnMouseEvent(ui::MouseEvent* event) override;