diff --git a/DEPS b/DEPS
index fdc6aaa..84579bba 100644
--- a/DEPS
+++ b/DEPS
@@ -146,7 +146,7 @@
   # 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': '13c48a77b19c1455c2d3f5a73d056ee8c636e406',
+  'v8_revision': 'ffcc6abeb7796e08165267d860887e8fe865f233',
   # 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.
@@ -154,7 +154,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': '3fe8c3a3039670535efcc10527ff9cd1b5bf67df',
+  'angle_revision': '4e71b2bc254677bdeac521a371402a92f6747776',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -162,7 +162,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': 'babd1d4ac7c4060fb907ac90ee08f52c603c6358',
+  'pdfium_revision': '808e9b88cf2e17fc4fe8cbfe6454e0680edd099d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -177,7 +177,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling googletest
   # and whatever else without interference from each other.
-  'googletest_revision': 'd7003576dd133856432e2e07340f45926242cc3a',
+  'googletest_revision': '437e1008c97b6bf595fec85da42c6925babd96b2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling lighttpd
   # and whatever else without interference from each other.
@@ -205,7 +205,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '123c46068daa0b5660f02168df9b76a2a255ef71',
+  'catapult_revision': '5cc5f6ebf6015e0b69d04148f07aa33abe8a4b76',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -806,7 +806,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'ad2a337ea4a61727f898d8d18eb2aae3a2e13027',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'a9ca4d92eba0effcf4cab93d6d8e6019696321d3',
       'condition': 'checkout_linux',
   },
 
@@ -1191,7 +1191,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  'cdf9e8af571361f67af4df2ff051aaeba89c950c',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '579543531e4b15861bb2f1975e39d55aec4f7b5f',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1310,7 +1310,7 @@
     Var('chromium_git') + '/external/smhasher.git' + '@' + 'e87738e57558e0ec472b2fc3a643b838e5b6e88f',
 
   'src/third_party/snappy/src':
-    Var('chromium_git') + '/external/github.com/google/snappy.git' + '@' + '3f194acb57e0487531c96b97af61dcbd025a78a3',
+    Var('chromium_git') + '/external/github.com/google/snappy.git' + '@' + '156cd8939c5fba7fa68ae08db843377ecc07b4b5',
 
   'src/third_party/sqlite4java': {
       'packages': [
@@ -1400,7 +1400,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@75b74536acd5cf16f92f6e9864d1ba17afcf51cc',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@e46877813496a66cdc6d74a0d04f9573e52daca8',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 2f43503..fda251c5 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -478,7 +478,7 @@
 # * Sequence of paths to *not* check (regexps).
 _BANNED_CPP_FUNCTIONS = (
     (
-      r'\bNULL\b',
+      r'/\bNULL\b',
       (
        'New code should not use NULL. Use nullptr instead.',
       ),
@@ -498,7 +498,7 @@
       (),
     ),
     (
-      r'XSelectInput|CWEventMask|XCB_CW_EVENT_MASK',
+      r'/XSelectInput|CWEventMask|XCB_CW_EVENT_MASK',
       (
        'Chrome clients wishing to select events on X windows should use',
        'ui::XScopedEventSelector.  It is safe to ignore this warning only if',
@@ -513,7 +513,7 @@
       ),
     ),
     (
-      r'XInternAtom|xcb_intern_atom',
+      r'/XInternAtom|xcb_intern_atom',
       (
        'Use gfx::GetAtom() instead of interning atoms directly.',
       ),
@@ -707,7 +707,7 @@
       (),
     ),
     (
-      r'std::regex',
+      'std::regex',
       (
         'Using std::regex adds unnecessary binary size to Chrome. Please use',
         're2::RE2 instead (crbug.com/755321)',
@@ -935,7 +935,7 @@
       (),
     ),
     (
-      r'RunThisRunLoop',
+      'RunThisRunLoop',
       (
           'RunThisRunLoop is deprecated, use RunLoop directly instead.',
       ),
@@ -943,7 +943,7 @@
       (),
     ),
     (
-      r'RunAllPendingInMessageLoop()',
+      'RunAllPendingInMessageLoop()',
       (
           "Prefer RunLoop over RunAllPendingInMessageLoop, please contact gab@",
           "if you're convinced you need this.",
@@ -952,7 +952,7 @@
       (),
     ),
     (
-      r'RunAllPendingInMessageLoop(BrowserThread',
+      'RunAllPendingInMessageLoop(BrowserThread',
       (
           'RunAllPendingInMessageLoop is deprecated. Use RunLoop for',
           'BrowserThread::UI, TestBrowserThreadBundle::RunIOThreadUntilIdle',
@@ -971,7 +971,7 @@
       (),
     ),
     (
-      r'GetDeferredQuitTaskForRunLoop',
+      'GetDeferredQuitTaskForRunLoop',
       (
           "GetDeferredQuitTaskForRunLoop shouldn't be needed, please contact",
           "gab@ if you found a use case where this is the only solution.",
@@ -1011,7 +1011,7 @@
       ),
     ),
     (
-      r'std::random_shuffle',
+      'std::random_shuffle',
       (
         'std::random_shuffle is deprecated in C++14, and removed in C++17. Use',
         'base::RandomShuffle instead.'
diff --git a/WATCHLISTS b/WATCHLISTS
index 737203c..add4f7b3 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -837,7 +837,12 @@
       'filepath': 'tools/perf/contrib/cros_benchmarks',
     },
     'crostini': {
-      'filepath': 'crostini',
+      'filepath': 'cicerone'\
+                  '|components/exo'\
+                  '|concierge'\
+                  '|crostini'\
+                  '|guest_os'\
+                  '|plugin_vm',
     },
     'cups_printing' : {
       'filepath': 'chrome/browser/resources/settings/printing_page/'\
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index a554873..f5d3487b 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1195,8 +1195,6 @@
     "wm/tablet_mode/internal_input_devices_event_blocker.h",
     "wm/tablet_mode/scoped_skip_user_session_blocked_check.cc",
     "wm/tablet_mode/scoped_skip_user_session_blocked_check.h",
-    "wm/tablet_mode/tablet_mode_backdrop_delegate_impl.cc",
-    "wm/tablet_mode/tablet_mode_backdrop_delegate_impl.h",
     "wm/tablet_mode/tablet_mode_browser_window_drag_delegate.cc",
     "wm/tablet_mode/tablet_mode_controller.cc",
     "wm/tablet_mode/tablet_mode_event_handler.cc",
@@ -1252,7 +1250,6 @@
     "wm/work_area_insets.h",
     "wm/workspace/backdrop_controller.cc",
     "wm/workspace/backdrop_controller.h",
-    "wm/workspace/backdrop_delegate.h",
     "wm/workspace/magnetism_matcher.cc",
     "wm/workspace/magnetism_matcher.h",
     "wm/workspace/multi_window_resize_controller.cc",
@@ -1643,7 +1640,6 @@
     "keyboard/keyboard_controller_impl_unittest.cc",
     "keyboard/virtual_keyboard_controller_unittest.cc",
     "keyboard/virtual_keyboard_unittest.cc",
-    "kiosk_next/kiosk_next_home_controller_unittest.cc",
     "kiosk_next/kiosk_next_shell_controller_unittest.cc",
     "kiosk_next/kiosk_next_shell_test_util.cc",
     "kiosk_next/kiosk_next_shell_test_util.h",
@@ -2185,6 +2181,7 @@
 
   public_deps = [
     "//ash",
+    "//dbus",
     "//services/service_manager/public/cpp/test:test_support",
     "//testing/gtest",
     "//third_party/blink/public:blink_headers",
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 07c5f7f3..b8e4b19 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -737,7 +737,7 @@
 
       <!-- Virtual Desks -->
       <message name="IDS_ASH_DESKS_NEW_DESK_BUTTON" desc="The label of the new virtual desk (a.k.a. workspaces) button.">
-        New Desk
+        New desk
       </message>
       <message name="IDS_ASH_DESKS_DESK_1_MINI_VIEW_TITLE" desc="The label of the first virtual desk thumbnail.">
         Desk 1
diff --git a/ash/ash_strings_grd/IDS_ASH_DESKS_NEW_DESK_BUTTON.png.sha1 b/ash/ash_strings_grd/IDS_ASH_DESKS_NEW_DESK_BUTTON.png.sha1
index 030473f..e8de1b4 100644
--- a/ash/ash_strings_grd/IDS_ASH_DESKS_NEW_DESK_BUTTON.png.sha1
+++ b/ash/ash_strings_grd/IDS_ASH_DESKS_NEW_DESK_BUTTON.png.sha1
@@ -1 +1 @@
-8254d07d8a4676a52a79a98e0287ea5f80843262
\ No newline at end of file
+2811b931f24f85df00b6918842a8e381dad7e27e
\ No newline at end of file
diff --git a/ash/autoclick/autoclick_controller.cc b/ash/autoclick/autoclick_controller.cc
index fe6e1f0d..980360e 100644
--- a/ash/autoclick/autoclick_controller.cc
+++ b/ash/autoclick/autoclick_controller.cc
@@ -268,8 +268,8 @@
   // TODO(katie): Don't call this on the very first time scrollable bounds
   // are found for each time the type is changed to scroll. We want the
   // default first position of the scrollbar to be next to the menu bubble.
-  // TODO(katie): Set the scroll bubble position using the bounds.
-  menu_bubble_controller_->SetScrollPoint(scroll_location_);
+  menu_bubble_controller_->SetScrollPosition(bounds_in_screen,
+                                             scroll_location_);
 }
 
 void AutoclickController::UpdateAutoclickMenuBoundsIfNeeded() {
diff --git a/ash/kiosk_next/kiosk_next_home_controller_unittest.cc b/ash/kiosk_next/kiosk_next_home_controller_unittest.cc
deleted file mode 100644
index 10ec17c..0000000
--- a/ash/kiosk_next/kiosk_next_home_controller_unittest.cc
+++ /dev/null
@@ -1,116 +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 "ash/kiosk_next/kiosk_next_home_controller.h"
-
-#include "ash/home_screen/home_screen_controller.h"
-#include "ash/kiosk_next/kiosk_next_shell_test_util.h"
-#include "ash/kiosk_next/mock_kiosk_next_shell_client.h"
-#include "ash/public/cpp/app_types.h"
-#include "ash/public/cpp/ash_features.h"
-#include "ash/shell.h"
-#include "ash/test/ash_test_base.h"
-#include "ash/wm/overview/overview_controller.h"
-#include "ash/wm/window_state.h"
-#include "base/test/scoped_feature_list.h"
-#include "ui/aura/client/aura_constants.h"
-#include "ui/aura/client/window_types.h"
-#include "ui/aura/test/test_window_delegate.h"
-#include "ui/base/hit_test.h"
-#include "ui/events/test/event_generator.h"
-
-namespace ash {
-namespace {
-
-class KioskNextHomeControllerTest : public AshTestBase {
- public:
-  KioskNextHomeControllerTest() = default;
-  ~KioskNextHomeControllerTest() override = default;
-
-  void SetUp() override {
-    scoped_feature_list_.InitAndEnableFeature(features::kKioskNextShell);
-    set_start_session(false);
-    AshTestBase::SetUp();
-    client_ = std::make_unique<MockKioskNextShellClient>();
-    LogInKioskNextUser(GetSessionControllerClient());
-    SetUpHomeWindow();
-  }
-
-  void TearDown() override {
-    home_screen_window_.reset();
-    client_.reset();
-    AshTestBase::TearDown();
-  }
-
-  void SetUpHomeWindow() {
-    auto* delegate =
-        aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate();
-
-    home_screen_window_.reset(
-        CreateTestWindowInShellWithDelegate(delegate, 0, gfx::Rect()));
-    home_screen_window_->SetProperty(aura::client::kAppType,
-                                     static_cast<int>(AppType::CHROME_APP));
-    home_screen_window_->set_owned_by_parent(false);
-    Shell::GetContainer(Shell::GetPrimaryRootWindow(),
-                        kShellWindowId_HomeScreenContainer)
-        ->AddChild(home_screen_window_.get());
-
-    auto* window_state = wm::GetWindowState(home_screen_window_.get());
-    window_state->Maximize();
-    home_screen_window_->Show();
-  }
-
-  // TestWindowDelegate always returns its |window_component_| when
-  // TestWindowDelegate::GetNonClientComponent(const gfx::Point& point) is
-  // called, regardless of the location. Therefore individual tests have to set
-  // the |window_component_|. KioskNextHomeController's event handler starts
-  // overview only if the window component which the gesture event touches is
-  // HTCLIENT.
-  void SetWindowComponent(int component) {
-    auto* delegate = static_cast<aura::test::TestWindowDelegate*>(
-        home_screen_window_->delegate());
-    delegate->set_window_component(component);
-  }
-
- protected:
-  std::unique_ptr<aura::Window> home_screen_window_;
-  std::unique_ptr<MockKioskNextShellClient> client_;
-  base::test::ScopedFeatureList scoped_feature_list_;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(KioskNextHomeControllerTest);
-};
-
-TEST_F(KioskNextHomeControllerTest, CheckWindows) {
-  auto* kiosk_next_home_controller =
-      Shell::Get()->home_screen_controller()->delegate();
-
-  EXPECT_EQ(kiosk_next_home_controller->GetHomeScreenWindow(),
-            home_screen_window_.get());
-}
-
-TEST_F(KioskNextHomeControllerTest, TestGestureToOverview) {
-  SetWindowComponent(HTCLIENT);
-
-  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
-
-  generator.GestureScrollSequence(gfx::Point(50, 0), gfx::Point(50, 20),
-                                  base::TimeDelta::FromMilliseconds(10), 10);
-
-  EXPECT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
-}
-
-TEST_F(KioskNextHomeControllerTest, TestGestureToNoOverview) {
-  SetWindowComponent(HTNOWHERE);
-
-  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
-
-  generator.GestureScrollSequence(gfx::Point(50, 0), gfx::Point(50, 20),
-                                  base::TimeDelta::FromMilliseconds(10), 10);
-
-  EXPECT_FALSE(Shell::Get()->overview_controller()->InOverviewSession());
-}
-
-}  // namespace
-}  // namespace ash
diff --git a/ash/multi_user/multi_user_window_manager_impl.cc b/ash/multi_user/multi_user_window_manager_impl.cc
index f43ce74..a7440e5 100644
--- a/ash/multi_user/multi_user_window_manager_impl.cc
+++ b/ash/multi_user/multi_user_window_manager_impl.cc
@@ -530,7 +530,8 @@
 
   bool unowned_view_state = visibility_item->second;
   transient_window_to_visibility_.erase(visibility_item);
-  if (unowned_view_state && !window->IsVisible()) {
+  if (unowned_view_state && !window->IsVisible() &&
+      desks_util::BelongsToActiveDesk(window)) {
     // To prevent these commands from being recorded as any other commands, we
     // are suppressing any window entry changes while this is going on.
     // Instead of calling SetWindowVisible, only show gets called here since all
diff --git a/ash/shelf/back_button_unittest.cc b/ash/shelf/back_button_unittest.cc
index 51f1479..a279527 100644
--- a/ash/shelf/back_button_unittest.cc
+++ b/ash/shelf/back_button_unittest.cc
@@ -9,18 +9,13 @@
 #include "ash/accelerators/accelerator_controller_impl.h"
 #include "ash/app_list/test/app_list_test_helper.h"
 #include "ash/app_list/views/app_list_view.h"
-#include "ash/kiosk_next/kiosk_next_shell_test_util.h"
-#include "ash/kiosk_next/mock_kiosk_next_shell_client.h"
-#include "ash/public/cpp/ash_features.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shelf/shelf_view.h"
 #include "ash/shelf/shelf_view_test_api.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
-#include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/run_loop.h"
-#include "base/test/scoped_feature_list.h"
 #include "ui/base/accelerators/accelerator.h"
 #include "ui/base/accelerators/test_accelerator_target.h"
 #include "ui/events/test/event_generator.h"
@@ -124,72 +119,4 @@
   EXPECT_EQ(1, target_back_release.accelerator_count());
 }
 
-class KioskNextBackButtonTest : public BackButtonTest {
- public:
-  KioskNextBackButtonTest() {
-    scoped_feature_list_.InitAndEnableFeature(features::kKioskNextShell);
-  }
-
-  void SetUp() override {
-    set_start_session(false);
-    BackButtonTest::SetUp();
-    client_ = std::make_unique<MockKioskNextShellClient>();
-  }
-
-  void TearDown() override {
-    client_.reset();
-    BackButtonTest::TearDown();
-  }
-
-  void SimulateKioskNextSession() {
-    LogInKioskNextUser(GetSessionControllerClient());
-
-    // Update test_api_ because its reference to ShelfView is outdated.
-    test_api_ = std::make_unique<ShelfViewTestAPI>(
-        GetPrimaryShelf()->GetShelfViewForTesting());
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-  std::unique_ptr<MockKioskNextShellClient> client_;
-
-  DISALLOW_COPY_AND_ASSIGN(KioskNextBackButtonTest);
-};
-
-TEST_F(KioskNextBackButtonTest, BackKeySequenceGenerated) {
-  SimulateKioskNextSession();
-
-  // Tablet mode should be enabled in Kiosk Next.
-  ASSERT_TRUE(Shell::Get()->tablet_mode_controller()->InTabletMode());
-  test_api()->RunMessageLoopUntilAnimationsDone();
-
-  // Enter Overview mode, since the shelf view is hidden from the Kiosk Next
-  // home screen.
-  OverviewController* overview_controller = Shell::Get()->overview_controller();
-  ASSERT_TRUE(overview_controller->StartOverview());
-  ASSERT_TRUE(overview_controller->InOverviewSession());
-  test_api()->RunMessageLoopUntilAnimationsDone();
-
-  // Register an accelerator that looks for back releases.
-  ui::Accelerator accelerator_back_release(ui::VKEY_BROWSER_BACK, ui::EF_NONE);
-  accelerator_back_release.set_key_state(ui::Accelerator::KeyState::RELEASED);
-  ui::TestAcceleratorTarget target_back_release;
-  Shell::Get()->accelerator_controller()->Register({accelerator_back_release},
-                                                   &target_back_release);
-
-  // Verify that when pressing down the back button, the accelerator is not
-  // triggered yet.
-  ui::test::EventGenerator* generator = GetEventGenerator();
-  generator->MoveMouseTo(back_button()->GetBoundsInScreen().CenterPoint());
-  generator->PressLeftButton();
-  EXPECT_EQ(0, target_back_release.accelerator_count());
-  EXPECT_TRUE(overview_controller->InOverviewSession());
-
-  // Verify that by releasing the back button, the accelerator is triggered,
-  // exiting Overview mode and sending a release event.
-  generator->ReleaseLeftButton();
-  EXPECT_EQ(1, target_back_release.accelerator_count());
-  EXPECT_FALSE(overview_controller->InOverviewSession());
-}
-
 }  // namespace ash
diff --git a/ash/shelf/home_button.cc b/ash/shelf/home_button.cc
index ccb2863..caaaffc 100644
--- a/ash/shelf/home_button.cc
+++ b/ash/shelf/home_button.cc
@@ -7,7 +7,6 @@
 #include <math.h>  // std::ceil
 
 #include "ash/app_list/app_list_controller_impl.h"
-#include "ash/kiosk_next/kiosk_next_shell_controller_impl.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/shelf/shelf_button_delegate.h"
 #include "ash/shelf/shelf_constants.h"
diff --git a/ash/shelf/home_button_unittest.cc b/ash/shelf/home_button_unittest.cc
index f3eb520..ac2a066 100644
--- a/ash/shelf/home_button_unittest.cc
+++ b/ash/shelf/home_button_unittest.cc
@@ -13,9 +13,6 @@
 #include "ash/assistant/assistant_ui_controller.h"
 #include "ash/assistant/model/assistant_ui_model.h"
 #include "ash/assistant/test/test_assistant_service.h"
-#include "ash/kiosk_next/kiosk_next_shell_test_util.h"
-#include "ash/kiosk_next/mock_kiosk_next_shell_client.h"
-#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/voice_interaction_controller.h"
 #include "ash/root_window_controller.h"
 #include "ash/session/session_controller_impl.h"
@@ -25,7 +22,6 @@
 #include "ash/shelf/shelf_view_test_api.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
-#include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/command_line.h"
 #include "base/run_loop.h"
@@ -249,50 +245,4 @@
                                                ->visibility());
 }
 
-class KioskNextHomeButtonTest : public HomeButtonTest {
- public:
-  KioskNextHomeButtonTest() {
-    scoped_feature_list_.InitAndEnableFeature(features::kKioskNextShell);
-  }
-
-  void SetUp() override {
-    set_start_session(false);
-    HomeButtonTest::SetUp();
-    client_ = std::make_unique<MockKioskNextShellClient>();
-  }
-
-  void TearDown() override {
-    client_.reset();
-    HomeButtonTest::TearDown();
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-  std::unique_ptr<MockKioskNextShellClient> client_;
-
-  DISALLOW_COPY_AND_ASSIGN(KioskNextHomeButtonTest);
-};
-
-TEST_F(KioskNextHomeButtonTest, TapToGoHome) {
-  LogInKioskNextUser(GetSessionControllerClient());
-
-  ShelfViewTestAPI test_api(GetPrimaryShelf()->GetShelfViewForTesting());
-  test_api.RunMessageLoopUntilAnimationsDone();
-
-  // Enter Overview mode.
-  OverviewController* overview_controller = Shell::Get()->overview_controller();
-  ASSERT_TRUE(overview_controller->StartOverview());
-  ASSERT_TRUE(overview_controller->InOverviewSession());
-  test_api.RunMessageLoopUntilAnimationsDone();
-
-  // Tapping the home button should exit Overview mode.
-  gfx::Point center = home_button()->GetCenterPoint();
-  views::View::ConvertPointToScreen(home_button(), &center);
-  GetEventGenerator()->GestureTapDownAndUp(center);
-  test_api.RunMessageLoopUntilAnimationsDone();
-  EXPECT_FALSE(overview_controller->InOverviewSession());
-
-  // TODO(michaelpg): Create a Home Screen aura::Window* and verify its state.
-}
-
 }  // namespace ash
diff --git a/ash/shell/content/client/shell_browser_main_parts.cc b/ash/shell/content/client/shell_browser_main_parts.cc
index 897d07d..331007d 100644
--- a/ash/shell/content/client/shell_browser_main_parts.cc
+++ b/ash/shell/content/client/shell_browser_main_parts.cc
@@ -19,115 +19,105 @@
 #include "ash/shell/window_type_launcher.h"
 #include "ash/shell/window_watcher.h"
 #include "ash/shell_init_params.h"
-#include "ash/wallpaper/wallpaper_controller_impl.h"
+#include "ash/test/ash_test_helper.h"
 #include "base/bind.h"
 #include "base/command_line.h"
-#include "base/i18n/icu_util.h"
 #include "base/run_loop.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/time/time.h"
-#include "chromeos/audio/cras_audio_handler.h"
 #include "chromeos/dbus/biod/biod_client.h"
-#include "chromeos/dbus/power/power_manager_client.h"
-#include "chromeos/dbus/power/power_policy_controller.h"
 #include "components/exo/file_helper.h"
 #include "content/public/browser/context_factory.h"
 #include "content/public/browser/system_connector.h"
 #include "content/public/common/content_switches.h"
 #include "content/shell/browser/shell_browser_context.h"
-#include "device/bluetooth/dbus/bluez_dbus_manager.h"
 #include "net/base/net_module.h"
 #include "services/service_manager/public/cpp/connector.h"
-#include "ui/aura/env.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_tree_host.h"
-#include "ui/base/material_design/material_design_controller.h"
 #include "ui/base/ui_base_features.h"
-#include "ui/base/ui_base_paths.h"
-#include "ui/compositor/compositor.h"
 #include "ui/views/examples/examples_window_with_content.h"
-#include "ui/wm/core/wm_state.h"
 
 namespace ash {
 namespace shell {
 
 ShellBrowserMainParts::ShellBrowserMainParts(
-    const content::MainFunctionParams& parameters) {}
+    const content::MainFunctionParams& parameters)
+    : parameters_(parameters) {}
 
 ShellBrowserMainParts::~ShellBrowserMainParts() = default;
 
 void ShellBrowserMainParts::PreMainMessageLoopStart() {}
 
 void ShellBrowserMainParts::PostMainMessageLoopStart() {
-  chromeos::PowerManagerClient::InitializeFake();
   chromeos::BiodClient::InitializeFake();
 }
 
 void ShellBrowserMainParts::ToolkitInitialized() {
-  wm_state_.reset(new ::wm::WMState);
+  // A ViewsDelegate is required.
+  views_delegate_ = std::make_unique<ShellViewsDelegate>();
 }
 
 void ShellBrowserMainParts::PreMainMessageLoopRun() {
   browser_context_.reset(new content::ShellBrowserContext(false));
 
-  // A ViewsDelegate is required.
-  if (!views::ViewsDelegate::GetInstance())
-    views_delegate_ = std::make_unique<ShellViewsDelegate>();
+  ash_test_helper_ = std::make_unique<AshTestHelper>();
 
-  // Create CrasAudioHandler for testing since g_browser_process
-  // is absent.
-  chromeos::CrasAudioHandler::InitializeForTesting();
+  AshTestHelper::InitParams init_params;
+  init_params.start_session = true;
+  init_params.provide_local_state = true;
 
-  bluez::BluezDBusManager::InitializeFake();
+  if (parameters_.ui_task) {
+    init_params.config_type = AshTestHelper::kUnitTest;
+  } else {
+    init_params.config_type = AshTestHelper::kShell;
+  }
 
-  chromeos::PowerPolicyController::Initialize(
-      chromeos::PowerManagerClient::Get());
+  ShellInitParams shell_init_params;
+  shell_init_params.delegate =
+      std::make_unique<ash::shell::ShellDelegateImpl>();
+  shell_init_params.context_factory = content::GetContextFactory();
+  shell_init_params.context_factory_private =
+      content::GetContextFactoryPrivate();
+  shell_init_params.connector = content::GetSystemConnector();
+  shell_init_params.keyboard_ui_factory =
+      std::make_unique<TestKeyboardUIFactory>();
 
-  ui::MaterialDesignController::Initialize();
-  ash::ShellInitParams init_params;
-  init_params.delegate = std::make_unique<ash::shell::ShellDelegateImpl>();
-  init_params.context_factory = content::GetContextFactory();
-  init_params.context_factory_private = content::GetContextFactoryPrivate();
-  init_params.connector = content::GetSystemConnector();
-  init_params.keyboard_ui_factory = std::make_unique<TestKeyboardUIFactory>();
-  ash::Shell::CreateInstance(std::move(init_params));
-
-  prefs_provider_ = std::make_unique<TestPrefServiceProvider>();
-
-  // Initialize session controller client and create fake user sessions. The
-  // fake user sessions makes ash into the logged in state.
-  example_session_controller_client_ =
-      std::make_unique<ExampleSessionControllerClient>(
-          Shell::Get()->session_controller(), prefs_provider_.get());
-  example_session_controller_client_->Initialize();
+  ash_test_helper_->SetUp(init_params, std::move(shell_init_params));
 
   window_watcher_ = std::make_unique<WindowWatcher>();
 
-  ash::shell::InitWindowTypeLauncher(
-      base::BindRepeating(&views::examples::ShowExamplesWindowWithContent,
-                          base::Passed(base::OnceClosure()),
-                          base::Unretained(browser_context_.get()), nullptr),
-      base::BindRepeating(&EmbeddedBrowser::Create,
-                          base::Unretained(browser_context_.get()),
-                          GURL("https://www.google.com")));
-
-  example_app_list_client_ = std::make_unique<ExampleAppListClient>(
-      Shell::Get()->app_list_controller());
-
-  ash::Shell::Get()->wallpaper_controller()->ShowDefaultWallpaperForTesting();
-
   ash::Shell::GetPrimaryRootWindow()->GetHost()->Show();
 
   ash::Shell::Get()->InitWaylandServer(nullptr);
+
+  if (!parameters_.ui_task) {
+    // Initialize session controller client and create fake user sessions. The
+    // fake user sessions makes ash into the logged in state.
+    example_session_controller_client_ =
+        std::make_unique<ExampleSessionControllerClient>(
+            Shell::Get()->session_controller(),
+            ash_test_helper_->prefs_provider());
+    example_session_controller_client_->Initialize();
+
+    example_app_list_client_ = std::make_unique<ExampleAppListClient>(
+        Shell::Get()->app_list_controller());
+
+    ash::shell::InitWindowTypeLauncher(
+        base::BindRepeating(views::examples::ShowExamplesWindowWithContent,
+                            base::Passed(base::OnceClosure()),
+                            base::Unretained(browser_context_.get()), nullptr),
+        base::BindRepeating(base::IgnoreResult(&EmbeddedBrowser::Create),
+                            base::Unretained(browser_context_.get()),
+                            GURL("https://www.google.com")));
+  }
 }
 
 void ShellBrowserMainParts::PostMainMessageLoopRun() {
   window_watcher_.reset();
-  ash::Shell::DeleteInstance();
+  example_app_list_client_.reset();
+  example_session_controller_client_.reset();
 
-  chromeos::CrasAudioHandler::Shutdown();
-
-  chromeos::PowerPolicyController::Shutdown();
+  ash_test_helper_->TearDown();
+  ash_test_helper_.reset();
 
   views_delegate_.reset();
 
@@ -139,10 +129,15 @@
 }
 
 bool ShellBrowserMainParts::MainMessageLoopRun(int* result_code) {
-  base::RunLoop run_loop;
-  example_session_controller_client_->set_quit_closure(
-      run_loop.QuitWhenIdleClosure());
-  run_loop.Run();
+  if (parameters_.ui_task) {
+    parameters_.ui_task->Run();
+    delete parameters_.ui_task;
+  } else {
+    base::RunLoop run_loop;
+    example_session_controller_client_->set_quit_closure(
+        run_loop.QuitWhenIdleClosure());
+    run_loop.Run();
+  }
   return true;
 }
 
diff --git a/ash/shell/content/client/shell_browser_main_parts.h b/ash/shell/content/client/shell_browser_main_parts.h
index db781041..067890a 100644
--- a/ash/shell/content/client/shell_browser_main_parts.h
+++ b/ash/shell/content/client/shell_browser_main_parts.h
@@ -9,9 +9,10 @@
 
 #include "base/macros.h"
 #include "content/public/browser/browser_main_parts.h"
+#include "content/public/common/main_function_params.h"
 
 namespace content {
-class ShellBrowserContext;
+class BrowserContext;
 struct MainFunctionParams;
 }
 
@@ -19,12 +20,8 @@
 class ViewsDelegate;
 }
 
-namespace wm {
-class WMState;
-}
-
 namespace ash {
-class TestPrefServiceProvider;
+class AshTestHelper;
 
 namespace shell {
 class ExampleAppListClient;
@@ -44,19 +41,17 @@
   bool MainMessageLoopRun(int* result_code) override;
   void PostMainMessageLoopRun() override;
 
-  content::ShellBrowserContext* browser_context() {
-    return browser_context_.get();
-  }
+  content::BrowserContext* browser_context() { return browser_context_.get(); }
 
  private:
-  std::unique_ptr<content::ShellBrowserContext> browser_context_;
+  std::unique_ptr<content::BrowserContext> browser_context_;
   std::unique_ptr<views::ViewsDelegate> views_delegate_;
   std::unique_ptr<WindowWatcher> window_watcher_;
-  std::unique_ptr<wm::WMState> wm_state_;
   std::unique_ptr<ExampleSessionControllerClient>
       example_session_controller_client_;
   std::unique_ptr<ExampleAppListClient> example_app_list_client_;
-  std::unique_ptr<TestPrefServiceProvider> prefs_provider_;
+  std::unique_ptr<ash::AshTestHelper> ash_test_helper_;
+  content::MainFunctionParams parameters_;
 
   DISALLOW_COPY_AND_ASSIGN(ShellBrowserMainParts);
 };
diff --git a/ash/system/accessibility/autoclick_menu_bubble_controller.cc b/ash/system/accessibility/autoclick_menu_bubble_controller.cc
index 3eee0b6..f4c237b1 100644
--- a/ash/system/accessibility/autoclick_menu_bubble_controller.cc
+++ b/ash/system/accessibility/autoclick_menu_bubble_controller.cc
@@ -194,9 +194,13 @@
       resting_bounds, GetScrollAnchorAlignmentForPosition(new_position));
 }
 
-void AutoclickMenuBubbleController::SetScrollPoint(
-    gfx::Point scroll_location_in_dips) {
-  scroll_bubble_controller_->SetScrollPoint(scroll_location_in_dips);
+void AutoclickMenuBubbleController::SetScrollPosition(
+    gfx::Rect scroll_bounds_in_dips,
+    const gfx::Point& scroll_point_in_dips) {
+  if (!scroll_bubble_controller_)
+    return;
+  scroll_bubble_controller_->SetScrollPosition(scroll_bounds_in_dips,
+                                               scroll_point_in_dips);
 }
 
 void AutoclickMenuBubbleController::ShowBubble(AutoclickEventType type,
diff --git a/ash/system/accessibility/autoclick_menu_bubble_controller.h b/ash/system/accessibility/autoclick_menu_bubble_controller.h
index ec3e904f..2dc3b4a 100644
--- a/ash/system/accessibility/autoclick_menu_bubble_controller.h
+++ b/ash/system/accessibility/autoclick_menu_bubble_controller.h
@@ -29,8 +29,10 @@
   // Sets the menu's position on the screen.
   void SetPosition(AutoclickMenuPosition position);
 
-  // Set the scroll menu's position on the screen.
-  void SetScrollPoint(gfx::Point scroll_location_in_dips);
+  // Set the scroll menu's position on the screen. The rect is the bounds of
+  // the scrollable area, and the point is the user-selected scroll point.
+  void SetScrollPosition(gfx::Rect scroll_bounds_in_dips,
+                         const gfx::Point& scroll_point_in_dips);
 
   void ShowBubble(AutoclickEventType event_type,
                   AutoclickMenuPosition position);
diff --git a/ash/system/accessibility/autoclick_menu_bubble_controller_unittest.cc b/ash/system/accessibility/autoclick_menu_bubble_controller_unittest.cc
index ff7f6aa..939f76f 100644
--- a/ash/system/accessibility/autoclick_menu_bubble_controller_unittest.cc
+++ b/ash/system/accessibility/autoclick_menu_bubble_controller_unittest.cc
@@ -26,6 +26,7 @@
 // The buffers in dips around a scroll point where the scroll menu is shown.
 const int kScrollViewBoundsXBuffer = 110;
 const int kScrollViewBoundsYBuffer = 10;
+const int kScrollViewBoundsRectBuffer = 18;
 
 ui::GestureEvent CreateTapEvent() {
   return ui::GestureEvent(0, 0, 0, base::TimeTicks(),
@@ -305,20 +306,35 @@
   }
 }
 
-TEST_F(AutoclickMenuBubbleControllerTest, ScrollBubbleManualPositioning) {
-  UpdateDisplay("1000x800");
+TEST_F(AutoclickMenuBubbleControllerTest,
+       ScrollBubbleManualPositioningLargeScrollBounds) {
   AccessibilityControllerImpl* controller =
       Shell::Get()->accessibility_controller();
   controller->SetAutoclickEventType(AutoclickEventType::kScroll);
 
-  const struct { bool is_RTL; } kTestCases[] = {{true}, {false}};
+  // With large scrollable bounds, the scroll bubble should just be positioned
+  // near the scroll point. Try with high density bounds and LTR/RTL languages.
+  const struct {
+    bool is_RTL;
+    gfx::Rect scroll_bounds;
+    std::string display_spec;
+  } kTestCases[] = {
+      {true, gfx::Rect(0, 0, 1000, 800), "1000x800"},
+      {false, gfx::Rect(0, 0, 1000, 800), "1000x800"},
+      {false, gfx::Rect(100, 100, 800, 600), "1000x800"},
+      {true, gfx::Rect(200, 0, 600, 600), "1000x800"},
+      {true, gfx::Rect(0, 0, 1000, 800), "2000x1600*2"},
+      {false, gfx::Rect(0, 0, 1000, 800), "2000x1600*2"},
+  };
   for (auto& test : kTestCases) {
+    UpdateDisplay(test.display_spec);
     base::i18n::SetRTLForTesting(test.is_RTL);
+    gfx::Rect scroll_bounds = test.scroll_bounds;
     controller->SetAutoclickMenuPosition(AutoclickMenuPosition::kTopRight);
 
     // Start with a point no where near the autoclick menu.
     gfx::Point point = gfx::Point(400, 400);
-    GetBubbleController()->SetScrollPoint(point);
+    GetBubbleController()->SetScrollPosition(scroll_bounds, point);
 
     // In-line with the point in the Y axis.
     EXPECT_LT(abs(GetScrollViewBounds().right_center().y() - point.y()),
@@ -335,21 +351,21 @@
 
     // Moving the autoclick bubble doesn't impact the scroll bubble once it
     // has been manually set.
-    gfx::Rect scroll_bounds = GetScrollViewBounds();
+    gfx::Rect bubble_bounds = GetScrollViewBounds();
     controller->SetAutoclickMenuPosition(AutoclickMenuPosition::kBottomRight);
-    EXPECT_EQ(scroll_bounds, GetScrollViewBounds());
+    EXPECT_EQ(bubble_bounds, GetScrollViewBounds());
 
     // If we position it by the edge of the screen, it should stay on-screen,
     // regardless of LTR vs RTL.
     point = gfx::Point(0, 0);
-    GetBubbleController()->SetScrollPoint(point);
+    GetBubbleController()->SetScrollPosition(scroll_bounds, point);
     EXPECT_LT(abs(GetScrollViewBounds().x() - point.x()),
               kScrollViewBoundsXBuffer);
     EXPECT_LT(abs(GetScrollViewBounds().y() - point.y()),
               kScrollViewBoundsYBuffer);
 
     point = gfx::Point(1000, 400);
-    GetBubbleController()->SetScrollPoint(point);
+    GetBubbleController()->SetScrollPosition(scroll_bounds, point);
     EXPECT_LT(abs(GetScrollViewBounds().right() - point.x()),
               kScrollViewBoundsXBuffer);
     EXPECT_LT(abs(GetScrollViewBounds().left_center().y() - point.y()),
@@ -357,4 +373,132 @@
   }
 }
 
+TEST_F(AutoclickMenuBubbleControllerTest,
+       ScrollBubbleManualPositioningSmallScrollBounds) {
+  UpdateDisplay("1200x1000");
+  AccessibilityControllerImpl* controller =
+      Shell::Get()->accessibility_controller();
+  controller->SetAutoclickEventType(AutoclickEventType::kScroll);
+
+  const struct {
+    bool is_RTL;
+    gfx::Rect scroll_bounds;
+    gfx::Point scroll_point;
+    bool expect_bounds_on_right;
+    bool expect_bounds_on_left;
+    bool expect_bounds_on_top;
+    bool expect_bounds_on_bottom;
+  } kTestCases[] = {
+      // Small centered bounds, point closest to the right side.
+      {true, gfx::Rect(400, 350, 300, 300), gfx::Point(555, 500),
+       true /* on right */, false, false, false},
+      {false, gfx::Rect(400, 350, 300, 300), gfx::Point(555, 500),
+       true /* on right */, false, false, false},
+      {false, gfx::Rect(400, 350, 300, 300), gfx::Point(650, 400),
+       true /* on right */, false, false, false},
+
+      // Small centered bounds, point closest to the left side.
+      {true, gfx::Rect(400, 350, 300, 300), gfx::Point(545, 500), false,
+       true /* on left */, false, false},
+      {false, gfx::Rect(400, 350, 300, 300), gfx::Point(545, 500), false,
+       true /* on left */, false, false},
+      {false, gfx::Rect(400, 350, 300, 300), gfx::Point(410, 400), false,
+       true /* on left */, false, false},
+
+      // Point closest to the top of the bounds.
+      {true, gfx::Rect(400, 350, 300, 300), gfx::Point(550, 400), false, false,
+       true /* on top */, false},
+      {false, gfx::Rect(400, 350, 300, 300), gfx::Point(550, 400), false, false,
+       true /* on top */, false},
+      {false, gfx::Rect(400, 350, 300, 300), gfx::Point(402, 351), false, false,
+       true /* on top */, false},
+
+      // Point closest to the bottom of the bounds.
+      {true, gfx::Rect(400, 350, 300, 300), gfx::Point(550, 550), false, false,
+       false, true /* on bottom */},
+      {false, gfx::Rect(400, 350, 300, 300), gfx::Point(550, 550), false, false,
+       false, true /* on bottom */},
+      {false, gfx::Rect(400, 350, 300, 300), gfx::Point(450, 649), false, false,
+       false, true /* on bottom */},
+
+      // These bounds only have space on the right and bottom. Even points
+      // close to the top left get mapped to the right or bottom.
+      {false, gfx::Rect(100, 100, 300, 300), gfx::Point(130, 120),
+       true /* on right */, false, false, false},
+      {true, gfx::Rect(100, 100, 300, 300), gfx::Point(130, 120),
+       true /* on right */, false, false, false},
+      {false, gfx::Rect(100, 100, 300, 300), gfx::Point(120, 130), false, false,
+       false, true /* on bottom */},
+
+      // These bounds only have space on the top and left. Even points
+      // close to the bottom right get mapped to the top or left.
+      {false, gfx::Rect(900, 600, 300, 300), gfx::Point(1075, 800), false,
+       true /* on left */, false, false},
+      {false, gfx::Rect(900, 600, 300, 300), gfx::Point(1075, 800), false,
+       true /* on left */, false, false},
+      {false, gfx::Rect(900, 600, 300, 300), gfx::Point(1100, 775), false,
+       false, true /* on top */, false},
+
+      // These bounds have space above/below, but not left/right.
+      {false, gfx::Rect(400, 100, 300, 800), gfx::Point(525, 110), false,
+       true /* on left */, false, false},
+      {false, gfx::Rect(400, 100, 300, 800), gfx::Point(525, 845), false,
+       true /* on left */, false, false},
+      {false, gfx::Rect(400, 100, 300, 800), gfx::Point(575, 845),
+       true /* on right */, false, false, false},
+
+      // These bounds have space left/right, but not above/below.
+      {false, gfx::Rect(100, 350, 1000, 300), gfx::Point(200, 450), false,
+       false, true /* on top */, false},
+      {false, gfx::Rect(100, 350, 1000, 300), gfx::Point(200, 550), false,
+       false, false, true /* on bottom */},
+      {false, gfx::Rect(100, 350, 1000, 300), gfx::Point(1000, 550), false,
+       false, false, true /* on bottom */},
+  };
+  for (auto& test : kTestCases) {
+    base::i18n::SetRTLForTesting(test.is_RTL);
+    gfx::Rect scroll_bounds = test.scroll_bounds;
+    gfx::Point scroll_point = test.scroll_point;
+    GetBubbleController()->SetScrollPosition(scroll_bounds, scroll_point);
+
+    if (test.expect_bounds_on_right) {
+      // The scroll view should be on the right of the bounds and centered
+      // vertically on the scroll point.
+      EXPECT_LT(abs(GetScrollViewBounds().y() +
+                    GetScrollViewBounds().height() / 2 - scroll_point.y()),
+                kScrollViewBoundsYBuffer);
+      EXPECT_LT(GetScrollViewBounds().x() - scroll_bounds.right(),
+                kScrollViewBoundsRectBuffer);
+      EXPECT_GT(GetScrollViewBounds().x() - scroll_bounds.right(), 0);
+    } else if (test.expect_bounds_on_left) {
+      // The scroll view should be on the left of the bounds and centered
+      // vertically on the scroll point.
+      EXPECT_LT(abs(GetScrollViewBounds().y() +
+                    GetScrollViewBounds().height() / 2 - scroll_point.y()),
+                kScrollViewBoundsYBuffer);
+      EXPECT_LT(scroll_bounds.x() - GetScrollViewBounds().right(),
+                kScrollViewBoundsRectBuffer);
+      EXPECT_GT(scroll_bounds.x() - GetScrollViewBounds().right(), 0);
+    } else if (test.expect_bounds_on_top) {
+      // The scroll view should be on the top of the bounds and centered
+      // horizontally on the scroll point.
+      EXPECT_LT(abs(GetScrollViewBounds().x() +
+                    GetScrollViewBounds().width() / 2 - scroll_point.x()),
+                kScrollViewBoundsYBuffer);
+      EXPECT_LT(scroll_bounds.y() - GetScrollViewBounds().bottom(),
+                kScrollViewBoundsRectBuffer);
+      EXPECT_GT(scroll_bounds.y() - GetScrollViewBounds().bottom(), 0);
+    } else if (test.expect_bounds_on_bottom) {
+      // The scroll view should be on the bottom of the bounds and centered
+      // horizontally on the scroll point.
+      EXPECT_LT(abs(GetScrollViewBounds().x() +
+                    GetScrollViewBounds().width() / 2 - scroll_point.x()),
+                kScrollViewBoundsYBuffer);
+      EXPECT_LT(GetScrollViewBounds().y() - scroll_bounds.bottom(),
+                kScrollViewBoundsRectBuffer);
+      EXPECT_GT(GetScrollViewBounds().y() - scroll_bounds.bottom(), -1);
+    }
+  }
+}
+
 }  // namespace ash
diff --git a/ash/system/accessibility/autoclick_scroll_bubble_controller.cc b/ash/system/accessibility/autoclick_scroll_bubble_controller.cc
index fa14d4f..0c16237 100644
--- a/ash/system/accessibility/autoclick_scroll_bubble_controller.cc
+++ b/ash/system/accessibility/autoclick_scroll_bubble_controller.cc
@@ -16,6 +16,7 @@
 #include "ash/wm/workspace/workspace_layout_manager.h"
 #include "ash/wm/workspace_controller.h"
 #include "ui/aura/window_tree_host.h"
+#include "ui/display/manager/display_manager.h"
 #include "ui/events/event_utils.h"
 
 namespace ash {
@@ -24,7 +25,17 @@
 // Autoclick scroll menu constants.
 constexpr int kAutoclickScrollMenuSizeDips = 192;
 const int kScrollPointBufferDips = 100;
+const int kScrollRectBufferDips = 8;
 
+struct Position {
+  int distance;
+  views::BubbleBorder::Arrow arrow;
+  bool is_horizontal;
+};
+
+bool comparePositions(Position first, Position second) {
+  return first.distance < second.distance;
+}
 }  // namespace
 
 AutoclickScrollBubbleController::AutoclickScrollBubbleController() {}
@@ -38,22 +49,121 @@
     gfx::Rect rect,
     views::BubbleBorder::Arrow alignment) {
   menu_bubble_rect_ = rect;
-  if (set_scroll_point_)
+  menu_bubble_alignment_ = alignment;
+  if (set_scroll_rect_)
     return;
   bubble_view_->SetArrow(alignment);
   bubble_view_->UpdateAnchorRect(rect);
 }
 
-void AutoclickScrollBubbleController::SetScrollPoint(
-    gfx::Point scroll_location_in_dips) {
-  set_scroll_point_ = true;
-  gfx::Rect anchor =
-      gfx::Rect(scroll_location_in_dips.x(), scroll_location_in_dips.y(), 0, 0);
-  // Buffer around the point so that the scroll bubble does not overlap it.
-  // ScrollBubbleController will automatically layout to avoid edges.
-  anchor.Inset(-kScrollPointBufferDips, -kScrollPointBufferDips);
-  bubble_view_->SetArrow(views::BubbleBorder::Arrow::LEFT_CENTER);
-  bubble_view_->UpdateAnchorRect(anchor);
+void AutoclickScrollBubbleController::SetScrollPosition(
+    gfx::Rect scroll_bounds_in_dips,
+    const gfx::Point& scroll_point_in_dips) {
+  // TODO(katie): Support multiple displays.
+  aura::Window* window = Shell::GetPrimaryRootWindow();
+  gfx::Rect work_area =
+      WorkAreaInsets::ForWindow(window)->user_work_area_bounds();
+
+  // If the on-screen width and height of the scroll bounds are larger than a
+  // threshold size, simply offset the scroll menu bubble from the scroll point
+  // position. This ensures the scroll bubble does not end up too far away from
+  // the user's scroll point.
+  gfx::Rect on_screen_scroll_bounds(scroll_bounds_in_dips);
+  on_screen_scroll_bounds.Intersect(work_area);
+  if (on_screen_scroll_bounds.width() > 2 * kAutoclickScrollMenuSizeDips &&
+      on_screen_scroll_bounds.height() > 2 * kAutoclickScrollMenuSizeDips) {
+    set_scroll_rect_ = true;
+    gfx::Rect anchor =
+        gfx::Rect(scroll_point_in_dips.x(), scroll_point_in_dips.y(), 0, 0);
+    // Buffer around the point so that the scroll bubble does not overlap it.
+    // ScrollBubbleController will automatically layout to avoid edges.
+    anchor.Inset(-kScrollPointBufferDips, -kScrollPointBufferDips);
+    bubble_view_->SetArrow(views::BubbleBorder::Arrow::LEFT_CENTER);
+    bubble_view_->UpdateAnchorRect(anchor);
+    return;
+  }
+
+  // Otherwise, the scrollable area bounds are small compared to the scroll
+  // scroll bubble, so we should not overlap the scroll bubble if possible.
+  // Try to show the scroll bubble on the side of the rect closest to the point.
+  scroll_bounds_in_dips.Inset(
+      -kScrollRectBufferDips, -kScrollRectBufferDips, -kScrollRectBufferDips,
+      -kScrollRectBufferDips - kCollisionWindowWorkAreaInsetsDp);
+
+  // Determine whether there will be space to show the scroll bubble between the
+  // scroll bounds and display bounds.
+  work_area.Inset(kAutoclickScrollMenuSizeDips, kAutoclickScrollMenuSizeDips);
+  bool fits_left = scroll_bounds_in_dips.x() > work_area.x();
+  bool fits_right = scroll_bounds_in_dips.right() < work_area.right();
+  bool fits_above = scroll_bounds_in_dips.y() > work_area.y();
+  bool fits_below = scroll_bounds_in_dips.bottom() < work_area.bottom();
+
+  // The scroll bubble will fit outside the bounds of the scrollable area.
+  // Determine its exact position based on the scroll point:
+  // First, try to lay out the scroll bubble on the side of the scroll bounds
+  // closest to the scroll point.
+  //
+  //   ------------------
+  //   |                |
+  //   |               *|  <-- Here, the scroll point is closest to the right
+  //   |                |      edge so the bubble should be laid out on the
+  //   |                |      right of the scroll bounds.
+  //   ------------------
+  //
+  std::vector<Position> positions;
+  if (fits_right) {
+    positions.push_back(
+        {scroll_bounds_in_dips.right() - scroll_point_in_dips.x(),
+         // Put it on the right. Note that in RTL languages right and left
+         // arrows
+         // are swapped.
+         base::i18n::IsRTL() ? views::BubbleBorder::Arrow::RIGHT_CENTER
+                             : views::BubbleBorder::Arrow::LEFT_CENTER,
+         true});
+  }
+  if (fits_left) {
+    positions.push_back({scroll_point_in_dips.x() - scroll_bounds_in_dips.x(),
+                         // Put it on the left. Note that in RTL languages right
+                         // and left arrows are swapped.
+                         base::i18n::IsRTL()
+                             ? views::BubbleBorder::Arrow::LEFT_CENTER
+                             : views::BubbleBorder::Arrow::RIGHT_CENTER,
+                         true});
+  }
+  if (fits_below) {
+    positions.push_back(
+        {scroll_bounds_in_dips.bottom() - scroll_point_in_dips.y(),
+         views::BubbleBorder::Arrow::TOP_CENTER, false});
+  }
+  if (fits_above) {
+    positions.push_back({scroll_point_in_dips.y() - scroll_bounds_in_dips.y(),
+                         views::BubbleBorder::Arrow::BOTTOM_CENTER, false});
+  }
+
+  // If the scroll bubble doesn't fit between the scroll bounds and display
+  // edges, re-pin the scroll bubble to the menu bubble.
+  set_scroll_rect_ = !positions.empty();
+  if (!set_scroll_rect_) {
+    UpdateAnchorRect(menu_bubble_rect_, menu_bubble_alignment_);
+    return;
+  }
+
+  // Sort positions based on distance.
+  std::stable_sort(positions.begin(), positions.end(), comparePositions);
+
+  // Position on the optimal side depending on the shortest distance.
+  bubble_view_->SetArrow(positions.front().arrow);
+  if (positions.front().is_horizontal) {
+    // Center it vertically on the scroll point.
+    bubble_view_->UpdateAnchorRect(gfx::Rect(scroll_bounds_in_dips.x(),
+                                             scroll_point_in_dips.y(),
+                                             scroll_bounds_in_dips.width(), 0));
+  } else {
+    // Center horizontally on scroll point.
+    bubble_view_->UpdateAnchorRect(gfx::Rect(scroll_point_in_dips.x(),
+                                             scroll_bounds_in_dips.y(), 0,
+                                             scroll_bounds_in_dips.height()));
+  }
 }
 
 void AutoclickScrollBubbleController::ShowBubble(
diff --git a/ash/system/accessibility/autoclick_scroll_bubble_controller.h b/ash/system/accessibility/autoclick_scroll_bubble_controller.h
index 75ff362..8357656 100644
--- a/ash/system/accessibility/autoclick_scroll_bubble_controller.h
+++ b/ash/system/accessibility/autoclick_scroll_bubble_controller.h
@@ -20,7 +20,8 @@
 
   void UpdateAnchorRect(gfx::Rect rect, views::BubbleBorder::Arrow alignment);
 
-  void SetScrollPoint(gfx::Point scroll_location_in_dips);
+  void SetScrollPosition(gfx::Rect scroll_bounds_in_dips,
+                         const gfx::Point& scroll_point_in_dips);
 
   void ShowBubble(gfx::Rect anchor_rect, views::BubbleBorder::Arrow alignment);
   void CloseBubble();
@@ -47,11 +48,13 @@
   AutoclickScrollView* scroll_view_ = nullptr;
   views::Widget* bubble_widget_ = nullptr;
 
-  // Whether the scroll bubble should be positioned based on a fixed point
+  // Whether the scroll bubble should be positioned based on a fixed rect
   // or just relative to the rect passed in UpdateAnchorRect.
-  bool set_scroll_point_ = false;
+  bool set_scroll_rect_ = false;
 
   gfx::Rect menu_bubble_rect_;
+  views::BubbleBorder::Arrow menu_bubble_alignment_ =
+      views::BubbleBorder::Arrow::TOP_LEFT;
 
   DISALLOW_COPY_AND_ASSIGN(AutoclickScrollBubbleController);
 };
diff --git a/ash/test/ash_test_base.cc b/ash/test/ash_test_base.cc
index f3553310..7ced499 100644
--- a/ash/test/ash_test_base.cc
+++ b/ash/test/ash_test_base.cc
@@ -137,7 +137,11 @@
   // default state.
   shell::ToplevelWindow::ClearSavedStateForTest();
 
-  ash_test_helper_->SetUp(start_session_, provide_local_state_);
+  AshTestHelper::InitParams params;
+  params.start_session = start_session_;
+  params.provide_local_state = provide_local_state_;
+  params.config_type = AshTestHelper::kUnitTest;
+  ash_test_helper_->SetUp(params);
 
   Shell::GetPrimaryRootWindow()->Show();
   Shell::GetPrimaryRootWindow()->GetHost()->Show();
diff --git a/ash/test/ash_test_helper.cc b/ash/test/ash_test_helper.cc
index fe162ef..01946ed 100644
--- a/ash/test/ash_test_helper.cc
+++ b/ash/test/ash_test_helper.cc
@@ -25,7 +25,6 @@
 #include "ash/session/test_pref_service_provider.h"
 #include "ash/session/test_session_controller_client.h"
 #include "ash/shell.h"
-#include "ash/shell_init_params.h"
 #include "ash/system/message_center/test_notifier_settings_controller.h"
 #include "ash/system/model/system_tray_model.h"
 #include "ash/system/screen_layout_observer.h"
@@ -72,7 +71,6 @@
 
 AshTestHelper::AshTestHelper()
     : command_line_(std::make_unique<base::test::ScopedCommandLine>()) {
-  ui::test::EnableTestConfigForPlatformWindows();
 }
 
 AshTestHelper::~AshTestHelper() {
@@ -81,7 +79,8 @@
   ScreenAsh::DeleteScreenForShutdown();
 }
 
-void AshTestHelper::SetUp(bool start_session, bool provide_local_state) {
+void AshTestHelper::SetUp(const InitParams& init_params,
+                          base::Optional<ShellInitParams> shell_init_params) {
   // TODO(jamescook): Can we do this without changing command line?
   // Use the origin (1,1) so that it doesn't over
   // lap with the native mouse cursor.
@@ -91,23 +90,27 @@
         ::switches::kHostWindowBounds, "1+1-800x600");
   }
 
+  if (init_params.config_type == kUnitTest) {
+    // Default for unit tests but not for perf tests.
+    zero_duration_mode_.reset(new ui::ScopedAnimationDurationScaleMode(
+        ui::ScopedAnimationDurationScaleMode::ZERO_DURATION));
+    TabletModeController::SetUseScreenshotForTest(false);
+    ui::test::EnableTestConfigForPlatformWindows();
+    display::ResetDisplayIdForTest();
+    ui::InitializeInputMethodForTesting();
+  }
+
   statistics_provider_ =
       std::make_unique<chromeos::system::ScopedFakeStatisticsProvider>();
 
   ui::test::EventGeneratorDelegate::SetFactoryFunction(
       base::BindRepeating(&aura::test::EventGeneratorDelegateAura::Create));
 
-  display::ResetDisplayIdForTest();
   wm_state_ = std::make_unique<::wm::WMState>();
   // Only create a ViewsDelegate if the test didn't create one already.
   if (!views::ViewsDelegate::GetInstance())
     test_views_delegate_ = std::make_unique<AshTestViewsDelegate>();
 
-  // Disable animations during tests.
-  zero_duration_mode_.reset(new ui::ScopedAnimationDurationScaleMode(
-      ui::ScopedAnimationDurationScaleMode::ZERO_DURATION));
-  ui::InitializeInputMethodForTesting();
-
   // Creates Shell and hook with Desktop.
   if (!test_shell_delegate_)
     test_shell_delegate_ = new TestShellDelegate;
@@ -137,7 +140,7 @@
 
   ui::MaterialDesignController::Initialize();
 
-  CreateShell(provide_local_state);
+  CreateShell(init_params.provide_local_state, std::move(shell_init_params));
 
   // Reset aura::Env to eliminate test dependency (https://crbug.com/586514).
   aura::test::EnvTestHelper env_helper(aura::Env::GetInstance());
@@ -167,38 +170,43 @@
   system_tray_client_ = std::make_unique<TestSystemTrayClient>();
   shell->system_tray_model()->SetClient(system_tray_client_.get());
 
-  if (start_session)
+  if (init_params.start_session)
     session_controller_client_->CreatePredefinedUserSessions(1);
 
-  // Tests that change the display configuration generally don't care about
-  // the notifications and the popup UI can interfere with things like
-  // cursors.
-  shell->screen_layout_observer()->set_show_notifications_for_testing(false);
-
-  display::test::DisplayManagerTestApi(shell->display_manager())
-      .DisableChangeDisplayUponHostResize();
-  DisplayConfigurationControllerTestApi(
-      shell->display_configuration_controller())
-      .SetDisplayAnimator(false);
-
   app_list_test_helper_ = std::make_unique<AppListTestHelper>();
 
-  // Create the test keyboard controller observer to respond to
-  // OnLoadKeyboardContentsRequested().
-  test_keyboard_controller_observer_ =
-      std::make_unique<TestKeyboardControllerObserver>(
-          shell->keyboard_controller());
-
   new_window_delegate_ = std::make_unique<TestNewWindowDelegate>();
 
-  // Remove the app dragging animations delay for testing purposes.
-  shell->overview_controller()->set_delayed_animation_task_delay_for_test(
-      base::TimeDelta());
+  // Post shell creation config init.
+  if (init_params.config_type == kUnitTest) {
+    // Tests that change the display configuration generally don't care about
+    // the notifications and the popup UI can interfere with things like
+    // cursors.
+    shell->screen_layout_observer()->set_show_notifications_for_testing(false);
 
-  // Ensure tests have a wallpaper as placeholder.
-  shell->wallpaper_controller()->CreateEmptyWallpaperForTesting();
+    // Disable display change animations in unit tests.
+    DisplayConfigurationControllerTestApi(
+        shell->display_configuration_controller())
+        .SetDisplayAnimator(false);
 
-  TabletModeController::SetUseScreenshotForTest(false);
+    // Remove the app dragging animations delay for testing purposes.
+    shell->overview_controller()->set_delayed_animation_task_delay_for_test(
+        base::TimeDelta());
+
+    // Don't change the display size due to host size resize.
+    display::test::DisplayManagerTestApi(shell->display_manager())
+        .DisableChangeDisplayUponHostResize();
+
+    // Create the test keyboard controller observer to respond to
+    // OnLoadKeyboardContentsRequested().
+    test_keyboard_controller_observer_ =
+        std::make_unique<TestKeyboardControllerObserver>(
+            shell->keyboard_controller());
+    // Unit test expects empty wallpaper.
+    shell->wallpaper_controller()->CreateEmptyWallpaperForTesting();
+  } else {
+    shell->wallpaper_controller()->ShowDefaultWallpaperForTesting();
+  }
 }
 
 void AshTestHelper::TearDown() {
@@ -257,6 +265,8 @@
       ui::test::EventGeneratorDelegate::FactoryFunction());
 
   statistics_provider_.reset();
+
+  TabletModeController::SetUseScreenshotForTest(true);
 }
 
 PrefService* AshTestHelper::GetLocalStatePrefService() {
@@ -275,26 +285,28 @@
   return Shell::Get()->display_manager()->GetSecondaryDisplay();
 }
 
-void AshTestHelper::CreateShell(bool provide_local_state) {
-  const bool enable_pixel_output = false;
-  context_factories_ =
-      std::make_unique<ui::TestContextFactories>(enable_pixel_output);
-  ShellInitParams init_params;
-  init_params.delegate.reset(test_shell_delegate_);
-  init_params.context_factory = context_factories_->GetContextFactory();
-  init_params.context_factory_private =
-      context_factories_->GetContextFactoryPrivate();
-  init_params.keyboard_ui_factory = std::make_unique<TestKeyboardUIFactory>();
-
+void AshTestHelper::CreateShell(bool provide_local_state,
+                                base::Optional<ShellInitParams> init_params) {
+  if (init_params == base::nullopt) {
+    context_factories_ = std::make_unique<ui::TestContextFactories>(
+        /*enable_pixel_output=*/false);
+    init_params.emplace(ShellInitParams());
+    init_params->delegate.reset(test_shell_delegate_);
+    init_params->context_factory = context_factories_->GetContextFactory();
+    init_params->context_factory_private =
+        context_factories_->GetContextFactoryPrivate();
+    init_params->keyboard_ui_factory =
+        std::make_unique<TestKeyboardUIFactory>();
+  }
   if (provide_local_state) {
     auto pref_service = std::make_unique<TestingPrefServiceSimple>();
     RegisterLocalStatePrefs(pref_service->registry(), true);
 
     local_state_ = std::move(pref_service);
-    init_params.local_state = local_state_.get();
+    init_params->local_state = local_state_.get();
   }
 
-  Shell::CreateInstance(std::move(init_params));
+  Shell::CreateInstance(std::move(*init_params));
 }
 
 }  // namespace ash
diff --git a/ash/test/ash_test_helper.h b/ash/test/ash_test_helper.h
index 57745c7..60ab6830 100644
--- a/ash/test/ash_test_helper.h
+++ b/ash/test/ash_test_helper.h
@@ -12,7 +12,9 @@
 
 #include "ash/assistant/test/test_assistant_service.h"
 #include "ash/session/test_session_controller_client.h"
+#include "ash/shell_init_params.h"
 #include "base/macros.h"
+#include "base/optional.h"
 #include "base/test/scoped_command_line.h"
 
 class PrefService;
@@ -58,11 +60,26 @@
   AshTestHelper();
   ~AshTestHelper();
 
-  // Creates the ash::Shell and performs associated initialization.  Set
-  // |start_session| to true if the user should log in before the test is run.
-  // Set |provide_local_state| to true to inject local-state PrefService into
-  // the Shell before the test is run.
-  void SetUp(bool start_session, bool provide_local_state = true);
+  enum ConfigType {
+    // The configuration for shell executable.
+    kShell,
+    // The configuration for unit tests.
+    kUnitTest,
+  };
+
+  struct InitParams {
+    // True if the user should log in.
+    bool start_session = true;
+    // True to inject local-state PrefService into the Shell.
+    bool provide_local_state = true;
+    ConfigType config_type = kUnitTest;
+  };
+
+  // Creates the ash::Shell and performs associated initialization according
+  // to |init_params|. |shell_init_params| is used to initialize ash::Shell,
+  // or it uses test settings if omitted.
+  void SetUp(const InitParams& init_params,
+             base::Optional<ShellInitParams> shell_init_params = base::nullopt);
 
   // Destroys the ash::Shell and performs associated cleanup.
   void TearDown();
@@ -111,7 +128,8 @@
 
  private:
   // Called when running in ash to create Shell.
-  void CreateShell(bool provide_local_state);
+  void CreateShell(bool provide_local_state,
+                   base::Optional<ShellInitParams> init_params);
 
   std::unique_ptr<chromeos::system::ScopedFakeStatisticsProvider>
       statistics_provider_;
diff --git a/ash/test/ash_test_helper_unittest.cc b/ash/test/ash_test_helper_unittest.cc
index 113bb9f4..65410e1 100644
--- a/ash/test/ash_test_helper_unittest.cc
+++ b/ash/test/ash_test_helper_unittest.cc
@@ -21,7 +21,8 @@
   void SetUp() override {
     testing::Test::SetUp();
     ash_test_helper_ = std::make_unique<AshTestHelper>();
-    ash_test_helper_->SetUp(true);
+    AshTestHelper::InitParams init_params;
+    ash_test_helper_->SetUp(std::move(init_params));
   }
 
   void TearDown() override {
diff --git a/ash/wm/desks/desk.cc b/ash/wm/desks/desk.cc
index 512c3a78..b37a99a7 100644
--- a/ash/wm/desks/desk.cc
+++ b/ash/wm/desks/desk.cc
@@ -10,6 +10,7 @@
 #include "ash/public/cpp/window_properties.h"
 #include "ash/shell.h"
 #include "ash/wm/mru_window_tracker.h"
+#include "ash/wm/window_positioner.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_transient_descendant_iterator.h"
 #include "ash/wm/window_util.h"
@@ -57,6 +58,22 @@
   return has_transient_children || CanIncludeWindowInMruList(window);
 }
 
+// Used to temporarily turn off the automatic window positioning while windows
+// are being moved between desks.
+class ScopedWindowPositionerDisabler {
+ public:
+  ScopedWindowPositionerDisabler() {
+    WindowPositioner::DisableAutoPositioning(true);
+  }
+
+  ~ScopedWindowPositionerDisabler() {
+    WindowPositioner::DisableAutoPositioning(false);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ScopedWindowPositionerDisabler);
+};
+
 }  // namespace
 
 class DeskContainerObserver : public aura::WindowObserver {
@@ -225,6 +242,8 @@
   DCHECK(target_desk);
 
   {
+    ScopedWindowPositionerDisabler window_positioner_disabler;
+
     // Throttle notifying the observers, while we move those windows and notify
     // them only once when done.
     auto this_desk_throttled = GetScopedNotifyContentChangedDisabler();
@@ -260,6 +279,8 @@
   DCHECK(this != target_desk);
 
   {
+    ScopedWindowPositionerDisabler window_positioner_disabler;
+
     // Throttling here is necessary even though we're attempting to move a
     // single window. This is because that window might exist in a transient
     // window tree, which will result in actually moving multiple windows if the
diff --git a/ash/wm/desks/desks_unittests.cc b/ash/wm/desks/desks_unittests.cc
index f2e376e..ac34dc77 100644
--- a/ash/wm/desks/desks_unittests.cc
+++ b/ash/wm/desks/desks_unittests.cc
@@ -410,6 +410,36 @@
   EXPECT_FALSE(desk_4->GetDeskContainerForRoot(root)->IsVisible());
 }
 
+TEST_F(DesksTest, TestWindowPositioningPaused) {
+  auto* controller = DesksController::Get();
+  controller->NewDesk();
+
+  // Create two windows whose window positioning is managed.
+  const auto win0_bounds = gfx::Rect{10, 20, 250, 100};
+  const auto win1_bounds = gfx::Rect{50, 50, 200, 200};
+  auto win0 = CreateTestWindow(win0_bounds);
+  auto win1 = CreateTestWindow(win1_bounds);
+  wm::WindowState* window_state = wm::GetWindowState(win0.get());
+  window_state->SetWindowPositionManaged(true);
+  window_state = wm::GetWindowState(win1.get());
+  window_state->SetWindowPositionManaged(true);
+  EXPECT_EQ(win0_bounds, win0->GetBoundsInScreen());
+  EXPECT_EQ(win1_bounds, win1->GetBoundsInScreen());
+
+  // Moving one window to the second desk should not affect the bounds of either
+  // windows.
+  Desk* desk_2 = controller->desks()[1].get();
+  controller->MoveWindowFromActiveDeskTo(win1.get(), desk_2);
+  EXPECT_EQ(win0_bounds, win0->GetBoundsInScreen());
+  EXPECT_EQ(win1_bounds, win1->GetBoundsInScreen());
+
+  // Removing a desk, which results in moving its windows to another desk should
+  // not affect the positions of those managed windows.
+  controller->RemoveDesk(desk_2);
+  EXPECT_EQ(win0_bounds, win0->GetBoundsInScreen());
+  EXPECT_EQ(win1_bounds, win1->GetBoundsInScreen());
+}
+
 // This test makes sure we have coverage for that desk switch animation when run
 // with multiple displays.
 TEST_F(DesksTest, DeskActivationDualDisplay) {
diff --git a/ash/wm/tablet_mode/tablet_mode_backdrop_delegate_impl.cc b/ash/wm/tablet_mode/tablet_mode_backdrop_delegate_impl.cc
deleted file mode 100644
index d01c9917..0000000
--- a/ash/wm/tablet_mode/tablet_mode_backdrop_delegate_impl.cc
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ash/wm/tablet_mode/tablet_mode_backdrop_delegate_impl.h"
-#include "ash/shell.h"
-#include "ash/wm/splitview/split_view_controller.h"
-#include "ash/wm/window_state.h"
-#include "ash/wm/window_util.h"
-
-namespace ash {
-
-namespace {
-
-// returns true if window |upper| is stacked above window |lower| in the window
-// stacking order.
-bool IsWindowAbove(aura::Window* upper, aura::Window* lower) {
-  if (!upper || !lower || upper == lower || upper->parent() != lower->parent())
-    return false;
-
-  const aura::Window::Windows windows = upper->parent()->children();
-  auto upper_i = std::find(windows.begin(), windows.end(), upper);
-  auto lower_i = std::find(windows.begin(), windows.end(), lower);
-  return upper_i > lower_i;
-}
-
-}  // namespace
-
-TabletModeBackdropDelegateImpl::TabletModeBackdropDelegateImpl() = default;
-
-TabletModeBackdropDelegateImpl::~TabletModeBackdropDelegateImpl() = default;
-
-bool TabletModeBackdropDelegateImpl::HasBackdrop(aura::Window* window) {
-  // Don't show the backdrop in tablet mode for PIP windows.
-  if (wm::GetWindowState(window)->IsPip())
-    return false;
-
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
-  if (!split_view_controller->InSplitViewMode())
-    return true;
-
-  // If the split view mode is active, we should place the backdrop below the
-  // snapped window whose window stacking order is lower.
-  aura::Window* left_window = split_view_controller->left_window();
-  aura::Window* right_window = split_view_controller->right_window();
-  if (window == left_window && IsWindowAbove(window, right_window))
-    return false;
-  if (window == right_window && IsWindowAbove(window, left_window))
-    return false;
-  return true;
-}
-
-}  // namespace ash
diff --git a/ash/wm/tablet_mode/tablet_mode_backdrop_delegate_impl.h b/ash/wm/tablet_mode/tablet_mode_backdrop_delegate_impl.h
deleted file mode 100644
index db8e0b9..0000000
--- a/ash/wm/tablet_mode/tablet_mode_backdrop_delegate_impl.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef ASH_WM_TABLET_MODE_TABLET_MODE_BACKDROP_DELEGATE_IMPL_H_
-#define ASH_WM_TABLET_MODE_TABLET_MODE_BACKDROP_DELEGATE_IMPL_H_
-
-#include "ash/wm/workspace/backdrop_delegate.h"
-
-#include "ash/ash_export.h"
-#include "base/macros.h"
-
-namespace ash {
-
-// A backdrop delegate for tablet mode, which always creates a backdrop.
-// This is also used in the WorkspaceLayoutManagerBackdropTest, hence
-// is public.
-class ASH_EXPORT TabletModeBackdropDelegateImpl : public BackdropDelegate {
- public:
-  TabletModeBackdropDelegateImpl();
-  ~TabletModeBackdropDelegateImpl() override;
-
- protected:
-  bool HasBackdrop(aura::Window* window) override;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(TabletModeBackdropDelegateImpl);
-};
-
-}  // namespace ash
-
-#endif  // ASH_WM_TABLET_MODE_TABLET_MODE_BACKDROP_DELEGATE_IMPL_H_
diff --git a/ash/wm/tablet_mode/tablet_mode_window_manager.cc b/ash/wm/tablet_mode/tablet_mode_window_manager.cc
index 316a269..423ce7a 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_manager.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_manager.cc
@@ -23,11 +23,12 @@
 #include "ash/wm/splitview/split_view_controller.h"
 #include "ash/wm/splitview/split_view_utils.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"
 #include "ash/wm/tablet_mode/tablet_mode_window_state.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/wm_event.h"
+#include "ash/wm/workspace/backdrop_controller.h"
+#include "ash/wm/workspace/workspace_layout_manager.h"
 #include "ash/wm/workspace_controller.h"
 #include "base/command_line.h"
 #include "base/stl_util.h"
@@ -161,6 +162,18 @@
   }
 }
 
+void UpdateDeskContainersBackdrops() {
+  for (auto* root : Shell::GetAllRootWindows()) {
+    for (auto* desk_container : desks_util::GetDesksContainers(root)) {
+      WorkspaceController* controller = GetWorkspaceController(desk_container);
+      WorkspaceLayoutManager* layout_manager = controller->layout_manager();
+      BackdropController* backdrop_controller =
+          layout_manager->backdrop_controller();
+      backdrop_controller->UpdateBackdrop();
+    }
+  }
+}
+
 }  // namespace
 
 // Class which tells tablet mode controller to observe a given window for UMA
@@ -240,7 +253,6 @@
     ArrangeWindowsForTabletMode();
   }
   AddWindowCreationObservers();
-  EnableBackdropBehindTopWindowOnEachDisplay(true);
   display::Screen::GetScreen()->AddObserver(this);
   Shell::Get()->AddShellObserver(this);
   Shell::Get()->session_controller()->AddObserver(this);
@@ -293,7 +305,6 @@
   Shell::Get()->session_controller()->RemoveObserver(this);
   Shell::Get()->overview_controller()->RemoveObserver(this);
   display::Screen::GetScreen()->RemoveObserver(this);
-  EnableBackdropBehindTopWindowOnEachDisplay(false);
   RemoveWindowCreationObservers();
 
   ScopedObserveWindowAnimation scoped_observe(GetTopWindow(), this,
@@ -717,30 +728,13 @@
 }
 
 void TabletModeWindowManager::DisplayConfigurationChanged() {
-  EnableBackdropBehindTopWindowOnEachDisplay(false);
   RemoveWindowCreationObservers();
   AddWindowCreationObservers();
-  EnableBackdropBehindTopWindowOnEachDisplay(true);
+  UpdateDeskContainersBackdrops();
 }
 
 bool TabletModeWindowManager::IsContainerWindow(aura::Window* window) {
   return base::Contains(observed_container_windows_, window);
 }
 
-void TabletModeWindowManager::EnableBackdropBehindTopWindowOnEachDisplay(
-    bool enable) {
-  // Inform the WorkspaceLayoutManager that we want to show a backdrop behind
-  // the topmost window of its container.
-  for (auto* root : Shell::GetAllRootWindows()) {
-    for (auto* desk_container : desks_util::GetDesksContainers(root)) {
-      WorkspaceController* controller = GetWorkspaceController(desk_container);
-      DCHECK(controller);
-
-      controller->SetBackdropDelegate(
-          enable ? std::make_unique<TabletModeBackdropDelegateImpl>()
-                 : nullptr);
-    }
-  }
-}
-
 }  // namespace ash
diff --git a/ash/wm/tablet_mode/tablet_mode_window_manager.h b/ash/wm/tablet_mode/tablet_mode_window_manager.h
index ddd75d6..ce242d2 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_manager.h
+++ b/ash/wm/tablet_mode/tablet_mode_window_manager.h
@@ -154,9 +154,6 @@
   // Returns true when the |window| is a container window.
   bool IsContainerWindow(aura::Window* window);
 
-  // Add a backdrop behind the currently active window on each desktop.
-  void EnableBackdropBehindTopWindowOnEachDisplay(bool enable);
-
   // Every window which got touched by our window manager gets added here.
   WindowToState window_state_map_;
 
diff --git a/ash/wm/workspace/backdrop_controller.cc b/ash/wm/workspace/backdrop_controller.cc
index 1ab3c93..b96c3c3 100644
--- a/ash/wm/workspace/backdrop_controller.cc
+++ b/ash/wm/workspace/backdrop_controller.cc
@@ -23,7 +23,6 @@
 #include "ash/wm/window_animations.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
-#include "ash/wm/workspace/backdrop_delegate.h"
 #include "base/auto_reset.h"
 #include "chromeos/audio/chromeos_sounds.h"
 #include "ui/aura/client/aura_constants.h"
@@ -73,23 +72,64 @@
   return overview_controller && overview_controller->InOverviewSession();
 }
 
+// Returns the bottom-most snapped window in the given |desk_container|, and
+// nullptr if no such window was found.
+aura::Window* GetBottomMostSnappedWindowForDeskContainer(
+    aura::Window* desk_container) {
+  DCHECK(desks_util::IsDeskContainer(desk_container));
+  DCHECK(Shell::Get()->tablet_mode_controller()->InTabletMode());
+
+  // For the active desk, only use the windows snapped in SplitViewController if
+  // SplitView mode is active.
+  SplitViewController* split_view_controller =
+      Shell::Get()->split_view_controller();
+  if (desks_util::IsActiveDeskContainer(desk_container) &&
+      split_view_controller->InSplitViewMode()) {
+    aura::Window* left_window = split_view_controller->left_window();
+    aura::Window* right_window = split_view_controller->right_window();
+    for (auto* child : desk_container->children()) {
+      if (child == left_window || child == right_window)
+        return child;
+    }
+
+    return nullptr;
+  }
+
+  // For the inactive desks, we can't use the SplitViewController, since it only
+  // tracks left/right snapped windows in the active desk only.
+  // TODO(afakhry|xdai): SplitViewController should be changed to track snapped
+  // windows per desk per display.
+  for (auto* child : desk_container->children()) {
+    if (wm::GetWindowState(child)->IsSnapped())
+      return child;
+  }
+
+  return nullptr;
+}
+
 }  // namespace
 
 BackdropController::BackdropController(aura::Window* container)
     : container_(container) {
   DCHECK(container_);
-  Shell::Get()->AddShellObserver(this);
-  Shell::Get()->overview_controller()->AddObserver(this);
-  Shell::Get()->accessibility_controller()->AddObserver(this);
-  Shell::Get()->wallpaper_controller()->AddObserver(this);
+  auto* shell = Shell::Get();
+  shell->AddShellObserver(this);
+  shell->overview_controller()->AddObserver(this);
+  shell->accessibility_controller()->AddObserver(this);
+  shell->wallpaper_controller()->AddObserver(this);
+  shell->tablet_mode_controller()->AddObserver(this);
 }
 
 BackdropController::~BackdropController() {
-  Shell::Get()->accessibility_controller()->RemoveObserver(this);
-  Shell::Get()->wallpaper_controller()->RemoveObserver(this);
-  if (Shell::Get()->overview_controller())
-    Shell::Get()->overview_controller()->RemoveObserver(this);
-  Shell::Get()->RemoveShellObserver(this);
+  auto* shell = Shell::Get();
+  // Shell destroys the TabletModeController before destroying all root windows.
+  if (shell->tablet_mode_controller())
+    shell->tablet_mode_controller()->RemoveObserver(this);
+  shell->accessibility_controller()->RemoveObserver(this);
+  shell->wallpaper_controller()->RemoveObserver(this);
+  if (shell->overview_controller())
+    shell->overview_controller()->RemoveObserver(this);
+  shell->RemoveShellObserver(this);
   // TODO(oshima): animations won't work right with mus:
   // http://crbug.com/548396.
   Hide(/*destroy=*/true);
@@ -128,12 +168,6 @@
   UpdateBackdropInternal();
 }
 
-void BackdropController::SetBackdropDelegate(
-    std::unique_ptr<BackdropDelegate> delegate) {
-  delegate_ = std::move(delegate);
-  UpdateBackdrop();
-}
-
 void BackdropController::UpdateBackdrop() {
   // Skip updating while overview mode is active, since the backdrop is hidden.
   if (pause_update_ || InOverviewSession())
@@ -207,7 +241,9 @@
 
 void BackdropController::OnSplitViewStateChanged(SplitViewState previous_state,
                                                  SplitViewState state) {
-  UpdateBackdrop();
+  // Force the update of the backdrop, even if overview is active, so that the
+  // backdrop shows up properly in the mini_views.
+  UpdateBackdropInternal();
 }
 
 void BackdropController::OnSplitViewDividerPositionChanged() {
@@ -223,6 +259,14 @@
   UpdateBackdrop();
 }
 
+void BackdropController::OnTabletModeStarted() {
+  UpdateBackdrop();
+}
+
+void BackdropController::OnTabletModeEnded() {
+  UpdateBackdrop();
+}
+
 void BackdropController::UpdateBackdropInternal() {
   // Skip the recursive updates.
   if (pause_update_)
@@ -325,7 +369,25 @@
     return true;
   }
 
-  return delegate_ ? delegate_->HasBackdrop(window) : false;
+  if (!desks_util::IsDeskContainer(container_))
+    return false;
+
+  if (!Shell::Get()->tablet_mode_controller()->InTabletMode())
+    return false;
+
+  // Don't show the backdrop in tablet mode for PIP windows.
+  auto* state = wm::GetWindowState(window);
+  if (state->IsPip())
+    return false;
+
+  if (!state->IsSnapped())
+    return true;
+
+  auto* bottom_most_snapped_window =
+      GetBottomMostSnappedWindowForDeskContainer(container_);
+  if (!bottom_most_snapped_window)
+    return true;
+  return window == bottom_most_snapped_window;
 }
 
 void BackdropController::Show() {
@@ -370,6 +432,8 @@
 }
 
 bool BackdropController::BackdropShouldFullscreen() {
+  // TODO(afakhry): Define the correct behavior and revise this in a follow-up
+  // CL.
   aura::Window* window = GetTopmostWindowWithBackdrop();
   SplitViewController* split_view_controller =
       Shell::Get()->split_view_controller();
diff --git a/ash/wm/workspace/backdrop_controller.h b/ash/wm/workspace/backdrop_controller.h
index ab571df..14155f18 100644
--- a/ash/wm/workspace/backdrop_controller.h
+++ b/ash/wm/workspace/backdrop_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 ASH_WM_WORKSPACE_WORKSPACE_BACKDROP_DELEGATE_IMPL_H_
-#define ASH_WM_WORKSPACE_WORKSPACE_BACKDROP_DELEGATE_IMPL_H_
+#ifndef ASH_WM_WORKSPACE_BACKDROP_CONTROLLER_H_
+#define ASH_WM_WORKSPACE_BACKDROP_CONTROLLER_H_
 
 #include <memory>
 
@@ -13,6 +13,7 @@
 #include "ash/public/cpp/wallpaper_controller_observer.h"
 #include "ash/shell_observer.h"
 #include "ash/wm/overview/overview_observer.h"
+#include "ash/wm/tablet_mode/tablet_mode_observer.h"
 #include "base/macros.h"
 #include "ui/gfx/geometry/rect.h"
 
@@ -30,20 +31,21 @@
 
 namespace ash {
 
-class BackdropDelegate;
-
 // A backdrop which gets created for a container |window| and which gets
 // stacked behind the top level, activatable window that meets the following
 // criteria.
 //
 // 1) Has a aura::client::kHasBackdrop property = true.
-// 2) BackdropDelegate::HasBackdrop(aura::Window* window) returns true.
-// 3) Active ARC window when the spoken feedback is enabled.
+// 2) Active ARC window when the spoken feedback is enabled.
+// 3) In tablet mode:
+//        - Bottom-most snapped window in splitview,
+//        - Top-most activatable window if splitview is inactive.
 class ASH_EXPORT BackdropController : public AccessibilityObserver,
                                       public ShellObserver,
                                       public OverviewObserver,
                                       public SplitViewObserver,
-                                      public WallpaperControllerObserver {
+                                      public WallpaperControllerObserver,
+                                      public TabletModeObserver {
  public:
   explicit BackdropController(aura::Window* container);
   ~BackdropController() override;
@@ -59,8 +61,6 @@
   // backdrop even if overview mode is active.
   void OnDeskContentChanged();
 
-  void SetBackdropDelegate(std::unique_ptr<BackdropDelegate> delegate);
-
   // Update the visibility of, and restack the backdrop relative to
   // the other windows in the container.
   void UpdateBackdrop();
@@ -90,6 +90,10 @@
   // WallpaperControllerObserver:
   void OnWallpaperPreviewStarted() override;
 
+  // TabletModeObserver:
+  void OnTabletModeStarted() override;
+  void OnTabletModeEnded() override;
+
  private:
   friend class WorkspaceControllerTestApi;
 
@@ -134,8 +138,6 @@
   // The container of the window that should have a backdrop.
   aura::Window* container_;
 
-  std::unique_ptr<BackdropDelegate> delegate_;
-
   // Event hanlder used to implement actions for accessibility.
   std::unique_ptr<ui::EventHandler> backdrop_event_handler_;
   ui::EventHandler* original_event_handler_ = nullptr;
@@ -150,4 +152,4 @@
 
 }  // namespace ash
 
-#endif  // ASH_WM_WORKSPACE_WORKSPACE_BACKDROP_DELEGATE_IMPL_H_
+#endif  // ASH_WM_WORKSPACE_BACKDROP_CONTROLLER_H_
diff --git a/ash/wm/workspace/backdrop_delegate.h b/ash/wm/workspace/backdrop_delegate.h
deleted file mode 100644
index bbf3ddf..0000000
--- a/ash/wm/workspace/backdrop_delegate.h
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef ASH_WM_WORKSPACE_WORKSPACE_LAYOUT_MANAGER_BACKDROP_DELEGATE_H_
-#define ASH_WM_WORKSPACE_WORKSPACE_LAYOUT_MANAGER_BACKDROP_DELEGATE_H_
-
-#include "ash/ash_export.h"
-
-namespace aura {
-class Window;
-}
-
-namespace ash {
-
-// A delegate which can be set to create and control a backdrop which gets
-// placed below the top level window.
-class ASH_EXPORT BackdropDelegate {
- public:
-  virtual ~BackdropDelegate() {}
-
-  // Returns true if |window| should have a backdrop.
-  virtual bool HasBackdrop(aura::Window* window) = 0;
-};
-
-}  // namespace ash
-
-#endif  // ASH_WM_WORKSPACE_WORKSPACE_LAYOUT_MANAGER_BACKDROP_DELEGATE_H_
diff --git a/ash/wm/workspace/workspace_layout_manager.cc b/ash/wm/workspace/workspace_layout_manager.cc
index 8d9c7c7..8ed01ff 100644
--- a/ash/wm/workspace/workspace_layout_manager.cc
+++ b/ash/wm/workspace/workspace_layout_manager.cc
@@ -28,7 +28,6 @@
 #include "ash/wm/window_util.h"
 #include "ash/wm/wm_event.h"
 #include "ash/wm/workspace/backdrop_controller.h"
-#include "ash/wm/workspace/backdrop_delegate.h"
 #include "base/command_line.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/base/ui_base_switches.h"
@@ -134,11 +133,6 @@
   keyboard::KeyboardUIController::Get()->RemoveObserver(this);
 }
 
-void WorkspaceLayoutManager::SetBackdropDelegate(
-    std::unique_ptr<BackdropDelegate> delegate) {
-  backdrop_controller_->SetBackdropDelegate(std::move(delegate));
-}
-
 //////////////////////////////////////////////////////////////////////////////
 // WorkspaceLayoutManager, aura::LayoutManager implementation:
 
diff --git a/ash/wm/workspace/workspace_layout_manager.h b/ash/wm/workspace/workspace_layout_manager.h
index 540a6b2..e8498ab 100644
--- a/ash/wm/workspace/workspace_layout_manager.h
+++ b/ash/wm/workspace/workspace_layout_manager.h
@@ -24,7 +24,6 @@
 namespace ash {
 
 class RootWindowController;
-class BackdropDelegate;
 class BackdropController;
 
 namespace wm {
@@ -45,11 +44,6 @@
   explicit WorkspaceLayoutManager(aura::Window* window);
   ~WorkspaceLayoutManager() override;
 
-  // A delegate which can be set to add a backdrop behind the top most visible
-  // window. With the call the ownership of the delegate will be transferred to
-  // the WorkspaceLayoutManager.
-  void SetBackdropDelegate(std::unique_ptr<BackdropDelegate> delegate);
-
   BackdropController* backdrop_controller() {
     return backdrop_controller_.get();
   }
diff --git a/ash/wm/workspace/workspace_layout_manager_unittest.cc b/ash/wm/workspace/workspace_layout_manager_unittest.cc
index 0d34111..ea208b0a 100644
--- a/ash/wm/workspace/workspace_layout_manager_unittest.cc
+++ b/ash/wm/workspace/workspace_layout_manager_unittest.cc
@@ -39,14 +39,12 @@
 #include "ash/wm/fullscreen_window_finder.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/splitview/split_view_controller.h"
-#include "ash/wm/tablet_mode/tablet_mode_backdrop_delegate_impl.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "ash/wm/window_properties.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
 #include "ash/wm/wm_event.h"
 #include "ash/wm/workspace/backdrop_controller.h"
-#include "ash/wm/workspace/backdrop_delegate.h"
 #include "ash/wm/workspace/workspace_window_resizer.h"
 #include "ash/wm/workspace_controller_test_api.h"
 #include "base/bind_helpers.h"
@@ -1108,15 +1106,10 @@
         desks_util::GetActiveDeskContainerId());
   }
 
-  // Turn the top window back drop on / off.
-  void ShowTopWindowBackdropForContainer(aura::Window* container, bool show) {
-    std::unique_ptr<BackdropDelegate> backdrop;
-    if (show)
-      backdrop = std::make_unique<TabletModeBackdropDelegateImpl>();
-    GetWorkspaceLayoutManager(container)->SetBackdropDelegate(
-        std::move(backdrop));
-    // Closing and / or opening can be a delayed operation.
-    base::RunLoop().RunUntilIdle();
+  // Turn tablet mode on / off.
+  void SetTabletModeEnabled(bool enabled) {
+    Shell::Get()->tablet_mode_controller()->SetEnabledForTest(enabled);
+    ASSERT_EQ(enabled, Shell::Get()->tablet_mode_controller()->InTabletMode());
   }
 
   aura::Window* CreateTestWindowInParent(aura::Window* root_window) {
@@ -1172,13 +1165,13 @@
 // Check that creating the BackDrop without destroying it does not lead into
 // a crash.
 TEST_F(WorkspaceLayoutManagerBackdropTest, BackdropCrashTest) {
-  ShowTopWindowBackdropForContainer(default_container(), true);
+  SetTabletModeEnabled(true);
 }
 
 // Verify basic assumptions about the backdrop.
 TEST_F(WorkspaceLayoutManagerBackdropTest, BasicBackdropTests) {
   // The background widget will be created when there is a window.
-  ShowTopWindowBackdropForContainer(default_container(), true);
+  SetTabletModeEnabled(true);
   ASSERT_EQ(0u, default_container()->children().size());
 
   {
@@ -1222,9 +1215,9 @@
   EXPECT_EQ("C,B,A", GetWindowOrderAsString(backdrop, window1.get(),
                                             window2.get(), window3.get()));
 
-  // Turn on the backdrop mode and check that the window shows up where it
+  // Enter tablet mode and check that the backdrop window shows up where it
   // should be (second highest number).
-  ShowTopWindowBackdropForContainer(default_container(), true);
+  SetTabletModeEnabled(true);
   backdrop = default_container()->children()[2];
   EXPECT_EQ("C,X,B,A", GetWindowOrderAsString(backdrop, window1.get(),
                                               window2.get(), window3.get()));
@@ -1253,7 +1246,7 @@
        ShelfVisibilityDoesNotChangesBounds) {
   Shelf* shelf = GetPrimaryShelf();
   ShelfLayoutManager* shelf_layout_manager = shelf->shelf_layout_manager();
-  ShowTopWindowBackdropForContainer(default_container(), true);
+  SetTabletModeEnabled(true);
   base::RunLoop().RunUntilIdle();
   const gfx::Size fullscreen_size = GetPrimaryDisplay().size();
 
@@ -1376,9 +1369,8 @@
     EXPECT_EQ(window3.get(), children[3]);
   }
 
-  // Enabling the backdrop delegate for tablet mode will put the
-  // backdrop on the top most window.
-  ShowTopWindowBackdropForContainer(default_container(), true);
+  // Enabling tablet mode will put the backdrop on the top most window.
+  SetTabletModeEnabled(true);
   {
     aura::Window::Windows children = window1->parent()->children();
     EXPECT_EQ(4U, children.size());
@@ -1408,8 +1400,8 @@
     EXPECT_EQ(window3.get(), children[3]);
   }
 
-  // Removing the delegate will move the backdrop back to window1.
-  ShowTopWindowBackdropForContainer(default_container(), false);
+  // Exiting tablet mode will move the backdrop back to window1.
+  SetTabletModeEnabled(false);
   {
     aura::Window::Windows children = window1->parent()->children();
     EXPECT_EQ(4U, children.size());
@@ -1419,11 +1411,9 @@
     EXPECT_EQ(window3.get(), children[3]);
   }
 
-  // Re-enable the backdrop delegate for tablet mode. Clearing the property is a
-  // no-op when the delegate is enabled.
-  ShowTopWindowBackdropForContainer(default_container(), true);
+  // Re-enter tablet mode. Clearing the property is a no-op in this case.
+  SetTabletModeEnabled(true);
   window3->ClearProperty(kBackdropWindowMode);
-  ShowTopWindowBackdropForContainer(default_container(), true);
   {
     aura::Window::Windows children = window1->parent()->children();
     EXPECT_EQ(4U, children.size());
@@ -1459,9 +1449,9 @@
       CreateTestWindow(gfx::Rect(0, 0, 100, 100)));
   wm::GetWindowState(wallpaper_picker_window.get())->Activate();
 
-  // Enable the backdrop delegate for tablet mode. The backdrop is shown behind
-  // the wallpaper picker window.
-  ShowTopWindowBackdropForContainer(default_container(), true);
+  // Enter tablet mode. The backdrop is shown behind the wallpaper picker
+  // window.
+  SetTabletModeEnabled(true);
   aura::Window* backdrop = test_helper.GetBackdropWindow();
   {
     aura::Window::Windows children =
@@ -1545,7 +1535,7 @@
   EXPECT_FALSE(test_helper.GetBackdropWindow());
 
   // Turn the top window backdrop on.
-  ShowTopWindowBackdropForContainer(default_container(), true);
+  SetTabletModeEnabled(true);
   EXPECT_TRUE(test_helper.GetBackdropWindow());
 
   // Enter overview mode.
@@ -1787,7 +1777,7 @@
 
 // Test that backdrop works in split view mode.
 TEST_F(WorkspaceLayoutManagerBackdropTest, BackdropForSplitScreenTest) {
-  ShowTopWindowBackdropForContainer(default_container(), true);
+  SetTabletModeEnabled(true);
   Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
 
   class SplitViewTestWindowDelegate : public aura::test::TestWindowDelegate {
@@ -2010,10 +2000,11 @@
       CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 3, 4)));
   always_on_top_window->Show();
   always_on_top_window->SetProperty(aura::client::kAlwaysOnTopKey, true);
+  always_on_top_window->SetProperty(kBackdropWindowMode,
+                                    BackdropWindowMode::kEnabled);
 
   aura::Window* always_on_top_container =
   always_on_top_controller->GetContainer(always_on_top_window.get());
-  ShowTopWindowBackdropForContainer(always_on_top_container, true);
   // AlwaysOnTopContainer has |always_on_top_window| and a backdrop window
   // at this moment.
   ASSERT_EQ(always_on_top_container->children().size(), 2U);
diff --git a/ash/wm/workspace_controller.cc b/ash/wm/workspace_controller.cc
index 8a7820a..d5b4027 100644
--- a/ash/wm/workspace_controller.cc
+++ b/ash/wm/workspace_controller.cc
@@ -17,7 +17,6 @@
 #include "ash/wm/window_state.h"
 #include "ash/wm/wm_window_animations.h"
 #include "ash/wm/workspace/backdrop_controller.h"
-#include "ash/wm/workspace/backdrop_delegate.h"
 #include "ash/wm/workspace/workspace_event_handler.h"
 #include "ash/wm/workspace/workspace_layout_manager.h"
 #include "ui/aura/window.h"
@@ -126,11 +125,6 @@
   }
 }
 
-void WorkspaceController::SetBackdropDelegate(
-    std::unique_ptr<BackdropDelegate> delegate) {
-  layout_manager_->SetBackdropDelegate(std::move(delegate));
-}
-
 void WorkspaceController::OnWindowDestroying(aura::Window* window) {
   DCHECK_EQ(window, viewport_);
   viewport_->RemoveObserver(this);
diff --git a/ash/wm/workspace_controller.h b/ash/wm/workspace_controller.h
index 0b00f9e..aab5942 100644
--- a/ash/wm/workspace_controller.h
+++ b/ash/wm/workspace_controller.h
@@ -15,7 +15,6 @@
 
 namespace ash {
 
-class BackdropDelegate;
 class WorkspaceEventHandler;
 class WorkspaceLayoutManager;
 
@@ -33,10 +32,6 @@
   // Starts the animation that occurs on first login.
   void DoInitialAnimation();
 
-  // Add a delegate which adds a backdrop behind the top window of the default
-  // workspace.
-  void SetBackdropDelegate(std::unique_ptr<BackdropDelegate> delegate);
-
   WorkspaceLayoutManager* layout_manager() { return layout_manager_; }
 
  private:
diff --git a/base/trace_event/memory_infra_background_whitelist.cc b/base/trace_event/memory_infra_background_whitelist.cc
index ed5ff3b7..c52fb802 100644
--- a/base/trace_event/memory_infra_background_whitelist.cc
+++ b/base/trace_event/memory_infra_background_whitelist.cc
@@ -355,6 +355,7 @@
     "sync/0x?/model_type/USER_CONSENT",
     "sync/0x?/model_type/USER_EVENT",
     "sync/0x?/model_type/WALLET_METADATA",
+    "sync/0x?/model_type/WEB_APP",
     "sync/0x?/model_type/WIFI_CONFIGURATION",
     "sync/0x?/model_type/WIFI_CREDENTIAL",
     "tab_restore/service_helper_0x?/entries",
diff --git a/build/check_gn_headers_whitelist.txt b/build/check_gn_headers_whitelist.txt
index d6f01d6..0d8df0a 100644
--- a/build/check_gn_headers_whitelist.txt
+++ b/build/check_gn_headers_whitelist.txt
@@ -115,7 +115,6 @@
 components/wifi/wifi_export.h
 components/wifi/wifi_service.h
 content/browser/background_fetch/background_fetch_constants.h
-content/browser/service_worker/service_worker_response_type.h
 content/common/mac/attributed_string_coder.h
 content/public/browser/context_factory.h
 content/public/browser/media_observer.h
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 417e8e38..e690a66 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8908406653168684048
\ No newline at end of file
+8908346996268693152
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index a8058b5..cd33a36 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-8908409860290660320
\ No newline at end of file
+8908346972979601936
\ No newline at end of file
diff --git a/build/toolchain/OWNERS b/build/toolchain/OWNERS
index b329d48..0a8dcda5 100644
--- a/build/toolchain/OWNERS
+++ b/build/toolchain/OWNERS
@@ -6,3 +6,4 @@
 
 # Code Coverage.
 per-file *code_coverage*=mmoroz@chromium.org
+per-file *code_coverage*=liaoyuke@chromium.org
diff --git a/cc/DEPS b/cc/DEPS
index 0186be31..8d3576c 100644
--- a/cc/DEPS
+++ b/cc/DEPS
@@ -22,6 +22,7 @@
   "+gpu/command_buffer/common/sync_token.h",
   "+gpu/command_buffer/common/texture_in_use_response.h",
   "+gpu/config/gpu_feature_info.h",
+  "+gpu/config/gpu_finch_features.h",
   "+gpu/vulkan",
   "+media",
   "+mojo/public/cpp/system/buffer.h",
@@ -58,6 +59,8 @@
   ".*_(unit|pixel|perf)test.*\.cc": [
     "+components/viz/service/display",
     "+components/viz/test",
+    "+gpu/command_buffer/common/command_buffer_id.h",
+    "+gpu/command_buffer/common/constants.h",
   ],
   "oop_pixeltest\.cc" : [
     "+gpu/command_buffer/client",
diff --git a/cc/paint/image_transfer_cache_entry.cc b/cc/paint/image_transfer_cache_entry.cc
index c0fdd92..e6d52001 100644
--- a/cc/paint/image_transfer_cache_entry.cc
+++ b/cc/paint/image_transfer_cache_entry.cc
@@ -122,7 +122,7 @@
     const SkPixmap* pixmap,
     const SkColorSpace* target_color_space,
     bool needs_mips)
-    : id_(s_next_id_.GetNext()),
+    : id_(GetNextId()),
       pixmap_(pixmap),
       target_color_space_(target_color_space),
       needs_mips_(needs_mips) {
diff --git a/cc/paint/image_transfer_cache_entry.h b/cc/paint/image_transfer_cache_entry.h
index 6f8de73..8421565 100644
--- a/cc/paint/image_transfer_cache_entry.h
+++ b/cc/paint/image_transfer_cache_entry.h
@@ -42,6 +42,8 @@
   uint32_t SerializedSize() const final;
   bool Serialize(base::span<uint8_t> data) const final;
 
+  static uint32_t GetNextId() { return s_next_id_.GetNext(); }
+
  private:
   uint32_t id_;
   const SkPixmap* const pixmap_;
diff --git a/cc/test/fake_paint_image_generator.cc b/cc/test/fake_paint_image_generator.cc
index f26443e5..728a58c 100644
--- a/cc/test/fake_paint_image_generator.cc
+++ b/cc/test/fake_paint_image_generator.cc
@@ -37,11 +37,11 @@
 FakePaintImageGenerator::~FakePaintImageGenerator() = default;
 
 bool FakePaintImageGenerator::IsEligibleForAcceleratedDecoding() const {
-  return false;
+  return is_eligible_for_accelerated_decode_;
 }
 
 sk_sp<SkData> FakePaintImageGenerator::GetEncodedData() const {
-  return nullptr;
+  return SkData::MakeEmpty();
 }
 
 bool FakePaintImageGenerator::GetPixels(const SkImageInfo& info,
diff --git a/cc/test/fake_paint_image_generator.h b/cc/test/fake_paint_image_generator.h
index 268c89e..003afdf 100644
--- a/cc/test/fake_paint_image_generator.h
+++ b/cc/test/fake_paint_image_generator.h
@@ -60,6 +60,9 @@
   }
   void reset_frames_decoded() { frames_decoded_count_.clear(); }
   void SetExpectFallbackToRGB() { expect_fallback_to_rgb_ = true; }
+  void SetEligibleForAcceleratedDecoding() {
+    is_eligible_for_accelerated_decode_ = true;
+  }
 
  private:
   std::vector<uint8_t> image_backing_memory_;
@@ -73,6 +76,7 @@
   // planes and after Chrome implements it, we should no longer expect RGB
   // fallback.
   bool expect_fallback_to_rgb_ = false;
+  bool is_eligible_for_accelerated_decode_ = false;
 };
 
 }  // namespace cc
diff --git a/cc/test/skia_common.cc b/cc/test/skia_common.cc
index 75a009a2..5889e894 100644
--- a/cc/test/skia_common.cc
+++ b/cc/test/skia_common.cc
@@ -136,7 +136,7 @@
 
   SkImageInfo info = SkImageInfo::Make(size.width(), size.height(), color_type,
                                        kPremul_SkAlphaType, color_space);
-  sk_sp<PaintImageGenerator> generator;
+  sk_sp<FakePaintImageGenerator> generator;
   if (is_yuv) {
     // TODO(crbug.com/915972): Remove assumption of YUV420 in tests once we
     // support other subsamplings.
@@ -148,6 +148,7 @@
         info, std::vector<FrameMetadata>{FrameMetadata()},
         allocate_encoded_data);
   }
+  generator->SetEligibleForAcceleratedDecoding();
   auto paint_image =
       PaintImageBuilder::WithDefault()
           .set_id(id)
diff --git a/cc/tiles/gpu_image_decode_cache.cc b/cc/tiles/gpu_image_decode_cache.cc
index df36750..92a5f4c 100644
--- a/cc/tiles/gpu_image_decode_cache.cc
+++ b/cc/tiles/gpu_image_decode_cache.cc
@@ -8,7 +8,9 @@
 
 #include "base/auto_reset.h"
 #include "base/bind.h"
+#include "base/containers/span.h"
 #include "base/debug/alias.h"
+#include "base/feature_list.h"
 #include "base/hash/hash.h"
 #include "base/memory/discardable_memory_allocator.h"
 #include "base/metrics/histogram_macros.h"
@@ -26,6 +28,8 @@
 #include "gpu/command_buffer/client/context_support.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
 #include "gpu/command_buffer/client/raster_interface.h"
+#include "gpu/command_buffer/common/sync_token.h"
+#include "gpu/config/gpu_finch_features.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "third_party/skia/include/core/SkRefCnt.h"
 #include "third_party/skia/include/core/SkSurface.h"
@@ -33,6 +37,8 @@
 #include "third_party/skia/include/gpu/GrBackendSurface.h"
 #include "third_party/skia/include/gpu/GrContext.h"
 #include "third_party/skia/include/gpu/GrTexture.h"
+#include "ui/gfx/color_space.h"
+#include "ui/gfx/geometry/size.h"
 #include "ui/gfx/skia_util.h"
 #include "ui/gl/trace_util.h"
 
@@ -576,10 +582,12 @@
   ImageUploadTaskImpl(GpuImageDecodeCache* cache,
                       const DrawImage& draw_image,
                       scoped_refptr<TileTask> decode_dependency,
+                      sk_sp<SkData> encoded_data,
                       const ImageDecodeCache::TracingInfo& tracing_info)
       : TileTask(false),
         cache_(cache),
         image_(draw_image),
+        encoded_data_(std::move(encoded_data)),
         tracing_info_(tracing_info) {
     DCHECK(!SkipImage(draw_image));
     // If an image is already decoded and locked, we will not generate a
@@ -595,7 +603,7 @@
   void RunOnWorkerThread() override {
     TRACE_EVENT2("cc", "ImageUploadTaskImpl::RunOnWorkerThread", "mode", "gpu",
                  "source_prepare_tiles_id", tracing_info_.prepare_tiles_id);
-    cache_->UploadImageInTask(image_);
+    cache_->UploadImageInTask(image_, std::move(encoded_data_));
   }
 
   // Overridden from TileTask:
@@ -609,6 +617,7 @@
  private:
   GpuImageDecodeCache* cache_;
   DrawImage image_;
+  sk_sp<SkData> encoded_data_;
   const ImageDecodeCache::TracingInfo tracing_info_;
 };
 
@@ -657,8 +666,11 @@
   return state;
 }
 
-GpuImageDecodeCache::DecodedImageData::DecodedImageData(bool is_bitmap_backed)
-    : is_bitmap_backed_(is_bitmap_backed) {}
+GpuImageDecodeCache::DecodedImageData::DecodedImageData(
+    bool is_bitmap_backed,
+    bool do_hardware_accelerated_decode)
+    : is_bitmap_backed_(is_bitmap_backed),
+      do_hardware_accelerated_decode_(do_hardware_accelerated_decode) {}
 GpuImageDecodeCache::DecodedImageData::~DecodedImageData() {
   ResetData();
 }
@@ -740,6 +752,14 @@
 }
 
 void GpuImageDecodeCache::DecodedImageData::ReportUsageStats() const {
+  if (do_hardware_accelerated_decode_) {
+    // When doing hardware decode acceleration, we don't want to record usage
+    // stats for the decode data. The reason is that the decode is done in the
+    // GPU process and the decoded result stays there. On the renderer side, we
+    // don't use or lock the decoded data, so reporting this status would
+    // incorrectly distort the software decoding statistics.
+    return;
+  }
   UMA_HISTOGRAM_ENUMERATION("Renderer4.GpuImageDecodeState",
                             static_cast<ImageUsageState>(UsageState()),
                             IMAGE_USAGE_STATE_COUNT);
@@ -842,6 +862,7 @@
     int upload_scale_mip_level,
     bool needs_mips,
     bool is_bitmap_backed,
+    bool do_hardware_accelerated_decode,
     bool is_yuv_format)
     : paint_image_id(paint_image_id),
       mode(mode),
@@ -852,7 +873,7 @@
       needs_mips(needs_mips),
       is_bitmap_backed(is_bitmap_backed),
       is_yuv(is_yuv_format),
-      decode(is_bitmap_backed) {}
+      decode(is_bitmap_backed, do_hardware_accelerated_decode) {}
 
 GpuImageDecodeCache::ImageData::~ImageData() {
   // We should never delete ImageData while it is in use or before it has been
@@ -979,9 +1000,17 @@
   const InUseCacheKey cache_key = InUseCacheKey::FromDrawImage(draw_image);
   ImageData* image_data = GetImageDataForDrawImage(draw_image, cache_key);
   scoped_refptr<ImageData> new_data;
+  sk_sp<SkData> encoded_data;
   if (!image_data) {
-    // We need an ImageData, create one now.
-    new_data = CreateImageData(draw_image);
+    // We need an ImageData, create one now. Note that hardware decode
+    // acceleration is allowed only in the DecodeTaskType::kPartOfUploadTask
+    // case. This prevents the img.decode() and checkerboard images paths from
+    // going through hardware decode acceleration.
+    new_data = CreateImageData(
+        draw_image,
+        task_type ==
+            DecodeTaskType::kPartOfUploadTask /* allow_hardware_decode */,
+        &encoded_data);
     image_data = new_data.get();
   } else if (image_data->decode.decode_failure) {
     // We have already tried and failed to decode this image, so just return.
@@ -1032,7 +1061,7 @@
     task = base::MakeRefCounted<ImageUploadTaskImpl>(
         this, draw_image,
         GetImageDecodeTaskAndRef(draw_image, tracing_info, task_type),
-        tracing_info);
+        std::move(encoded_data), tracing_info);
     image_data->upload.task = task;
   } else {
     task = GetImageDecodeTaskAndRef(draw_image, tracing_info, task_type);
@@ -1071,9 +1100,11 @@
   base::AutoLock lock(lock_);
   const InUseCacheKey cache_key = InUseCacheKey::FromDrawImage(draw_image);
   ImageData* image_data = GetImageDataForDrawImage(draw_image, cache_key);
+  sk_sp<SkData> encoded_data;
   if (!image_data) {
     // We didn't find the image, create a new entry.
-    auto data = CreateImageData(draw_image);
+    auto data = CreateImageData(draw_image, true /* allow_hardware_decode */,
+                                &encoded_data);
     image_data = data.get();
     AddToPersistentCache(draw_image, std::move(data));
   }
@@ -1088,7 +1119,7 @@
   // We may or may not need to decode and upload the image we've found, the
   // following functions early-out to if we already decoded.
   DecodeImageIfNecessary(draw_image, image_data, TaskType::kInRaster);
-  UploadImageIfNecessary(draw_image, image_data);
+  UploadImageIfNecessary(draw_image, image_data, std::move(encoded_data));
   // Unref the image decode, but not the image. The image ref will be released
   // in DrawWithImageFinished.
   UnrefImageDecode(draw_image, cache_key);
@@ -1355,7 +1386,8 @@
   DecodeImageIfNecessary(draw_image, image_data, task_type);
 }
 
-void GpuImageDecodeCache::UploadImageInTask(const DrawImage& draw_image) {
+void GpuImageDecodeCache::UploadImageInTask(const DrawImage& draw_image,
+                                            sk_sp<SkData> encoded_data) {
   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
                "GpuImageDecodeCache::UploadImage");
   base::Optional<viz::RasterContextProvider::ScopedRasterContextLock>
@@ -1375,7 +1407,7 @@
 
   if (image_data->is_bitmap_backed)
     DecodeImageIfNecessary(draw_image, image_data, TaskType::kInRaster);
-  UploadImageIfNecessary(draw_image, image_data);
+  UploadImageIfNecessary(draw_image, image_data, std::move(encoded_data));
 }
 
 void GpuImageDecodeCache::OnImageDecodeTaskCompleted(
@@ -1421,8 +1453,7 @@
   UnrefImageInternal(draw_image, cache_key);
 }
 
-// Checks if an existing image decode exists. If not, returns a task to produce
-// the requested decode.
+// Checks if an image decode needs a decode task and returns it.
 scoped_refptr<TileTask> GpuImageDecodeCache::GetImageDecodeTaskAndRef(
     const DrawImage& draw_image,
     const TracingInfo& tracing_info,
@@ -1440,6 +1471,9 @@
 
   ImageData* image_data = GetImageDataForDrawImage(draw_image, cache_key);
   DCHECK(image_data);
+  if (image_data->decode.do_hardware_accelerated_decode())
+    return nullptr;
+
   // No decode is necessary for bitmap backed images.
   if (image_data->decode.is_locked() || image_data->is_bitmap_backed) {
     // We should never be creating a decode task for a not budgeted image.
@@ -1688,6 +1722,11 @@
 
   DCHECK_GT(image_data->decode.ref_count, 0u);
 
+  if (image_data->decode.do_hardware_accelerated_decode()) {
+    // We get here in the case of an at-raster decode.
+    return;
+  }
+
   if (image_data->decode.decode_failure) {
     // We have already tried and failed to decode this image. Don't try again.
     return;
@@ -1802,7 +1841,8 @@
 }
 
 void GpuImageDecodeCache::UploadImageIfNecessary(const DrawImage& draw_image,
-                                                 ImageData* image_data) {
+                                                 ImageData* image_data,
+                                                 sk_sp<SkData> encoded_data) {
   CheckContextLockAcquiredIfNecessary();
   lock_.AssertAcquired();
 
@@ -1831,11 +1871,15 @@
     return;
 
   TRACE_EVENT0("cc", "GpuImageDecodeCache::UploadImage");
-  DCHECK(image_data->decode.is_locked());
+  if (!image_data->decode.do_hardware_accelerated_decode()) {
+    // These are not needed for accelerated decodes because there was no decode
+    // task.
+    DCHECK(image_data->decode.is_locked());
+    image_data->decode.mark_used();
+  }
   DCHECK_GT(image_data->decode.ref_count, 0u);
   DCHECK_GT(image_data->upload.ref_count, 0u);
 
-  image_data->decode.mark_used();
   sk_sp<SkColorSpace> color_space =
       SupportsColorSpaceConversion() &&
               draw_image.target_color_space().IsValid()
@@ -1854,6 +1898,51 @@
 
   if (image_data->mode == DecodedDataMode::kTransferCache) {
     DCHECK(use_transfer_cache_);
+    if (image_data->decode.do_hardware_accelerated_decode()) {
+      // The assumption is that scaling is not currently supported for
+      // hardware-accelerated decodes.
+      DCHECK_EQ(0, image_data->upload_scale_mip_level);
+      const gfx::Size output_size(draw_image.paint_image().width(),
+                                  draw_image.paint_image().height());
+      // Try to get the encoded data if we don't have it already: this can
+      // happen, e.g., if we create an upload task using a pre-existing
+      // ImageData. In that case, we previously decided to do hardware decode
+      // acceleration but we didn't cache the encoded data.
+      if (!encoded_data) {
+        encoded_data = draw_image.paint_image().GetSkImage()->refEncodedData();
+        DCHECK(encoded_data);
+      }
+      const uint32_t transfer_cache_id =
+          ClientImageTransferCacheEntry::GetNextId();
+      const gpu::SyncToken decode_sync_token =
+          context_->RasterInterface()->ScheduleImageDecode(
+              base::make_span<const uint8_t>(encoded_data->bytes(),
+                                             encoded_data->size()),
+              output_size, transfer_cache_id,
+              color_space ? gfx::ColorSpace(*color_space) : gfx::ColorSpace(),
+              image_data->needs_mips);
+
+      if (!decode_sync_token.HasData()) {
+        image_data->decode.decode_failure = true;
+        return;
+      }
+
+      image_data->upload.SetTransferCacheId(transfer_cache_id);
+
+      // Note that we wait for the decode sync token here for two reasons:
+      //
+      // 1) To make sure that raster work that depends on the image decode
+      //    happens after the decode completes.
+      //
+      // 2) To protect the transfer cache entry from being unlocked on the
+      //    service side before the decode is completed.
+      context_->RasterInterface()->WaitSyncTokenCHROMIUM(
+          decode_sync_token.GetConstData());
+
+      return;
+    }
+
+    // Non-hardware-accelerated path.
     SkPixmap pixmap;
     if (!image_data->decode.image()->peekPixels(&pixmap))
       return;
@@ -2023,7 +2112,9 @@
 }
 
 scoped_refptr<GpuImageDecodeCache::ImageData>
-GpuImageDecodeCache::CreateImageData(const DrawImage& draw_image) {
+GpuImageDecodeCache::CreateImageData(const DrawImage& draw_image,
+                                     bool allow_hardware_decode,
+                                     sk_sp<SkData>* encoded_data) {
   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
                "GpuImageDecodeCache::CreateImageData");
   lock_.AssertAcquired();
@@ -2064,8 +2155,64 @@
   const bool is_bitmap_backed = !draw_image.paint_image().IsLazyGenerated() &&
                                 upload_scale_mip_level == 0 &&
                                 !cache_color_conversion_on_cpu;
+
+  // Figure out if we will do hardware accelerated decoding. The criteria is as
+  // follows:
+  //
+  // - The kVaapiJpegImageDecodeAcceleration feature is enabled.
+  // - The caller allows hardware decodes.
+  // - We are using the transfer cache (OOP-R).
+  // - The image does not require downscaling for uploading (see TODO below).
+  // - All the encoded data was received prior to any decoding work. Otherwise,
+  //   it means that the software decoder has already started decoding the
+  //   image, so we just let it finish.
+  // - The image's color space is sRGB. This is because we don't currently
+  //   support detecting embedded color profiles.
+  // - The image is supported according to the profiles advertised by the GPU
+  //   service. Checking this involves obtaining the contiguous encoded data
+  //   which may require a copy if the data is not already contiguous. Because
+  //   of this, we return a pointer to the contiguous data (as |encoded_data|)
+  //   so that we can re-use it later (when requesting the image decode).
+  //
+  // TODO(crbug.com/953363): ideally, we can make the hardware decoder support
+  // decision without requiring contiguous data.
+  //
+  // TODO(crbug.com/953367): currently, we don't support scaling with hardware
+  // decode acceleration. Note that it's still okay for the image to be
+  // downscaled by Skia using the GPU.
+  //
+  // TODO(crbug.com/981208): |data_size| needs to be set to the size of the
+  // decoded data, but for accelerated decodes we won't know until the driver
+  // gives us the result in the GPU process. Figure out what to do.
+  bool do_hardware_accelerated_decode = false;
+  if (base::FeatureList::IsEnabled(
+          features::kVaapiJpegImageDecodeAcceleration) &&
+      allow_hardware_decode && mode == DecodedDataMode::kTransferCache &&
+      upload_scale_mip_level == 0 &&
+      draw_image.paint_image().IsEligibleForAcceleratedDecoding() &&
+      draw_image.paint_image().color_space() &&
+      draw_image.paint_image().color_space()->isSRGB()) {
+    sk_sp<SkData> tmp_encoded_data =
+        draw_image.paint_image().GetSkImage()
+            ? draw_image.paint_image().GetSkImage()->refEncodedData()
+            : nullptr;
+    if (tmp_encoded_data &&
+        context_->ContextSupport()->CanDecodeWithHardwareAcceleration(
+            base::make_span<const uint8_t>(tmp_encoded_data->bytes(),
+                                           tmp_encoded_data->size()))) {
+      do_hardware_accelerated_decode = true;
+      DCHECK(encoded_data);
+      *encoded_data = std::move(tmp_encoded_data);
+    }
+  }
+
+  // If draw_image.paint_image().IsEligibleForAcceleratedDecoding() returns
+  // true, the image should not be backed by a bitmap.
+  DCHECK(!do_hardware_accelerated_decode || !is_bitmap_backed);
+
   SkYUVASizeInfo target_yuva_size_info;
-  const bool is_yuv = draw_image.paint_image().IsYuv(&target_yuva_size_info) &&
+  const bool is_yuv = !do_hardware_accelerated_decode &&
+                      draw_image.paint_image().IsYuv(&target_yuva_size_info) &&
                       mode == DecodedDataMode::kGpu;
 
   // TODO(crbug.com/910276): Change after alpha support.
@@ -2092,7 +2239,7 @@
       draw_image.paint_image().stable_id(), mode, data_size,
       draw_image.target_color_space(),
       CalculateDesiredFilterQuality(draw_image), upload_scale_mip_level,
-      needs_mips, is_bitmap_backed, is_yuv));
+      needs_mips, is_bitmap_backed, do_hardware_accelerated_decode, is_yuv));
 }
 
 void GpuImageDecodeCache::WillAddCacheEntry(const DrawImage& draw_image) {
@@ -2414,7 +2561,8 @@
 
 size_t GpuImageDecodeCache::GetDrawImageSizeForTesting(const DrawImage& image) {
   base::AutoLock lock(lock_);
-  scoped_refptr<ImageData> data = CreateImageData(image);
+  scoped_refptr<ImageData> data = CreateImageData(
+      image, false /* allow_hardware_decode */, nullptr /* encoded_data */);
   return data->size;
 }
 
diff --git a/cc/tiles/gpu_image_decode_cache.h b/cc/tiles/gpu_image_decode_cache.h
index 81ca628..86c443b 100644
--- a/cc/tiles/gpu_image_decode_cache.h
+++ b/cc/tiles/gpu_image_decode_cache.h
@@ -17,6 +17,7 @@
 #include "base/trace_event/memory_dump_provider.h"
 #include "cc/cc_export.h"
 #include "cc/tiles/image_decode_cache.h"
+#include "third_party/skia/include/core/SkData.h"
 #include "third_party/skia/include/core/SkRefCnt.h"
 #include "third_party/skia/include/core/SkYUVAIndex.h"
 #include "third_party/skia/include/gpu/gl/GrGLTypes.h"
@@ -36,9 +37,9 @@
 // Generally, when an image is required for raster, GpuImageDecodeCache
 // creates two tasks, one to decode the image, and one to upload the image to
 // the GPU. These tasks are completed before the raster task which depends on
-// the image. We need to seperate decode and upload tasks, as decode can occur
+// the image. We need to separate decode and upload tasks, as decode can occur
 // simultaneously on multiple threads, while upload requires the GL context
-// lock must happen on our non-concurrent raster thread.
+// lock so it must happen on our non-concurrent raster thread.
 //
 // Decoded and Uploaded image data share a single cache entry. Depending on how
 // far we've progressed, this cache entry may contain CPU-side decoded data,
@@ -97,6 +98,35 @@
 //      keeps an ImageData alive while it is present in either the
 //      |persistent_cache_| or |in_use_cache_|.
 //
+// HARDWARE ACCELERATED DECODES:
+//
+// In Chrome OS, we have the ability to use specialized hardware to decode
+// certain images. Because this requires interacting with drivers, it must be
+// done in the GPU process. Therefore, we follow a different path than the usual
+// decode -> upload tasks:
+//   1) We decide whether to do hardware decode acceleration for an image before
+//      we create the decode/upload tasks. Under the hood, this involves parsing
+//      the image and checking if it's supported by the hardware decoder
+//      according to information advertised by the GPU process. Also, we only
+//      allow hardware decoding in OOP-R mode.
+//   2) If we do decide to do hardware decoding, we don't create a decode task.
+//      Instead, we create only an upload task and store enough state to
+//      indicate that the image will go through this hardware accelerated path.
+//      The reason that we use the upload task is that we need to hold the
+//      context lock in order to schedule the image decode.
+//   3) When the upload task runs, we send a request to the GPU process to start
+//      the image decode. This is an IPC message that does not require us to
+//      wait for the response. Instead, we get a sync token that is signalled
+//      when the decode completes. We insert a wait for this sync token right
+//      after sending the decode request.
+//
+// We also handle the more unusual case where images are decoded at raster time.
+// The process is similar: we skip the software decode and then request the
+// hardware decode in the same way as step (3) above.
+//
+// Note that the decoded data never makes it back to the renderer. It stays in
+// the GPU process. The sync token ensures that any raster work that needs the
+// image happens after the decode completes.
 class CC_EXPORT GpuImageDecodeCache
     : public ImageDecodeCache,
       public base::trace_event::MemoryDumpProvider {
@@ -144,7 +174,7 @@
 
   // Called by Decode / Upload tasks.
   void DecodeImageInTask(const DrawImage& image, TaskType task_type);
-  void UploadImageInTask(const DrawImage& image);
+  void UploadImageInTask(const DrawImage& image, sk_sp<SkData> encoded_data);
 
   // Called by Decode / Upload tasks when tasks are finished.
   void OnImageDecodeTaskCompleted(const DrawImage& image,
@@ -216,7 +246,8 @@
 
   // Stores the CPU-side decoded bits of an image and supporting fields.
   struct DecodedImageData : public ImageDataBase {
-    explicit DecodedImageData(bool is_bitmap_backed);
+    explicit DecodedImageData(bool is_bitmap_backed,
+                              bool do_hardware_accelerated_decode);
     ~DecodedImageData();
 
     bool Lock();
@@ -255,6 +286,10 @@
 
     bool is_yuv() const { return image_yuv_planes_.has_value(); }
 
+    bool do_hardware_accelerated_decode() const {
+      return do_hardware_accelerated_decode_;
+    }
+
     // Test-only functions.
     sk_sp<SkImage> ImageForTesting() const { return image_; }
 
@@ -278,6 +313,12 @@
     std::unique_ptr<base::DiscardableMemory> data_;
     sk_sp<SkImage> image_;  // RGBX (or null in YUV decode path)
     base::Optional<YUVSkImages> image_yuv_planes_;
+
+    // |do_hardware_accelerated_decode_| keeps track of images that should go
+    // through hardware decode acceleration. Currently, this path is intended
+    // only for Chrome OS and only for some JPEG images (see
+    // https://crbug.com/868400).
+    bool do_hardware_accelerated_decode_;
   };
 
   // Stores the GPU-side image and supporting fields.
@@ -447,6 +488,7 @@
               int upload_scale_mip_level,
               bool needs_mips,
               bool is_bitmap_backed,
+              bool do_hardware_accelerated_decode,
               bool is_yuv_format);
 
     bool IsGpuOrTransferCache() const;
@@ -557,7 +599,9 @@
       sk_sp<SkColorSpace> decoded_color_space) const;
 
   scoped_refptr<GpuImageDecodeCache::ImageData> CreateImageData(
-      const DrawImage& image);
+      const DrawImage& image,
+      bool allow_hardware_decode,
+      sk_sp<SkData>* encoded_data);
   void WillAddCacheEntry(const DrawImage& draw_image);
   SkImageInfo CreateImageInfoForDrawImage(const DrawImage& draw_image,
                                           int upload_scale_mip_level) const;
@@ -591,7 +635,8 @@
 
   // Requires that the |context_| lock be held when calling.
   void UploadImageIfNecessary(const DrawImage& draw_image,
-                              ImageData* image_data);
+                              ImageData* image_data,
+                              sk_sp<SkData> encoded_data);
 
   // Flush pending operations on context_->GrContext() for each element of
   // |yuv_images| and then clear the vector.
diff --git a/cc/tiles/gpu_image_decode_cache_unittest.cc b/cc/tiles/gpu_image_decode_cache_unittest.cc
index 6431bdb9..3e181f6 100644
--- a/cc/tiles/gpu_image_decode_cache_unittest.cc
+++ b/cc/tiles/gpu_image_decode_cache_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/test/scoped_feature_list.h"
 #include "cc/paint/draw_image.h"
 #include "cc/paint/image_transfer_cache_entry.h"
 #include "cc/paint/paint_image_builder.h"
@@ -15,12 +16,20 @@
 #include "cc/test/transfer_cache_test_helper.h"
 #include "components/viz/test/test_context_provider.h"
 #include "components/viz/test/test_gles2_interface.h"
+#include "gpu/command_buffer/client/raster_implementation_gles.h"
+#include "gpu/command_buffer/common/command_buffer_id.h"
+#include "gpu/command_buffer/common/constants.h"
+#include "gpu/config/gpu_finch_features.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkImageGenerator.h"
 #include "third_party/skia/include/core/SkRefCnt.h"
 #include "third_party/skia/include/gpu/GrBackendSurface.h"
 #include "third_party/skia/include/gpu/GrContext.h"
 
+using testing::_;
+using testing::StrictMock;
+
 namespace cc {
 namespace {
 
@@ -101,13 +110,15 @@
  public:
   explicit FakeGPUImageDecodeTestGLES2Interface(
       FakeDiscardableManager* discardable_manager,
-      TransferCacheTestHelper* transfer_cache_helper)
+      TransferCacheTestHelper* transfer_cache_helper,
+      bool advertise_accelerated_decoding)
       : extension_string_(
             "GL_EXT_texture_format_BGRA8888 GL_OES_rgb8_rgba8 "
             "GL_OES_texture_npot GL_EXT_texture_rg "
             "GL_OES_texture_half_float GL_OES_texture_half_float_linear"),
         discardable_manager_(discardable_manager),
-        transfer_cache_helper_(transfer_cache_helper) {}
+        transfer_cache_helper_(transfer_cache_helper),
+        advertise_accelerated_decoding_(advertise_accelerated_decoding) {}
 
   ~FakeGPUImageDecodeTestGLES2Interface() override {
     // All textures / framebuffers / renderbuffers should be cleaned up.
@@ -161,6 +172,11 @@
     transfer_cache_helper_->DeleteEntryDirect(MakeEntryKey(type, id));
   }
 
+  bool CanDecodeWithHardwareAcceleration(
+      base::span<const uint8_t> encoded_data) const override {
+    return advertise_accelerated_decoding_;
+  }
+
   std::pair<TransferCacheEntryType, uint32_t> MakeEntryKey(uint32_t type,
                                                            uint32_t id) {
     DCHECK_LE(type, static_cast<uint32_t>(TransferCacheEntryType::kLast));
@@ -213,28 +229,73 @@
   const std::string extension_string_;
   FakeDiscardableManager* discardable_manager_;
   TransferCacheTestHelper* transfer_cache_helper_;
+  bool advertise_accelerated_decoding_ = false;
   size_t mapped_entry_size_ = 0;
   std::unique_ptr<uint8_t[]> mapped_entry_;
 };
 
+class MockRasterImplementation : public gpu::raster::RasterImplementationGLES {
+ public:
+  explicit MockRasterImplementation(gpu::gles2::GLES2Interface* gl)
+      : RasterImplementationGLES(gl) {}
+  ~MockRasterImplementation() override = default;
+
+  gpu::SyncToken ScheduleImageDecode(base::span<const uint8_t> encoded_data,
+                                     const gfx::Size& output_size,
+                                     uint32_t transfer_cache_entry_id,
+                                     const gfx::ColorSpace& target_color_space,
+                                     bool needs_mips) override {
+    DoScheduleImageDecode(output_size, transfer_cache_entry_id,
+                          target_color_space, needs_mips);
+    if (!next_accelerated_decode_fails_) {
+      return gpu::SyncToken(gpu::CommandBufferNamespace::GPU_IO,
+                            gpu::CommandBufferId::FromUnsafeValue(1u),
+                            next_release_count_++);
+    }
+    return gpu::SyncToken();
+  }
+
+  void SetAcceleratedDecodingFailed() { next_accelerated_decode_fails_ = true; }
+
+  MOCK_METHOD4(DoScheduleImageDecode,
+               void(const gfx::Size& /* output_size */,
+                    uint32_t /* transfer_cache_entry_id */,
+                    const gfx::ColorSpace& /* target_color_space */,
+                    bool /* needs_mips */));
+
+ private:
+  bool next_accelerated_decode_fails_ = false;
+  uint64_t next_release_count_ = 1u;
+};
+
 class GPUImageDecodeTestMockContextProvider : public viz::TestContextProvider {
  public:
   static scoped_refptr<GPUImageDecodeTestMockContextProvider> Create(
       FakeDiscardableManager* discardable_manager,
-      TransferCacheTestHelper* transfer_cache_helper) {
+      TransferCacheTestHelper* transfer_cache_helper,
+      bool advertise_accelerated_decoding) {
+    auto support = std::make_unique<FakeGPUImageDecodeTestGLES2Interface>(
+        discardable_manager, transfer_cache_helper,
+        advertise_accelerated_decoding);
+    auto gl = std::make_unique<FakeGPUImageDecodeTestGLES2Interface>(
+        discardable_manager, transfer_cache_helper,
+        false /* advertise_accelerated_decoding */);
+    auto raster =
+        std::make_unique<StrictMock<MockRasterImplementation>>(gl.get());
     return new GPUImageDecodeTestMockContextProvider(
-        std::make_unique<FakeGPUImageDecodeTestGLES2Interface>(
-            discardable_manager, transfer_cache_helper),
-        std::make_unique<FakeGPUImageDecodeTestGLES2Interface>(
-            discardable_manager, transfer_cache_helper));
+        std::move(support), std::move(gl), std::move(raster));
   }
 
  private:
   ~GPUImageDecodeTestMockContextProvider() override = default;
   GPUImageDecodeTestMockContextProvider(
       std::unique_ptr<viz::TestContextSupport> support,
-      std::unique_ptr<viz::TestGLES2Interface> gl)
-      : TestContextProvider(std::move(support), std::move(gl), true) {}
+      std::unique_ptr<viz::TestGLES2Interface> gl,
+      std::unique_ptr<gpu::raster::RasterInterface> raster)
+      : TestContextProvider(std::move(support),
+                            std::move(gl),
+                            std::move(raster),
+                            true) {}
 };
 
 SkMatrix CreateMatrix(const SkSize& scale, bool is_decomposable) {
@@ -260,13 +321,21 @@
 size_t kGpuMemoryLimitBytes = 96 * 1024 * 1024;
 
 class GpuImageDecodeCacheTest
-    : public ::testing::TestWithParam<std::tuple<SkColorType,
-                                                 bool /* use_transfer_cache */,
-                                                 bool /* do_yuv_decode */>> {
+    : public ::testing::TestWithParam<
+          std::tuple<SkColorType,
+                     bool /* use_transfer_cache */,
+                     bool /* do_yuv_decode */,
+                     bool /* advertise_accelerated_decoding */>> {
  public:
   void SetUp() override {
+    advertise_accelerated_decoding_ = std::get<3>(GetParam());
+    if (advertise_accelerated_decoding_) {
+      feature_list_.InitAndEnableFeature(
+          features::kVaapiJpegImageDecodeAcceleration);
+    }
     context_provider_ = GPUImageDecodeTestMockContextProvider::Create(
-        &discardable_manager_, &transfer_cache_helper_);
+        &discardable_manager_, &transfer_cache_helper_,
+        advertise_accelerated_decoding_);
     discardable_manager_.SetGLES2Interface(
         context_provider_->UnboundTestContextGL());
     context_provider_->BindToCurrentThread();
@@ -437,12 +506,18 @@
   }
 
  protected:
+  base::test::ScopedFeatureList feature_list_;
+
+  // The order of these members is important because |context_provider_| depends
+  // on |discardable_manager_| and |transfer_cache_helper_|.
   FakeDiscardableManager discardable_manager_;
-  scoped_refptr<GPUImageDecodeTestMockContextProvider> context_provider_;
   TransferCacheTestHelper transfer_cache_helper_;
+  scoped_refptr<GPUImageDecodeTestMockContextProvider> context_provider_;
+
   bool use_transfer_cache_;
   SkColorType color_type_;
   bool do_yuv_decode_;
+  bool advertise_accelerated_decoding_;
   int max_texture_size_ = 0;
 };
 
@@ -3091,16 +3166,347 @@
 INSTANTIATE_TEST_SUITE_P(
     GpuImageDecodeCacheTestsInProcessRaster,
     GpuImageDecodeCacheTest,
-    testing::Combine(testing::ValuesIn(test_color_types),
-                     testing::ValuesIn(false_array) /* use_transfer_cache */,
-                     testing::Bool() /* do_yuv_decode */));
+    testing::Combine(
+        testing::ValuesIn(test_color_types),
+        testing::ValuesIn(false_array) /* use_transfer_cache */,
+        testing::Bool() /* do_yuv_decode */,
+        testing::ValuesIn(false_array) /* advertise_accelerated_decoding */));
 
 INSTANTIATE_TEST_SUITE_P(
     GpuImageDecodeCacheTestsOOPR,
     GpuImageDecodeCacheTest,
-    testing::Combine(testing::ValuesIn(test_color_types),
-                     testing::ValuesIn(true_array) /* use_transfer_cache */,
-                     testing::ValuesIn(false_array) /* do_yuv_decode */));
+    testing::Combine(
+        testing::ValuesIn(test_color_types),
+        testing::ValuesIn(true_array) /* use_transfer_cache */,
+        testing::ValuesIn(false_array) /* do_yuv_decode */,
+        testing::ValuesIn(false_array) /* advertise_accelerated_decoding */));
+
+class GpuImageDecodeCacheWithAcceleratedDecodesTest
+    : public GpuImageDecodeCacheTest {
+ public:
+  PaintImage CreatePaintImageForDecodeAcceleration(
+      const gfx::Size& size,
+      sk_sp<SkColorSpace> color_space = nullptr,
+      bool is_eligible_for_accelerated_decoding = true) {
+    SkImageInfo info =
+        SkImageInfo::Make(size.width(), size.height(), color_type_,
+                          kPremul_SkAlphaType, color_space);
+    sk_sp<FakePaintImageGenerator> generator;
+    if (do_yuv_decode_) {
+      generator =
+          sk_make_sp<FakePaintImageGenerator>(info, GetYUV420SizeInfo(size));
+
+      // TODO(crbug.com/968125): even though the paint image can be decoded to
+      // YUV, the cache won't follow that path because it's not yet supported in
+      // OOPR. Remove this expectation when it is.
+      generator->SetExpectFallbackToRGB();
+    } else {
+      generator = sk_make_sp<FakePaintImageGenerator>(info);
+    }
+    if (is_eligible_for_accelerated_decoding)
+      generator->SetEligibleForAcceleratedDecoding();
+    PaintImage image = PaintImageBuilder::WithDefault()
+                           .set_id(PaintImage::GetNextId())
+                           .set_paint_image_generator(generator)
+                           .TakePaintImage();
+    return image;
+  }
+
+  StrictMock<MockRasterImplementation>* raster_implementation() const {
+    return static_cast<StrictMock<MockRasterImplementation>*>(
+        context_provider_->RasterInterface());
+  }
+};
+
+TEST_P(GpuImageDecodeCacheWithAcceleratedDecodesTest,
+       RequestAcceleratedDecodeSuccessfully) {
+  auto cache = CreateCache();
+  const gfx::Size image_size = GetNormalImageSize();
+  const sk_sp<SkColorSpace> image_color_space = SkColorSpace::MakeSRGB();
+  const gfx::ColorSpace target_color_space(*image_color_space);
+  ASSERT_TRUE(target_color_space.IsValid());
+  const PaintImage image =
+      CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
+  const bool is_decomposable = true;
+  const SkFilterQuality quality = kHigh_SkFilterQuality;
+  DrawImage draw_image(
+      image, SkIRect::MakeWH(image.width(), image.height()), quality,
+      CreateMatrix(SkSize::Make(0.75f, 0.75f), is_decomposable),
+      PaintImage::kDefaultFrameIndex, target_color_space);
+  ImageDecodeCache::TaskResult result =
+      cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
+  EXPECT_TRUE(result.need_unref);
+  ASSERT_TRUE(result.task);
+
+  // Accelerated decodes should not produce decode tasks.
+  ASSERT_TRUE(result.task->dependencies().empty());
+  EXPECT_CALL(*raster_implementation(),
+              DoScheduleImageDecode(image_size, _, gfx::ColorSpace(), _))
+      .Times(1);
+  TestTileTaskRunner::ProcessTask(result.task.get());
+
+  // Must hold context lock before calling GetDecodedImageForDraw /
+  // DrawWithImageFinished.
+  viz::ContextProvider::ScopedContextLock context_lock(context_provider());
+  const DecodedDrawImage decoded_draw_image =
+      cache->GetDecodedImageForDraw(draw_image);
+  EXPECT_TRUE(decoded_draw_image.transfer_cache_entry_id().has_value());
+  cache->DrawWithImageFinished(draw_image, decoded_draw_image);
+  cache->UnrefImage(draw_image);
+}
+
+TEST_P(GpuImageDecodeCacheWithAcceleratedDecodesTest,
+       RequestAcceleratedDecodeSuccessfullyWithColorSpaceConversion) {
+  auto cache = CreateCache();
+  const gfx::Size image_size = GetNormalImageSize();
+  const sk_sp<SkColorSpace> image_color_space = SkColorSpace::MakeSRGB();
+  const gfx::ColorSpace target_color_space = gfx::ColorSpace::CreateXYZD50();
+  ASSERT_TRUE(target_color_space.IsValid());
+  const PaintImage image =
+      CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
+  const bool is_decomposable = true;
+  const SkFilterQuality quality = kHigh_SkFilterQuality;
+  DrawImage draw_image(
+      image, SkIRect::MakeWH(image.width(), image.height()), quality,
+      CreateMatrix(SkSize::Make(0.75f, 0.75f), is_decomposable),
+      PaintImage::kDefaultFrameIndex, target_color_space);
+  ImageDecodeCache::TaskResult result =
+      cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
+  EXPECT_TRUE(result.need_unref);
+  ASSERT_TRUE(result.task);
+
+  // Accelerated decodes should not produce decode tasks.
+  ASSERT_TRUE(result.task->dependencies().empty());
+  EXPECT_CALL(*raster_implementation(),
+              DoScheduleImageDecode(image_size, _,
+                                    cache->SupportsColorSpaceConversion()
+                                        ? target_color_space
+                                        : gfx::ColorSpace(),
+                                    _))
+      .Times(1);
+  TestTileTaskRunner::ProcessTask(result.task.get());
+
+  // Must hold context lock before calling GetDecodedImageForDraw /
+  // DrawWithImageFinished.
+  viz::ContextProvider::ScopedContextLock context_lock(context_provider());
+  const DecodedDrawImage decoded_draw_image =
+      cache->GetDecodedImageForDraw(draw_image);
+  EXPECT_TRUE(decoded_draw_image.transfer_cache_entry_id().has_value());
+  cache->DrawWithImageFinished(draw_image, decoded_draw_image);
+  cache->UnrefImage(draw_image);
+}
+
+TEST_P(GpuImageDecodeCacheWithAcceleratedDecodesTest,
+       AcceleratedDecodeRequestFails) {
+  auto cache = CreateCache();
+  const gfx::Size image_size = GetNormalImageSize();
+  const sk_sp<SkColorSpace> image_color_space = SkColorSpace::MakeSRGB();
+  const gfx::ColorSpace target_color_space = gfx::ColorSpace::CreateXYZD50();
+  ASSERT_TRUE(target_color_space.IsValid());
+  const PaintImage image =
+      CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
+  const bool is_decomposable = true;
+  const SkFilterQuality quality = kHigh_SkFilterQuality;
+  DrawImage draw_image(
+      image, SkIRect::MakeWH(image.width(), image.height()), quality,
+      CreateMatrix(SkSize::Make(0.75f, 0.75f), is_decomposable),
+      PaintImage::kDefaultFrameIndex, target_color_space);
+  ImageDecodeCache::TaskResult result =
+      cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
+  EXPECT_TRUE(result.need_unref);
+  ASSERT_TRUE(result.task);
+
+  // Accelerated decodes should not produce decode tasks.
+  ASSERT_TRUE(result.task->dependencies().empty());
+  raster_implementation()->SetAcceleratedDecodingFailed();
+  EXPECT_CALL(*raster_implementation(),
+              DoScheduleImageDecode(image_size, _,
+                                    cache->SupportsColorSpaceConversion()
+                                        ? target_color_space
+                                        : gfx::ColorSpace(),
+                                    _))
+      .Times(1);
+  TestTileTaskRunner::ProcessTask(result.task.get());
+
+  // Must hold context lock before calling GetDecodedImageForDraw /
+  // DrawWithImageFinished.
+  viz::ContextProvider::ScopedContextLock context_lock(context_provider());
+  const DecodedDrawImage decoded_draw_image =
+      cache->GetDecodedImageForDraw(draw_image);
+  EXPECT_FALSE(decoded_draw_image.transfer_cache_entry_id().has_value());
+  cache->DrawWithImageFinished(draw_image, decoded_draw_image);
+  cache->UnrefImage(draw_image);
+}
+
+TEST_P(GpuImageDecodeCacheWithAcceleratedDecodesTest,
+       CannotRequestAcceleratedDecodeBecauseOfStandAloneDecode) {
+  auto cache = CreateCache();
+  const gfx::Size image_size = GetNormalImageSize();
+  const sk_sp<SkColorSpace> image_color_space = SkColorSpace::MakeSRGB();
+  const gfx::ColorSpace target_color_space(*image_color_space);
+  ASSERT_TRUE(target_color_space.IsValid());
+  const PaintImage image =
+      CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
+  const bool is_decomposable = true;
+  const SkFilterQuality quality = kHigh_SkFilterQuality;
+  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
+                       quality,
+                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
+                       PaintImage::kDefaultFrameIndex, target_color_space);
+  ImageDecodeCache::TaskResult result =
+      cache->GetOutOfRasterDecodeTaskForImageAndRef(draw_image);
+  EXPECT_TRUE(result.need_unref);
+  ASSERT_TRUE(result.task);
+
+  // A non-accelerated standalone decode should produce only a decode task.
+  ASSERT_TRUE(result.task->dependencies().empty());
+  TestTileTaskRunner::ProcessTask(result.task.get());
+  cache->UnrefImage(draw_image);
+}
+
+TEST_P(GpuImageDecodeCacheWithAcceleratedDecodesTest,
+       CannotRequestAcceleratedDecodeBecauseOfNonZeroUploadMipLevel) {
+  auto cache = CreateCache();
+  const gfx::Size image_size = GetNormalImageSize();
+  const sk_sp<SkColorSpace> image_color_space = SkColorSpace::MakeSRGB();
+  const gfx::ColorSpace target_color_space(*image_color_space);
+  ASSERT_TRUE(target_color_space.IsValid());
+  const PaintImage image =
+      CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
+  const bool is_decomposable = true;
+  const SkFilterQuality quality = kHigh_SkFilterQuality;
+  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
+                       quality,
+                       CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
+                       PaintImage::kDefaultFrameIndex, target_color_space);
+  ImageDecodeCache::TaskResult result =
+      cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
+  EXPECT_TRUE(result.need_unref);
+  ASSERT_TRUE(result.task);
+
+  // A non-accelerated normal decode should produce a decode dependency.
+  ASSERT_EQ(result.task->dependencies().size(), 1u);
+  ASSERT_TRUE(result.task->dependencies()[0]);
+  TestTileTaskRunner::ProcessTask(result.task->dependencies()[0].get());
+  TestTileTaskRunner::ProcessTask(result.task.get());
+  cache->UnrefImage(draw_image);
+}
+
+TEST_P(GpuImageDecodeCacheWithAcceleratedDecodesTest,
+       CannotRequestAcceleratedDecodeBecauseOfIneligiblePaintImage) {
+  auto cache = CreateCache();
+  const gfx::Size image_size = GetNormalImageSize();
+  const sk_sp<SkColorSpace> image_color_space = SkColorSpace::MakeSRGB();
+  const gfx::ColorSpace target_color_space(*image_color_space);
+  ASSERT_TRUE(target_color_space.IsValid());
+  const PaintImage image = CreatePaintImageForDecodeAcceleration(
+      image_size, image_color_space,
+      false /* is_eligible_for_accelerated_decoding */);
+  const bool is_decomposable = true;
+  const SkFilterQuality quality = kHigh_SkFilterQuality;
+  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
+                       quality,
+                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
+                       PaintImage::kDefaultFrameIndex, target_color_space);
+  ImageDecodeCache::TaskResult result =
+      cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
+  EXPECT_TRUE(result.need_unref);
+  ASSERT_TRUE(result.task);
+
+  // A non-accelerated normal decode should produce a decode dependency.
+  ASSERT_EQ(result.task->dependencies().size(), 1u);
+  ASSERT_TRUE(result.task->dependencies()[0]);
+  TestTileTaskRunner::ProcessTask(result.task->dependencies()[0].get());
+  TestTileTaskRunner::ProcessTask(result.task.get());
+  cache->UnrefImage(draw_image);
+}
+
+TEST_P(GpuImageDecodeCacheWithAcceleratedDecodesTest,
+       CannotRequestAcceleratedDecodeBecauseOfNonSRGBColorSpace) {
+  auto cache = CreateCache();
+  const gfx::Size image_size = GetNormalImageSize();
+  const sk_sp<SkColorSpace> image_color_space =
+      SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kAdobeRGB);
+  const gfx::ColorSpace target_color_space(*image_color_space);
+  ASSERT_TRUE(target_color_space.IsValid());
+  const PaintImage image =
+      CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
+  const bool is_decomposable = true;
+  const SkFilterQuality quality = kHigh_SkFilterQuality;
+  DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
+                       quality,
+                       CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
+                       PaintImage::kDefaultFrameIndex, target_color_space);
+  ImageDecodeCache::TaskResult result =
+      cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
+  EXPECT_TRUE(result.need_unref);
+  ASSERT_TRUE(result.task);
+
+  // A non-accelerated normal decode should produce a decode dependency.
+  ASSERT_EQ(result.task->dependencies().size(), 1u);
+  ASSERT_TRUE(result.task->dependencies()[0]);
+  TestTileTaskRunner::ProcessTask(result.task->dependencies()[0].get());
+  TestTileTaskRunner::ProcessTask(result.task.get());
+  cache->UnrefImage(draw_image);
+}
+
+TEST_P(GpuImageDecodeCacheWithAcceleratedDecodesTest,
+       RequestAcceleratedDecodeSuccessfullyAfterCancellation) {
+  auto cache = CreateCache();
+  const gfx::Size image_size = GetNormalImageSize();
+  const sk_sp<SkColorSpace> image_color_space = SkColorSpace::MakeSRGB();
+  const gfx::ColorSpace target_color_space(*image_color_space);
+  ASSERT_TRUE(target_color_space.IsValid());
+  const PaintImage image =
+      CreatePaintImageForDecodeAcceleration(image_size, image_color_space);
+  const bool is_decomposable = true;
+  const SkFilterQuality quality = kHigh_SkFilterQuality;
+  DrawImage draw_image(
+      image, SkIRect::MakeWH(image.width(), image.height()), quality,
+      CreateMatrix(SkSize::Make(0.75f, 0.75f), is_decomposable),
+      PaintImage::kDefaultFrameIndex, target_color_space);
+  ImageDecodeCache::TaskResult result =
+      cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
+  EXPECT_TRUE(result.need_unref);
+  ASSERT_TRUE(result.task);
+
+  // Accelerated decodes should not produce decode tasks.
+  ASSERT_TRUE(result.task->dependencies().empty());
+
+  // Cancel the upload.
+  TestTileTaskRunner::CancelTask(result.task.get());
+  TestTileTaskRunner::CompleteTask(result.task.get());
+
+  // Get the image again - we should have an upload task.
+  ImageDecodeCache::TaskResult another_result =
+      cache->GetTaskForImageAndRef(draw_image, ImageDecodeCache::TracingInfo());
+  EXPECT_TRUE(another_result.need_unref);
+  ASSERT_TRUE(another_result.task);
+  EXPECT_EQ(another_result.task->dependencies().size(), 0u);
+  EXPECT_CALL(*raster_implementation(),
+              DoScheduleImageDecode(image_size, _, gfx::ColorSpace(), _))
+      .Times(1);
+  TestTileTaskRunner::ProcessTask(another_result.task.get());
+
+  // Must hold context lock before calling GetDecodedImageForDraw /
+  // DrawWithImageFinished.
+  viz::ContextProvider::ScopedContextLock context_lock(context_provider());
+  const DecodedDrawImage decoded_draw_image =
+      cache->GetDecodedImageForDraw(draw_image);
+  EXPECT_TRUE(decoded_draw_image.transfer_cache_entry_id().has_value());
+  cache->DrawWithImageFinished(draw_image, decoded_draw_image);
+  cache->UnrefImage(draw_image);
+  cache->UnrefImage(draw_image);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    GpuImageDecodeCacheTestsOOPR,
+    GpuImageDecodeCacheWithAcceleratedDecodesTest,
+    testing::Combine(
+        testing::ValuesIn(test_color_types),
+        testing::ValuesIn(true_array) /* use_transfer_cache */,
+        testing::Bool() /* do_yuv_decode */,
+        testing::ValuesIn(true_array) /* advertise_accelerated_decoding */));
 
 #undef EXPECT_TRUE_IF_NOT_USING_TRANSFER_CACHE
 #undef EXPECT_FALSE_IF_NOT_USING_TRANSFER_CACHE
diff --git a/chrome/VERSION b/chrome/VERSION
index fc2beed..451f84b 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=77
 MINOR=0
-BUILD=3849
+BUILD=3850
 PATCH=0
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 28c9e8e06..e65c96a 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -718,7 +718,6 @@
   "java/src/org/chromium/chrome/browser/gesturenav/NavigationGlowFactory.java",
   "java/src/org/chromium/chrome/browser/gesturenav/NavigationHandler.java",
   "java/src/org/chromium/chrome/browser/gesturenav/SideSlideLayout.java",
-  "java/src/org/chromium/chrome/browser/gesturenav/TabSwitcherActionDelegate.java",
   "java/src/org/chromium/chrome/browser/gesturenav/TabbedActionDelegate.java",
   "java/src/org/chromium/chrome/browser/gsa/ContextReporter.java",
   "java/src/org/chromium/chrome/browser/gsa/GSAAccountChangeListener.java",
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingComponentBridge.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingComponentBridge.java
index 60cb374..4a4b3dc 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingComponentBridge.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingComponentBridge.java
@@ -124,8 +124,8 @@
     }
 
     @CalledByNative
-    private Object addUserInfoToAccessorySheetData(Object objAccessorySheetData) {
-        UserInfo userInfo = new UserInfo(this::fetchFavicon);
+    private Object addUserInfoToAccessorySheetData(Object objAccessorySheetData, String title) {
+        UserInfo userInfo = new UserInfo(title, this::fetchFavicon);
         ((AccessorySheetData) objAccessorySheetData).getUserInfoList().add(userInfo);
         return userInfo;
     }
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewTest.java
index 20fec9f..8a0d98e 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetViewTest.java
@@ -167,7 +167,7 @@
             String addressHomeZip, String addressHomeCity, String addressHomeState,
             String addressHomeCountry, String phoneHomeWholeNumber, String emailAddress,
             AtomicBoolean clickRecorder) {
-        UserInfo info = new UserInfo(null);
+        UserInfo info = new UserInfo("", null);
         info.addField(new UserInfoField(
                 nameFirst, nameFirst, "", false, item -> clickRecorder.set(true)));
         info.addField(new UserInfoField(
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/CreditCardAccessorySheetViewTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/CreditCardAccessorySheetViewTest.java
index add50ba..c1cfbea 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/CreditCardAccessorySheetViewTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/CreditCardAccessorySheetViewTest.java
@@ -160,7 +160,7 @@
 
     private UserInfo createInfo(
             String number, String month, String year, String name, AtomicBoolean clickRecorder) {
-        UserInfo info = new UserInfo(null);
+        UserInfo info = new UserInfo("", null);
         info.addField(
                 new UserInfoField(number, number, "", false, item -> clickRecorder.set(true)));
         info.addField(new UserInfoField(month, month, "", false, item -> clickRecorder.set(true)));
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetModernViewTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetModernViewTest.java
index 1f3d6c1..f52134f 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetModernViewTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetModernViewTest.java
@@ -112,7 +112,7 @@
         final AtomicReference<Boolean> clicked = new AtomicReference<>(false);
         assertThat(mView.get().getChildCount(), is(0));
 
-        UserInfo testInfo = new UserInfo(null);
+        UserInfo testInfo = new UserInfo("", null);
         testInfo.addField(new UserInfoField(
                 "Name Suggestion", "Name Suggestion", "", false, item -> clicked.set(true)));
         testInfo.addField(new UserInfoField(
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetViewTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetViewTest.java
index ea48be0..36a4aff6 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetViewTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetViewTest.java
@@ -124,7 +124,7 @@
         final AtomicReference<Boolean> clicked = new AtomicReference<>(false);
         assertThat(mView.get().getChildCount(), is(0));
 
-        UserInfo testInfo = new UserInfo(null);
+        UserInfo testInfo = new UserInfo("", null);
         testInfo.addField(new UserInfoField(
                 "Name Suggestion", "Name Suggestion", "", false, item -> clicked.set(true)));
         testInfo.addField(new UserInfoField(
diff --git a/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingControllerTest.java b/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingControllerTest.java
index 9d2b738..1e1851f 100644
--- a/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingControllerTest.java
+++ b/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingControllerTest.java
@@ -203,7 +203,7 @@
         void providePasswordSheet(String passwordString) {
             AccessorySheetData sheetData =
                     new AccessorySheetData(AccessoryTabType.PASSWORDS, "Passwords");
-            UserInfo userInfo = new UserInfo(null);
+            UserInfo userInfo = new UserInfo("", null);
             userInfo.addField(
                     new UserInfoField("(No username)", "No username", /*id=*/"", false, null));
             userInfo.addField(new UserInfoField(passwordString, "Password", /*id=*/"", true, null));
diff --git a/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetControllerTest.java b/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetControllerTest.java
index be346150..85ce466 100644
--- a/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetControllerTest.java
+++ b/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessorySheetControllerTest.java
@@ -121,7 +121,7 @@
         final PropertyProvider<AccessorySheetData> testProvider = new PropertyProvider<>();
         final AccessorySheetData testData =
                 new AccessorySheetData(AccessoryTabType.ADDRESSES, "Addresses for this site");
-        testData.getUserInfoList().add(new UserInfo(null));
+        testData.getUserInfoList().add(new UserInfo("", null));
         testData.getUserInfoList().get(0).addField(
                 new UserInfoField("Name", "Name", "", false, null));
         testData.getUserInfoList().get(0).addField(
@@ -149,7 +149,7 @@
         assertThat(mSheetDataPieces.get(0).getDataPiece(), is(equalTo("No addresses")));
 
         // As soon UserInfo is available, discard the title.
-        testData.getUserInfoList().add(new UserInfo(null));
+        testData.getUserInfoList().add(new UserInfo("", null));
         testData.getUserInfoList().get(0).addField(
                 new UserInfoField("Name", "Name", "", false, null));
         testData.getUserInfoList().get(0).addField(
diff --git a/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetControllerTest.java b/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetControllerTest.java
index 1004255..b8f4fe7 100644
--- a/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetControllerTest.java
+++ b/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/PasswordAccessorySheetControllerTest.java
@@ -125,7 +125,7 @@
         final PropertyProvider<AccessorySheetData> testProvider = new PropertyProvider<>();
         final AccessorySheetData testData =
                 new AccessorySheetData(AccessoryTabType.PASSWORDS, "Passwords for this site");
-        testData.getUserInfoList().add(new UserInfo(null));
+        testData.getUserInfoList().add(new UserInfo("", null));
         testData.getUserInfoList().get(0).addField(
                 new UserInfoField("Name", "Name", "", false, null));
         testData.getUserInfoList().get(0).addField(
@@ -162,7 +162,7 @@
         assertThat(mSheetDataPieces.get(0).getDataPiece(), is(equalTo("No passwords for this")));
 
         // As soon UserInfo is available, discard the title.
-        testData.getUserInfoList().add(new UserInfo(null));
+        testData.getUserInfoList().add(new UserInfo("", null));
         testData.getUserInfoList().get(0).addField(
                 new UserInfoField("Name", "Name", "", false, null));
         testData.getUserInfoList().get(0).addField(
@@ -206,15 +206,15 @@
         assertThat(getSuggestionsImpressions(AccessoryTabType.ALL, 0), is(1));
 
         // If the tab is shown with X interactive item, record "X" samples.
-        UserInfo userInfo1 = new UserInfo(null);
+        UserInfo userInfo1 = new UserInfo("", null);
         userInfo1.addField(new UserInfoField("Interactive 1", "", "", false, (v) -> {}));
         userInfo1.addField(new UserInfoField("Non-Interactive 1", "", "", true, null));
         accessorySheetData.getUserInfoList().add(userInfo1);
-        UserInfo userInfo2 = new UserInfo(null);
+        UserInfo userInfo2 = new UserInfo("", null);
         userInfo2.addField(new UserInfoField("Interactive 2", "", "", false, (v) -> {}));
         userInfo2.addField(new UserInfoField("Non-Interactive 2", "", "", true, null));
         accessorySheetData.getUserInfoList().add(userInfo2);
-        UserInfo userInfo3 = new UserInfo(null);
+        UserInfo userInfo3 = new UserInfo("other.origin.eg", null);
         userInfo3.addField(new UserInfoField("Interactive 3", "", "", false, (v) -> {}));
         userInfo3.addField(new UserInfoField("Non-Interactive 3", "", "", true, null));
         accessorySheetData.getUserInfoList().add(userInfo3);
diff --git a/chrome/android/features/keyboard_accessory/public/java/src/org/chromium/chrome/browser/keyboard_accessory/data/KeyboardAccessoryData.java b/chrome/android/features/keyboard_accessory/public/java/src/org/chromium/chrome/browser/keyboard_accessory/data/KeyboardAccessoryData.java
index 8d76c5f..73c690ef 100644
--- a/chrome/android/features/keyboard_accessory/public/java/src/org/chromium/chrome/browser/keyboard_accessory/data/KeyboardAccessoryData.java
+++ b/chrome/android/features/keyboard_accessory/public/java/src/org/chromium/chrome/browser/keyboard_accessory/data/KeyboardAccessoryData.java
@@ -175,6 +175,7 @@
      * (username + password), to be shown on the manual fallback UI.
      */
     public final static class UserInfo {
+        private final String mTitle;
         private final List<UserInfoField> mFields = new ArrayList<>();
         private final @Nullable FaviconProvider mFaviconProvider;
 
@@ -190,7 +191,8 @@
             void fetchFavicon(@Px int desiredSize, Callback<Bitmap> favicon);
         }
 
-        public UserInfo(@Nullable FaviconProvider faviconProvider) {
+        public UserInfo(String title, @Nullable FaviconProvider faviconProvider) {
+            mTitle = title;
             mFaviconProvider = faviconProvider;
         }
 
@@ -203,13 +205,20 @@
         }
 
         /**
-         * Returns the list of fields in this group.
+         * @return A list of {@link UserInfoField}s in this group.
          */
         public List<UserInfoField> getFields() {
             return mFields;
         }
 
         /**
+         * @return A string to be used as title. May be empty but not null.
+         */
+        public String getTitle() {
+            return mTitle;
+        }
+
+        /**
          * Possibly holds a favicon provider.
          * @return A {@link FaviconProvider}. Optional.
          */
diff --git a/chrome/android/features/tab_ui/java/res/layout/grid_tab_switcher_layout.xml b/chrome/android/features/tab_ui/java/res/layout/grid_tab_switcher_layout.xml
deleted file mode 100644
index 9d21981..0000000
--- a/chrome/android/features/tab_ui/java/res/layout/grid_tab_switcher_layout.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2019 The Chromium Authors. All rights reserved.
-     Use of this source code is governed by a BSD-style license that can be
-     found in the LICENSE file. -->
-<org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/history_navigation"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-    <org.chromium.chrome.browser.tasks.tab_management.TabListRecyclerView
-        android:id="@+id/tab_list_view"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:clipToPadding="false"
-        android:paddingStart="8dp"
-        android:paddingEnd="8dp"
-        android:visibility="invisible"/>
-</org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout>
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherCoordinator.java
index 2310a08..45e53677 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherCoordinator.java
@@ -17,8 +17,6 @@
 import org.chromium.chrome.browser.compositor.CompositorViewHolder;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
-import org.chromium.chrome.browser.gesturenav.HistoryNavigationDelegate;
-import org.chromium.chrome.browser.gesturenav.HistoryNavigationLayout;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.lifecycle.Destroyable;
 import org.chromium.chrome.browser.snackbar.SnackbarManager;
@@ -118,14 +116,7 @@
                 tabModelSelector, mMultiThumbnailCardProvider, titleProvider, true,
                 mMediator::getCreateGroupButtonOnClickListener, gridCardOnClickListenerProvider,
                 null, null, null, compositorViewHolder,
-                compositorViewHolder.getDynamicResourceLoader(), true,
-                R.layout.grid_tab_switcher_layout, COMPONENT_NAME);
-
-        HistoryNavigationLayout navigation =
-                compositorViewHolder.findViewById(R.id.history_navigation);
-
-        navigation.setNavigationDelegate(HistoryNavigationDelegate.createForTabSwitcher(
-                context, backPress, tabModelSelector::getCurrentTab));
+                compositorViewHolder.getDynamicResourceLoader(), true, COMPONENT_NAME);
         mContainerViewChangeProcessor = PropertyModelChangeProcessor.create(containerViewModel,
                 mTabGridCoordinator.getContainerView(), TabGridContainerViewBinder::bind);
 
@@ -218,4 +209,12 @@
     void setBitmapCallbackForTesting(Callback<Bitmap> callback) {
         TabListMediator.ThumbnailFetcher.sBitmapCallbackForTesting = callback;
     }
+
+    /**
+     * @return The number of thumbnail fetching for testing.
+     */
+    @VisibleForTesting
+    int getBitmapFetchCountForTesting() {
+        return TabListMediator.ThumbnailFetcher.sFetchCountForTesting;
+    }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
index 09ee3e6..9035def 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
@@ -12,7 +12,6 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
-import org.chromium.chrome.tab_ui.R;
 import org.chromium.ui.modelutil.PropertyModel;
 
 import java.util.List;
@@ -47,7 +46,7 @@
         mTabListCoordinator = new TabListCoordinator(TabListCoordinator.TabListMode.GRID, context,
                 tabModelSelector, tabContentManager::getTabThumbnailWithCallback, null, false, null,
                 null, mMediator.getTabGridDialogHandler(), null, null, compositorViewHolder, null,
-                false, R.layout.tab_list_recycler_view_layout, COMPONENT_NAME);
+                false, COMPONENT_NAME);
 
         mParentLayout = new TabGridDialogParent(context, compositorViewHolder);
     }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetCoordinator.java
index 48bb0c8..fb18034 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetCoordinator.java
@@ -17,7 +17,6 @@
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupUtils;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
-import org.chromium.chrome.tab_ui.R;
 import org.chromium.ui.modelutil.PropertyModel;
 
 import java.util.List;
@@ -46,7 +45,7 @@
         mTabGridCoordinator = new TabListCoordinator(TabListCoordinator.TabListMode.GRID, context,
                 tabModelSelector, tabContentManager::getTabThumbnailWithCallback, null, false, null,
                 null, null, null, null, bottomSheetController.getBottomSheet(), null, false,
-                R.layout.tab_list_recycler_view_layout, COMPONENT_NAME);
+                COMPONENT_NAME);
 
         mMediator = new TabGridSheetMediator(mContext, bottomSheetController,
                 this::resetWithListOfTabs, mToolbarPropertyModel, tabModelSelector,
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
index 0074321..bcff5e2f 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
@@ -24,7 +24,6 @@
 import org.chromium.chrome.browser.tasks.tabgroup.TabGroupModelFilter;
 import org.chromium.chrome.browser.toolbar.bottom.BottomControlsCoordinator;
 import org.chromium.chrome.browser.util.FeatureUtilities;
-import org.chromium.chrome.tab_ui.R;
 import org.chromium.ui.modelutil.PropertyModel;
 
 import java.util.List;
@@ -78,8 +77,7 @@
 
         mTabStripCoordinator = new TabListCoordinator(TabListCoordinator.TabListMode.STRIP,
                 mContext, tabModelSelector, null, null, false, null, null, null, null, null,
-                mTabStripToolbarCoordinator.getTabListContainerView(), null, true,
-                R.layout.tab_list_recycler_view_layout, COMPONENT_NAME);
+                mTabStripToolbarCoordinator.getTabListContainerView(), null, true, COMPONENT_NAME);
 
         if (FeatureUtilities.isTabGroupsAndroidUiImprovementsEnabled()) {
             // TODO(yuezhanggg): find a way to enable interactions between grid tab switcher and the
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
index 3103aff..99c26b5 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
@@ -8,7 +8,6 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.support.annotation.IntDef;
-import android.support.annotation.LayoutRes;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v7.widget.GridLayoutManager;
@@ -80,7 +79,6 @@
      * @param dynamicResourceLoader The {@link DynamicResourceLoader} to register dynamic UI
      *                              resource for compositor layer animation.
      * @param attachToParent Whether the UI should attach to root view.
-     * @param layoutId ID of the layout resource.
      * @param componentName A unique string uses to identify different components for UMA recording.
      *                      Recommended to use the class name or make sure the string is unique
      *                      through actions.xml file.
@@ -95,7 +93,7 @@
             SimpleRecyclerViewMcpBase.ItemViewTypeCallback<PropertyModel> itemViewTypeCallback,
             TabListMediator.SelectionDelegateProvider selectionDelegateProvider,
             @NonNull ViewGroup parentView, @Nullable DynamicResourceLoader dynamicResourceLoader,
-            boolean attachToParent, @LayoutRes int layoutId, String componentName) {
+            boolean attachToParent, String componentName) {
         TabListModel tabListModel = new TabListModel();
         mMode = mode;
         mTabModelSelector = tabModelSelector;
@@ -127,11 +125,13 @@
             throw new IllegalArgumentException(
                     "Attempting to create a tab list UI with invalid mode");
         }
+
         if (!attachToParent) {
             mRecyclerView = (TabListRecyclerView) LayoutInflater.from(context).inflate(
-                    layoutId, parentView, false);
+                    R.layout.tab_list_recycler_view_layout, parentView, false);
         } else {
-            LayoutInflater.from(context).inflate(layoutId, parentView, true);
+            LayoutInflater.from(context).inflate(
+                    R.layout.tab_list_recycler_view_layout, parentView, true);
             mRecyclerView = parentView.findViewById(R.id.tab_list_view);
         }
 
@@ -230,6 +230,7 @@
 
     void postHiding() {
         mRecyclerView.postHiding();
+        mMediator.postHiding();
     }
 
     /**
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
index b081112..f5007cc 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
@@ -29,6 +29,7 @@
 import org.chromium.chrome.browser.tab.TabObserver;
 import org.chromium.chrome.browser.tabmodel.EmptyTabModelFilter;
 import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver;
+import org.chromium.chrome.browser.tabmodel.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabList;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelObserver;
@@ -56,6 +57,7 @@
  * TODO(yusufo): Move some of the logic here to a parent component to make the above true.
  */
 class TabListMediator {
+    private boolean mVisible;
     private boolean mShownIPH;
 
     /**
@@ -100,6 +102,7 @@
      */
     static class ThumbnailFetcher {
         static Callback<Bitmap> sBitmapCallbackForTesting;
+        static int sFetchCountForTesting;
         private ThumbnailProvider mThumbnailProvider;
         private Tab mTab;
         private boolean mForceUpdate;
@@ -118,6 +121,7 @@
                 if (sBitmapCallbackForTesting != null) sBitmapCallbackForTesting.onResult(bitmap);
                 callback.onResult(bitmap);
             };
+            sFetchCountForTesting++;
             mThumbnailProvider.getTabThumbnailWithCallback(
                     mTab, forking, mForceUpdate, mWriteToCache);
         }
@@ -393,7 +397,8 @@
             }
 
             @Override
-            public void didAddTab(Tab tab, int type) {
+            public void didAddTab(Tab tab, @TabLaunchType int type) {
+                if (type == TabLaunchType.FROM_RESTORE) return;
                 onTabAdded(tab, !mActionsOnAllRelatedTabs);
             }
 
@@ -656,6 +661,7 @@
      * The selected border should re-appear in the final fading-in stage.
      */
     void prepareOverview() {
+        assert mVisible;
         int count = 0;
         for (int i = 0; i < mModel.size(); i++) {
             if (mModel.get(i).get(TabProperties.IS_SELECTED)) count++;
@@ -684,6 +690,7 @@
      * @return Whether the {@link TabListRecyclerView} can be shown quickly.
      */
     boolean resetWithListOfTabs(@Nullable List<Tab> tabs, boolean quickMode) {
+        mVisible = tabs != null;
         if (areTabsUnchanged(tabs)) {
             if (tabs == null) return true;
 
@@ -708,6 +715,10 @@
         return false;
     }
 
+    void postHiding() {
+        mVisible = false;
+    }
+
     private boolean isSelectedTab(int tabId, int tabModelSelectedTabId) {
         SelectionDelegate<Integer> selectionDelegate = getTabSelectionDelegate();
         if (selectionDelegate == null) {
@@ -721,6 +732,7 @@
      * @see GridTabSwitcherMediator.ResetHandler#softCleanup
      */
     void softCleanup() {
+        assert !mVisible;
         for (int i = 0; i < mModel.size(); i++) {
             mModel.get(i).set(TabProperties.THUMBNAIL_FETCHER, null);
         }
@@ -757,7 +769,7 @@
         mTabListFaviconProvider.getFaviconForUrlAsync(
                 tab.getUrl(), tab.isIncognito(), faviconCallback);
         boolean forceUpdate = isSelected && !quickMode;
-        if (mThumbnailProvider != null
+        if (mThumbnailProvider != null && mVisible
                 && (mModel.get(index).get(TabProperties.THUMBNAIL_FETCHER) == null || forceUpdate
                         || isUpdatingId)) {
             ThumbnailFetcher callback = new ThumbnailFetcher(mThumbnailProvider, tab, forceUpdate,
@@ -870,7 +882,7 @@
         mTabListFaviconProvider.getFaviconForUrlAsync(
                 tab.getUrl(), tab.isIncognito(), faviconCallback);
 
-        if (mThumbnailProvider != null) {
+        if (mThumbnailProvider != null && mVisible) {
             ThumbnailFetcher callback = new ThumbnailFetcher(mThumbnailProvider, tab, isSelected,
                     isSelected
                             && !ChromeFeatureList.isEnabled(
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorCoordinator.java
index 7540ccb..12a7963b 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSelectionEditorCoordinator.java
@@ -59,7 +59,7 @@
         mTabListCoordinator = new TabListCoordinator(TabListCoordinator.TabListMode.GRID, context,
                 mTabModelSelector, tabContentManager::getTabThumbnailWithCallback, null, false,
                 null, null, null, this::getItemViewType, this::getSelectionDelegate, null, null,
-                false, R.layout.tab_list_recycler_view_layout, COMPONENT_NAME);
+                false, COMPONENT_NAME);
 
         mTabSelectionEditorLayout = LayoutInflater.from(context)
                 .inflate(R.layout.tab_selection_editor_layout, null)
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherLayoutTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherLayoutTest.java
index b92c130..de7b05d 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherLayoutTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/GridTabSwitcherLayoutTest.java
@@ -43,6 +43,7 @@
 import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.util.ApplicationTestUtils;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.chrome.test.util.MenuUtils;
 import org.chromium.chrome.test.util.browser.Features;
@@ -97,6 +98,7 @@
         GridTabSwitcherCoordinator coordinator =
                 (GridTabSwitcherCoordinator) mGtsLayout.getGridTabSwitcherForTesting();
         coordinator.setBitmapCallbackForTesting(mBitmapListener);
+        Assert.assertEquals(0, coordinator.getBitmapFetchCountForTesting());
 
         mActivityTestRule.getActivity().getTabContentManager().setCaptureMinRequestTimeForTesting(
                 0);
@@ -325,6 +327,98 @@
         assertThumbnailsAreReleased();
     }
 
+    @Test
+    @MediumTest
+    @CommandLineFlags.
+    Add({"force-fieldtrial-params=Study.Group:soft-cleanup-delay/0/cleanup-delay/0"})
+    public void testRestoredTabsDontFetch() throws Exception {
+        prepareTabs(2, mUrl);
+        GridTabSwitcherCoordinator coordinator =
+                (GridTabSwitcherCoordinator) mGtsLayout.getGridTabSwitcherForTesting();
+        int oldCount = coordinator.getBitmapFetchCountForTesting();
+
+        // Restart Chrome.
+        // Although we're destroying the activity, the Application will still live on since its in
+        // the same process as this test.
+        ApplicationTestUtils.finishActivity(mActivityTestRule.getActivity());
+        mActivityTestRule.startMainActivityOnBlankPage();
+        Assert.assertEquals(3, mActivityTestRule.tabsCount(false));
+
+        Layout layout = mActivityTestRule.getActivity().getLayoutManager().getOverviewLayout();
+        assertTrue(layout instanceof GridTabSwitcherLayout);
+        mGtsLayout = (GridTabSwitcherLayout) layout;
+        coordinator = (GridTabSwitcherCoordinator) mGtsLayout.getGridTabSwitcherForTesting();
+        Assert.assertEquals(0, coordinator.getBitmapFetchCountForTesting() - oldCount);
+    }
+
+    @Test
+    @MediumTest
+    @CommandLineFlags.
+    Add({"force-fieldtrial-params=Study.Group:soft-cleanup-delay/0/cleanup-delay/0"})
+    public void testInvisibleTabsDontFetch() throws InterruptedException {
+        // Open a few new tabs.
+        final int count = mAllBitmaps.size();
+        for (int i = 0; i < 3; i++) {
+            MenuUtils.invokeCustomMenuActionSync(InstrumentationRegistry.getInstrumentation(),
+                    mActivityTestRule.getActivity(), org.chromium.chrome.R.id.new_tab_menu_id);
+        }
+        // Fetching might not happen instantly.
+        Thread.sleep(1000);
+
+        // No fetching should happen.
+        Assert.assertEquals(0, mAllBitmaps.size() - count);
+    }
+
+    @Test
+    @MediumTest
+    @CommandLineFlags.
+    Add({"force-fieldtrial-params=Study.Group:soft-cleanup-delay/10000/cleanup-delay/10000"})
+    public void testInvisibleTabsDontFetchWarm() throws InterruptedException {
+        // Get the GTS in the warm state.
+        prepareTabs(2, NTP_URL);
+        mRepeat = 2;
+        testTabToGrid(NTP_URL);
+
+        Thread.sleep(1000);
+
+        // Open a few new tabs.
+        final int count = mAllBitmaps.size();
+        for (int i = 0; i < 3; i++) {
+            MenuUtils.invokeCustomMenuActionSync(InstrumentationRegistry.getInstrumentation(),
+                    mActivityTestRule.getActivity(), org.chromium.chrome.R.id.new_tab_menu_id);
+        }
+        // Fetching might not happen instantly.
+        Thread.sleep(1000);
+
+        // No fetching should happen.
+        Assert.assertEquals(0, mAllBitmaps.size() - count);
+    }
+
+    @Test
+    @MediumTest
+    @CommandLineFlags.
+    Add({"force-fieldtrial-params=Study.Group:soft-cleanup-delay/0/cleanup-delay/10000"})
+    public void testInvisibleTabsDontFetchSoft() throws InterruptedException {
+        // Get the GTS in the soft cleaned up state.
+        prepareTabs(2, NTP_URL);
+        mRepeat = 2;
+        testTabToGrid(NTP_URL);
+
+        Thread.sleep(1000);
+
+        // Open a few new tabs.
+        final int count = mAllBitmaps.size();
+        for (int i = 0; i < 3; i++) {
+            MenuUtils.invokeCustomMenuActionSync(InstrumentationRegistry.getInstrumentation(),
+                    mActivityTestRule.getActivity(), org.chromium.chrome.R.id.new_tab_menu_id);
+        }
+        // Fetching might not happen instantly.
+        Thread.sleep(1000);
+
+        // No fetching should happen.
+        Assert.assertEquals(0, mAllBitmaps.size() - count);
+    }
+
     private void enterGTS() throws InterruptedException {
         Tab currentTab = mActivityTestRule.getActivity().getTabModelSelector().getCurrentTab();
         // Native tabs need to be invalidated first to trigger thumbnail taking, so skip them.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialog.java
index 47e66a8..572c3126 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialog.java
@@ -24,6 +24,8 @@
 
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JCaller;
+import org.chromium.base.annotations.NativeMethods;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.omnibox.OmniboxUrlEmphasizer;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -246,7 +248,8 @@
     // Called to report the permission dialog's results back to native code.
     private void finishDialog(int resultCode) {
         if (mNativeBluetoothScanningPermissionDialogPtr == 0) return;
-        nativeOnDialogFinished(mNativeBluetoothScanningPermissionDialogPtr, resultCode);
+        Natives jni = BluetoothScanningPermissionDialogJni.get();
+        jni.onDialogFinished(this, mNativeBluetoothScanningPermissionDialogPtr, resultCode);
     }
 
     /**
@@ -265,6 +268,9 @@
         return mItemAdapter;
     }
 
-    @VisibleForTesting
-    native void nativeOnDialogFinished(long nativeBluetoothScanningPromptAndroid, int eventType);
+    @NativeMethods
+    interface Natives {
+        void onDialogFinished(@JCaller BluetoothScanningPermissionDialog self,
+                long nativeBluetoothScanningPromptAndroid, int eventType);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java
index 27aa683..cc87225 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/StackLayoutBase.java
@@ -12,7 +12,6 @@
 import android.os.SystemClock;
 import android.support.annotation.IntDef;
 import android.util.Pair;
-import android.view.MotionEvent;
 import android.view.ViewGroup;
 import android.view.ViewGroup.LayoutParams;
 import android.widget.FrameLayout;
@@ -41,9 +40,6 @@
 import org.chromium.chrome.browser.compositor.scene_layer.SceneLayer;
 import org.chromium.chrome.browser.compositor.scene_layer.TabListSceneLayer;
 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
-import org.chromium.chrome.browser.gesturenav.NavigationGlowFactory;
-import org.chromium.chrome.browser.gesturenav.NavigationHandler;
-import org.chromium.chrome.browser.gesturenav.TabSwitcherActionDelegate;
 import org.chromium.chrome.browser.partnercustomizations.HomepageManager;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabList;
@@ -230,10 +226,8 @@
 
     private final GestureEventFilter mGestureEventFilter;
     private final TabListSceneLayer mSceneLayer;
-    private final boolean mNavigationEnabled;
 
     private StackLayoutGestureHandler mGestureHandler;
-    private NavigationHandler mNavigationHandler;
 
     private final ArrayList<Pair<CompositorAnimator, FloatProperty>> mLayoutAnimations =
             new ArrayList<>();
@@ -248,7 +242,6 @@
             mLastOnDownTimeStamp = time;
 
             if (shouldIgnoreTouchInput()) return;
-            if (mNavigationHandler != null) mNavigationHandler.onDown();
             mStacks.get(getTabStackIndex()).onDown(time);
         }
 
@@ -261,15 +254,6 @@
         public void drag(float x, float y, float dx, float dy, float tx, float ty) {
             if (shouldIgnoreTouchInput()) return;
 
-            if (mNavigationHandler != null) {
-                mNavigationHandler.onScroll(mLastOnDownX * mDpToPx, -dx * mDpToPx, -dy * mDpToPx,
-                        x * mDpToPx, y * mDpToPx);
-                if (mNavigationHandler.isActive()) {
-                    cancelDragTabs(time());
-                    return;
-                }
-            }
-
             @SwipeMode
             int oldInputMode = mInputMode;
             long time = time();
@@ -353,10 +337,6 @@
 
         private void onUpOrCancel(long time) {
             if (shouldIgnoreTouchInput()) return;
-
-            if (mNavigationHandler != null && mNavigationHandler.isActive()) {
-                mNavigationHandler.onTouchEvent(MotionEvent.ACTION_UP);
-            }
             cancelDragTabs(time);
         }
 
@@ -405,8 +385,6 @@
         mStackRects = new ArrayList<RectF>();
         mViewContainer = new FrameLayout(getContext());
         mSceneLayer = new TabListSceneLayer();
-        mNavigationEnabled =
-                ChromeFeatureList.isEnabled(ChromeFeatureList.OVERSCROLL_HISTORY_NAVIGATION);
         mDpToPx = context.getResources().getDisplayMetrics().density;
     }
 
@@ -520,16 +498,6 @@
                 onTabClosureCancelled(LayoutManager.time(), tab.getId(), tab.isIncognito());
             }
         };
-        if (mNavigationEnabled && mNavigationHandler == null) {
-            Tab currentTab = mTabModelSelector.getCurrentTab();
-            if (currentTab != null) {
-                mNavigationHandler = new NavigationHandler(mViewContainer,
-                        new TabSwitcherActionDelegate(currentTab.getActivity()::onBackPressed,
-                                mTabModelSelector::getCurrentTab),
-                        NavigationGlowFactory.forSceneLayer(mViewContainer, mSceneLayer,
-                                currentTab.getActivity().getWindowAndroid()));
-            }
-        }
     }
 
     /**
@@ -1677,7 +1645,6 @@
 
     @Override
     public void destroy() {
-        if (mNavigationHandler != null) mNavigationHandler.destroy();
         super.destroy();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CategoryCardViewHolderFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CategoryCardViewHolderFactory.java
index 4e79013..c257319 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CategoryCardViewHolderFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CategoryCardViewHolderFactory.java
@@ -17,7 +17,7 @@
         CategoryCardViewHolderFactory.CategoryCardViewHolder> {
     private int mTileViewResource;
 
-    CategoryCardViewHolderFactory() {
+    public CategoryCardViewHolderFactory() {
         final int exploreSitesDenseVariation = ExploreSitesBridge.getDenseVariation();
         // Set the tile view to use based on the condensed variation.
         if (ExploreSitesBridge.isDense(exploreSitesDenseVariation)) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/CompositorNavigationGlow.java b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/CompositorNavigationGlow.java
index a0bc146c..5cc8166 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/CompositorNavigationGlow.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/CompositorNavigationGlow.java
@@ -7,9 +7,7 @@
 import android.view.ViewGroup;
 
 import org.chromium.base.annotations.JNINamespace;
-import org.chromium.chrome.browser.compositor.scene_layer.SceneLayer;
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.ui.base.WindowAndroid;
 
 /**
  * Implements navigation glow using compositor layer for tab switcher or rendered web contents.
@@ -25,36 +23,10 @@
      *        for rendering glow effect.
      * @return NavigationGlow object for rendered pages
      */
-    public static NavigationGlow forWebContents(ViewGroup parentView, WebContents webContents) {
-        CompositorNavigationGlow glow = new CompositorNavigationGlow(parentView);
-        glow.initWithWebContents(webContents);
-        return glow;
-    }
-
-    /**
-     * @pararm parentView Parent view where the glow view gets attached to.
-     * @pararm sceneLayer SceneLayer whose cc layer will be used for rendering glow effect.
-     * @pararm window WindowAndroid object to get WindowAndroidCompositor from for animation effect.
-     * @return {@link NavigationGlow} object for the screen rendered with {@link SceneLayer}
-     */
-    public static NavigationGlow forSceneLayer(
-            ViewGroup parentView, SceneLayer sceneLayer, WindowAndroid window) {
-        CompositorNavigationGlow glow = new CompositorNavigationGlow(parentView);
-        glow.initWithSceneLayer(sceneLayer, window);
-        return glow;
-    }
-
-    private CompositorNavigationGlow(ViewGroup parentView) {
+    public CompositorNavigationGlow(ViewGroup parentView, WebContents webContents) {
         super(parentView);
-        mNativeNavigationGlow = nativeInit(parentView.getResources().getDisplayMetrics().density);
-    }
-
-    private void initWithSceneLayer(SceneLayer sceneLayer, WindowAndroid window) {
-        nativeInitWithSceneLayer(mNativeNavigationGlow, sceneLayer, window);
-    }
-
-    private void initWithWebContents(WebContents webContents) {
-        nativeInitWithWebContents(mNativeNavigationGlow, webContents);
+        mNativeNavigationGlow =
+                nativeInit(parentView.getResources().getDisplayMetrics().density, webContents);
     }
 
     @Override
@@ -91,11 +63,7 @@
         mNativeNavigationGlow = 0;
     }
 
-    private native long nativeInit(float dipScale);
-    private native void nativeInitWithSceneLayer(
-            long nativeNavigationGlow, SceneLayer sceneLayer, WindowAndroid window);
-    private native void nativeInitWithWebContents(
-            long nativeNavigationGlow, WebContents webContents);
+    private native long nativeInit(float dipScale, WebContents webContents);
     private native void nativePrepare(
             long nativeNavigationGlow, float startX, float startY, int width, int height);
     private native void nativeOnOverscroll(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationDelegate.java
index 4ed99c2b..9632dfeb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationDelegate.java
@@ -6,7 +6,6 @@
 
 import android.content.Context;
 
-import org.chromium.base.Supplier;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.tab.Tab;
@@ -65,31 +64,4 @@
     public static HistoryNavigationDelegate createForNativePage(Tab tab) {
         return new NativePageDelegate(tab);
     }
-
-    // Implementation for tab switcher. Can't go forward, and going back exits
-    // the switcher. Can exit Chrome if there's no current tab to go back to.
-    private static class TabSwitcherNavigationDelegate extends HistoryNavigationDelegate {
-        private final Runnable mBackPress;
-        private final Supplier<Tab> mCurrentTab;
-
-        private TabSwitcherNavigationDelegate(
-                Context context, Runnable backPress, Supplier<Tab> currentTab) {
-            super(context);
-            mBackPress = backPress;
-            mCurrentTab = currentTab;
-        }
-
-        @Override
-        public NavigationHandler.ActionDelegate createActionDelegate() {
-            return new TabSwitcherActionDelegate(mBackPress, mCurrentTab);
-        }
-    }
-
-    /**
-     * Creates {@link HistoryNavigationDelegate} for tab switcher.
-     */
-    public static HistoryNavigationDelegate createForTabSwitcher(
-            Context context, Runnable backPress, Supplier<Tab> currentTab) {
-        return new TabSwitcherNavigationDelegate(context, backPress, currentTab);
-    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationGlowFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationGlowFactory.java
index d0bb5ec..c31a6e0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationGlowFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationGlowFactory.java
@@ -7,9 +7,7 @@
 import android.view.ViewGroup;
 
 import org.chromium.base.Supplier;
-import org.chromium.chrome.browser.compositor.scene_layer.SceneLayer;
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.ui.base.WindowAndroid;
 
 /**
  * Factory class that provides {@link NavigationGlow} according to the actual surface
@@ -18,25 +16,13 @@
 public class NavigationGlowFactory {
     /**
      * @pararm parentView Parent view where the glow view gets attached to.
-     * @pararm sceneLayer SceneLayer whose cc layer will be used for rendering glow effect.
-     * @pararm window WindowAndroid object to get WindowAndroidCompositor from for animation effect.
-     * @return Supplier for {@link NavigationGlow} object for the screen rendered with {@link
-     *         SceneLayer}.
-     */
-    public static Supplier<NavigationGlow> forSceneLayer(
-            ViewGroup parentView, SceneLayer sceneLayer, WindowAndroid window) {
-        return () -> CompositorNavigationGlow.forSceneLayer(parentView, sceneLayer, window);
-    }
-
-    /**
-     * @pararm parentView Parent view where the glow view gets attached to.
      * @pararm webContents WebContents whose native view's cc layer will be used
      *        for rendering glow effect.
      * @return Supplier for {@link NavigationGlow} object for rendered pages.
      */
     public static Supplier<NavigationGlow> forRenderedPage(
             ViewGroup parentView, WebContents webContents) {
-        return () -> CompositorNavigationGlow.forWebContents(parentView, webContents);
+        return () -> new CompositorNavigationGlow(parentView, webContents);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/TabSwitcherActionDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/TabSwitcherActionDelegate.java
deleted file mode 100644
index a8122e7..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/TabSwitcherActionDelegate.java
+++ /dev/null
@@ -1,39 +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.
-
-package org.chromium.chrome.browser.gesturenav;
-
-import org.chromium.base.Supplier;
-import org.chromium.chrome.browser.tab.Tab;
-
-/**
- * Implementation of {@link NavigationHandler#ActionDelegate} that works for Tab switcher.
- * Swipe from left exits the tab switcher and goes back to the current tab. Can exit
- * Chrome app itself if there's no current tab.
- */
-public class TabSwitcherActionDelegate implements NavigationHandler.ActionDelegate {
-    private final Supplier<Tab> mCurrentTab;
-    private final Runnable mBackPress;
-
-    public TabSwitcherActionDelegate(Runnable backPress, Supplier<Tab> currentTab) {
-        mBackPress = backPress;
-        mCurrentTab = currentTab;
-    }
-
-    @Override
-    public boolean canNavigate(boolean forward) {
-        return !forward;
-    }
-
-    @Override
-    public void navigate(boolean forward) {
-        assert !forward : "Should be called only for back navigation";
-        mBackPress.run();
-    }
-
-    @Override
-    public boolean willBackExitApp() {
-        return mCurrentTab.get() == null;
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelFilter.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelFilter.java
index cfacc71..bee34dc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelFilter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelFilter.java
@@ -171,7 +171,7 @@
     }
 
     @Override
-    public void didAddTab(Tab tab, int type) {
+    public void didAddTab(Tab tab, @TabLaunchType int type) {
         addTab(tab);
         for (TabModelObserver observer : mFilteredObservers) {
             observer.didAddTab(tab, type);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
index 6c5e472..0377dc93 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
@@ -1810,7 +1810,7 @@
 
         if (inTabSwitcherMode) {
             if (mUrlFocusLayoutAnimator != null && mUrlFocusLayoutAnimator.isRunning()) {
-                mUrlFocusLayoutAnimator.cancel();
+                mUrlFocusLayoutAnimator.end();
                 mUrlFocusLayoutAnimator = null;
                 // After finishing the animation, force a re-layout of the location bar,
                 // so that the final translation position is correct (since onMeasure updates
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialogTest.java
index a5a9cf8e..5d431fc 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/BluetoothScanningPermissionDialogTest.java
@@ -16,7 +16,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.annotations.JCaller;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.JniMocker;
 import org.chromium.chrome.R;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@@ -25,7 +27,6 @@
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.base.ActivityWindowAndroid;
-import org.chromium.ui.base.WindowAndroid;
 
 /**
  * Tests for the BluetoothScanningPermissionDialog class.
@@ -33,45 +34,41 @@
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class BluetoothScanningPermissionDialogTest {
-    /**
-     * Works like the BluetoothScanningPermissionDialog class, but records calls to native methods
-     * instead of calling back to C++.
-     */
-    static class BluetoothScanningPermissionDialogWithFakeNatives
-            extends BluetoothScanningPermissionDialog {
-        int mFinishedEventType = -1;
-
-        BluetoothScanningPermissionDialogWithFakeNatives(
-                WindowAndroid windowAndroid, String origin, int securityLevel) {
-            super(windowAndroid, origin, securityLevel,
-                    /*nativeBluetoothScanningPermissionDialogPtr=*/42);
-        }
-
-        @Override
-        void nativeOnDialogFinished(long nativeBluetoothScanningPromptAndroid, int eventType) {
-            mFinishedEventType = eventType;
-        }
-    }
-
-    private ActivityWindowAndroid mWindowAndroid;
-    private BluetoothScanningPermissionDialogWithFakeNatives mPermissionDialog;
-
     @Rule
     public ChromeActivityTestRule<ChromeActivity> mActivityTestRule =
             new ChromeActivityTestRule<>(ChromeActivity.class);
 
+    @Rule
+    public JniMocker mocker = new JniMocker();
+
+    private int mFinishedEventType = -1;
+
+    private ActivityWindowAndroid mWindowAndroid;
+    private BluetoothScanningPermissionDialog mPermissionDialog;
+
+    private class TestBluetoothScanningPermissionDialogJni
+            implements BluetoothScanningPermissionDialog.Natives {
+        @Override
+        public void onDialogFinished(@JCaller BluetoothScanningPermissionDialog self,
+                long nativeBluetoothScanningPromptAndroid, int eventType) {
+            mFinishedEventType = eventType;
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
+        mocker.mock(BluetoothScanningPermissionDialogJni.TEST_HOOKS,
+                new TestBluetoothScanningPermissionDialogJni());
         mActivityTestRule.startMainActivityOnBlankPage();
         mPermissionDialog = createDialog();
     }
 
-    private BluetoothScanningPermissionDialogWithFakeNatives createDialog() {
+    private BluetoothScanningPermissionDialog createDialog() {
         return TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
             mWindowAndroid = new ActivityWindowAndroid(mActivityTestRule.getActivity());
-            BluetoothScanningPermissionDialogWithFakeNatives dialog =
-                    new BluetoothScanningPermissionDialogWithFakeNatives(mWindowAndroid,
-                            "https://origin.example.com/", ConnectionSecurityLevel.SECURE);
+            BluetoothScanningPermissionDialog dialog = new BluetoothScanningPermissionDialog(
+                    mWindowAndroid, "https://origin.example.com/", ConnectionSecurityLevel.SECURE,
+                    /*nativeBluetoothScanningPermissionDialogPtr=*/42);
             return dialog;
         });
     }
@@ -123,7 +120,7 @@
 
         dialog.cancel();
 
-        CriteriaHelper.pollUiThread(Criteria.equals(BluetoothScanningPermissionEvent.CANCELED,
-                () -> mPermissionDialog.mFinishedEventType));
+        CriteriaHelper.pollUiThread(Criteria.equals(
+                BluetoothScanningPermissionEvent.CANCELED, () -> mFinishedEventType));
     }
 }
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index 524966f..a295d15 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-77.0.3847.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
+chromeos-chrome-amd64-77.0.3849.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index 60b8e1f..29009266 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -2857,6 +2857,12 @@
   <message name="IDS_NETWORK_UI_NO_CELLULAR_ERROR_TEXT" desc="Text displayed when the cellular activation UI cannot be opened because no cellular network exists.">
     No cellular network exists
   </message>
+  <message name="IDS_NETWORK_UI_ADD_NEW_WIFI_LABEL" desc="Label for section dealing with showing the 'Add new Wi-Fi network' dialog.">
+    New Wi-Fi Network Dialog
+  </message>
+  <message name="IDS_NETWORK_UI_ADD_NEW_WIFI_BUTTON_TEXT" desc="Text for button which, when pressed, opens the 'Add new Wi-Fi network' dialog.">
+    Show 'Add new Wi-Fi' dialog
+  </message>
 
   <message name="IDS_DEVICE_LOG_LINK_TEXT" desc="Message preceeding link to chrome://device-log">
     For network logs, see: <ph name="DEVICE_LOG_LINK">&lt;a href="chrome://device-log"&gt;chrome://device-log&lt;/a&gt;</ph>
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 00f4bd3..acc07aaa 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -9506,14 +9506,29 @@
     <message name="IDS_NATIVE_FILE_SYSTEM_DIRECTORY_USAGE_TOOLTIP" desc="Tooltip for native file system omnibox usage indicator.">
       This page is allowed to read a folder on your device.
     </message>
-    <message name="IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_FILES_TEXT" desc="Text of the bubble showing what files and directories an origin can currently access">
+    <message name="IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_FILES_TEXT" desc="Text of the bubble showing what files an origin can currently write to">
       <ph name="ORIGIN">$1<ex>example.com</ex></ph> can save your changes directly to the following files. This site can save changes only while this tab is open.
     </message>
-    <message name="IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_DIRECTORIES_TEXT" desc="Text of the bubble showing what files and directories an origin can currently access">
-      <ph name="ORIGIN">$1<ex>example.com</ex></ph> can save your changes directly to the files in the following folder. This site can save changes only while this tab is open.
+    <message name="IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_FILES_NO_LIFETIME_TEXT" desc="Text of the bubble showing what files an origin can currently write to">
+      <ph name="ORIGIN">$1<ex>example.com</ex></ph> can save your changes directly to the following files.
+    </message>
+    <message name="IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_DIRECTORIES_TEXT" desc="Text of the bubble showing what directories an origin can currently write to">
+      <ph name="ORIGIN">$1<ex>example.com</ex></ph> can save your changes directly to the files in the following folders. This site can save changes only while this tab is open.
+    </message>
+    <message name="IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_DIRECTORIES_NO_LIFETIME_TEXT" desc="Text of the bubble showing what directories an origin can currently write to">
+      <ph name="ORIGIN">$1<ex>example.com</ex></ph> can save your changes directly to the files in the following folders.
+    </message>
+    <message name="IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_FILES_AND_DIRECTORIES_TEXT" desc="Text of the bubble showing what files and directories an origin can currently write to">
+      <ph name="ORIGIN">$1<ex>example.com</ex></ph> can save your changes directly to the following files and folders.
+    </message>
+    <message name="IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_LIFETIME_TEXT" desc="Text explaining lifetimes of grants in the bubble showing what files and directories an origin can currently write to">
+      This site can save and read changes only while the tab is open.
     </message>
     <message name="IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_READABLE_DIRECTORIES_TEXT" desc="Text of the bubble showing what files and directories an origin can currently access">
-      <ph name="ORIGIN">$1<ex>example.com</ex></ph> can read all the files in the following folder. This site can see changes to the folder only while this tab is open.
+      <ph name="ORIGIN">$1<ex>example.com</ex></ph> can read all the files in the following folders. This site can see changes to the folder only while this tab is open.
+    </message>
+    <message name="IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_ALSO_READABLE_DIRECTORIES_TEXT" desc="Text of the bubble showing what files and directories an origin can currently access">
+      This site can also read all the files in the following folders.
     </message>
     <message name="IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_FILES_TEXT" desc="Text to display a list of files in the native file system usage bubble">
       {0, plural,
diff --git a/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_ALSO_READABLE_DIRECTORIES_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_ALSO_READABLE_DIRECTORIES_TEXT.png.sha1
new file mode 100644
index 0000000..27a22cd
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_ALSO_READABLE_DIRECTORIES_TEXT.png.sha1
@@ -0,0 +1 @@
+8281f3e983197754ef1f84d5cd74c19cfd384a1f
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_LIFETIME_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_LIFETIME_TEXT.png.sha1
new file mode 100644
index 0000000..27a22cd
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_LIFETIME_TEXT.png.sha1
@@ -0,0 +1 @@
+8281f3e983197754ef1f84d5cd74c19cfd384a1f
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_DIRECTORIES_NO_LIFETIME_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_DIRECTORIES_NO_LIFETIME_TEXT.png.sha1
new file mode 100644
index 0000000..11800eb
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_DIRECTORIES_NO_LIFETIME_TEXT.png.sha1
@@ -0,0 +1 @@
+18d719856329a6912245907a086caf534dd8b8e4
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_FILES_AND_DIRECTORIES_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_FILES_AND_DIRECTORIES_TEXT.png.sha1
new file mode 100644
index 0000000..27a22cd
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_FILES_AND_DIRECTORIES_TEXT.png.sha1
@@ -0,0 +1 @@
+8281f3e983197754ef1f84d5cd74c19cfd384a1f
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_FILES_NO_LIFETIME_TEXT.png.sha1 b/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_FILES_NO_LIFETIME_TEXT.png.sha1
new file mode 100644
index 0000000..736c6db
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_FILES_NO_LIFETIME_TEXT.png.sha1
@@ -0,0 +1 @@
+270b20d3901258d1767a7d8fe2ebd8c71e96aabe
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 41473502..f4236da 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -806,6 +806,10 @@
     "metrics/ukm_background_recorder_service.h",
     "metrics/variations/chrome_variations_service_client.cc",
     "metrics/variations/chrome_variations_service_client.h",
+    "native_file_system/chrome_native_file_system_permission_context.cc",
+    "native_file_system/chrome_native_file_system_permission_context.h",
+    "native_file_system/native_file_system_permission_context_factory.cc",
+    "native_file_system/native_file_system_permission_context_factory.h",
     "native_window_notification_source.h",
     "navigation_predictor/navigation_predictor.cc",
     "navigation_predictor/navigation_predictor.h",
@@ -3245,8 +3249,6 @@
       "search/chrome_colors/chrome_colors_factory.h",
       "search/chrome_colors/chrome_colors_service.cc",
       "search/chrome_colors/chrome_colors_service.h",
-      "search/iframe_source.cc",
-      "search/iframe_source.h",
       "search/instant_service.cc",
       "search/instant_service.h",
       "search/instant_service_factory.cc",
@@ -5004,13 +5006,6 @@
     ]
   }
 
-  if (!is_chrome_branded && !is_android) {
-    sources += [
-      "search/local_files_ntp_source.cc",
-      "search/local_files_ntp_source.h",
-    ]
-  }
-
   if (use_cups) {
     configs += [ "//printing:cups" ]
   }
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 2b2f9eb2..f0f1b94f 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -4064,6 +4064,12 @@
      FEATURE_VALUE_TYPE(features::kAnimatedAvatarButton)},
 #endif  // defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX)
 
+#if defined(OS_CHROMEOS)
+    {"crostini-webui-installer", flag_descriptions::kCrostiniWebUIInstallerName,
+     flag_descriptions::kCrostiniWebUIInstallerDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(chromeos::features::kCrostiniWebUIInstaller)},
+#endif  // OS_CHROMEOS
+
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
     // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
     // Histograms" in tools/metrics/histograms/README.md (run the
diff --git a/chrome/browser/android/compositor/navigation_glow.cc b/chrome/browser/android/compositor/navigation_glow.cc
index c12b2c6..6174092 100644
--- a/chrome/browser/android/compositor/navigation_glow.cc
+++ b/chrome/browser/android/compositor/navigation_glow.cc
@@ -30,27 +30,10 @@
 
 namespace android {
 
-NavigationGlow::NavigationGlow(float dip_scale)
+NavigationGlow::NavigationGlow(float dip_scale,
+                               content::WebContents* web_contents)
     : dip_scale_(dip_scale),
-      glow_effect_(std::make_unique<ui::OverscrollGlow>(this)) {}
-
-NavigationGlow::~NavigationGlow() = default;
-
-void NavigationGlow::InitWithSceneLayer(
-    JNIEnv* env,
-    const JavaParamRef<jobject>& obj,
-    const JavaParamRef<jobject>& jscene_layer,
-    const JavaParamRef<jobject>& jwindow_android) {
-  layer_ = SceneLayer::FromJavaObject(env, jscene_layer)->layer().get();
-  window_ = ui::WindowAndroid::FromJavaWindowAndroid(jwindow_android);
-  window_->AddObserver(this);
-}
-
-void NavigationGlow::InitWithWebContents(
-    JNIEnv* env,
-    const JavaParamRef<jobject>& obj,
-    const JavaParamRef<jobject>& jweb_contents) {
-  auto* web_contents = content::WebContents::FromJavaWebContents(jweb_contents);
+      glow_effect_(std::make_unique<ui::OverscrollGlow>(this)) {
   DCHECK(web_contents);
   view_ = web_contents->GetNativeView();
   view_->AddObserver(this);
@@ -58,9 +41,9 @@
   OnAttachedToWindow();
 }
 
+NavigationGlow::~NavigationGlow() = default;
+
 void NavigationGlow::OnAttachedToWindow() {
-  if (!view_)
-    return;
   window_ = view_->GetWindowAndroid();
   if (window_) {
     window_->AddObserver(this);
@@ -112,8 +95,7 @@
 
 void NavigationGlow::Destroy(JNIEnv* env, const JavaParamRef<jobject>& obj) {
   OnDetachedFromWindow();
-  if (view_)
-    view_->RemoveObserver(this);
+  view_->RemoveObserver(this);
   delete this;
 }
 
@@ -130,10 +112,15 @@
   return std::make_unique<ui::EdgeEffect>(&resource_manager, dip_scale_);
 }
 
-static jlong JNI_CompositorNavigationGlow_Init(JNIEnv* env,
-                                               const JavaParamRef<jobject>& obj,
-                                               const jfloat dip_scale) {
-  return reinterpret_cast<intptr_t>(new NavigationGlow(dip_scale));
+static jlong JNI_CompositorNavigationGlow_Init(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& obj,
+    const jfloat dip_scale,
+    const JavaParamRef<jobject>& jweb_contents) {
+  auto* web_contents = content::WebContents::FromJavaWebContents(jweb_contents);
+  DCHECK(web_contents);
+  return reinterpret_cast<intptr_t>(
+      new NavigationGlow(dip_scale, web_contents));
 }
 
 }  // namespace android
diff --git a/chrome/browser/android/compositor/navigation_glow.h b/chrome/browser/android/compositor/navigation_glow.h
index 048790e..2b72c6e 100644
--- a/chrome/browser/android/compositor/navigation_glow.h
+++ b/chrome/browser/android/compositor/navigation_glow.h
@@ -11,6 +11,10 @@
 #include "ui/android/view_android_observer.h"
 #include "ui/android/window_android_observer.h"
 
+namespace content {
+class WebContents;
+}
+
 namespace ui {
 class ViewAndroid;
 class WindowAndroid;
@@ -23,18 +27,9 @@
                        public ui::WindowAndroidObserver,
                        public ui::ViewAndroidObserver {
  public:
-  explicit NavigationGlow(float dip_scale);
+  explicit NavigationGlow(float dip_scale, content::WebContents* web_contents);
   ~NavigationGlow() override;
 
-  void InitWithSceneLayer(
-      JNIEnv* env,
-      const base::android::JavaParamRef<jobject>& obj,
-      const base::android::JavaParamRef<jobject>& jscene_layer,
-      const base::android::JavaParamRef<jobject>& jwindow_android);
-  void InitWithWebContents(
-      JNIEnv* env,
-      const base::android::JavaParamRef<jobject>& obj,
-      const base::android::JavaParamRef<jobject>& jweb_contents);
   void Prepare(JNIEnv* env,
                const base::android::JavaParamRef<jobject>& obj,
                jfloat start_x,
diff --git a/chrome/browser/apps/app_shim/app_shim_interactive_uitest_mac.mm b/chrome/browser/apps/app_shim/app_shim_interactive_uitest_mac.mm
index 34acde2..8bb8c31 100644
--- a/chrome/browser/apps/app_shim/app_shim_interactive_uitest_mac.mm
+++ b/chrome/browser/apps/app_shim/app_shim_interactive_uitest_mac.mm
@@ -310,9 +310,9 @@
 #define MAYBE_ShowWindow DISABLED_ShowWindow
 #define MAYBE_RebuildShim DISABLED_RebuildShim
 #else
-#define MAYBE_Launch Launch
+#define MAYBE_Launch DISABLED_Launch  // http://crbug.com/913490
 #define MAYBE_HostedAppLaunch DISABLED_HostedAppLaunch
-#define MAYBE_ShowWindow ShowWindow
+#define MAYBE_ShowWindow DISABLED_ShowWindow  // https://crbug.com/980072
 // http://crbug.com/517744 HostedAppLaunch fails with open as tab for apps
 // http://crbug.com/509774 this test is flaky so is disabled even in the
 // static build.
diff --git a/chrome/browser/apps/intent_helper/apps_navigation_throttle.cc b/chrome/browser/apps/intent_helper/apps_navigation_throttle.cc
index dd090c0f..67b7581d 100644
--- a/chrome/browser/apps/intent_helper/apps_navigation_throttle.cc
+++ b/chrome/browser/apps/intent_helper/apps_navigation_throttle.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/apps/intent_helper/apps_navigation_throttle.h"
 
+#include <algorithm>
 #include <utility>
 
 #include "base/bind.h"
@@ -117,8 +118,7 @@
   std::vector<IntentPickerAppInfo> apps = FindPwaForUrl(web_contents, url, {});
 
   ShowIntentPickerBubbleForApps(
-      web_contents, std::move(apps),
-      /*show_remember_selection=*/false,
+      web_contents, std::move(apps), ShouldShowRememberSelection(apps),
       base::BindOnce(&OnIntentPickerClosed, web_contents,
                      ui_auto_display_service, url));
 }
@@ -333,6 +333,25 @@
     web_contents->ClosePage();
 }
 
+// static
+bool AppsNavigationThrottle::ContainsOnlyPwas(
+    const std::vector<apps::IntentPickerAppInfo>& apps) {
+  return std::all_of(apps.begin(), apps.end(),
+                     [](const apps::IntentPickerAppInfo& app_info) {
+                       return app_info.type == apps::mojom::AppType::kWeb;
+                     });
+}
+
+// static
+bool AppsNavigationThrottle::ShouldShowRememberSelection(
+    std::vector<apps::IntentPickerAppInfo>& apps) {
+  // There is no support persistence for PWA so the selection should be hidden
+  // if only PWAs are present.
+  // TODO(crbug.com/826982): Provide the "Remember my choice" option when the
+  // app registry can support persistence for PWAs.
+  return !ContainsOnlyPwas(apps);
+}
+
 bool AppsNavigationThrottle::ShouldDeferNavigationForArc(
     content::NavigationHandle* handle) {
   return false;
@@ -361,7 +380,7 @@
       break;
     case PickerShowState::kPopOut:
       ShowIntentPickerBubbleForApps(web_contents, std::move(apps),
-                                    ShouldShowRememberSelection(),
+                                    ShouldShowRememberSelection(apps),
                                     std::move(callback));
       break;
     default:
@@ -385,10 +404,6 @@
                         ui_auto_display_service, url);
 }
 
-bool AppsNavigationThrottle::ShouldShowRememberSelection() {
-  return false;
-}
-
 bool AppsNavigationThrottle::navigate_from_link() {
   return navigate_from_link_;
 }
diff --git a/chrome/browser/apps/intent_helper/apps_navigation_throttle.h b/chrome/browser/apps/intent_helper/apps_navigation_throttle.h
index 73c3dea..49aacf8 100644
--- a/chrome/browser/apps/intent_helper/apps_navigation_throttle.h
+++ b/chrome/browser/apps/intent_helper/apps_navigation_throttle.h
@@ -166,6 +166,12 @@
 
   static void CloseOrGoBack(content::WebContents* web_contents);
 
+  static bool ContainsOnlyPwas(
+      const std::vector<apps::IntentPickerAppInfo>& apps);
+
+  static bool ShouldShowRememberSelection(
+      std::vector<apps::IntentPickerAppInfo>& apps);
+
   // Overridden for Chrome OS to allow arc handling.
   virtual void MaybeRemoveComingFromArcFlag(content::WebContents* web_contents,
                                             const GURL& previous_url,
@@ -190,8 +196,6 @@
       IntentPickerAutoDisplayService* ui_auto_display_service,
       const GURL& url);
 
-  virtual bool ShouldShowRememberSelection();
-
   bool navigate_from_link();
 
   // Keeps track of whether we already shown the UI or preferred app. Since
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
index 7a42e84..53ddb10 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
@@ -2139,11 +2139,11 @@
   base::Time one_hour_ago = base::Time::Now() - base::TimeDelta::FromHours(1);
   base::Time yesterday = base::Time::Now() - base::TimeDelta::FromDays(1);
   registry->OnAcceptRegisterProtocolHandler(
-      ProtocolHandler::CreateProtocolHandler("test1", kOrigin1));
+      ProtocolHandler::CreateProtocolHandler("news", kOrigin1));
   registry->OnAcceptRegisterProtocolHandler(
-      ProtocolHandler("test2", kOrigin1, yesterday));
-  EXPECT_TRUE(registry->IsHandledProtocol("test1"));
-  EXPECT_TRUE(registry->IsHandledProtocol("test2"));
+      ProtocolHandler("mailto", kOrigin1, yesterday));
+  EXPECT_TRUE(registry->IsHandledProtocol("news"));
+  EXPECT_TRUE(registry->IsHandledProtocol("mailto"));
   EXPECT_EQ(
       2U,
       registry->GetUserDefinedHandlers(base::Time(), base::Time::Max()).size());
@@ -2151,8 +2151,8 @@
   BlockUntilBrowsingDataRemoved(
       one_hour_ago, base::Time::Max(),
       ChromeBrowsingDataRemoverDelegate::DATA_TYPE_CONTENT_SETTINGS, false);
-  EXPECT_FALSE(registry->IsHandledProtocol("test1"));
-  EXPECT_TRUE(registry->IsHandledProtocol("test2"));
+  EXPECT_FALSE(registry->IsHandledProtocol("news"));
+  EXPECT_TRUE(registry->IsHandledProtocol("mailto"));
   EXPECT_EQ(
       1U,
       registry->GetUserDefinedHandlers(base::Time(), base::Time::Max()).size());
@@ -2160,8 +2160,8 @@
   BlockUntilBrowsingDataRemoved(
       base::Time(), base::Time::Max(),
       ChromeBrowsingDataRemoverDelegate::DATA_TYPE_CONTENT_SETTINGS, false);
-  EXPECT_FALSE(registry->IsHandledProtocol("test1"));
-  EXPECT_FALSE(registry->IsHandledProtocol("test2"));
+  EXPECT_FALSE(registry->IsHandledProtocol("news"));
+  EXPECT_FALSE(registry->IsHandledProtocol("mailto"));
   EXPECT_EQ(
       0U,
       registry->GetUserDefinedHandlers(base::Time(), base::Time::Max()).size());
diff --git a/chrome/browser/browsing_data/counters/site_settings_counter_unittest.cc b/chrome/browser/browsing_data/counters/site_settings_counter_unittest.cc
index 8f3e90e..410e4a89 100644
--- a/chrome/browser/browsing_data/counters/site_settings_counter_unittest.cc
+++ b/chrome/browser/browsing_data/counters/site_settings_counter_unittest.cc
@@ -226,11 +226,11 @@
 
   base::Time now = base::Time::Now();
   handler_registry()->OnAcceptRegisterProtocolHandler(
-      ProtocolHandler("test1", GURL("http://www.google.com"), now));
+      ProtocolHandler("news", GURL("http://www.google.com"), now));
   handler_registry()->OnAcceptRegisterProtocolHandler(
-      ProtocolHandler("test1", GURL("http://docs.google.com"), now));
+      ProtocolHandler("news", GURL("http://docs.google.com"), now));
   handler_registry()->OnAcceptRegisterProtocolHandler(
-      ProtocolHandler("test1", GURL("http://slides.google.com"), now));
+      ProtocolHandler("news", GURL("http://slides.google.com"), now));
 
   auto translate_prefs =
       ChromeTranslateClient::CreateTranslatePrefs(profile()->GetPrefs());
@@ -246,12 +246,12 @@
   base::Time now = base::Time::Now();
 
   handler_registry()->OnAcceptRegisterProtocolHandler(
-      ProtocolHandler("test1", GURL("http://www.google.com"), now));
+      ProtocolHandler("news", GURL("http://www.google.com"), now));
   handler_registry()->OnAcceptRegisterProtocolHandler(
-      ProtocolHandler("test2", GURL("http://maps.google.com"),
+      ProtocolHandler("mailto", GURL("http://maps.google.com"),
                       now - base::TimeDelta::FromMinutes(90)));
-  EXPECT_TRUE(handler_registry()->IsHandledProtocol("test1"));
-  EXPECT_TRUE(handler_registry()->IsHandledProtocol("test2"));
+  EXPECT_TRUE(handler_registry()->IsHandledProtocol("news"));
+  EXPECT_TRUE(handler_registry()->IsHandledProtocol("mailto"));
 
   SetDeletionPeriodPref(browsing_data::TimePeriod::ALL_TIME);
   EXPECT_EQ(2, GetResult());
diff --git a/chrome/browser/chrome_content_browser_client_browsertest.cc b/chrome/browser/chrome_content_browser_client_browsertest.cc
index f1da002c..29a649cb 100644
--- a/chrome/browser/chrome_content_browser_client_browsertest.cc
+++ b/chrome/browser/chrome_content_browser_client_browsertest.cc
@@ -759,9 +759,9 @@
 };
 
 IN_PROC_BROWSER_TEST_F(ProtocolHandlerTest, CustomHandler) {
-  AddProtocolHandler("abc", "https://abc.xyz/?url=%s");
+  AddProtocolHandler("news", "https://abc.xyz/?url=%s");
 
-  ui_test_utils::NavigateToURL(browser(), GURL("abc:something"));
+  ui_test_utils::NavigateToURL(browser(), GURL("news:something"));
 
   base::string16 expected_title = base::ASCIIToUTF16("abc.xyz");
   content::TitleWatcher title_watcher(
@@ -771,10 +771,10 @@
 
 // This is a regression test for crbug.com/969177.
 IN_PROC_BROWSER_TEST_F(ProtocolHandlerTest, HandlersIgnoredWhenDisabled) {
-  AddProtocolHandler("abc", "https://abc.xyz/?url=%s");
+  AddProtocolHandler("bitcoin", "https://abc.xyz/?url=%s");
   protocol_handler_registry()->Disable();
 
-  ui_test_utils::NavigateToURL(browser(), GURL("abc:something"));
+  ui_test_utils::NavigateToURL(browser(), GURL("bitcoin:something"));
 
   base::string16 tab_title;
   ASSERT_TRUE(ui_test_utils::GetCurrentTabTitle(browser(), &tab_title));
diff --git a/chrome/browser/chromeos/apps/intent_helper/chromeos_apps_navigation_throttle.cc b/chrome/browser/chromeos/apps/intent_helper/chromeos_apps_navigation_throttle.cc
index 32b534a..13e3e27 100644
--- a/chrome/browser/chromeos/apps/intent_helper/chromeos_apps_navigation_throttle.cc
+++ b/chrome/browser/chromeos/apps/intent_helper/chromeos_apps_navigation_throttle.cc
@@ -4,7 +4,6 @@
 
 #include "chrome/browser/chromeos/apps/intent_helper/chromeos_apps_navigation_throttle.h"
 
-#include <algorithm>
 #include <utility>
 
 #include "base/bind.h"
@@ -136,7 +135,7 @@
       FindPwaForUrl(web_contents, url, std::move(apps));
   apps::AppsNavigationThrottle::ShowIntentPickerBubbleForApps(
       web_contents, std::move(apps_for_picker),
-      /*show_remember_selection=*/true,
+      ShouldShowRememberSelection(apps_for_picker),
       base::BindOnce(&OnIntentPickerClosed, web_contents,
                      ui_auto_display_service, url));
 }
@@ -266,10 +265,6 @@
                         ui_auto_display_service, url);
 }
 
-bool ChromeOsAppsNavigationThrottle::ShouldShowRememberSelection() {
-  return true;
-}
-
 void ChromeOsAppsNavigationThrottle::CloseTab() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   content::WebContents* web_contents = navigation_handle()->GetWebContents();
@@ -289,12 +284,7 @@
   // until "Remember my choice" is available for desktop PWAs.
   // TODO(crbug.com/826982): show the intent picker when the app registry is
   // available to persist "Remember my choice" for PWAs.
-  bool only_pwa_apps =
-      std::all_of(apps_for_picker.begin(), apps_for_picker.end(),
-                  [](const apps::IntentPickerAppInfo& app_info) {
-                    return app_info.type == apps::mojom::AppType::kWeb;
-                  });
-  if (only_pwa_apps)
+  if (ContainsOnlyPwas(apps_for_picker))
     return false;
 
   DCHECK(ui_auto_display_service_);
diff --git a/chrome/browser/chromeos/apps/intent_helper/chromeos_apps_navigation_throttle.h b/chrome/browser/chromeos/apps/intent_helper/chromeos_apps_navigation_throttle.h
index f7f4dfef..00d51b4d 100644
--- a/chrome/browser/chromeos/apps/intent_helper/chromeos_apps_navigation_throttle.h
+++ b/chrome/browser/chromeos/apps/intent_helper/chromeos_apps_navigation_throttle.h
@@ -112,8 +112,6 @@
       IntentPickerAutoDisplayService* ui_auto_display_service,
       const GURL& url) override;
 
-  bool ShouldShowRememberSelection() override;
-
   void CloseTab();
 
   // Whether or not the intent picker UI should be displayed without the user
diff --git a/chrome/browser/chromeos/settings/device_oauth2_token_service.cc b/chrome/browser/chromeos/settings/device_oauth2_token_service.cc
index c1bc597..715e9ad 100644
--- a/chrome/browser/chromeos/settings/device_oauth2_token_service.cc
+++ b/chrome/browser/chromeos/settings/device_oauth2_token_service.cc
@@ -52,14 +52,12 @@
     PrefService* local_state) {
   delegate_ = std::make_unique<DeviceOAuth2TokenServiceDelegate>(
       url_loader_factory, local_state, this);
-  delegate_->AddObserver(this);
   token_manager_ = std::make_unique<OAuth2AccessTokenManager>(
       this /* OAuth2AccessTokenManager::Delegate* */);
   delegate_->InitializeWithValidationStatusDelegate(this);
 }
 
 DeviceOAuth2TokenService::~DeviceOAuth2TokenService() {
-  delegate_->RemoveObserver(this);
   delegate_->ClearValidationStatusDelegate();
   FlushPendingRequests(false, GoogleServiceAuthError::REQUEST_CANCELED);
 }
@@ -133,39 +131,18 @@
   return RefreshTokenIsAvailable(account_id);
 }
 
-bool DeviceOAuth2TokenService::FixRequestErrorIfPossible() {
-  return delegate_->FixRequestErrorIfPossible();
-}
-
 scoped_refptr<network::SharedURLLoaderFactory>
 DeviceOAuth2TokenService::GetURLLoaderFactory() const {
   return delegate_->GetURLLoaderFactory();
 }
 
-void DeviceOAuth2TokenService::OnAccessTokenInvalidated(
-    const CoreAccountId& account_id,
-    const std::string& client_id,
-    const std::set<std::string>& scopes,
-    const std::string& access_token) {
-  delegate_->OnAccessTokenInvalidated(account_id, client_id, scopes,
-                                      access_token);
-}
-
-void DeviceOAuth2TokenService::OnAccessTokenFetched(
-    const CoreAccountId& account_id,
-    const GoogleServiceAuthError& error) {
-  // Update the auth error state so auth errors are appropriately communicated
-  // to the user.
-  delegate_->UpdateAuthError(account_id, error);
-}
-
-void DeviceOAuth2TokenService::OnRefreshTokenAvailable(
+void DeviceOAuth2TokenService::FireRefreshTokenAvailable(
     const CoreAccountId& account_id) {
   if (on_refresh_token_available_callback_)
     on_refresh_token_available_callback_.Run(account_id);
 }
 
-void DeviceOAuth2TokenService::OnRefreshTokenRevoked(
+void DeviceOAuth2TokenService::FireRefreshTokenRevoked(
     const CoreAccountId& account_id) {
   if (on_refresh_token_revoked_callback_)
     on_refresh_token_revoked_callback_.Run(account_id);
diff --git a/chrome/browser/chromeos/settings/device_oauth2_token_service.h b/chrome/browser/chromeos/settings/device_oauth2_token_service.h
index 0732921..0c8b3fe 100644
--- a/chrome/browser/chromeos/settings/device_oauth2_token_service.h
+++ b/chrome/browser/chromeos/settings/device_oauth2_token_service.h
@@ -15,7 +15,6 @@
 #include "google_apis/gaia/google_service_auth_error.h"
 #include "google_apis/gaia/oauth2_access_token_consumer.h"
 #include "google_apis/gaia/oauth2_access_token_manager.h"
-#include "google_apis/gaia/oauth2_token_service_observer.h"
 
 namespace network {
 class SharedURLLoaderFactory;
@@ -34,8 +33,7 @@
 //
 // Note that requests must be made from the UI thread.
 class DeviceOAuth2TokenService
-    : public OAuth2TokenServiceObserver,
-      public OAuth2AccessTokenManager::Delegate,
+    : public OAuth2AccessTokenManager::Delegate,
       public DeviceOAuth2TokenServiceDelegate::ValidationStatusDelegate {
  public:
   typedef base::RepeatingCallback<void(const CoreAccountId& /* account_id */)>
@@ -95,6 +93,9 @@
   OAuth2AccessTokenManager* GetAccessTokenManager();
 
  private:
+  // TODO(https://crbug.com/967598): Merge DeviceOAuth2TokenServiceDelegate
+  // into DeviceOAuth2TokenService.
+  friend class DeviceOAuth2TokenServiceDelegate;
   friend class DeviceOAuth2TokenServiceFactory;
   friend class DeviceOAuth2TokenServiceTest;
   struct PendingRequest;
@@ -105,7 +106,6 @@
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
       OAuth2AccessTokenConsumer* consumer) override;
   bool HasRefreshToken(const CoreAccountId& account_id) const override;
-  bool FixRequestErrorIfPossible() override;
   scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory()
       const override;
   bool HandleAccessTokenFetch(
@@ -115,16 +115,9 @@
       const std::string& client_id,
       const std::string& client_secret,
       const OAuth2AccessTokenManager::ScopeSet& scopes) override;
-  void OnAccessTokenInvalidated(const CoreAccountId& account_id,
-                                const std::string& client_id,
-                                const std::set<std::string>& scopes,
-                                const std::string& access_token) override;
-  void OnAccessTokenFetched(const CoreAccountId& account_id,
-                            const GoogleServiceAuthError& error) override;
 
-  // OAuth2TokenServiceObserver:
-  void OnRefreshTokenAvailable(const CoreAccountId& account_id) override;
-  void OnRefreshTokenRevoked(const CoreAccountId& account_id) override;
+  void FireRefreshTokenAvailable(const CoreAccountId& account_id);
+  void FireRefreshTokenRevoked(const CoreAccountId& account_id);
 
   // Implementation of
   // DeviceOAuth2TokenServiceDelegate::ValidationStatusDelegate.
diff --git a/chrome/browser/chromeos/settings/device_oauth2_token_service_delegate.cc b/chrome/browser/chromeos/settings/device_oauth2_token_service_delegate.cc
index 6be3110..3d4329d 100644
--- a/chrome/browser/chromeos/settings/device_oauth2_token_service_delegate.cc
+++ b/chrome/browser/chromeos/settings/device_oauth2_token_service_delegate.cc
@@ -11,6 +11,7 @@
 #include "base/bind_helpers.h"
 #include "base/memory/weak_ptr.h"
 #include "base/values.h"
+#include "chrome/browser/chromeos/settings/device_oauth2_token_service.h"
 #include "chrome/browser/chromeos/settings/token_encryptor.h"
 #include "chrome/common/pref_names.h"
 #include "chromeos/cryptohome/system_salt_getter.h"
@@ -20,7 +21,6 @@
 #include "components/prefs/pref_service.h"
 #include "google_apis/gaia/gaia_constants.h"
 #include "google_apis/gaia/gaia_urls.h"
-#include "google_apis/gaia/google_service_auth_error.h"
 #include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
@@ -28,7 +28,7 @@
 
 void DeviceOAuth2TokenServiceDelegate::OnServiceAccountIdentityChanged() {
   if (!GetRobotAccountId().empty() && !refresh_token_.empty())
-    FireRefreshTokenAvailable(GetRobotAccountId());
+    service_->FireRefreshTokenAvailable(GetRobotAccountId());
 }
 
 DeviceOAuth2TokenServiceDelegate::DeviceOAuth2TokenServiceDelegate(
@@ -47,6 +47,7 @@
               base::Bind(&DeviceOAuth2TokenServiceDelegate::
                              OnServiceAccountIdentityChanged,
                          base::Unretained(this)))),
+      service_(service),
       weak_ptr_factory_(this) {
   // Pull in the system salt.
   SystemSaltGetter::Get()->GetSystemSalt(
@@ -71,7 +72,7 @@
   // will be done from OnServiceAccountIdentityChanged() once the robot account
   // ID becomes available as well.
   if (!GetRobotAccountId().empty())
-    FireRefreshTokenAvailable(GetRobotAccountId());
+    service_->FireRefreshTokenAvailable(GetRobotAccountId());
 
   token_save_callbacks_.push_back(result_callback);
   if (!waiting_for_salt) {
@@ -227,7 +228,7 @@
 
   // Announce the token.
   if (!GetRobotAccountId().empty()) {
-    FireRefreshTokenAvailable(GetRobotAccountId());
+    service_->FireRefreshTokenAvailable(GetRobotAccountId());
   }
 }
 
@@ -324,13 +325,6 @@
 void DeviceOAuth2TokenServiceDelegate::InitializeWithValidationStatusDelegate(
     ValidationStatusDelegate* delegate) {
   validation_status_delegate_ = delegate;
-
-  // Now that |delegate| (i.e., DeviceOAuth2TokenService) has been initialized
-  // and is listening to this object as an observer, fire the notification that
-  // refresh tokens were loaded; otherwise,
-  // OAuth2TokenService::{GetAccounts(), RefreshTokenIsAvailable()} will short-
-  // circuit out to match O2TS semantics.
-  FireRefreshTokensLoaded();
 }
 
 void DeviceOAuth2TokenServiceDelegate::ClearValidationStatusDelegate() {
diff --git a/chrome/browser/chromeos/settings/device_oauth2_token_service_delegate.h b/chrome/browser/chromeos/settings/device_oauth2_token_service_delegate.h
index 6251482..58db8af 100644
--- a/chrome/browser/chromeos/settings/device_oauth2_token_service_delegate.h
+++ b/chrome/browser/chromeos/settings/device_oauth2_token_service_delegate.h
@@ -14,8 +14,9 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/chromeos/settings/cros_settings.h"
+#include "google_apis/gaia/core_account_id.h"
 #include "google_apis/gaia/gaia_oauth_client.h"
-#include "google_apis/gaia/oauth2_token_service_delegate.h"
+#include "google_apis/gaia/google_service_auth_error.h"
 #include "net/url_request/url_request_context_getter.h"
 
 namespace gaia {
@@ -27,14 +28,15 @@
 }
 
 class PrefService;
+class OAuth2AccessTokenFetcher;
+class OAuth2AccessTokenConsumer;
 
 namespace chromeos {
 
 class DeviceOAuth2TokenService;
 
 class DeviceOAuth2TokenServiceDelegate
-    : public OAuth2TokenServiceDelegate,
-      public gaia::GaiaOAuthClient::Delegate {
+    : public gaia::GaiaOAuthClient::Delegate {
  public:
   DeviceOAuth2TokenServiceDelegate(
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
@@ -57,17 +59,13 @@
     robot_account_id_for_testing_ = account_id;
   }
 
-  // Implementation of OAuth2TokenServiceDelegate.
-  bool RefreshTokenIsAvailable(const CoreAccountId& account_id) const override;
-  scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory()
-      const override;
-
+  bool RefreshTokenIsAvailable(const CoreAccountId& account_id) const;
+  scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory() const;
   std::unique_ptr<OAuth2AccessTokenFetcher> CreateAccessTokenFetcher(
       const CoreAccountId& account_id,
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-      OAuth2AccessTokenConsumer* consumer) override;
-
-  std::vector<CoreAccountId> GetAccounts() const override;
+      OAuth2AccessTokenConsumer* consumer);
+  std::vector<CoreAccountId> GetAccounts() const;
 
   // gaia::GaiaOAuthClient::Delegate implementation.
   void OnRefreshTokenResponse(const std::string& access_token,
@@ -164,6 +162,10 @@
 
   CoreAccountId robot_account_id_for_testing_;
 
+  // TODO(https://crbug.com/967598): Completely merge this class into
+  // DeviceOAuth2TokenService.
+  DeviceOAuth2TokenService* service_;
+
   base::WeakPtrFactory<DeviceOAuth2TokenServiceDelegate> weak_ptr_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(DeviceOAuth2TokenServiceDelegate);
diff --git a/chrome/browser/chromeos/settings/device_oauth2_token_service_unittest.cc b/chrome/browser/chromeos/settings/device_oauth2_token_service_unittest.cc
index 4654e73..c8c627f 100644
--- a/chrome/browser/chromeos/settings/device_oauth2_token_service_unittest.cc
+++ b/chrome/browser/chromeos/settings/device_oauth2_token_service_unittest.cc
@@ -43,22 +43,6 @@
 
 namespace chromeos {
 
-namespace {
-
-class MockOAuth2TokenServiceObserver : public OAuth2TokenServiceObserver {
- public:
-  MockOAuth2TokenServiceObserver();
-  ~MockOAuth2TokenServiceObserver() override;
-
-  MOCK_METHOD1(OnRefreshTokenAvailable, void(const CoreAccountId&));
-};
-
-MockOAuth2TokenServiceObserver::MockOAuth2TokenServiceObserver() {}
-
-MockOAuth2TokenServiceObserver::~MockOAuth2TokenServiceObserver() {}
-
-}  // namespace
-
 class DeviceOAuth2TokenServiceTest : public testing::Test {
  public:
   DeviceOAuth2TokenServiceTest()
@@ -462,23 +446,28 @@
 TEST_F(DeviceOAuth2TokenServiceTest, DoNotAnnounceTokenWithoutAccountID) {
   CreateService();
 
-  testing::StrictMock<MockOAuth2TokenServiceObserver> observer;
-  GetDelegate()->AddObserver(&observer);
+  auto callback_without_id = base::BindRepeating(
+      [](const CoreAccountId& account_id) { EXPECT_TRUE(false); });
+  oauth2_service_->SetRefreshTokenAvailableCallback(
+      std::move(callback_without_id));
 
   // Make a token available during enrollment. Verify that the token is not
   // announced yet.
   oauth2_service_->SetAndSaveRefreshToken(
       "test-token", DeviceOAuth2TokenService::StatusCallback());
-  testing::Mock::VerifyAndClearExpectations(&observer);
+
+  base::RunLoop run_loop;
+  auto callback_with_id =
+      base::BindRepeating([](base::RunLoop* loop,
+                             const CoreAccountId& account_id) { loop->Quit(); },
+                          &run_loop);
+  oauth2_service_->SetRefreshTokenAvailableCallback(
+      std::move(callback_with_id));
 
   // Also make the robot account ID available. Verify that the token is
   // announced now.
-  EXPECT_CALL(observer,
-              OnRefreshTokenAvailable(CoreAccountId("robot@example.com")));
   SetRobotAccountId("robot@example.com");
-  testing::Mock::VerifyAndClearExpectations(&observer);
-
-  GetDelegate()->RemoveObserver(&observer);
+  run_loop.Run();
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/custom_handlers/protocol_handler_registry.cc b/chrome/browser/custom_handlers/protocol_handler_registry.cc
index fda23ba..b1c4255 100644
--- a/chrome/browser/custom_handlers/protocol_handler_registry.cc
+++ b/chrome/browser/custom_handlers/protocol_handler_registry.cc
@@ -208,7 +208,8 @@
 void ProtocolHandlerRegistry::OnAcceptRegisterProtocolHandler(
     const ProtocolHandler& handler) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  RegisterProtocolHandler(handler, USER);
+  if (!RegisterProtocolHandler(handler, USER))
+    return;
   SetDefault(handler);
   Save();
   NotifyChanged();
@@ -729,23 +730,29 @@
     observer.OnProtocolHandlerRegistryChanged();
 }
 
-void ProtocolHandlerRegistry::RegisterProtocolHandler(
+bool ProtocolHandlerRegistry::RegisterProtocolHandler(
     const ProtocolHandler& handler,
     const HandlerSource source) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(CanSchemeBeOverridden(handler.protocol()));
   DCHECK(!handler.IsEmpty());
+
+  // Ignore invalid handlers.
+  if (!handler.IsValid())
+    return false;
+
   ProtocolHandlerMultiMap& map =
       (source == POLICY) ? policy_protocol_handlers_ : user_protocol_handlers_;
   ProtocolHandlerList& list = map[handler.protocol()];
   if (!HandlerExists(handler, list))
     list.push_back(handler);
   if (IsRegistered(handler)) {
-    return;
+    return true;
   }
   if (enabled_ && !delegate_->IsExternalHandlerRegistered(handler.protocol()))
     delegate_->RegisterExternalHandler(handler.protocol());
   InsertHandler(handler);
+  return true;
 }
 
 std::vector<const base::DictionaryValue*>
@@ -781,7 +788,8 @@
        p != registered_handlers.end();
        ++p) {
     ProtocolHandler handler = ProtocolHandler::CreateProtocolHandler(*p);
-    RegisterProtocolHandler(handler, source);
+    if (!RegisterProtocolHandler(handler, source))
+      continue;
     bool is_default = false;
     if ((*p)->GetBoolean("default", &is_default) && is_default) {
       SetDefault(handler);
diff --git a/chrome/browser/custom_handlers/protocol_handler_registry.h b/chrome/browser/custom_handlers/protocol_handler_registry.h
index 20b6009..b10a595 100644
--- a/chrome/browser/custom_handlers/protocol_handler_registry.h
+++ b/chrome/browser/custom_handlers/protocol_handler_registry.h
@@ -302,7 +302,7 @@
   void NotifyChanged();
 
   // Registers a new protocol handler.
-  void RegisterProtocolHandler(const ProtocolHandler& handler,
+  bool RegisterProtocolHandler(const ProtocolHandler& handler,
                                const HandlerSource source);
 
   // Registers protocol handlers from the preference.
diff --git a/chrome/browser/custom_handlers/protocol_handler_registry_browsertest.cc b/chrome/browser/custom_handlers/protocol_handler_registry_browsertest.cc
index 3725feac..56cf28b3d 100644
--- a/chrome/browser/custom_handlers/protocol_handler_registry_browsertest.cc
+++ b/chrome/browser/custom_handlers/protocol_handler_registry_browsertest.cc
@@ -48,8 +48,7 @@
     return menu;
   }
 
-  void AddProtocolHandler(const std::string& protocol,
-                          const GURL& url) {
+  void AddProtocolHandler(const std::string& protocol, const GURL& url) {
     ProtocolHandler handler = ProtocolHandler::CreateProtocolHandler(protocol,
                                                                      url);
     ProtocolHandlerRegistry* registry =
@@ -116,17 +115,17 @@
 
 IN_PROC_BROWSER_TEST_F(RegisterProtocolHandlerBrowserTest, CustomHandler) {
   ASSERT_TRUE(embedded_test_server()->Start());
-  GURL handler_url = embedded_test_server()->GetURL("/custom_handler_foo.html");
-  AddProtocolHandler("foo", handler_url);
+  GURL handler_url = embedded_test_server()->GetURL("/custom_handler.html");
+  AddProtocolHandler("news", handler_url);
 
-  ui_test_utils::NavigateToURL(browser(), GURL("foo:test"));
+  ui_test_utils::NavigateToURL(browser(), GURL("news:test"));
 
   ASSERT_EQ(handler_url,
             browser()->tab_strip_model()->GetActiveWebContents()->GetURL());
 
   // Also check redirects.
   GURL redirect_url =
-      embedded_test_server()->GetURL("/server-redirect?foo:test");
+      embedded_test_server()->GetURL("/server-redirect?news:test");
   ui_test_utils::NavigateToURL(browser(), redirect_url);
 
   ASSERT_EQ(handler_url,
diff --git a/chrome/browser/custom_handlers/protocol_handler_registry_unittest.cc b/chrome/browser/custom_handlers/protocol_handler_registry_unittest.cc
index f6fe7772..8b0bc89 100644
--- a/chrome/browser/custom_handlers/protocol_handler_registry_unittest.cc
+++ b/chrome/browser/custom_handlers/protocol_handler_registry_unittest.cc
@@ -197,7 +197,7 @@
 class ProtocolHandlerRegistryTest : public testing::Test {
  protected:
   ProtocolHandlerRegistryTest()
-      : test_protocol_handler_(CreateProtocolHandler("test", "test")) {}
+      : test_protocol_handler_(CreateProtocolHandler("news", "news")) {}
 
   FakeDelegate* delegate() const { return delegate_; }
   ProtocolHandlerRegistry* registry() { return registry_.get(); }
@@ -268,7 +268,7 @@
     CHECK(profile_->GetPrefs());
     SetUpRegistry(true);
     test_protocol_handler_ =
-        CreateProtocolHandler("test", GURL("http://test.com/%s"));
+        CreateProtocolHandler("news", GURL("http://test.com/%s"));
   }
 
   void TearDown() override { TeadDownRegistry(); }
@@ -283,34 +283,34 @@
 };
 
 TEST_F(ProtocolHandlerRegistryTest, AcceptProtocolHandlerHandlesProtocol) {
-  ASSERT_FALSE(registry()->IsHandledProtocol("test"));
+  ASSERT_FALSE(registry()->IsHandledProtocol("news"));
   registry()->OnAcceptRegisterProtocolHandler(test_protocol_handler());
-  ASSERT_TRUE(registry()->IsHandledProtocol("test"));
+  ASSERT_TRUE(registry()->IsHandledProtocol("news"));
 }
 
 TEST_F(ProtocolHandlerRegistryTest, DeniedProtocolIsntHandledUntilAccepted) {
   registry()->OnDenyRegisterProtocolHandler(test_protocol_handler());
-  ASSERT_FALSE(registry()->IsHandledProtocol("test"));
+  ASSERT_FALSE(registry()->IsHandledProtocol("news"));
   registry()->OnAcceptRegisterProtocolHandler(test_protocol_handler());
-  ASSERT_TRUE(registry()->IsHandledProtocol("test"));
+  ASSERT_TRUE(registry()->IsHandledProtocol("news"));
 }
 
 TEST_F(ProtocolHandlerRegistryTest, ClearDefaultMakesProtocolNotHandled) {
   registry()->OnAcceptRegisterProtocolHandler(test_protocol_handler());
-  registry()->ClearDefault("test");
-  ASSERT_FALSE(registry()->IsHandledProtocol("test"));
-  ASSERT_TRUE(registry()->GetHandlerFor("test").IsEmpty());
+  registry()->ClearDefault("news");
+  ASSERT_FALSE(registry()->IsHandledProtocol("news"));
+  ASSERT_TRUE(registry()->GetHandlerFor("news").IsEmpty());
 }
 
 TEST_F(ProtocolHandlerRegistryTest, DisableDeregistersProtocolHandlers) {
-  ASSERT_FALSE(delegate()->IsExternalHandlerRegistered("test"));
+  ASSERT_FALSE(delegate()->IsExternalHandlerRegistered("news"));
   registry()->OnAcceptRegisterProtocolHandler(test_protocol_handler());
-  ASSERT_TRUE(delegate()->IsExternalHandlerRegistered("test"));
+  ASSERT_TRUE(delegate()->IsExternalHandlerRegistered("news"));
 
   registry()->Disable();
-  ASSERT_FALSE(delegate()->IsExternalHandlerRegistered("test"));
+  ASSERT_FALSE(delegate()->IsExternalHandlerRegistered("news"));
   registry()->Enable();
-  ASSERT_TRUE(delegate()->IsExternalHandlerRegistered("test"));
+  ASSERT_TRUE(delegate()->IsExternalHandlerRegistered("news"));
 }
 
 TEST_F(ProtocolHandlerRegistryTest, IgnoreProtocolHandler) {
@@ -322,8 +322,8 @@
 }
 
 TEST_F(ProtocolHandlerRegistryTest, IgnoreEquivalentProtocolHandler) {
-  ProtocolHandler ph1 = CreateProtocolHandler("test", GURL("http://test/%s"));
-  ProtocolHandler ph2 = CreateProtocolHandler("test", GURL("http://test/%s"));
+  ProtocolHandler ph1 = CreateProtocolHandler("news", GURL("http://test/%s"));
+  ProtocolHandler ph2 = CreateProtocolHandler("news", GURL("http://test/%s"));
 
   registry()->OnIgnoreRegisterProtocolHandler(ph1);
   ASSERT_TRUE(registry()->IsIgnored(ph1));
@@ -340,21 +340,21 @@
   registry()->OnAcceptRegisterProtocolHandler(test_protocol_handler());
   registry()->OnIgnoreRegisterProtocolHandler(stuff_protocol_handler);
 
-  ASSERT_TRUE(registry()->IsHandledProtocol("test"));
+  ASSERT_TRUE(registry()->IsHandledProtocol("news"));
   ASSERT_TRUE(registry()->IsIgnored(stuff_protocol_handler));
   delegate()->Reset();
   RecreateRegistry(true);
-  ASSERT_TRUE(registry()->IsHandledProtocol("test"));
+  ASSERT_TRUE(registry()->IsHandledProtocol("news"));
   ASSERT_TRUE(registry()->IsIgnored(stuff_protocol_handler));
 }
 
 TEST_F(ProtocolHandlerRegistryTest, Encode) {
   base::Time now = base::Time::Now();
-  ProtocolHandler handler("test", GURL("http://example.com"), now);
+  ProtocolHandler handler("news", GURL("http://example.com"), now);
   auto value = handler.Encode();
   ProtocolHandler recreated =
       ProtocolHandler::CreateProtocolHandler(value.get());
-  EXPECT_EQ("test", recreated.protocol());
+  EXPECT_EQ("news", recreated.protocol());
   EXPECT_EQ(GURL("http://example.com"), recreated.url());
   EXPECT_EQ(now, recreated.last_modified());
 }
@@ -363,9 +363,10 @@
   base::Time now = base::Time::Now();
   base::Time one_hour_ago = now - base::TimeDelta::FromHours(1);
   base::Time two_hours_ago = now - base::TimeDelta::FromHours(2);
-  ProtocolHandler handler1("test1", GURL("http://example.com"), two_hours_ago);
-  ProtocolHandler handler2("test2", GURL("http://example.com"), one_hour_ago);
-  ProtocolHandler handler3("test3", GURL("http://example.com"), now);
+  ProtocolHandler handler1("bitcoin", GURL("http://example.com"),
+                           two_hours_ago);
+  ProtocolHandler handler2("geo", GURL("http://example.com"), one_hour_ago);
+  ProtocolHandler handler3("im", GURL("http://example.com"), now);
   registry()->OnAcceptRegisterProtocolHandler(handler1);
   registry()->OnAcceptRegisterProtocolHandler(handler2);
   registry()->OnAcceptRegisterProtocolHandler(handler3);
@@ -385,12 +386,12 @@
   base::Time one_hour_ago = now - base::TimeDelta::FromHours(1);
   base::Time two_hours_ago = now - base::TimeDelta::FromHours(2);
   GURL url("http://example.com");
-  ProtocolHandler handler1("test1", url, two_hours_ago);
-  ProtocolHandler handler2("test2", url, one_hour_ago);
-  ProtocolHandler handler3("test3", url, now);
-  ProtocolHandler ignored1("ignored1", url, two_hours_ago);
-  ProtocolHandler ignored2("ignored2", url, one_hour_ago);
-  ProtocolHandler ignored3("ignored3", url, now);
+  ProtocolHandler handler1("bitcoin", url, two_hours_ago);
+  ProtocolHandler handler2("geo", url, one_hour_ago);
+  ProtocolHandler handler3("im", url, now);
+  ProtocolHandler ignored1("irc", url, two_hours_ago);
+  ProtocolHandler ignored2("ircs", url, one_hour_ago);
+  ProtocolHandler ignored3("magnet", url, now);
   registry()->OnAcceptRegisterProtocolHandler(handler1);
   registry()->OnAcceptRegisterProtocolHandler(handler2);
   registry()->OnAcceptRegisterProtocolHandler(handler3);
@@ -398,27 +399,27 @@
   registry()->OnIgnoreRegisterProtocolHandler(ignored2);
   registry()->OnIgnoreRegisterProtocolHandler(ignored3);
 
-  EXPECT_TRUE(registry()->IsHandledProtocol("test1"));
-  EXPECT_TRUE(registry()->IsHandledProtocol("test2"));
-  EXPECT_TRUE(registry()->IsHandledProtocol("test3"));
+  EXPECT_TRUE(registry()->IsHandledProtocol("bitcoin"));
+  EXPECT_TRUE(registry()->IsHandledProtocol("geo"));
+  EXPECT_TRUE(registry()->IsHandledProtocol("im"));
   EXPECT_TRUE(registry()->IsIgnored(ignored1));
   EXPECT_TRUE(registry()->IsIgnored(ignored2));
   EXPECT_TRUE(registry()->IsIgnored(ignored3));
 
   // Delete handler2 and ignored2.
   registry()->ClearUserDefinedHandlers(one_hour_ago, now);
-  EXPECT_TRUE(registry()->IsHandledProtocol("test1"));
-  EXPECT_FALSE(registry()->IsHandledProtocol("test2"));
-  EXPECT_TRUE(registry()->IsHandledProtocol("test3"));
+  EXPECT_TRUE(registry()->IsHandledProtocol("bitcoin"));
+  EXPECT_FALSE(registry()->IsHandledProtocol("geo"));
+  EXPECT_TRUE(registry()->IsHandledProtocol("im"));
   EXPECT_TRUE(registry()->IsIgnored(ignored1));
   EXPECT_FALSE(registry()->IsIgnored(ignored2));
   EXPECT_TRUE(registry()->IsIgnored(ignored3));
 
   // Delete all.
   registry()->ClearUserDefinedHandlers(base::Time(), base::Time::Max());
-  EXPECT_FALSE(registry()->IsHandledProtocol("test1"));
-  EXPECT_FALSE(registry()->IsHandledProtocol("test2"));
-  EXPECT_FALSE(registry()->IsHandledProtocol("test3"));
+  EXPECT_FALSE(registry()->IsHandledProtocol("bitcoin"));
+  EXPECT_FALSE(registry()->IsHandledProtocol("geo"));
+  EXPECT_FALSE(registry()->IsHandledProtocol("im"));
   EXPECT_FALSE(registry()->IsIgnored(ignored1));
   EXPECT_FALSE(registry()->IsIgnored(ignored2));
   EXPECT_FALSE(registry()->IsIgnored(ignored3));
@@ -433,15 +434,15 @@
 
 TEST_F(ProtocolHandlerRegistryTest,
     DisallowRegisteringExternallyHandledProtocols) {
-  delegate()->RegisterExternalHandler("test");
-  ASSERT_FALSE(registry()->CanSchemeBeOverridden("test"));
+  delegate()->RegisterExternalHandler("news");
+  ASSERT_FALSE(registry()->CanSchemeBeOverridden("news"));
 }
 
 TEST_F(ProtocolHandlerRegistryTest, RemovingHandlerMeansItCanBeAddedAgain) {
   registry()->OnAcceptRegisterProtocolHandler(test_protocol_handler());
-  ASSERT_TRUE(registry()->CanSchemeBeOverridden("test"));
+  ASSERT_TRUE(registry()->CanSchemeBeOverridden("news"));
   registry()->RemoveHandler(test_protocol_handler());
-  ASSERT_TRUE(registry()->CanSchemeBeOverridden("test"));
+  ASSERT_TRUE(registry()->CanSchemeBeOverridden("news"));
 }
 
 TEST_F(ProtocolHandlerRegistryTest, TestStartsAsDefault) {
@@ -450,31 +451,31 @@
 }
 
 TEST_F(ProtocolHandlerRegistryTest, TestClearDefault) {
-  ProtocolHandler ph1 = CreateProtocolHandler("test", "test1");
-  ProtocolHandler ph2 = CreateProtocolHandler("test", "test2");
+  ProtocolHandler ph1 = CreateProtocolHandler("news", "test1");
+  ProtocolHandler ph2 = CreateProtocolHandler("news", "test2");
   registry()->OnAcceptRegisterProtocolHandler(ph1);
   registry()->OnAcceptRegisterProtocolHandler(ph2);
 
   registry()->OnAcceptRegisterProtocolHandler(ph1);
-  registry()->ClearDefault("test");
+  registry()->ClearDefault("news");
   ASSERT_FALSE(registry()->IsDefault(ph1));
   ASSERT_FALSE(registry()->IsDefault(ph2));
 }
 
 TEST_F(ProtocolHandlerRegistryTest, TestGetHandlerFor) {
-  ProtocolHandler ph1 = CreateProtocolHandler("test", "test1");
-  ProtocolHandler ph2 = CreateProtocolHandler("test", "test2");
+  ProtocolHandler ph1 = CreateProtocolHandler("news", "test1");
+  ProtocolHandler ph2 = CreateProtocolHandler("news", "test2");
   registry()->OnAcceptRegisterProtocolHandler(ph1);
   registry()->OnAcceptRegisterProtocolHandler(ph2);
 
   registry()->OnAcceptRegisterProtocolHandler(ph2);
-  ASSERT_EQ(ph2, registry()->GetHandlerFor("test"));
-  ASSERT_TRUE(registry()->IsHandledProtocol("test"));
+  ASSERT_EQ(ph2, registry()->GetHandlerFor("news"));
+  ASSERT_TRUE(registry()->IsHandledProtocol("news"));
 }
 
 TEST_F(ProtocolHandlerRegistryTest, TestMostRecentHandlerIsDefault) {
-  ProtocolHandler ph1 = CreateProtocolHandler("test", "test1");
-  ProtocolHandler ph2 = CreateProtocolHandler("test", "test2");
+  ProtocolHandler ph1 = CreateProtocolHandler("news", "test1");
+  ProtocolHandler ph2 = CreateProtocolHandler("news", "test2");
   registry()->OnAcceptRegisterProtocolHandler(ph1);
   registry()->OnAcceptRegisterProtocolHandler(ph2);
   ASSERT_FALSE(registry()->IsDefault(ph1));
@@ -482,8 +483,8 @@
 }
 
 TEST_F(ProtocolHandlerRegistryTest, TestOnAcceptRegisterProtocolHandler) {
-  ProtocolHandler ph1 = CreateProtocolHandler("test", "test1");
-  ProtocolHandler ph2 = CreateProtocolHandler("test", "test2");
+  ProtocolHandler ph1 = CreateProtocolHandler("news", "test1");
+  ProtocolHandler ph2 = CreateProtocolHandler("news", "test2");
   registry()->OnAcceptRegisterProtocolHandler(ph1);
   registry()->OnAcceptRegisterProtocolHandler(ph2);
 
@@ -497,8 +498,8 @@
 }
 
 TEST_F(ProtocolHandlerRegistryTest, TestDefaultSaveLoad) {
-  ProtocolHandler ph1 = CreateProtocolHandler("test", "test1");
-  ProtocolHandler ph2 = CreateProtocolHandler("test", "test2");
+  ProtocolHandler ph1 = CreateProtocolHandler("news", "test1");
+  ProtocolHandler ph2 = CreateProtocolHandler("news", "test2");
   registry()->OnDenyRegisterProtocolHandler(ph1);
   registry()->OnDenyRegisterProtocolHandler(ph2);
 
@@ -517,13 +518,13 @@
 }
 
 TEST_F(ProtocolHandlerRegistryTest, TestRemoveHandler) {
-  ProtocolHandler ph1 = CreateProtocolHandler("test", "test1");
+  ProtocolHandler ph1 = CreateProtocolHandler("news", "test1");
   registry()->OnAcceptRegisterProtocolHandler(ph1);
   registry()->OnAcceptRegisterProtocolHandler(ph1);
 
   registry()->RemoveHandler(ph1);
   ASSERT_FALSE(registry()->IsRegistered(ph1));
-  ASSERT_FALSE(registry()->IsHandledProtocol("test"));
+  ASSERT_FALSE(registry()->IsHandledProtocol("news"));
 
   registry()->OnIgnoreRegisterProtocolHandler(ph1);
   ASSERT_FALSE(registry()->IsRegistered(ph1));
@@ -535,8 +536,8 @@
 }
 
 TEST_F(ProtocolHandlerRegistryTest, TestIsRegistered) {
-  ProtocolHandler ph1 = CreateProtocolHandler("test", "test1");
-  ProtocolHandler ph2 = CreateProtocolHandler("test", "test2");
+  ProtocolHandler ph1 = CreateProtocolHandler("news", "test1");
+  ProtocolHandler ph2 = CreateProtocolHandler("news", "test2");
   registry()->OnAcceptRegisterProtocolHandler(ph1);
   registry()->OnAcceptRegisterProtocolHandler(ph2);
 
@@ -544,8 +545,8 @@
 }
 
 TEST_F(ProtocolHandlerRegistryTest, TestIsEquivalentRegistered) {
-  ProtocolHandler ph1 = CreateProtocolHandler("test", GURL("http://test/%s"));
-  ProtocolHandler ph2 = CreateProtocolHandler("test", GURL("http://test/%s"));
+  ProtocolHandler ph1 = CreateProtocolHandler("news", GURL("http://test/%s"));
+  ProtocolHandler ph2 = CreateProtocolHandler("news", GURL("http://test/%s"));
   registry()->OnAcceptRegisterProtocolHandler(ph1);
 
   ASSERT_TRUE(registry()->IsRegistered(ph1));
@@ -553,8 +554,8 @@
 }
 
 TEST_F(ProtocolHandlerRegistryTest, TestSilentlyRegisterHandler) {
-  ProtocolHandler ph1 = CreateProtocolHandler("test", GURL("http://test/1/%s"));
-  ProtocolHandler ph2 = CreateProtocolHandler("test", GURL("http://test/2/%s"));
+  ProtocolHandler ph1 = CreateProtocolHandler("news", GURL("http://test/1/%s"));
+  ProtocolHandler ph2 = CreateProtocolHandler("news", GURL("http://test/2/%s"));
   ProtocolHandler ph3 = CreateProtocolHandler("ignore", GURL("http://test/%s"));
   ProtocolHandler ph4 = CreateProtocolHandler("ignore", GURL("http://test/%s"));
 
@@ -581,9 +582,9 @@
 }
 
 TEST_F(ProtocolHandlerRegistryTest, TestRemoveHandlerRemovesDefault) {
-  ProtocolHandler ph1 = CreateProtocolHandler("test", "test1");
-  ProtocolHandler ph2 = CreateProtocolHandler("test", "test2");
-  ProtocolHandler ph3 = CreateProtocolHandler("test", "test3");
+  ProtocolHandler ph1 = CreateProtocolHandler("news", "test1");
+  ProtocolHandler ph2 = CreateProtocolHandler("news", "test2");
+  ProtocolHandler ph3 = CreateProtocolHandler("news", "test3");
 
   registry()->OnAcceptRegisterProtocolHandler(ph1);
   registry()->OnAcceptRegisterProtocolHandler(ph2);
@@ -595,15 +596,15 @@
 }
 
 TEST_F(ProtocolHandlerRegistryTest, TestGetHandlersFor) {
-  ProtocolHandler ph1 = CreateProtocolHandler("test", "test1");
-  ProtocolHandler ph2 = CreateProtocolHandler("test", "test2");
-  ProtocolHandler ph3 = CreateProtocolHandler("test", "test3");
+  ProtocolHandler ph1 = CreateProtocolHandler("news", "test1");
+  ProtocolHandler ph2 = CreateProtocolHandler("news", "test2");
+  ProtocolHandler ph3 = CreateProtocolHandler("news", "test3");
   registry()->OnAcceptRegisterProtocolHandler(ph1);
   registry()->OnAcceptRegisterProtocolHandler(ph2);
   registry()->OnAcceptRegisterProtocolHandler(ph3);
 
   ProtocolHandlerRegistry::ProtocolHandlerList handlers =
-      registry()->GetHandlersFor("test");
+      registry()->GetHandlersFor("news");
   ASSERT_EQ(static_cast<size_t>(3), handlers.size());
 
   ASSERT_EQ(ph3, handlers[0]);
@@ -616,7 +617,7 @@
   registry()->GetRegisteredProtocols(&protocols);
   ASSERT_EQ(static_cast<size_t>(0), protocols.size());
 
-  registry()->GetHandlersFor("test");
+  registry()->GetHandlersFor("news");
 
   protocols.clear();
   registry()->GetRegisteredProtocols(&protocols);
@@ -624,12 +625,12 @@
 }
 
 TEST_F(ProtocolHandlerRegistryTest, TestIsHandledProtocol) {
-  registry()->GetHandlersFor("test");
-  ASSERT_FALSE(registry()->IsHandledProtocol("test"));
+  registry()->GetHandlersFor("news");
+  ASSERT_FALSE(registry()->IsHandledProtocol("news"));
 }
 
 TEST_F(ProtocolHandlerRegistryTest, TestObserver) {
-  ProtocolHandler ph1 = CreateProtocolHandler("test", "test1");
+  ProtocolHandler ph1 = CreateProtocolHandler("news", "test1");
   ProtocolHandlerChangeListener counter(registry());
 
   registry()->OnAcceptRegisterProtocolHandler(ph1);
@@ -651,43 +652,43 @@
 
 TEST_F(ProtocolHandlerRegistryTest, TestReentrantObserver) {
   QueryProtocolHandlerOnChange queryer(registry());
-  ProtocolHandler ph1 = CreateProtocolHandler("test", "test1");
+  ProtocolHandler ph1 = CreateProtocolHandler("news", "test1");
   registry()->OnAcceptRegisterProtocolHandler(ph1);
   ASSERT_TRUE(queryer.called());
 }
 
 TEST_F(ProtocolHandlerRegistryTest, TestProtocolsWithNoDefaultAreHandled) {
-  ProtocolHandler ph1 = CreateProtocolHandler("test", "test1");
+  ProtocolHandler ph1 = CreateProtocolHandler("news", "test1");
   registry()->OnAcceptRegisterProtocolHandler(ph1);
-  registry()->ClearDefault("test");
+  registry()->ClearDefault("news");
   std::vector<std::string> handled_protocols;
   registry()->GetRegisteredProtocols(&handled_protocols);
   ASSERT_EQ(static_cast<size_t>(1), handled_protocols.size());
-  ASSERT_EQ("test", handled_protocols[0]);
+  ASSERT_EQ("news", handled_protocols[0]);
 }
 
 TEST_F(ProtocolHandlerRegistryTest, TestDisablePreventsHandling) {
-  ProtocolHandler ph1 = CreateProtocolHandler("test", "test1");
+  ProtocolHandler ph1 = CreateProtocolHandler("news", "test1");
   registry()->OnAcceptRegisterProtocolHandler(ph1);
-  ASSERT_TRUE(registry()->IsHandledProtocol("test"));
+  ASSERT_TRUE(registry()->IsHandledProtocol("news"));
   registry()->Disable();
-  ASSERT_FALSE(registry()->IsHandledProtocol("test"));
+  ASSERT_FALSE(registry()->IsHandledProtocol("news"));
 }
 
 TEST_F(ProtocolHandlerRegistryTest, TestOSRegistration) {
-  ProtocolHandler ph_do1 = CreateProtocolHandler("do", "test1");
-  ProtocolHandler ph_do2 = CreateProtocolHandler("do", "test2");
-  ProtocolHandler ph_dont = CreateProtocolHandler("dont", "test");
+  ProtocolHandler ph_do1 = CreateProtocolHandler("news", "test1");
+  ProtocolHandler ph_do2 = CreateProtocolHandler("news", "test2");
+  ProtocolHandler ph_dont = CreateProtocolHandler("im", "test3");
 
-  ASSERT_FALSE(delegate()->IsFakeRegisteredWithOS("do"));
-  ASSERT_FALSE(delegate()->IsFakeRegisteredWithOS("dont"));
+  ASSERT_FALSE(delegate()->IsFakeRegisteredWithOS("news"));
+  ASSERT_FALSE(delegate()->IsFakeRegisteredWithOS("im"));
 
   registry()->OnAcceptRegisterProtocolHandler(ph_do1);
   registry()->OnDenyRegisterProtocolHandler(ph_dont);
   base::RunLoop().RunUntilIdle();
 
-  ASSERT_TRUE(delegate()->IsFakeRegisteredWithOS("do"));
-  ASSERT_FALSE(delegate()->IsFakeRegisteredWithOS("dont"));
+  ASSERT_TRUE(delegate()->IsFakeRegisteredWithOS("news"));
+  ASSERT_FALSE(delegate()->IsFakeRegisteredWithOS("im"));
 
   // This should not register with the OS, if it does the delegate
   // will assert for us. We don't need to wait for the message loop
@@ -704,11 +705,11 @@
 #endif
 
 TEST_F(ProtocolHandlerRegistryTest, MAYBE_TestOSRegistrationFailure) {
-  ProtocolHandler ph_do = CreateProtocolHandler("do", "test1");
-  ProtocolHandler ph_dont = CreateProtocolHandler("dont", "test");
+  ProtocolHandler ph_do = CreateProtocolHandler("news", "test1");
+  ProtocolHandler ph_dont = CreateProtocolHandler("im", "test2");
 
-  ASSERT_FALSE(registry()->IsHandledProtocol("do"));
-  ASSERT_FALSE(registry()->IsHandledProtocol("dont"));
+  ASSERT_FALSE(registry()->IsHandledProtocol("news"));
+  ASSERT_FALSE(registry()->IsHandledProtocol("im"));
 
   registry()->OnAcceptRegisterProtocolHandler(ph_do);
   base::RunLoop().RunUntilIdle();
@@ -717,10 +718,10 @@
   registry()->OnAcceptRegisterProtocolHandler(ph_dont);
   base::RunLoop().RunUntilIdle();
 
-  ASSERT_TRUE(registry()->IsHandledProtocol("do"));
-  ASSERT_EQ(static_cast<size_t>(1), registry()->GetHandlersFor("do").size());
-  ASSERT_FALSE(registry()->IsHandledProtocol("dont"));
-  ASSERT_EQ(static_cast<size_t>(1), registry()->GetHandlersFor("dont").size());
+  ASSERT_TRUE(registry()->IsHandledProtocol("news"));
+  ASSERT_EQ(static_cast<size_t>(1), registry()->GetHandlersFor("news").size());
+  ASSERT_FALSE(registry()->IsHandledProtocol("im"));
+  ASSERT_EQ(static_cast<size_t>(1), registry()->GetHandlersFor("im").size());
 }
 
 TEST_F(ProtocolHandlerRegistryTest, TestRemovingDefaultFallsBackToOldDefault) {
@@ -822,17 +823,17 @@
 TEST_F(ProtocolHandlerRegistryTest, TestInstallDefaultHandler) {
   RecreateRegistry(false);
   registry()->AddPredefinedHandler(
-      CreateProtocolHandler("test", GURL("http://test.com/%s")));
+      CreateProtocolHandler("news", GURL("http://test.com/%s")));
   registry()->InitProtocolSettings();
   std::vector<std::string> protocols;
   registry()->GetRegisteredProtocols(&protocols);
   ASSERT_EQ(static_cast<size_t>(1), protocols.size());
-  EXPECT_TRUE(registry()->IsHandledProtocol("test"));
+  EXPECT_TRUE(registry()->IsHandledProtocol("news"));
   auto handlers =
       registry()->GetUserDefinedHandlers(base::Time(), base::Time::Max());
   EXPECT_TRUE(handlers.empty());
   registry()->ClearUserDefinedHandlers(base::Time(), base::Time::Max());
-  EXPECT_TRUE(registry()->IsHandledProtocol("test"));
+  EXPECT_TRUE(registry()->IsHandledProtocol("news"));
 }
 
 #define URL_p1u1 "http://p1u1.com/%s"
@@ -847,16 +848,16 @@
   base::ListValue handlers_registered_by_policy;
 
   handlers_registered_by_pref.Append(
-      GetProtocolHandlerValueWithDefault("p1", URL_p1u2, true));
+      GetProtocolHandlerValueWithDefault("news", URL_p1u2, true));
   handlers_registered_by_pref.Append(
-      GetProtocolHandlerValueWithDefault("p1", URL_p1u1, true));
+      GetProtocolHandlerValueWithDefault("news", URL_p1u1, true));
   handlers_registered_by_pref.Append(
-      GetProtocolHandlerValueWithDefault("p1", URL_p1u2, false));
+      GetProtocolHandlerValueWithDefault("news", URL_p1u2, false));
 
   handlers_registered_by_policy.Append(
-      GetProtocolHandlerValueWithDefault("p1", URL_p1u1, false));
+      GetProtocolHandlerValueWithDefault("news", URL_p1u1, false));
   handlers_registered_by_policy.Append(
-      GetProtocolHandlerValueWithDefault("p3", URL_p3u1, true));
+      GetProtocolHandlerValueWithDefault("mailto", URL_p3u1, true));
 
   profile()->GetPrefs()->Set(prefs::kRegisteredProtocolHandlers,
                              handlers_registered_by_pref);
@@ -865,14 +866,14 @@
   registry()->InitProtocolSettings();
 
   // Duplicate p1u2 eliminated in memory but not yet saved in pref
-  ProtocolHandler p1u1 = CreateProtocolHandler("p1", GURL(URL_p1u1));
-  ProtocolHandler p1u2 = CreateProtocolHandler("p1", GURL(URL_p1u2));
+  ProtocolHandler p1u1 = CreateProtocolHandler("news", GURL(URL_p1u1));
+  ProtocolHandler p1u2 = CreateProtocolHandler("news", GURL(URL_p1u2));
   ASSERT_EQ(InPrefHandlerCount(), 3);
   ASSERT_EQ(InMemoryHandlerCount(), 3);
   ASSERT_TRUE(registry()->IsDefault(p1u1));
   ASSERT_FALSE(registry()->IsDefault(p1u2));
 
-  ProtocolHandler p2u1 = CreateProtocolHandler("p2", GURL(URL_p2u1));
+  ProtocolHandler p2u1 = CreateProtocolHandler("im", GURL(URL_p2u1));
   registry()->OnDenyRegisterProtocolHandler(p2u1);
 
   // Duplicate p1u2 saved in pref and a new handler added to pref and memory
@@ -887,7 +888,7 @@
   ASSERT_EQ(InMemoryHandlerCount(), 4);
   ASSERT_TRUE(registry()->IsDefault(p1u1));
 
-  ProtocolHandler p3u1 = CreateProtocolHandler("p3", GURL(URL_p3u1));
+  ProtocolHandler p3u1 = CreateProtocolHandler("mailto", GURL(URL_p3u1));
   registry()->RemoveHandler(p3u1);
 
   // p3u1 not removed from memory due to policy and it was never in pref.
@@ -902,7 +903,7 @@
   ASSERT_EQ(InMemoryHandlerCount(), 3);
   ASSERT_TRUE(registry()->IsDefault(p1u1));
 
-  ProtocolHandler p1u3 = CreateProtocolHandler("p1", GURL(URL_p1u3));
+  ProtocolHandler p1u3 = CreateProtocolHandler("news", GURL(URL_p1u3));
   registry()->OnAcceptRegisterProtocolHandler(p1u3);
 
   // p1u3 added to pref and memory.
@@ -926,14 +927,14 @@
   base::ListValue handlers_ignored_by_pref;
   base::ListValue handlers_ignored_by_policy;
 
-  handlers_ignored_by_pref.Append(GetProtocolHandlerValue("p1", URL_p1u1));
-  handlers_ignored_by_pref.Append(GetProtocolHandlerValue("p1", URL_p1u2));
-  handlers_ignored_by_pref.Append(GetProtocolHandlerValue("p1", URL_p1u2));
-  handlers_ignored_by_pref.Append(GetProtocolHandlerValue("p3", URL_p3u1));
+  handlers_ignored_by_pref.Append(GetProtocolHandlerValue("news", URL_p1u1));
+  handlers_ignored_by_pref.Append(GetProtocolHandlerValue("news", URL_p1u2));
+  handlers_ignored_by_pref.Append(GetProtocolHandlerValue("news", URL_p1u2));
+  handlers_ignored_by_pref.Append(GetProtocolHandlerValue("mailto", URL_p3u1));
 
-  handlers_ignored_by_policy.Append(GetProtocolHandlerValue("p1", URL_p1u2));
-  handlers_ignored_by_policy.Append(GetProtocolHandlerValue("p1", URL_p1u3));
-  handlers_ignored_by_policy.Append(GetProtocolHandlerValue("p2", URL_p2u1));
+  handlers_ignored_by_policy.Append(GetProtocolHandlerValue("news", URL_p1u2));
+  handlers_ignored_by_policy.Append(GetProtocolHandlerValue("news", URL_p1u3));
+  handlers_ignored_by_policy.Append(GetProtocolHandlerValue("im", URL_p2u1));
 
   profile()->GetPrefs()->Set(prefs::kIgnoredProtocolHandlers,
                              handlers_ignored_by_pref);
@@ -945,21 +946,21 @@
   ASSERT_EQ(InPrefIgnoredHandlerCount(), 4);
   ASSERT_EQ(InMemoryIgnoredHandlerCount(), 5);
 
-  ProtocolHandler p2u2 = CreateProtocolHandler("p2", GURL(URL_p2u2));
+  ProtocolHandler p2u2 = CreateProtocolHandler("im", GURL(URL_p2u2));
   registry()->OnIgnoreRegisterProtocolHandler(p2u2);
 
   // Duplicate p1u2 eliminated in pref, p2u2 added to pref and memory.
   ASSERT_EQ(InPrefIgnoredHandlerCount(), 4);
   ASSERT_EQ(InMemoryIgnoredHandlerCount(), 6);
 
-  ProtocolHandler p2u1 = CreateProtocolHandler("p2", GURL(URL_p2u1));
+  ProtocolHandler p2u1 = CreateProtocolHandler("im", GURL(URL_p2u1));
   registry()->RemoveIgnoredHandler(p2u1);
 
   // p2u1 installed by policy so cant be removed.
   ASSERT_EQ(InPrefIgnoredHandlerCount(), 4);
   ASSERT_EQ(InMemoryIgnoredHandlerCount(), 6);
 
-  ProtocolHandler p1u2 = CreateProtocolHandler("p1", GURL(URL_p1u2));
+  ProtocolHandler p1u2 = CreateProtocolHandler("news", GURL(URL_p1u2));
   registry()->RemoveIgnoredHandler(p1u2);
 
   // p1u2 installed by policy and pref so it is removed from pref and not from
@@ -967,7 +968,7 @@
   ASSERT_EQ(InPrefIgnoredHandlerCount(), 3);
   ASSERT_EQ(InMemoryIgnoredHandlerCount(), 6);
 
-  ProtocolHandler p1u1 = CreateProtocolHandler("p1", GURL(URL_p1u1));
+  ProtocolHandler p1u1 = CreateProtocolHandler("news", GURL(URL_p1u1));
   registry()->RemoveIgnoredHandler(p1u1);
 
   // p1u1 installed by pref so it is removed from pref and memory.
@@ -1060,7 +1061,7 @@
 
 TEST_F(ProtocolHandlerRegistryTest, TestMultiplePlaceholders) {
   ProtocolHandler ph =
-      CreateProtocolHandler("test", GURL("http://example.com/%s/url=%s"));
+      CreateProtocolHandler("news", GURL("http://example.com/%s/url=%s"));
   registry()->OnAcceptRegisterProtocolHandler(ph);
 
   GURL translated_url = ph.TranslateUrl(GURL("test:duplicated_placeholders"));
@@ -1070,3 +1071,28 @@
   ASSERT_EQ(translated_url,
             GURL("http://example.com/test%3Aduplicated_placeholders/url=%s"));
 }
+
+TEST_F(ProtocolHandlerRegistryTest, InvalidHandlers) {
+  // Invalid protocol.
+  registry()->OnAcceptRegisterProtocolHandler(
+      CreateProtocolHandler("foo", GURL("https://www.google.com/handler%s")));
+  ASSERT_FALSE(registry()->IsHandledProtocol("foo"));
+  registry()->OnAcceptRegisterProtocolHandler(
+      CreateProtocolHandler("web", GURL("https://www.google.com/handler%s")));
+  ASSERT_FALSE(registry()->IsHandledProtocol("web"));
+  registry()->OnAcceptRegisterProtocolHandler(
+      CreateProtocolHandler("web+", GURL("https://www.google.com/handler%s")));
+  ASSERT_FALSE(registry()->IsHandledProtocol("web+"));
+  registry()->OnAcceptRegisterProtocolHandler(
+      CreateProtocolHandler("https", GURL("https://www.google.com/handler%s")));
+  ASSERT_FALSE(registry()->IsHandledProtocol("https"));
+
+  // Invalid handler URL.
+  registry()->OnAcceptRegisterProtocolHandler(CreateProtocolHandler(
+      "news",
+      GURL("data:text/html,<html><body><b>hello world</b></body></html>%s")));
+  ASSERT_FALSE(registry()->IsHandledProtocol("news"));
+  registry()->OnAcceptRegisterProtocolHandler(
+      CreateProtocolHandler("news", GURL("ftp://www.google.com/handler%s")));
+  ASSERT_FALSE(registry()->IsHandledProtocol("news"));
+}
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 8746bab3..1bd4259 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -511,6 +511,11 @@
     "expiry_milestone": 77
   },
   {
+    "name": "crostini-webui-installer",
+    "owners": [ "lxj", "timloh", "benwells" ],
+    "expiry_milestone": 82
+  },
+  {
     "name": "crostini-gpu-support",
     "owners": [ "nverne", "benwells" ],
     "expiry_milestone": 78
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 286341c..0f82b5f6 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2998,6 +2998,10 @@
 const char kCrostiniUsbSupportDescription[] =
     "Enable mounting Usb devices in Crostini.";
 
+const char kCrostiniWebUIInstallerName[] = "Crostini WebUI Installer";
+const char kCrostiniWebUIInstallerDescription[] =
+    "Enable the new WebUI Crostini Installer.";
+
 const char kCrosVmCupsProxyName[] = "Chrome OS CUPS Proxy";
 const char kCrosVmCupsProxyDescription[] =
     "Supports printing from VMs on Chrome OS.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 8441f905..d983668 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1791,6 +1791,9 @@
 extern const char kCrostiniUsbSupportName[];
 extern const char kCrostiniUsbSupportDescription[];
 
+extern const char kCrostiniWebUIInstallerName[];
+extern const char kCrostiniWebUIInstallerDescription[];
+
 extern const char kCrosVmCupsProxyName[];
 extern const char kCrosVmCupsProxyDescription[];
 
diff --git a/chrome/browser/history/top_sites_factory.cc b/chrome/browser/history/top_sites_factory.cc
index a365998..c0545fa 100644
--- a/chrome/browser/history/top_sites_factory.cc
+++ b/chrome/browser/history/top_sites_factory.cc
@@ -78,17 +78,30 @@
     history::PrepopulatedPageList* prepopulated_pages) {
 #if !defined(OS_ANDROID)
   DCHECK(prepopulated_pages);
-  bool hide_web_store_icon =
-      profile->GetPrefs()->GetBoolean(prefs::kHideWebStoreIcon);
+  PrefService* pref_service = profile->GetPrefs();
+  bool hide_web_store_icon = pref_service->GetBoolean(prefs::kHideWebStoreIcon);
+
+  // The default shortcut is shown for new profiles, beginning at first run, if
+  // the feature is enabled. A pref is persisted so that the shortcut continues
+  // to be shown through browser restarts, when the profile is no longer
+  // considered "new".
+  bool is_search_shortcut_feature_enabled =
+      base::FeatureList::IsEnabled(features::kFirstRunDefaultSearchShortcut);
+  if (profile->IsNewProfile() && is_search_shortcut_feature_enabled) {
+    pref_service->SetBoolean(prefs::kShowFirstRunDefaultSearchShortcut, true);
+  }
+  bool show_default_search_shortcut =
+      is_search_shortcut_feature_enabled &&
+      pref_service->GetBoolean(prefs::kShowFirstRunDefaultSearchShortcut);
+
   prepopulated_pages->reserve(base::size(kRawPrepopulatedPages));
   for (size_t i = 0; i < base::size(kRawPrepopulatedPages); ++i) {
     const RawPrepopulatedPage& page = kRawPrepopulatedPages[i];
     if (hide_web_store_icon && page.url_id == IDS_WEBSTORE_URL)
       continue;
-    if (page.url_id == IDS_NTP_DEFAULT_SEARCH_URL &&
-        !(profile->IsNewProfile() &&
-          base::FeatureList::IsEnabled(
-              features::kFirstRunDefaultSearchShortcut))) {
+
+    if (!show_default_search_shortcut &&
+        page.url_id == IDS_NTP_DEFAULT_SEARCH_URL) {
       continue;
     }
 
diff --git a/chrome/browser/native_file_system/OWNERS b/chrome/browser/native_file_system/OWNERS
new file mode 100644
index 0000000..7eb6aed
--- /dev/null
+++ b/chrome/browser/native_file_system/OWNERS
@@ -0,0 +1,6 @@
+file://content/browser/native_file_system/OWNERS
+
+per-file *permission_context*=file://chrome/browser/permissions/PERMISSIONS_OWNERS
+
+# TEAM: storage-dev@chromium.org
+# COMPONENT: Blink>Storage>FileSystem
diff --git a/chrome/browser/native_file_system/chrome_native_file_system_permission_context.cc b/chrome/browser/native_file_system/chrome_native_file_system_permission_context.cc
new file mode 100644
index 0000000..070e302
--- /dev/null
+++ b/chrome/browser/native_file_system/chrome_native_file_system_permission_context.cc
@@ -0,0 +1,201 @@
+// 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/native_file_system/chrome_native_file_system_permission_context.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/task/post_task.h"
+#include "chrome/browser/permissions/permission_util.h"
+#include "chrome/browser/ui/browser_dialogs.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+
+namespace {
+
+void ShowWritePermissionPromptOnUIThread(
+    int process_id,
+    int frame_id,
+    const url::Origin& origin,
+    const base::FilePath& path,
+    bool is_directory,
+    base::OnceCallback<void(PermissionAction result)> callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  content::RenderFrameHost* rfh =
+      content::RenderFrameHost::FromID(process_id, frame_id);
+  if (!rfh || !rfh->IsCurrent()) {
+    // Requested from a no longer valid render frame host.
+    std::move(callback).Run(PermissionAction::DISMISSED);
+    return;
+  }
+
+  content::WebContents* web_contents =
+      content::WebContents::FromRenderFrameHost(rfh);
+
+  if (!web_contents) {
+    // Requested from a worker, or a no longer existing tab.
+    std::move(callback).Run(PermissionAction::DISMISSED);
+    return;
+  }
+
+  ShowNativeFileSystemPermissionDialog(origin, path, is_directory,
+                                       std::move(callback), web_contents);
+}
+
+// Returns a callback that calls the passed in |callback| by posting a task to
+// the current sequenced task runner.
+base::OnceCallback<void(PermissionAction result)>
+BindPermissionActionCallbackToCurrentSequence(
+    base::OnceCallback<void(PermissionAction result)> callback) {
+  return base::BindOnce(
+      [](scoped_refptr<base::TaskRunner> task_runner,
+         base::OnceCallback<void(PermissionAction result)> callback,
+         PermissionAction result) {
+        task_runner->PostTask(FROM_HERE,
+                              base::BindOnce(std::move(callback), result));
+      },
+      base::SequencedTaskRunnerHandle::Get(), std::move(callback));
+}
+
+}  // namespace
+
+class ChromeNativeFileSystemPermissionContext::PermissionGrantImpl
+    : public content::NativeFileSystemPermissionGrant {
+ public:
+  PermissionGrantImpl(
+      scoped_refptr<ChromeNativeFileSystemPermissionContext> context,
+      const url::Origin& origin,
+      const base::FilePath& path,
+      bool is_directory)
+      : context_(std::move(context)),
+        origin_(origin),
+        path_(path),
+        is_directory_(is_directory) {}
+
+  const url::Origin& origin() const {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    return origin_;
+  }
+
+  const base::FilePath& path() const {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    return path_;
+  }
+
+  PermissionStatus GetStatus() override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    return status_;
+  }
+
+  void RequestPermission(int process_id,
+                         int frame_id,
+                         base::OnceClosure callback) override {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    if (GetStatus() != PermissionStatus::ASK) {
+      std::move(callback).Run();
+      return;
+    }
+
+    auto result_callback = BindPermissionActionCallbackToCurrentSequence(
+        base::BindOnce(&PermissionGrantImpl::OnPermissionRequestComplete, this,
+                       std::move(callback)));
+
+    base::PostTaskWithTraits(
+        FROM_HERE, {content::BrowserThread::UI},
+        base::BindOnce(&ShowWritePermissionPromptOnUIThread, process_id,
+                       frame_id, origin_, path_, is_directory_,
+                       std::move(result_callback)));
+  }
+
+ protected:
+  ~PermissionGrantImpl() override { context_->PermissionGrantDestroyed(this); }
+
+ private:
+  void OnPermissionRequestComplete(base::OnceClosure callback,
+                                   PermissionAction result) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    switch (result) {
+      case PermissionAction::GRANTED:
+        status_ = PermissionStatus::GRANTED;
+        break;
+      case PermissionAction::DENIED:
+        status_ = PermissionStatus::DENIED;
+        break;
+      case PermissionAction::DISMISSED:
+      case PermissionAction::IGNORED:
+        break;
+      case PermissionAction::REVOKED:
+      case PermissionAction::NUM:
+        NOTREACHED();
+        break;
+    }
+
+    std::move(callback).Run();
+  }
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  scoped_refptr<ChromeNativeFileSystemPermissionContext> const context_;
+  const url::Origin origin_;
+  const base::FilePath path_;
+  const bool is_directory_;
+  PermissionStatus status_ = PermissionStatus::ASK;
+
+  DISALLOW_COPY_AND_ASSIGN(PermissionGrantImpl);
+};
+
+struct ChromeNativeFileSystemPermissionContext::OriginState {
+  // Raw pointers, owned collectively by all the handles that reference this
+  // grant. When last reference goes away this state is cleared as well by
+  // PermissionGrantDestroyed().
+  // TODO(mek): Lifetime of grants might change depending on the outcome of
+  // the discussions surrounding lifetime of non-persistent permissions.
+  std::map<base::FilePath, PermissionGrantImpl*> grants;
+};
+
+ChromeNativeFileSystemPermissionContext::
+    ChromeNativeFileSystemPermissionContext(content::BrowserContext*) {
+  DETACH_FROM_SEQUENCE(sequence_checker_);
+}
+
+scoped_refptr<content::NativeFileSystemPermissionGrant>
+ChromeNativeFileSystemPermissionContext::GetWritePermissionGrant(
+    const url::Origin& origin,
+    const base::FilePath& path,
+    bool is_directory) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // operator[] might insert a new OriginState in |origins_|, but that is
+  // exactly what we want.
+  auto& origin_state = origins_[origin];
+  // TODO(mek): Do some kind of clever merging of grants, for example
+  // returning a parent directory's grant if that one already has write
+  // access.
+  auto*& existing_grant = origin_state.grants[path];
+  if (existing_grant)
+    return existing_grant;
+  auto result = base::MakeRefCounted<PermissionGrantImpl>(this, origin, path,
+                                                          is_directory);
+  existing_grant = result.get();
+  return result;
+}
+
+ChromeNativeFileSystemPermissionContext::
+    ~ChromeNativeFileSystemPermissionContext() = default;
+
+void ChromeNativeFileSystemPermissionContext::ShutdownOnUIThread() {}
+
+void ChromeNativeFileSystemPermissionContext::PermissionGrantDestroyed(
+    PermissionGrantImpl* grant) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  auto it = origins_.find(grant->origin());
+  DCHECK(it != origins_.end());
+  it->second.grants.erase(grant->path());
+  if (it->second.grants.empty()) {
+    // No more grants for the origin, so remove the state.
+    origins_.erase(it);
+  }
+}
diff --git a/chrome/browser/native_file_system/chrome_native_file_system_permission_context.h b/chrome/browser/native_file_system/chrome_native_file_system_permission_context.h
new file mode 100644
index 0000000..e4514ff
--- /dev/null
+++ b/chrome/browser/native_file_system/chrome_native_file_system_permission_context.h
@@ -0,0 +1,68 @@
+// 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_NATIVE_FILE_SYSTEM_CHROME_NATIVE_FILE_SYSTEM_PERMISSION_CONTEXT_H_
+#define CHROME_BROWSER_NATIVE_FILE_SYSTEM_CHROME_NATIVE_FILE_SYSTEM_PERMISSION_CONTEXT_H_
+
+#include <map>
+
+#include "base/sequence_checker.h"
+#include "components/keyed_service/core/refcounted_keyed_service.h"
+#include "content/public/browser/native_file_system_permission_context.h"
+#include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
+
+namespace content {
+class BrowserContext;
+}  // namespace content
+
+// Chrome implementation of NativeFileSystemPermissionContext. Currently
+// implements a single per-origin write permission state.
+//
+// All methods (other than the constructor and destructor) should be called on
+// the same sequence.
+//
+// TODO(mek): Reconsider if this class should just be UI-thread only, avoiding
+// the need to make this ref-counted.
+//
+// This class does not inherit from ChooserContextBase because the model this
+// API uses doesn't really match what ChooserContextBase has to provide. The
+// limited lifetime of native file system permission grants (scoped to the
+// lifetime of the handles that reference the grants), and the possible
+// interactions between grants for directories and grants for children of those
+// directories as well as possible interactions between read and write grants
+// make it harder to squeeze this into a shape that fits with
+// ChooserContextBase.
+class ChromeNativeFileSystemPermissionContext
+    : public content::NativeFileSystemPermissionContext,
+      public RefcountedKeyedService {
+ public:
+  explicit ChromeNativeFileSystemPermissionContext(
+      content::BrowserContext* context);
+
+  // content::NativeFileSystemPermissionContext:
+  scoped_refptr<content::NativeFileSystemPermissionGrant>
+  GetWritePermissionGrant(const url::Origin& origin,
+                          const base::FilePath& path,
+                          bool is_directory) override;
+
+  // RefcountedKeyedService:
+  void ShutdownOnUIThread() override;
+
+ private:
+  // Destructor is private because this class is refcounted.
+  ~ChromeNativeFileSystemPermissionContext() override;
+
+  class PermissionGrantImpl;
+  void PermissionGrantDestroyed(PermissionGrantImpl* grant);
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  // Permission state per origin.
+  struct OriginState;
+  std::map<url::Origin, OriginState> origins_;
+
+  DISALLOW_COPY_AND_ASSIGN(ChromeNativeFileSystemPermissionContext);
+};
+
+#endif  // CHROME_BROWSER_NATIVE_FILE_SYSTEM_CHROME_NATIVE_FILE_SYSTEM_PERMISSION_CONTEXT_H_
diff --git a/chrome/browser/native_file_system/native_file_system_permission_context_factory.cc b/chrome/browser/native_file_system/native_file_system_permission_context_factory.cc
new file mode 100644
index 0000000..4336f58
--- /dev/null
+++ b/chrome/browser/native_file_system/native_file_system_permission_context_factory.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 "chrome/browser/native_file_system/native_file_system_permission_context_factory.h"
+
+#include "base/no_destructor.h"
+#include "chrome/browser/native_file_system/chrome_native_file_system_permission_context.h"
+#include "chrome/browser/profiles/incognito_helpers.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+
+// static
+scoped_refptr<ChromeNativeFileSystemPermissionContext>
+NativeFileSystemPermissionContextFactory::GetForProfile(
+    content::BrowserContext* profile) {
+  return static_cast<ChromeNativeFileSystemPermissionContext*>(
+      GetInstance()->GetServiceForBrowserContext(profile, true).get());
+}
+
+// static
+NativeFileSystemPermissionContextFactory*
+NativeFileSystemPermissionContextFactory::GetInstance() {
+  static base::NoDestructor<NativeFileSystemPermissionContextFactory> instance;
+  return instance.get();
+}
+
+NativeFileSystemPermissionContextFactory::
+    NativeFileSystemPermissionContextFactory()
+    : RefcountedBrowserContextKeyedServiceFactory(
+          "NativeFileSystemPermissionContext",
+          BrowserContextDependencyManager::GetInstance()) {}
+
+NativeFileSystemPermissionContextFactory::
+    ~NativeFileSystemPermissionContextFactory() = default;
+
+content::BrowserContext*
+NativeFileSystemPermissionContextFactory::GetBrowserContextToUse(
+    content::BrowserContext* context) const {
+  return chrome::GetBrowserContextOwnInstanceInIncognito(context);
+}
+
+scoped_refptr<RefcountedKeyedService>
+NativeFileSystemPermissionContextFactory::BuildServiceInstanceFor(
+    content::BrowserContext* profile) const {
+  return new ChromeNativeFileSystemPermissionContext(profile);
+}
diff --git a/chrome/browser/native_file_system/native_file_system_permission_context_factory.h b/chrome/browser/native_file_system/native_file_system_permission_context_factory.h
new file mode 100644
index 0000000..432ba1f
--- /dev/null
+++ b/chrome/browser/native_file_system/native_file_system_permission_context_factory.h
@@ -0,0 +1,38 @@
+// 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_NATIVE_FILE_SYSTEM_NATIVE_FILE_SYSTEM_PERMISSION_CONTEXT_FACTORY_H_
+#define CHROME_BROWSER_NATIVE_FILE_SYSTEM_NATIVE_FILE_SYSTEM_PERMISSION_CONTEXT_FACTORY_H_
+
+#include "base/macros.h"
+#include "base/no_destructor.h"
+#include "components/keyed_service/content/refcounted_browser_context_keyed_service_factory.h"
+
+class ChromeNativeFileSystemPermissionContext;
+
+// Factory to get or create an instance of
+// ChromeNativeFileSystemPermissionContext from a Profile.
+class NativeFileSystemPermissionContextFactory
+    : public RefcountedBrowserContextKeyedServiceFactory {
+ public:
+  static scoped_refptr<ChromeNativeFileSystemPermissionContext> GetForProfile(
+      content::BrowserContext* profile);
+  static NativeFileSystemPermissionContextFactory* GetInstance();
+
+ private:
+  friend class base::NoDestructor<NativeFileSystemPermissionContextFactory>;
+
+  NativeFileSystemPermissionContextFactory();
+  ~NativeFileSystemPermissionContextFactory() override;
+
+  // RefcountedBrowserContextKeyedServiceFactory:
+  content::BrowserContext* GetBrowserContextToUse(
+      content::BrowserContext* context) const override;
+  scoped_refptr<RefcountedKeyedService> BuildServiceInstanceFor(
+      content::BrowserContext* profile) const override;
+
+  DISALLOW_COPY_AND_ASSIGN(NativeFileSystemPermissionContextFactory);
+};
+
+#endif  // CHROME_BROWSER_NATIVE_FILE_SYSTEM_NATIVE_FILE_SYSTEM_PERMISSION_CONTEXT_FACTORY_H_
diff --git a/chrome/browser/password_manager/password_accessory_controller_impl.cc b/chrome/browser/password_manager/password_accessory_controller_impl.cc
index 625771af..014e27a 100644
--- a/chrome/browser/password_manager/password_accessory_controller_impl.cc
+++ b/chrome/browser/password_manager/password_accessory_controller_impl.cc
@@ -30,6 +30,7 @@
 #include "components/favicon_base/favicon_types.h"
 #include "components/password_manager/content/browser/content_password_manager_driver.h"
 #include "components/password_manager/content/browser/content_password_manager_driver_factory.h"
+#include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h"
 #include "components/password_manager/core/browser/password_manager_driver.h"
 #include "components/password_manager/core/browser/password_manager_util.h"
 #include "components/password_manager/core/common/password_manager_features.h"
@@ -49,8 +50,15 @@
 namespace {
 
 autofill::UserInfo TranslateCredentials(bool current_field_is_password,
+                                        const GURL& origin_url,
                                         const CredentialPair& data) {
-  UserInfo user_info;
+  std::string user_info_origin;
+  // Use the origin only when it differs from the site origin. Android origins
+  // have a path but empty hosts. Since they are treated as first-party
+  // credentials, they will have an empty origin.
+  if (data.is_public_suffix_match)
+    user_info_origin = data.origin_url.host();
+  UserInfo user_info(user_info_origin);
 
   base::string16 username = GetDisplayUsername(data);
   user_info.add_field(UserInfo::Field(
@@ -243,10 +251,11 @@
     for (const auto& pair : suggestions) {
       if (pair.is_public_suffix_match &&
           !base::FeatureList::IsEnabled(
-              autofill::features::kAutofillKeyboardAccessory))
+              autofill::features::kAutofillKeyboardAccessory)) {
         continue;  // PSL origins have no representation in V1. Don't show them!
-      // TODO(crbug.com/976761): Mark PSL-matches with their origin.
-      info_to_add.push_back(TranslateCredentials(is_password_field, pair));
+      }
+      info_to_add.push_back(
+          TranslateCredentials(is_password_field, origin.GetURL(), pair));
     }
   }
 
diff --git a/chrome/browser/password_manager/password_accessory_controller_impl_unittest.cc b/chrome/browser/password_manager/password_accessory_controller_impl_unittest.cc
index bc9f003..3bdee5d 100644
--- a/chrome/browser/password_manager/password_accessory_controller_impl_unittest.cc
+++ b/chrome/browser/password_manager/password_accessory_controller_impl_unittest.cc
@@ -15,11 +15,13 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/mock_callback.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/autofill/mock_manual_filling_controller.h"
 #include "chrome/browser/password_manager/password_generation_controller.h"
 #include "chrome/browser/password_manager/password_generation_controller_impl.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/password_form.h"
 #include "components/autofill/core/common/signatures_util.h"
 #include "components/favicon/core/test/mock_favicon_service.h"
@@ -48,11 +50,14 @@
 using testing::Mock;
 using testing::NiceMock;
 using testing::Return;
+using testing::SaveArg;
 using testing::StrictMock;
 using FillingSource = ManualFillingController::FillingSource;
 
 constexpr char kExampleSite[] = "https://example.com";
+constexpr char kExampleSiteMobile[] = "https://m.example.com";
 constexpr char kExampleDomain[] = "example.com";
+constexpr char kExampleDomainMobile[] = "m.example.com";
 constexpr int kIconSize = 75;  // An example size for favicons (=> 3.5*20px).
 
 class MockPasswordGenerationController
@@ -220,34 +225,35 @@
        CreateEntry("Cat", "M1@u", GURL(kExampleDomain), false).first},
       url::Origin::Create(GURL(kExampleSite)));
 
-  EXPECT_CALL(
-      mock_manual_filling_controller_,
-      RefreshSuggestions(
-          PasswordAccessorySheetDataBuilder(passwords_title_str(kExampleDomain))
-              .AddUserInfo()
-              .AppendField(ASCIIToUTF16("Alf"), ASCIIToUTF16("Alf"), false,
-                           true)
-              .AppendField(ASCIIToUTF16("PWD"), password_for_str("Alf"), true,
-                           false)
-              .AddUserInfo()
-              .AppendField(ASCIIToUTF16("Ben"), ASCIIToUTF16("Ben"), false,
-                           true)
-              .AppendField(ASCIIToUTF16("S3cur3"), password_for_str("Ben"),
-                           true, false)
-              .AddUserInfo()
-              .AppendField(ASCIIToUTF16("Cat"), ASCIIToUTF16("Cat"), false,
-                           true)
-              .AppendField(ASCIIToUTF16("M1@u"), password_for_str("Cat"), true,
-                           false)
-              .AddUserInfo()
-              .AppendField(ASCIIToUTF16("Zebra"), ASCIIToUTF16("Zebra"), false,
-                           true)
-              .AppendField(ASCIIToUTF16("M3h"), password_for_str("Zebra"), true,
-                           false)
-              .Build()));
+  AccessorySheetData result(AccessoryTabType::PASSWORDS, base::string16());
+  EXPECT_CALL(mock_manual_filling_controller_, RefreshSuggestions)
+      .WillOnce(SaveArg<0>(&result));
+
   controller()->RefreshSuggestionsForField(
       FocusedFieldType::kFillableUsernameField,
       /*is_manual_generation_available=*/false);
+
+  EXPECT_EQ(
+      result,
+      PasswordAccessorySheetDataBuilder(passwords_title_str(kExampleDomain))
+          .AddUserInfo()
+          .AppendField(ASCIIToUTF16("Alf"), ASCIIToUTF16("Alf"), false, true)
+          .AppendField(ASCIIToUTF16("PWD"), password_for_str("Alf"), true,
+                       false)
+          .AddUserInfo()
+          .AppendField(ASCIIToUTF16("Ben"), ASCIIToUTF16("Ben"), false, true)
+          .AppendField(ASCIIToUTF16("S3cur3"), password_for_str("Ben"), true,
+                       false)
+          .AddUserInfo()
+          .AppendField(ASCIIToUTF16("Cat"), ASCIIToUTF16("Cat"), false, true)
+          .AppendField(ASCIIToUTF16("M1@u"), password_for_str("Cat"), true,
+                       false)
+          .AddUserInfo()
+          .AppendField(ASCIIToUTF16("Zebra"), ASCIIToUTF16("Zebra"), false,
+                       true)
+          .AppendField(ASCIIToUTF16("M3h"), password_for_str("Zebra"), true,
+                       false)
+          .Build());
 }
 
 TEST_F(PasswordAccessoryControllerTest, RepeatsSuggestionsForSameFrame) {
@@ -371,6 +377,67 @@
       /*is_manual_generation_available=*/false);
 }
 
+TEST_F(PasswordAccessoryControllerTest, HidesEntriesForPSLMatchedOriginsInV1) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(
+      autofill::features::kAutofillKeyboardAccessory);
+  cache()->SaveCredentialsForOrigin(
+      {CreateEntry("Ben", "S3cur3", GURL(kExampleSite), false).first,
+       CreateEntry("Alf", "R4nd0m", GURL(kExampleSiteMobile), true).first},
+      url::Origin::Create(GURL(kExampleSite)));
+
+  AccessorySheetData result(AccessoryTabType::PASSWORDS, base::string16());
+  EXPECT_CALL(mock_manual_filling_controller_, RefreshSuggestions)
+      .WillOnce(SaveArg<0>(&result));
+
+  controller()->RefreshSuggestionsForField(
+      FocusedFieldType::kFillableUsernameField,
+      /*is_manual_generation_available=*/false);
+
+  EXPECT_EQ(
+      result,
+      PasswordAccessorySheetDataBuilder(passwords_title_str(kExampleDomain))
+          .AddUserInfo()
+          .AppendField(ASCIIToUTF16("Ben"), ASCIIToUTF16("Ben"),
+                       /*is_obfuscated=*/false, /*selectable=*/true)
+          .AppendField(ASCIIToUTF16("S3cur3"), password_for_str("Ben"),
+                       /*is_obfuscated=*/true, /*selectable=*/false)
+          .Build());
+}
+
+TEST_F(PasswordAccessoryControllerTest, SetsTitleForPSLMatchedOriginsInV2) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      autofill::features::kAutofillKeyboardAccessory);
+  cache()->SaveCredentialsForOrigin(
+      {CreateEntry("Ben", "S3cur3", GURL(kExampleSite), false).first,
+       CreateEntry("Alf", "R4nd0m", GURL(kExampleSiteMobile), true).first},
+      url::Origin::Create(GURL(kExampleSite)));
+
+  AccessorySheetData result(AccessoryTabType::PASSWORDS, base::string16());
+  EXPECT_CALL(mock_manual_filling_controller_, RefreshSuggestions)
+      .WillOnce(SaveArg<0>(&result));
+
+  controller()->RefreshSuggestionsForField(
+      FocusedFieldType::kFillableUsernameField,
+      /*is_manual_generation_available=*/false);
+
+  EXPECT_EQ(
+      result,
+      PasswordAccessorySheetDataBuilder(passwords_title_str(kExampleDomain))
+          .AddUserInfo()
+          .AppendField(ASCIIToUTF16("Ben"), ASCIIToUTF16("Ben"),
+                       /*is_obfuscated=*/false, /*selectable=*/true)
+          .AppendField(ASCIIToUTF16("S3cur3"), password_for_str("Ben"),
+                       /*is_obfuscated=*/true, /*selectable=*/false)
+          .AddUserInfo(kExampleDomainMobile)
+          .AppendField(ASCIIToUTF16("Alf"), ASCIIToUTF16("Alf"),
+                       /*is_obfuscated=*/false, /*selectable=*/true)
+          .AppendField(ASCIIToUTF16("R4nd0m"), password_for_str("Alf"),
+                       /*is_obfuscated=*/true, /*selectable=*/false)
+          .Build());
+}
+
 TEST_F(PasswordAccessoryControllerTest, UnfillableFieldClearsSuggestions) {
   cache()->SaveCredentialsForOrigin(
       {CreateEntry("Ben", "S3cur3", GURL(kExampleDomain), false).first},
@@ -440,7 +507,7 @@
 TEST_F(PasswordAccessoryControllerTest, FetchFaviconForCurrentUrl) {
   base::MockCallback<base::OnceCallback<void(const gfx::Image&)>> mock_callback;
 
-  EXPECT_CALL(mock_manual_filling_controller_, RefreshSuggestions(_));
+  EXPECT_CALL(mock_manual_filling_controller_, RefreshSuggestions);
   controller()->RefreshSuggestionsForField(
       FocusedFieldType::kFillableUsernameField,
       /*is_manual_generation_available=*/false);
@@ -463,7 +530,7 @@
 TEST_F(PasswordAccessoryControllerTest, RequestsFaviconsOnceForOneOrigin) {
   base::MockCallback<base::OnceCallback<void(const gfx::Image&)>> mock_callback;
 
-  EXPECT_CALL(mock_manual_filling_controller_, RefreshSuggestions(_));
+  EXPECT_CALL(mock_manual_filling_controller_, RefreshSuggestions);
   controller()->RefreshSuggestionsForField(
       FocusedFieldType::kFillableUsernameField,
       /*is_manual_generation_available=*/false);
@@ -506,7 +573,7 @@
   non_empty_result.icon_url = GURL(kExampleSite);
 
   // Populate the cache by requesting a favicon.
-  EXPECT_CALL(mock_manual_filling_controller_, RefreshSuggestions(_));
+  EXPECT_CALL(mock_manual_filling_controller_, RefreshSuggestions);
   controller()->RefreshSuggestionsForField(
       FocusedFieldType::kFillableUsernameField,
       /*is_manual_generation_available=*/false);
@@ -537,7 +604,7 @@
   controller()->DidNavigateMainFrame();
   NavigateAndCommit(GURL(kExampleSite));  // Same origin as intially.
   controller()->DidNavigateMainFrame();
-  EXPECT_CALL(mock_manual_filling_controller_, RefreshSuggestions(_));
+  EXPECT_CALL(mock_manual_filling_controller_, RefreshSuggestions);
   controller()->RefreshSuggestionsForField(
       FocusedFieldType::kFillableUsernameField,
       /*is_manual_generation_available=*/false);
@@ -560,7 +627,7 @@
 TEST_F(PasswordAccessoryControllerTest, NoFaviconCallbacksWhenOriginChanges) {
   base::MockCallback<base::OnceCallback<void(const gfx::Image&)>> mock_callback;
 
-  EXPECT_CALL(mock_manual_filling_controller_, RefreshSuggestions(_)).Times(2);
+  EXPECT_CALL(mock_manual_filling_controller_, RefreshSuggestions).Times(2);
   controller()->RefreshSuggestionsForField(
       FocusedFieldType::kFillableUsernameField, false);
 
diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc
index 99396124..395887c 100644
--- a/chrome/browser/profiles/profile_impl.cc
+++ b/chrome/browser/profiles/profile_impl.cc
@@ -55,6 +55,8 @@
 #include "chrome/browser/download/download_manager_utils.h"
 #include "chrome/browser/gcm/gcm_profile_service_factory.h"
 #include "chrome/browser/media/media_device_id_salt.h"
+#include "chrome/browser/native_file_system/chrome_native_file_system_permission_context.h"
+#include "chrome/browser/native_file_system/native_file_system_permission_context_factory.h"
 #include "chrome/browser/net/system_network_context_manager.h"
 #include "chrome/browser/permissions/permission_manager.h"
 #include "chrome/browser/permissions/permission_manager_factory.h"
@@ -1354,6 +1356,11 @@
   return SmsServiceFactory::GetForProfile(this)->Get();
 }
 
+content::NativeFileSystemPermissionContext*
+ProfileImpl::GetNativeFileSystemPermissionContext() {
+  return NativeFileSystemPermissionContextFactory::GetForProfile(this).get();
+}
+
 bool ProfileImpl::IsSameProfile(Profile* profile) {
   if (profile == static_cast<Profile*>(this))
     return true;
diff --git a/chrome/browser/profiles/profile_impl.h b/chrome/browser/profiles/profile_impl.h
index c93da7f..01f29579 100644
--- a/chrome/browser/profiles/profile_impl.h
+++ b/chrome/browser/profiles/profile_impl.h
@@ -38,7 +38,7 @@
 class LocaleChangeGuard;
 class Preferences;
 class SupervisedUserTestBase;
-}
+}  // namespace chromeos
 #endif
 
 namespace base {
@@ -108,6 +108,8 @@
   download::InProgressDownloadManager* RetriveInProgressDownloadManager()
       override;
   content::SmsService* GetSmsService() override;
+  content::NativeFileSystemPermissionContext*
+  GetNativeFileSystemPermissionContext() override;
 
   // Profile implementation:
   scoped_refptr<base::SequencedTaskRunner> GetIOTaskRunner() override;
diff --git a/chrome/browser/resources/about_nacl/about_nacl.html b/chrome/browser/resources/about_nacl/about_nacl.html
index a67298c4..c804549 100644
--- a/chrome/browser/resources/about_nacl/about_nacl.html
+++ b/chrome/browser/resources/about_nacl/about_nacl.html
@@ -1,5 +1,5 @@
 <!doctype html>
-<html dir="$i18n{textdirection}" lang="$i18n{language}">
+<html dir="ltr" lang="en">
 <head>
   <meta charset="utf-8">
   <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
diff --git a/chrome/browser/resources/chromeos/network_ui/network_ui.html b/chrome/browser/resources/chromeos/network_ui/network_ui.html
index be60a47..6303311 100644
--- a/chrome/browser/resources/chromeos/network_ui/network_ui.html
+++ b/chrome/browser/resources/chromeos/network_ui/network_ui.html
@@ -37,6 +37,13 @@
     <div id="cellular-error-text" hidden>$i18n{noCellularErrorText}</div>
   </div>
 
+  <div>
+    <h2>$i18n{addNewWifiLabel}</h2>
+    <cr-button class="action-button" id="add-new-wifi-button">
+      $i18n{addNewWifiButtonText}
+    </cr-button>
+  </div>
+
   <h2>$i18n{globalPolicyLabel}</h2>
   <div id="global-policy"></div>
 
diff --git a/chrome/browser/resources/chromeos/network_ui/network_ui.js b/chrome/browser/resources/chromeos/network_ui/network_ui.js
index 0e672d7..cb60f5a 100644
--- a/chrome/browser/resources/chromeos/network_ui/network_ui.js
+++ b/chrome/browser/resources/chromeos/network_ui/network_ui.js
@@ -457,6 +457,13 @@
   };
 
   /**
+   * Requests that the "add Wi-Fi network" UI be displayed.
+   */
+  const showAddNewWifi = function() {
+    chrome.send('showAddNewWifi');
+  };
+
+  /**
    * Requests an update of all network info.
    */
   const requestNetworks = function() {
@@ -555,6 +562,7 @@
     ];
     select.addEventListener('network-item-selected', onNetworkItemSelected);
     $('cellular-activation-button').onclick = openCellularActivationUi;
+    $('add-new-wifi-button').onclick = showAddNewWifi;
     $('refresh').onclick = requestNetworks;
     init();
     requestNetworks();
diff --git a/chrome/browser/resources/chromeos/switch_access/icons/back.svg b/chrome/browser/resources/chromeos/switch_access/icons/back.svg
index 6d3ce94..22d935b 100644
--- a/chrome/browser/resources/chromeos/switch_access/icons/back.svg
+++ b/chrome/browser/resources/chromeos/switch_access/icons/back.svg
@@ -2,7 +2,7 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 24 24" fill="#FFFFFF">
+<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" viewBox="0 0 24 24" fill="#E8EAED">
     <path fill="none" d="M0 0h24v24H0V0z"/>
     <path d="M19 15l-6 6-1.42-1.42L15.17 16H4V4h2v10h9.17l-3.59-3.58L13 9l6 6z"/>
 </svg>
diff --git a/chrome/browser/resources/chromeos/switch_access/icons/decrement.svg b/chrome/browser/resources/chromeos/switch_access/icons/decrement.svg
index 1fe4eec2..0ac1823 100644
--- a/chrome/browser/resources/chromeos/switch_access/icons/decrement.svg
+++ b/chrome/browser/resources/chromeos/switch_access/icons/decrement.svg
@@ -2,4 +2,4 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<svg height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="m14 9v2h-8v-2zm-4-7c-4.424 0-8 3.576-8 8s3.576 8 8 8 8-3.576 8-8-3.576-8-8-8zm0 14c-3.3075 0-6-2.6925-6-6s2.6925-6 6-6 6 2.6925 6 6-2.6925 6-6 6z" fill="#5f6368" fill-rule="evenodd"/></svg>
+<svg height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="m14 9v2h-8v-2zm-4-7c-4.424 0-8 3.576-8 8s3.576 8 8 8 8-3.576 8-8-3.576-8-8-8zm0 14c-3.3075 0-6-2.6925-6-6s2.6925-6 6-6 6 2.6925 6 6-2.6925 6-6 6z" fill="#E8EAED" fill-rule="evenodd"/></svg>
diff --git a/chrome/browser/resources/chromeos/switch_access/icons/increment.svg b/chrome/browser/resources/chromeos/switch_access/icons/increment.svg
index 05129d68..f71fb78d 100644
--- a/chrome/browser/resources/chromeos/switch_access/icons/increment.svg
+++ b/chrome/browser/resources/chromeos/switch_access/icons/increment.svg
@@ -2,4 +2,4 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<svg height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="m14 9v2h-3v3h-2v-3h-3v-2h3v-3h2v3zm-4-7c-4.424 0-8 3.576-8 8s3.576 8 8 8 8-3.576 8-8-3.576-8-8-8zm0 14c-3.3075 0-6-2.6925-6-6s2.6925-6 6-6 6 2.6925 6 6-2.6925 6-6 6z" fill="#5f6368" fill-rule="evenodd"/></svg>
+<svg height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="m14 9v2h-3v3h-2v-3h-3v-2h3v-3h2v3zm-4-7c-4.424 0-8 3.576-8 8s3.576 8 8 8 8-3.576 8-8-3.576-8-8-8zm0 14c-3.3075 0-6-2.6925-6-6s2.6925-6 6-6 6 2.6925 6 6-2.6925 6-6 6z" fill="#E8EAED" fill-rule="evenodd"/></svg>
diff --git a/chrome/browser/resources/chromeos/switch_access/icons/keyboard.svg b/chrome/browser/resources/chromeos/switch_access/icons/keyboard.svg
index 56f56a3..0b9ef62 100644
--- a/chrome/browser/resources/chromeos/switch_access/icons/keyboard.svg
+++ b/chrome/browser/resources/chromeos/switch_access/icons/keyboard.svg
@@ -2,4 +2,4 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0zm0 0h24v24H0V0z"/><path fill="white" d="M20 7v10H4V7h16m0-2H4c-1.1 0-1.99.9-1.99 2L2 17c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-9 3h2v2h-2zm0 3h2v2h-2zM8 8h2v2H8zm0 3h2v2H8zm-3 0h2v2H5zm0-3h2v2H5zm3 6h8v2H8zm6-3h2v2h-2zm0-3h2v2h-2zm3 3h2v2h-2zm0-3h2v2h-2z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0zm0 0h24v24H0V0z"/><path fill="#E8EAED" d="M20 7v10H4V7h16m0-2H4c-1.1 0-1.99.9-1.99 2L2 17c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-9 3h2v2h-2zm0 3h2v2h-2zM8 8h2v2H8zm0 3h2v2H8zm-3 0h2v2H5zm0-3h2v2H5zm3 6h8v2H8zm6-3h2v2h-2zm0-3h2v2h-2zm3 3h2v2h-2zm0-3h2v2h-2z"/></svg>
diff --git a/chrome/browser/resources/chromeos/switch_access/icons/options.svg b/chrome/browser/resources/chromeos/switch_access/icons/options.svg
index a1111e5..ae7e005 100644
--- a/chrome/browser/resources/chromeos/switch_access/icons/options.svg
+++ b/chrome/browser/resources/chromeos/switch_access/icons/options.svg
@@ -2,4 +2,4 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<svg height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="m14 10c0-1.104.896-2 2-2 1.105 0 2 .896 2 2 0 1.105-.895 2-2 2-1.104 0-2-.895-2-2zm-2 0c0 1.105-.895 2-2 2-1.104 0-2-.895-2-2 0-1.104.896-2 2-2 1.105 0 2 .896 2 2zm-6 0c0 1.105-.895 2-2 2-1.104 0-2-.895-2-2 0-1.104.896-2 2-2 1.105 0 2 .896 2 2z" fill="#5f6368" fill-rule="evenodd" transform="matrix(0 1 -1 0 20 0)"/></svg>
+<svg height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="m14 10c0-1.104.896-2 2-2 1.105 0 2 .896 2 2 0 1.105-.895 2-2 2-1.104 0-2-.895-2-2zm-2 0c0 1.105-.895 2-2 2-1.104 0-2-.895-2-2 0-1.104.896-2 2-2 1.105 0 2 .896 2 2zm-6 0c0 1.105-.895 2-2 2-1.104 0-2-.895-2-2 0-1.104.896-2 2-2 1.105 0 2 .896 2 2z" fill="#E8EAED" fill-rule="evenodd" transform="matrix(0 1 -1 0 20 0)"/></svg>
diff --git a/chrome/browser/resources/chromeos/switch_access/icons/scrollDownOrForward.svg b/chrome/browser/resources/chromeos/switch_access/icons/scrollDownOrForward.svg
index 099c833..6a89643 100644
--- a/chrome/browser/resources/chromeos/switch_access/icons/scrollDownOrForward.svg
+++ b/chrome/browser/resources/chromeos/switch_access/icons/scrollDownOrForward.svg
@@ -2,4 +2,4 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<svg height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="m11 9.71012132 1.872-1.95466677 1.128 1.15363636-4 4.09090909-4-4.09090909 1.128-1.15363636 1.872 1.87363481v-6.62908936h2zm3 7.28987868h-8v-2h8z" fill="#5f6368" fill-rule="evenodd"/></svg>
+<svg height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="m11 9.71012132 1.872-1.95466677 1.128 1.15363636-4 4.09090909-4-4.09090909 1.128-1.15363636 1.872 1.87363481v-6.62908936h2zm3 7.28987868h-8v-2h8z" fill="#E8EAED" fill-rule="evenodd"/></svg>
diff --git a/chrome/browser/resources/chromeos/switch_access/icons/scrollLeft.svg b/chrome/browser/resources/chromeos/switch_access/icons/scrollLeft.svg
index 99c3b2c..6df8c319 100644
--- a/chrome/browser/resources/chromeos/switch_access/icons/scrollLeft.svg
+++ b/chrome/browser/resources/chromeos/switch_access/icons/scrollLeft.svg
@@ -2,4 +2,4 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<svg height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="m10.2898787 11 1.9546668 1.872-1.1536364 1.128-4.0909091-4 4.0909091-4 1.1536364 1.128-1.8736349 1.872h6.6290894v2zm-7.2898787 3v-8h2v8z" fill="#5f6368" fill-rule="evenodd"/></svg>
+<svg height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="m10.2898787 11 1.9546668 1.872-1.1536364 1.128-4.0909091-4 4.0909091-4 1.1536364 1.128-1.8736349 1.872h6.6290894v2zm-7.2898787 3v-8h2v8z" fill="#E8EAED" fill-rule="evenodd"/></svg>
diff --git a/chrome/browser/resources/chromeos/switch_access/icons/scrollRight.svg b/chrome/browser/resources/chromeos/switch_access/icons/scrollRight.svg
index 95b4116..72a01a6 100644
--- a/chrome/browser/resources/chromeos/switch_access/icons/scrollRight.svg
+++ b/chrome/browser/resources/chromeos/switch_access/icons/scrollRight.svg
@@ -2,4 +2,4 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<svg height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="m11 9.71012132 1.872-1.95466677 1.128 1.15363636-4 4.09090909-4-4.09090909 1.128-1.15363636 1.872 1.87363481v-6.62908936h2zm3 7.28987868h-8v-2h8z" fill="#5f6368" fill-rule="evenodd" transform="matrix(0 -1 1 0 0 20)"/></svg>
+<svg height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="m11 9.71012132 1.872-1.95466677 1.128 1.15363636-4 4.09090909-4-4.09090909 1.128-1.15363636 1.872 1.87363481v-6.62908936h2zm3 7.28987868h-8v-2h8z" fill="#E8EAED" fill-rule="evenodd" transform="matrix(0 -1 1 0 0 20)"/></svg>
diff --git a/chrome/browser/resources/chromeos/switch_access/icons/scrollUpOrBackward.svg b/chrome/browser/resources/chromeos/switch_access/icons/scrollUpOrBackward.svg
index 0fe61678..b6fff2ee 100644
--- a/chrome/browser/resources/chromeos/switch_access/icons/scrollUpOrBackward.svg
+++ b/chrome/browser/resources/chromeos/switch_access/icons/scrollUpOrBackward.svg
@@ -2,4 +2,4 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<svg height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="m9 10.2898787-1.872 1.9546668-1.128-1.1536364 4-4.0909091 4 4.0909091-1.128 1.1536364-1.872-1.8736349v6.6290894h-2zm-3-7.2898787h8v2h-8z" fill="#5f6368" fill-rule="evenodd"/></svg>
+<svg height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="m9 10.2898787-1.872 1.9546668-1.128-1.1536364 4-4.0909091 4 4.0909091-1.128 1.1536364-1.872-1.8736349v6.6290894h-2zm-3-7.2898787h8v2h-8z" fill="#E8EAED" fill-rule="evenodd"/></svg>
diff --git a/chrome/browser/resources/chromeos/switch_access/icons/select.svg b/chrome/browser/resources/chromeos/switch_access/icons/select.svg
index a90933e..f4a8ac1a 100644
--- a/chrome/browser/resources/chromeos/switch_access/icons/select.svg
+++ b/chrome/browser/resources/chromeos/switch_access/icons/select.svg
@@ -2,4 +2,4 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<svg height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="m3 3h2v2h-2zm0 4h2v2h-2zm0 4h2v2h-2zm0 4h2v2h-2zm4 0h2v2h-2zm4 0h2v2h-2zm4 0h2v2h-2zm0-4h2v2h-2zm0-4h2v2h-2zm0-4h2v2h-2zm-4 0h2v2h-2zm-4 0h2v2h-2z" fill="#5f6368" fill-rule="evenodd"/></svg>
+<svg height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="m3 3h2v2h-2zm0 4h2v2h-2zm0 4h2v2h-2zm0 4h2v2h-2zm4 0h2v2h-2zm4 0h2v2h-2zm4 0h2v2h-2zm0-4h2v2h-2zm0-4h2v2h-2zm0-4h2v2h-2zm-4 0h2v2h-2zm-4 0h2v2h-2z" fill="#E8EAED" fill-rule="evenodd"/></svg>
diff --git a/chrome/browser/resources/chromeos/switch_access/icons/showContextMenu.svg b/chrome/browser/resources/chromeos/switch_access/icons/showContextMenu.svg
index eea6f9b..ede6b6a 100644
--- a/chrome/browser/resources/chromeos/switch_access/icons/showContextMenu.svg
+++ b/chrome/browser/resources/chromeos/switch_access/icons/showContextMenu.svg
@@ -2,4 +2,4 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<svg height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="m5 2h10c1.1045695 0 2 .8954305 2 2v12c0 1.1045695-.8954305 2-2 2h-10c-1.1045695 0-2-.8954305-2-2v-12c0-1.1045695.8954305-2 2-2zm0 2v12h10v-12zm2 2h6v2h-6zm0 3h6v2h-6zm0 3h4v2h-4z" fill="#5f6368" fill-rule="evenodd"/></svg>
+<svg height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="m5 2h10c1.1045695 0 2 .8954305 2 2v12c0 1.1045695-.8954305 2-2 2h-10c-1.1045695 0-2-.8954305-2-2v-12c0-1.1045695.8954305-2 2-2zm0 2v12h10v-12zm2 2h6v2h-6zm0 3h6v2h-6zm0 3h4v2h-4z" fill="#E8EAED" fill-rule="evenodd"/></svg>
diff --git a/chrome/browser/resources/domain_reliability_internals/domain_reliability_internals.html b/chrome/browser/resources/domain_reliability_internals/domain_reliability_internals.html
index 7bb2a1b8..10203ebc 100644
--- a/chrome/browser/resources/domain_reliability_internals/domain_reliability_internals.html
+++ b/chrome/browser/resources/domain_reliability_internals/domain_reliability_internals.html
@@ -1,5 +1,5 @@
 <!doctype html>
-<html dir="$i18n{textdirection}" lang="$i18n{language}">
+<html dir="ltr" lang="en">
 <head>
   <meta charset="utf-8">
   <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
diff --git a/chrome/browser/resources/local_ntp/custom_links_edit.css b/chrome/browser/resources/local_ntp/custom_links_edit.css
index b0edd041..a8864a26 100644
--- a/chrome/browser/resources/local_ntp/custom_links_edit.css
+++ b/chrome/browser/resources/local_ntp/custom_links_edit.css
@@ -26,9 +26,8 @@
 
 @media (prefers-color-scheme: dark) {
   #edit-link-dialog {
-    background-color: rgb(41, 42, 45);
-    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .3),
-        0 4px 8px 3px rgba(0, 0, 0, .15);
+    background-color: rgb(var(--dark-mode-dialog-rgb));
+    box-shadow: var(--dark-mode-shadow);
   }
 }
 
diff --git a/chrome/browser/resources/local_ntp/customize.css b/chrome/browser/resources/local_ntp/customize.css
index 3414057..0022263 100644
--- a/chrome/browser/resources/local_ntp/customize.css
+++ b/chrome/browser/resources/local_ntp/customize.css
@@ -49,7 +49,7 @@
 
 @media (prefers-color-scheme: dark) {
   #edit-bg.ep-enhanced {
-    background-color: rgb(41, 42, 45);
+    background-color: rgb(var(--dark-mode-dialog-rgb));
   }
 }
 
@@ -142,9 +142,8 @@
 
 @media (prefers-color-scheme: dark) {
   #edit-bg-dialog {
-    background-color: rgb(41, 42, 45);
-    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .3),
-        0 4px 8px 3px rgba(0, 0, 0, .15);
+    background-color: rgb(var(--dark-mode-dialog-rgb));
+    box-shadow: var(--dark-mode-shadow);
   }
 }
 
@@ -298,10 +297,9 @@
 
 @media (prefers-color-scheme: dark) {
   #bg-sel-menu {
-    background-color: rgb(41, 42, 45);
-    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .3),
-        0 4px 8px 3px rgba(0, 0, 0, .15);
-    }
+    background-color: rgb(var(--dark-mode-dialog-rgb));
+    box-shadow: var(--dark-mode-shadow);
+  }
 }
 
 /* The width is decided by the longest text length plus 16px margin on the
diff --git a/chrome/browser/resources/local_ntp/doodles.css b/chrome/browser/resources/local_ntp/doodles.css
index e2472dd..4e797d9d 100644
--- a/chrome/browser/resources/local_ntp/doodles.css
+++ b/chrome/browser/resources/local_ntp/doodles.css
@@ -242,9 +242,8 @@
 
 @media (prefers-color-scheme: dark) {
   #ddlsd {
-    background-color: rgb(41, 42, 45);
-    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3),
-        0 4px 8px 3px rgba(0, 0, 0, 0.15);
+    background-color: rgb(var(--dark-mode-dialog-rgb));
+    box-shadow: var(--dark-mode-shadow);
   }
 
   #ddlsd::backdrop {
diff --git a/chrome/browser/resources/local_ntp/local_ntp.css b/chrome/browser/resources/local_ntp/local_ntp.css
index fa419de6..c82d91f4 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.css
+++ b/chrome/browser/resources/local_ntp/local_ntp.css
@@ -817,9 +817,8 @@
 
 @media (prefers-color-scheme: dark) {
   #customization-menu {
-    background-color: rgb(41, 42, 45);
-    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .3),
-        0 4px 8px 3px rgba(0, 0, 0, .15);
+    background-color: rgb(var(--dark-mode-dialog-rgb));
+    box-shadow: var(--dark-mode-shadow);
     color: rgb(var(--GG200-rgb));
   }
 }
@@ -1243,7 +1242,7 @@
 
 @media (prefers-color-scheme: dark) {
   #backgrounds-default-icon {
-    background-color: rgb(41, 42, 45);
+    background-color: rgb(var(--dark-mode-dialog-rgb));
     border-color: rgb(var(--GG700-rgb));
   }
 
@@ -1351,7 +1350,7 @@
 
 @media (prefers-color-scheme: dark) {
   .sh-option-mini {
-    background-color: rgb(41, 42, 45);
+    background-color: rgb(var(--dark-mode-dialog-rgb));
     border-color: rgb(var(--GG700-rgb));
   }
 }
@@ -1369,8 +1368,7 @@
 @media (prefers-color-scheme: dark) {
   .selected .sh-option-mini {
     border-color: transparent;
-    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .3),
-        0 4px 8px 3px rgba(0, 0, 0, .15);
+    box-shadow: var(--dark-mode-shadow);
   }
 }
 
@@ -1542,7 +1540,7 @@
 
 @media (prefers-color-scheme: dark) {
   .switch {
-    background-color: rgb(41, 42, 45);
+    background-color: rgb(var(--dark-mode-dialog-rgb));
   }
 }
 
diff --git a/chrome/browser/resources/local_ntp/local_ntp_common.css b/chrome/browser/resources/local_ntp/local_ntp_common.css
index cfa54671..102d85b 100644
--- a/chrome/browser/resources/local_ntp/local_ntp_common.css
+++ b/chrome/browser/resources/local_ntp/local_ntp_common.css
@@ -6,6 +6,7 @@
   /* Material Design colors. Keep in sync with ui/gfx/color_palette.h. */
 
   --dark-mode-bg-rgb: 50, 54, 57;
+  --dark-mode-dialog-rgb: 41, 42, 45;
 
   /* Google Grey */
   --GG050-rgb: 248, 249, 250;
@@ -47,6 +48,9 @@
   --GR500-dark-rgb: 230, 106, 94;
   --GR600-dark-rgb: 211, 59, 48;
   --GR800-dark-rgb: 180, 27, 26;
+
+  --dark-mode-shadow: 0 1px 3px 0 rgba(0, 0, 0, .3),
+      0 4px 8px 3px rgba(0, 0, 0, .15);
 }
 
 body * {
diff --git a/chrome/browser/resources/local_ntp/voice.css b/chrome/browser/resources/local_ntp/voice.css
index fde0cac..58f3dd51 100644
--- a/chrome/browser/resources/local_ntp/voice.css
+++ b/chrome/browser/resources/local_ntp/voice.css
@@ -57,7 +57,7 @@
 
 @media (prefers-color-scheme: dark) {
   .overlay-dialog::backdrop {
-    background-color: rgb(41, 42, 45);
+    background-color: rgb(var(--dark-mode-dialog-rgb));
   }
 }
 
@@ -75,7 +75,7 @@
 @media (prefers-color-scheme: dark) {
   .overlay,
   .overlay-hidden {
-    background-color: rgb(41, 42, 45);
+    background-color: rgb(var(--dark-mode-dialog-rgb));
   }
 }
 
diff --git a/chrome/browser/resources/quota_internals/main.html b/chrome/browser/resources/quota_internals/main.html
index a1a1b11..51eb50ca 100644
--- a/chrome/browser/resources/quota_internals/main.html
+++ b/chrome/browser/resources/quota_internals/main.html
@@ -4,7 +4,7 @@
 Use of this source code is governed by a BSD-style license that can be
 found in the LICENSE file.
 -->
-<html dir="$i18n{textdirection}" lang="$i18n{language}">
+<html dir="ltr" lang="en">
 <title>Quota Internals</title>
 <meta charset="utf-8">
 <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
diff --git a/chrome/browser/resources/settings/google_assistant_page/google_assistant_page.js b/chrome/browser/resources/settings/google_assistant_page/google_assistant_page.js
index 0fcdc63..3531de7 100644
--- a/chrome/browser/resources/settings/google_assistant_page/google_assistant_page.js
+++ b/chrome/browser/resources/settings/google_assistant_page/google_assistant_page.js
@@ -41,7 +41,7 @@
 Polymer({
   is: 'settings-google-assistant-page',
 
-  behaviors: [I18nBehavior, PrefsBehavior],
+  behaviors: [I18nBehavior, PrefsBehavior, WebUIListenerBehavior],
 
   properties: {
     /** @private */
@@ -124,6 +124,15 @@
     this.browserProxy_ = settings.GoogleAssistantBrowserProxyImpl.getInstance();
   },
 
+  /** @override */
+  ready: function() {
+    this.addWebUIListener('hotwordDeviceUpdated', (hasHotword) => {
+      this.hotwordDspAvailable_ = hasHotword;
+    });
+
+    chrome.send('initializeGoogleAssistantPage');
+  },
+
   /**
    * @param {boolean} toggleValue
    * @return {string}
diff --git a/chrome/browser/resources/sync_file_system_internals/main.html b/chrome/browser/resources/sync_file_system_internals/main.html
index b0fe206..f0c42da 100644
--- a/chrome/browser/resources/sync_file_system_internals/main.html
+++ b/chrome/browser/resources/sync_file_system_internals/main.html
@@ -4,7 +4,7 @@
 Use of this source code is governed by a BSD-style license that can be
 found in the LICENSE file.
 -->
-<html dir="$i18n{textdirection}" lang="$i18n{language}">
+<html dir="ltr" lang="en">
 <meta charset="utf-8">
 <title>Sync File System Internals</title>
 <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/google_apps/google_app_proxy.js b/chrome/browser/resources/welcome/onboarding_welcome/google_apps/google_app_proxy.js
index 4fc9f24..b4edc67 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/google_apps/google_app_proxy.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/google_apps/google_app_proxy.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('nux', function() {
+cr.define('welcome', function() {
   /**
    * NuxGoogleAppsSelections enum.
    * These values are persisted to logs and should not be renumbered or
@@ -30,7 +30,7 @@
 
     /**
      * Returns a promise for an array of Google apps.
-     * @return {!Promise<!Array<!nux.BookmarkListItem>>}
+     * @return {!Promise<!Array<!welcome.BookmarkListItem>>}
      */
     getAppList() {}
 
@@ -41,7 +41,7 @@
     recordProviderSelected(providerId) {}
   }
 
-  /** @implements {nux.GoogleAppProxy} */
+  /** @implements {welcome.GoogleAppProxy} */
   class GoogleAppProxyImpl {
     /** @override */
     cacheBookmarkIcon(appId) {
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/google_apps/nux_google_apps.js b/chrome/browser/resources/welcome/onboarding_welcome/google_apps/nux_google_apps.js
index 1e37e95..8452574c 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/google_apps/nux_google_apps.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/google_apps/nux_google_apps.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.exportPath('nux');
+cr.exportPath('welcome');
 
 /**
  * @typedef {{
@@ -14,15 +14,15 @@
  *   selected: boolean,
  * }}
  */
-nux.AppItem;
+welcome.AppItem;
 
 /**
  * @typedef {{
- *   item: !nux.AppItem,
+ *   item: !welcome.AppItem,
  *   set: function(string, boolean):void
  * }}
  */
-nux.AppItemModel;
+welcome.AppItemModel;
 
 const KEYBOARD_FOCUSED = 'keyboard-focused';
 
@@ -32,11 +32,11 @@
   behaviors: [welcome.NavigationBehavior, I18nBehavior],
 
   properties: {
-    /** @type {nux.stepIndicatorModel} */
+    /** @type {welcome.stepIndicatorModel} */
     indicatorModel: Object,
 
     /**
-     * @type {!Array<!nux.AppItem>}
+     * @type {!Array<!welcome.AppItem>}
      * @private
      */
     appList_: Array,
@@ -48,19 +48,19 @@
     },
   },
 
-  /** @private {nux.GoogleAppProxy} */
+  /** @private {welcome.GoogleAppProxy} */
   appProxy_: null,
 
-  /** @private {?nux.ModuleMetricsManager} */
+  /** @private {?welcome.ModuleMetricsManager} */
   metricsManager_: null,
 
   /** @private */
   finalized_: false,
 
-  /** @private {nux.BookmarkProxy} */
+  /** @private {welcome.BookmarkProxy} */
   bookmarkProxy_: null,
 
-  /** @private {nux.BookmarkBarManager} */
+  /** @private {welcome.BookmarkBarManager} */
   bookmarkBarManager_: null,
 
   /** @private {boolean} */
@@ -68,11 +68,11 @@
 
   /** @override */
   ready: function() {
-    this.appProxy_ = nux.GoogleAppProxyImpl.getInstance();
-    this.metricsManager_ = new nux.ModuleMetricsManager(
-        nux.GoogleAppsMetricsProxyImpl.getInstance());
-    this.bookmarkProxy_ = nux.BookmarkProxyImpl.getInstance();
-    this.bookmarkBarManager_ = nux.BookmarkBarManager.getInstance();
+    this.appProxy_ = welcome.GoogleAppProxyImpl.getInstance();
+    this.metricsManager_ = new welcome.ModuleMetricsManager(
+        welcome.GoogleAppsMetricsProxyImpl.getInstance());
+    this.bookmarkProxy_ = welcome.BookmarkProxyImpl.getInstance();
+    this.bookmarkBarManager_ = welcome.BookmarkBarManager.getInstance();
   },
 
   /** @override */
@@ -164,7 +164,7 @@
 
   /**
    * Handle toggling the apps selected.
-   * @param {!{model: !nux.AppItemModel}} e
+   * @param {!{model: !welcome.AppItemModel}} e
    * @private
    */
   onAppClick_: function(e) {
@@ -235,7 +235,7 @@
       this.appList_.forEach(app => this.updateBookmark_(app));
     } else {
       this.appProxy_.getAppList().then(list => {
-        this.appList_ = /** @type(!Array<!nux.AppItem>) */ (list);
+        this.appList_ = /** @type(!Array<!welcome.AppItem>) */ (list);
         this.appList_.forEach((app, index) => {
           // Default select first few items.
           app.selected = index < 3;
@@ -248,7 +248,7 @@
   },
 
   /**
-   * @param {!nux.AppItem} item
+   * @param {!welcome.AppItem} item
    * @private
    */
   updateBookmark_: function(item) {
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/landing_view.js b/chrome/browser/resources/welcome/onboarding_welcome/landing_view.js
index aa29004..94f6d4ad 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/landing_view.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/landing_view.js
@@ -15,7 +15,7 @@
     }
   },
 
-  /** @private {?nux.LandingViewProxy} */
+  /** @private {?welcome.LandingViewProxy} */
   landingViewProxy_: null,
 
   /** @private {boolean} */
@@ -23,7 +23,7 @@
 
   /** @override */
   ready() {
-    this.landingViewProxy_ = nux.LandingViewProxyImpl.getInstance();
+    this.landingViewProxy_ = welcome.LandingViewProxyImpl.getInstance();
   },
 
   onRouteEnter: function() {
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/landing_view_proxy.js b/chrome/browser/resources/welcome/onboarding_welcome/landing_view_proxy.js
index 50d9fc0..1ccfbec 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/landing_view_proxy.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/landing_view_proxy.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('nux', function() {
+cr.define('welcome', function() {
   const NUX_LANDING_PAGE_INTERACTION_METRIC_NAME =
       'FirstRun.NewUserExperience.LandingPageInteraction';
 
@@ -33,7 +33,7 @@
     recordExistingUser() {}
   }
 
-  /** @implements {nux.LandingViewProxy} */
+  /** @implements {welcome.LandingViewProxy} */
   class LandingViewProxyImpl {
     /** @override */
     recordPageShown() {
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/ntp_background_proxy.js b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/ntp_background_proxy.js
index d0715a1..17b8ee6 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/ntp_background_proxy.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/ntp_background_proxy.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('nux', function() {
+cr.define('welcome', function() {
   /**
    * @typedef {{
    *   id: number,
@@ -17,7 +17,7 @@
   class NtpBackgroundProxy {
     clearBackground() {}
 
-    /** @return {!Promise<!Array<!nux.NtpBackgroundData>>} */
+    /** @return {!Promise<!Array<!welcome.NtpBackgroundData>>} */
     getBackgrounds() {}
 
     /**
@@ -37,7 +37,7 @@
     setBackground(id) {}
   }
 
-  /** @implements {nux.NtpBackgroundProxy} */
+  /** @implements {welcome.NtpBackgroundProxy} */
   class NtpBackgroundProxyImpl {
     /** @override */
     clearBackground() {
@@ -62,7 +62,7 @@
     /** @override */
     recordBackgroundImageFailedToLoad() {
       const ntpInteractions =
-          nux.NtpBackgroundMetricsProxyImpl.getInstance().getInteractions();
+          welcome.NtpBackgroundMetricsProxyImpl.getInstance().getInteractions();
       chrome.metricsPrivate.recordEnumerationValue(
           'FirstRun.NewUserExperience.NtpBackgroundInteraction',
           ntpInteractions.BackgroundImageFailedToLoad,
@@ -78,7 +78,7 @@
     /** @override */
     recordBackgroundImageNeverLoaded() {
       const ntpInteractions =
-          nux.NtpBackgroundMetricsProxyImpl.getInstance().getInteractions();
+          welcome.NtpBackgroundMetricsProxyImpl.getInstance().getInteractions();
       chrome.metricsPrivate.recordEnumerationValue(
           'FirstRun.NewUserExperience.NtpBackgroundInteraction',
           ntpInteractions.BackgroundImageNeverLoaded,
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.js b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.js
index 99e37a65..1af8a41 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.js
@@ -13,17 +13,17 @@
   ],
 
   properties: {
-    /** @type {nux.stepIndicatorModel} */
+    /** @type {welcome.stepIndicatorModel} */
     indicatorModel: Object,
 
-    /** @private {?nux.NtpBackgroundData} */
+    /** @private {?welcome.NtpBackgroundData} */
     selectedBackground_: {
       observer: 'onSelectedBackgroundChange_',
       type: Object,
     },
   },
 
-  /** @private {?Array<!nux.NtpBackgroundData>} */
+  /** @private {?Array<!welcome.NtpBackgroundData>} */
   backgrounds_: null,
 
   /** @private */
@@ -32,17 +32,17 @@
   /** @private {boolean} */
   imageIsLoading_: false,
 
-  /** @private {?nux.ModuleMetricsManager} */
+  /** @private {?welcome.ModuleMetricsManager} */
   metricsManager_: null,
 
-  /** @private {?nux.NtpBackgroundProxy} */
+  /** @private {?welcome.NtpBackgroundProxy} */
   ntpBackgroundProxy_: null,
 
   /** @override */
   ready: function() {
-    this.ntpBackgroundProxy_ = nux.NtpBackgroundProxyImpl.getInstance();
-    this.metricsManager_ = new nux.ModuleMetricsManager(
-        nux.NtpBackgroundMetricsProxyImpl.getInstance());
+    this.ntpBackgroundProxy_ = welcome.NtpBackgroundProxyImpl.getInstance();
+    this.metricsManager_ = new welcome.ModuleMetricsManager(
+        welcome.NtpBackgroundMetricsProxyImpl.getInstance());
   },
 
   onRouteEnter: function() {
@@ -98,7 +98,7 @@
   },
 
   /**
-   * @param {!nux.NtpBackgroundData} background
+   * @param {!welcome.NtpBackgroundData} background
    * @private
    */
   isSelectedBackground_: function(background) {
@@ -144,7 +144,7 @@
   },
 
   /**
-   * @param {!{model: !{item: !nux.NtpBackgroundData}}} e
+   * @param {!{model: !{item: !welcome.NtpBackgroundData}}} e
    * @private
    */
   onBackgroundClick_: function(e) {
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default.js b/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default.js
index 6f0477b..37e69a2d 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default.js
@@ -11,7 +11,7 @@
   ],
 
   properties: {
-    /** @type {nux.stepIndicatorModel} */
+    /** @type {welcome.stepIndicatorModel} */
     indicatorModel: Object,
 
     // <if expr="is_win">
@@ -22,7 +22,7 @@
     // </if>
   },
 
-  /** @private {nux.NuxSetAsDefaultProxy} */
+  /** @private {welcome.NuxSetAsDefaultProxy} */
   browserProxy_: null,
 
   /** @private {boolean} */
@@ -30,7 +30,7 @@
 
   /** @override */
   ready: function() {
-    this.browserProxy_ = nux.NuxSetAsDefaultProxyImpl.getInstance();
+    this.browserProxy_ = welcome.NuxSetAsDefaultProxyImpl.getInstance();
 
     this.addWebUIListener(
         'browser-default-state-changed',
@@ -80,7 +80,7 @@
 
   /**
    * Automatically navigate to the next onboarding step once default changed.
-   * @param {!nux.DefaultBrowserInfo} status
+   * @param {!welcome.DefaultBrowserInfo} status
    * @private
    */
   onDefaultBrowserChange_: function(status) {
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default_proxy.js b/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default_proxy.js
index 1fe1cec..e7668ff 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default_proxy.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default_proxy.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('nux', function() {
+cr.define('welcome', function() {
   const NUX_SET_AS_DEFAULT_INTERACTION_METRIC_NAME =
       'FirstRun.NewUserExperience.SetAsDefaultInteraction';
 
@@ -26,7 +26,7 @@
 
   /** @interface */
   class NuxSetAsDefaultProxy {
-    /** @return {!Promise<!nux.DefaultBrowserInfo>} */
+    /** @return {!Promise<!welcome.DefaultBrowserInfo>} */
     requestDefaultBrowserState() {}
     setAsDefault() {}
     recordPageShown() {}
@@ -37,7 +37,7 @@
     recordSuccessfullySetDefault() {}
   }
 
-  /** @implements {nux.NuxSetAsDefaultProxy} */
+  /** @implements {welcome.NuxSetAsDefaultProxy} */
   class NuxSetAsDefaultProxyImpl {
     /** @override */
     requestDefaultBrowserState() {
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/shared/bookmark_proxy.js b/chrome/browser/resources/welcome/onboarding_welcome/shared/bookmark_proxy.js
index d7a4da7..42bf8060 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/shared/bookmark_proxy.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/shared/bookmark_proxy.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('nux', function() {
+cr.define('welcome', function() {
   /**
    * @typedef {{
    *    parentId: string,
@@ -30,7 +30,7 @@
     isBookmarkBarShown() {}
   }
 
-  /** @implements {nux.BookmarkProxy} */
+  /** @implements {welcome.BookmarkProxy} */
   class BookmarkProxyImpl {
     /** @override */
     addBookmark(data, callback) {
@@ -58,7 +58,7 @@
   // Wrapper for bookmark proxy to keep some additional states.
   class BookmarkBarManager {
     constructor() {
-      /** @private {nux.BookmarkProxy} */
+      /** @private {welcome.BookmarkProxy} */
       this.proxy_ = BookmarkProxyImpl.getInstance();
 
       /** @private {boolean} */
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/shared/module_metrics_proxy.js b/chrome/browser/resources/welcome/onboarding_welcome/shared/module_metrics_proxy.js
index 159a187..384ae2ac 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/shared/module_metrics_proxy.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/shared/module_metrics_proxy.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('nux', function() {
+cr.define('welcome', function() {
   /** @interface */
   class ModuleMetricsProxy {
     recordPageShown() {}
@@ -28,7 +28,7 @@
     recordNavigatedAwayThroughBrowserHistory() {}
   }
 
-  /** @implements {nux.ModuleMetricsProxy} */
+  /** @implements {welcome.ModuleMetricsProxy} */
   class ModuleMetricsProxyImpl {
     /**
      * @param {string} histogramName The histogram that will record the module
@@ -125,7 +125,7 @@
   }
 
   class ModuleMetricsManager {
-    /** @param {nux.ModuleMetricsProxy} metricsProxy */
+    /** @param {welcome.ModuleMetricsProxy} metricsProxy */
     constructor(metricsProxy) {
       this.metricsProxy_ = metricsProxy;
 
@@ -196,7 +196,8 @@
   };
 });
 
-nux.GoogleAppsMetricsProxyImpl = class extends nux.ModuleMetricsProxyImpl {
+welcome.GoogleAppsMetricsProxyImpl =
+    class extends welcome.ModuleMetricsProxyImpl {
   constructor() {
     /**
      * NuxGoogleAppsInteractions enum.
@@ -227,7 +228,8 @@
   }
 };
 
-nux.NtpBackgroundMetricsProxyImpl = class extends nux.ModuleMetricsProxyImpl {
+welcome.NtpBackgroundMetricsProxyImpl =
+    class extends welcome.ModuleMetricsProxyImpl {
   constructor() {
     /**
      * NuxNtpBackgroundInteractions enum.
@@ -259,5 +261,5 @@
   }
 };
 
-cr.addSingletonGetter(nux.GoogleAppsMetricsProxyImpl);
-cr.addSingletonGetter(nux.NtpBackgroundMetricsProxyImpl);
+cr.addSingletonGetter(welcome.GoogleAppsMetricsProxyImpl);
+cr.addSingletonGetter(welcome.NtpBackgroundMetricsProxyImpl);
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/shared/nux_types.js b/chrome/browser/resources/welcome/onboarding_welcome/shared/nux_types.js
index f022277..accc5c3 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/shared/nux_types.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/shared/nux_types.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.exportPath('nux');
+cr.exportPath('welcome');
 
 /**
  * @typedef {{
@@ -12,7 +12,7 @@
  *   url: string,
  * }}
  */
-nux.BookmarkListItem;
+welcome.BookmarkListItem;
 
 /**
  * @typedef {{
@@ -20,7 +20,7 @@
  *   active: number,
  * }}
  */
-nux.stepIndicatorModel;
+welcome.stepIndicatorModel;
 
 /**
  * TODO(hcarmona): somehow reuse from
@@ -32,4 +32,4 @@
  *   isUnknownError: boolean,
  * }};
  */
-nux.DefaultBrowserInfo;
\ No newline at end of file
+welcome.DefaultBrowserInfo;
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/shared/step_indicator.js b/chrome/browser/resources/welcome/onboarding_welcome/shared/step_indicator.js
index 4a0bee3..4af45dc8 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/shared/step_indicator.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/shared/step_indicator.js
@@ -10,7 +10,7 @@
   is: 'step-indicator',
 
   properties: {
-    /** @type {nux.stepIndicatorModel} */
+    /** @type {welcome.stepIndicatorModel} */
     model: Object,
 
     /** @private */
@@ -37,4 +37,4 @@
   getActiveClass_: function(index) {
     return index == this.model.active ? 'active' : '';
   },
-});
\ No newline at end of file
+});
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/signin_view.js b/chrome/browser/resources/welcome/onboarding_welcome/signin_view.js
index 6d8532f..15096824 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/signin_view.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/signin_view.js
@@ -13,13 +13,13 @@
   /** @private {?welcome.WelcomeBrowserProxy} */
   welcomeBrowserProxy_: null,
 
-  /** @private {?nux.SigninViewProxy} */
+  /** @private {?welcome.SigninViewProxy} */
   signinViewProxy_: null,
 
   /** @override */
   ready: function() {
     this.welcomeBrowserProxy_ = welcome.WelcomeBrowserProxyImpl.getInstance();
-    this.signinViewProxy_ = nux.SigninViewProxyImpl.getInstance();
+    this.signinViewProxy_ = welcome.SigninViewProxyImpl.getInstance();
   },
 
   onRouteEnter: function() {
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/signin_view_proxy.js b/chrome/browser/resources/welcome/onboarding_welcome/signin_view_proxy.js
index 0d90282..0e731b9 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/signin_view_proxy.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/signin_view_proxy.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('nux', function() {
+cr.define('welcome', function() {
   const NUX_SIGNIN_VIEW_INTERACTION_METRIC_NAME =
       'FirstRun.NewUserExperience.SignInInterstitialInteraction';
 
@@ -32,7 +32,7 @@
     recordSignIn() {}
   }
 
-  /** @implements {nux.SigninViewProxy} */
+  /** @implements {welcome.SigninViewProxy} */
   class SigninViewProxyImpl {
     /** @override */
     recordPageShown() {
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/welcome_app.js b/chrome/browser/resources/welcome/onboarding_welcome/welcome_app.js
index 0352988..9830c921 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/welcome_app.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/welcome_app.js
@@ -111,7 +111,7 @@
 
     /** @type {!Promise} */
     const defaultBrowserPromise =
-        nux.NuxSetAsDefaultProxyImpl.getInstance()
+        welcome.NuxSetAsDefaultProxyImpl.getInstance()
             .requestDefaultBrowserState()
             .then((status) => {
               if (status.isDefault || !status.canBeDefault) {
@@ -128,7 +128,7 @@
     return Promise
         .all([
           defaultBrowserPromise,
-          nux.BookmarkBarManager.getInstance().initialized,
+          welcome.BookmarkBarManager.getInstance().initialized,
         ])
         .then(([canSetDefault]) => {
           modules = modules.filter(module => {
diff --git a/chrome/browser/search/iframe_source.cc b/chrome/browser/search/iframe_source.cc
deleted file mode 100644
index 4eab320c..0000000
--- a/chrome/browser/search/iframe_source.cc
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/search/iframe_source.h"
-
-#include "base/memory/ref_counted_memory.h"
-#include "base/strings/string_piece.h"
-#include "base/strings/string_util.h"
-#include "chrome/browser/search/instant_io_context.h"
-#include "chrome/common/url_constants.h"
-#include "content/public/browser/navigation_entry.h"
-#include "content/public/browser/web_contents.h"
-#include "ui/base/resource/resource_bundle.h"
-#include "url/gurl.h"
-
-IframeSource::IframeSource() = default;
-
-IframeSource::~IframeSource() = default;
-
-std::string IframeSource::GetMimeType(const std::string& path_and_query) {
-  std::string path(GURL("chrome-search://host/" + path_and_query).path());
-  if (base::EndsWith(path, ".js", base::CompareCase::INSENSITIVE_ASCII))
-    return "application/javascript";
-  if (base::EndsWith(path, ".png", base::CompareCase::INSENSITIVE_ASCII))
-    return "image/png";
-  if (base::EndsWith(path, ".css", base::CompareCase::INSENSITIVE_ASCII))
-    return "text/css";
-  if (base::EndsWith(path, ".html", base::CompareCase::INSENSITIVE_ASCII))
-    return "text/html";
-  if (base::EndsWith(path, ".svg", base::CompareCase::INSENSITIVE_ASCII))
-    return "image/svg+xml";
-  return std::string();
-}
-
-bool IframeSource::AllowCaching() {
-  return false;
-}
-
-bool IframeSource::ShouldServiceRequest(
-    const GURL& url,
-    content::ResourceContext* resource_context,
-    int render_process_id) {
-  return InstantIOContext::ShouldServiceRequest(url, resource_context,
-                                                render_process_id) &&
-         url.SchemeIs(chrome::kChromeSearchScheme) &&
-         url.host_piece() == GetSource() && ServesPath(url.path());
-}
-
-bool IframeSource::ShouldDenyXFrameOptions() {
-  return false;
-}
-
-bool IframeSource::GetOrigin(
-    const content::ResourceRequestInfo::WebContentsGetter& wc_getter,
-    std::string* origin) const {
-  if (wc_getter.is_null())
-    return false;
-  content::WebContents* contents = wc_getter.Run();
-  if (!contents)
-    return false;
-  content::NavigationEntry* entry = contents->GetController().GetVisibleEntry();
-  if (!entry)
-    return false;
-
-  *origin = entry->GetURL().GetOrigin().spec();
-  // Origin should not include a trailing slash. That is part of the path.
-  base::TrimString(*origin, "/", origin);
-  return true;
-}
-
-void IframeSource::SendResource(
-    int resource_id,
-    const content::URLDataSource::GotDataCallback& callback,
-    const ui::TemplateReplacements* replacements) {
-  base::StringPiece resource =
-      ui::ResourceBundle::GetSharedInstance().GetRawDataResource(resource_id);
-  std::string response =
-      replacements != nullptr
-          ? ui::ReplaceTemplateExpressions(resource, *replacements)
-          : resource.as_string();
-  callback.Run(base::RefCountedString::TakeString(&response));
-}
-
-void IframeSource::SendJSWithOrigin(
-    int resource_id,
-    const content::ResourceRequestInfo::WebContentsGetter& wc_getter,
-    const content::URLDataSource::GotDataCallback& callback) {
-  std::string origin;
-  if (!GetOrigin(wc_getter, &origin)) {
-    callback.Run(nullptr);
-    return;
-  }
-
-  base::StringPiece template_js =
-      ui::ResourceBundle::GetSharedInstance().GetRawDataResource(resource_id);
-  std::string response(template_js.as_string());
-  base::ReplaceFirstSubstringAfterOffset(&response, 0, "{{ORIGIN}}", origin);
-  callback.Run(base::RefCountedString::TakeString(&response));
-}
diff --git a/chrome/browser/search/iframe_source.h b/chrome/browser/search/iframe_source.h
deleted file mode 100644
index fca56596..0000000
--- a/chrome/browser/search/iframe_source.h
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_SEARCH_IFRAME_SOURCE_H_
-#define CHROME_BROWSER_SEARCH_IFRAME_SOURCE_H_
-
-#include "base/macros.h"
-#include "build/build_config.h"
-#include "content/public/browser/url_data_source.h"
-#include "ui/base/template_expressions.h"
-
-#if defined(OS_ANDROID)
-#error "Instant is only used on desktop";
-#endif
-
-// Base class for URL data sources for chrome-search:// iframed content.
-// TODO(crbug.com/947608): This has only one subclass outside of tests,
-// MostVisitedIframeSource. Merge the two classes?
-class IframeSource : public content::URLDataSource {
- public:
-  IframeSource();
-  ~IframeSource() override;
-
- protected:
-  // Overridden from content::URLDataSource:
-  std::string GetMimeType(const std::string& path_and_query) override;
-  bool AllowCaching() override;
-  bool ShouldDenyXFrameOptions() override;
-  bool ShouldServiceRequest(const GURL& url,
-                            content::ResourceContext* resource_context,
-                            int render_process_id) override;
-
-  // Returns whether this source should serve data for a particular path.
-  virtual bool ServesPath(const std::string& path) const = 0;
-
-  // Sends unmodified resource bytes.
-  void SendResource(int resource_id,
-                    const content::URLDataSource::GotDataCallback& callback,
-                    const ui::TemplateReplacements* replacements = nullptr);
-
-  // Sends Javascript with an expected postMessage origin interpolated.
-  void SendJSWithOrigin(
-      int resource_id,
-      const content::ResourceRequestInfo::WebContentsGetter& wc_getter,
-      const content::URLDataSource::GotDataCallback& callback);
-
-  // This is exposed for testing and should not be overridden.
-  // Sets |origin| to the URL of the WebContents identified by |wc_getter|.
-  // Returns true if successful and false if not, for example if the WebContents
-  // does not exist
-  virtual bool GetOrigin(
-      const content::ResourceRequestInfo::WebContentsGetter& wc_getter,
-      std::string* origin) const;
-
-  DISALLOW_COPY_AND_ASSIGN(IframeSource);
-};
-
-#endif  // CHROME_BROWSER_SEARCH_IFRAME_SOURCE_H_
diff --git a/chrome/browser/search/local_files_ntp_source.cc b/chrome/browser/search/local_files_ntp_source.cc
deleted file mode 100644
index fc00f42..0000000
--- a/chrome/browser/search/local_files_ntp_source.cc
+++ /dev/null
@@ -1,138 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/search/local_files_ntp_source.h"
-
-#include <memory>
-
-#if !defined(GOOGLE_CHROME_BUILD)
-
-#include "base/bind.h"
-#include "base/files/file_path.h"
-#include "base/files/file_util.h"
-#include "base/location.h"
-#include "base/memory/ref_counted_memory.h"
-#include "base/path_service.h"
-#include "base/strings/string_util.h"
-#include "base/task/post_task.h"
-#include "base/threading/thread_restrictions.h"
-#include "build/build_config.h"
-#include "chrome/common/url_constants.h"
-#include "content/public/browser/url_data_source.h"
-#include "third_party/re2/src/re2/re2.h"
-#include "third_party/re2/src/re2/stringpiece.h"
-
-namespace {
-
-const char kBasePath[] = "chrome/browser/resources/local_ntp";
-
-// Matches lines of form '<include src="foo">' and captures 'foo'.
-// TODO(treib): None of the local NTP files use this. Remove it?
-const char kInlineResourceRegex[] = "<include.*?src\\=[\"'](.+?)[\"'].*?>";
-
-// TODO(treib): local_ntp.css contains url(...) references to images, which get
-// inlined by grit's "flattenhtml" feature during regular builds. Find some way
-// to make that work with local files.
-
-void CallbackWithLoadedResource(
-    const std::string& origin,
-    const content::URLDataSource::GotDataCallback& callback,
-    const std::string& content) {
-  std::string output = content;
-  if (!origin.empty())
-    base::ReplaceFirstSubstringAfterOffset(&output, 0, "{{ORIGIN}}", origin);
-
-  // Strip out the integrity placeholders. CSP is disabled in local-files mode,
-  // so the integrity values aren't required.
-  base::ReplaceFirstSubstringAfterOffset(&output, 0, "{{CONFIG_INTEGRITY}}",
-                                         std::string());
-  base::ReplaceFirstSubstringAfterOffset(&output, 0, "{{LOCAL_NTP_INTEGRITY}}",
-                                         std::string());
-
-  callback.Run(base::RefCountedString::TakeString(&output));
-}
-
-// Read a file to a string and return.
-std::string ReadFileAndReturn(const base::FilePath& path) {
-  std::string data;
-  // This call can fail, but it doesn't matter for our purposes. If it fails,
-  // we simply return an empty string.
-  base::ReadFileToString(path, &data);
-  return data;
-}
-
-}  // namespace
-
-namespace local_ntp {
-
-void FlattenLocalInclude(
-    const content::URLDataSource::GotDataCallback& callback,
-    std::string topLevelResource,
-    scoped_refptr<base::RefCountedMemory> inlineResource);
-
-// Helper method invoked by both CheckLocalIncludes and FlattenLocalInclude.
-// Checks for any <include> directives; if any are found, loads the associated
-// file and calls FlattenLocalInclude with the result. Otherwise, processing
-// is done, and so the original callback is invoked.
-void CheckLocalIncludesHelper(
-    const content::URLDataSource::GotDataCallback& callback,
-    std::string& resource) {
-  std::string filename;
-  re2::StringPiece resourceWrapper(resource);
-  if (re2::RE2::FindAndConsume(&resourceWrapper, kInlineResourceRegex,
-                               &filename)) {
-    content::URLDataSource::GotDataCallback wrapper =
-        base::Bind(&FlattenLocalInclude, callback, resource);
-    SendLocalFileResource(filename, wrapper);
-  } else {
-    callback.Run(base::RefCountedString::TakeString(&resource));
-  }
-}
-
-// Wrapper around the above helper function for use as a callback. Processes
-// local files to inline any files indicated by an <include> directive.
-void CheckLocalIncludes(const content::URLDataSource::GotDataCallback& callback,
-                        scoped_refptr<base::RefCountedMemory> resource) {
-  std::string resourceAsStr(resource->front_as<char>(), resource->size());
-  CheckLocalIncludesHelper(callback, resourceAsStr);
-}
-
-// Replaces the first <include> directive found with the given file contents.
-// Afterwards, re-invokes CheckLocalIncludesHelper to handle any subsequent
-// <include>s, including those which may have been added by the newly-inlined
-// resource.
-void FlattenLocalInclude(
-    const content::URLDataSource::GotDataCallback& callback,
-    std::string topLevelResource,
-    scoped_refptr<base::RefCountedMemory> inlineResource) {
-  std::string inlineAsStr(inlineResource->front_as<char>(),
-                          inlineResource->size());
-  re2::RE2::Replace(&topLevelResource, kInlineResourceRegex, inlineAsStr);
-  CheckLocalIncludesHelper(callback, topLevelResource);
-}
-
-void SendLocalFileResource(
-    const std::string& path,
-    const content::URLDataSource::GotDataCallback& callback) {
-  SendLocalFileResourceWithOrigin(path, std::string(), callback);
-}
-
-void SendLocalFileResourceWithOrigin(
-    const std::string& path,
-    const std::string& origin,
-    const content::URLDataSource::GotDataCallback& callback) {
-  base::FilePath fullpath;
-  base::PathService::Get(base::DIR_SOURCE_ROOT, &fullpath);
-  fullpath = fullpath.AppendASCII(kBasePath).AppendASCII(path);
-  content::URLDataSource::GotDataCallback wrapper =
-      base::Bind(&CheckLocalIncludes, callback);
-  base::PostTaskWithTraitsAndReplyWithResult(
-      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
-      base::Bind(&ReadFileAndReturn, fullpath),
-      base::Bind(&CallbackWithLoadedResource, origin, wrapper));
-}
-
-}  // namespace local_ntp
-
-#endif  //  !defined(GOOGLE_CHROME_BUILD)
diff --git a/chrome/browser/search/local_files_ntp_source.h b/chrome/browser/search/local_files_ntp_source.h
deleted file mode 100644
index e296597..0000000
--- a/chrome/browser/search/local_files_ntp_source.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_SEARCH_LOCAL_FILES_NTP_SOURCE_H_
-#define CHROME_BROWSER_SEARCH_LOCAL_FILES_NTP_SOURCE_H_
-
-#include <string>
-
-#include "build/build_config.h"
-#include "content/public/browser/url_data_source.h"
-
-#if defined(OS_ANDROID)
-#error "Instant is only used on desktop";
-#endif
-
-#if !defined(GOOGLE_CHROME_BUILD)
-
-namespace local_ntp {
-
-// Sends the content of |path| to |callback|, reading |path| as a local file.
-// This function is only used for dev builds.
-void SendLocalFileResource(
-    const std::string& path,
-    const content::URLDataSource::GotDataCallback& callback);
-
-// Sends the content of |path| to |callback|, reading |path| as a local file.
-// It also replaces the first occurrence of {{ORIGIN}} with |origin|.
-// This function is only used for dev builds.
-void SendLocalFileResourceWithOrigin(
-    const std::string& path,
-    const std::string& origin,
-    const content::URLDataSource::GotDataCallback& callback);
-
-}  // namespace local_ntp
-
-#endif  // !defined(GOOGLE_CHROME_BUILD)
-#endif  // CHROME_BROWSER_SEARCH_LOCAL_FILES_NTP_SOURCE_H_
diff --git a/chrome/browser/search/local_ntp_source.cc b/chrome/browser/search/local_ntp_source.cc
index a1b8659d..d8e2d30 100644
--- a/chrome/browser/search/local_ntp_source.cc
+++ b/chrome/browser/search/local_ntp_source.cc
@@ -30,7 +30,6 @@
 #include "chrome/browser/search/background/ntp_background_service.h"
 #include "chrome/browser/search/background/ntp_background_service_factory.h"
 #include "chrome/browser/search/instant_io_context.h"
-#include "chrome/browser/search/local_files_ntp_source.h"
 #include "chrome/browser/search/local_ntp_js_integrity.h"
 #include "chrome/browser/search/ntp_features.h"
 #include "chrome/browser/search/one_google_bar/one_google_bar_data.h"
@@ -50,7 +49,6 @@
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_paths.h"
-#include "chrome/common/chrome_switches.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/grit/browser_resources.h"
@@ -957,19 +955,6 @@
     return;
   }
 
-#if !defined(GOOGLE_CHROME_BUILD)
-  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
-  if (command_line->HasSwitch(switches::kLocalNtpReload)) {
-    if (stripped_path == "local-ntp.html" || stripped_path == "local-ntp.js" ||
-        stripped_path == "local-ntp.css" || stripped_path == "voice.js" ||
-        stripped_path == "voice.css") {
-      base::ReplaceChars(stripped_path, "-", "_", &stripped_path);
-      local_ntp::SendLocalFileResource(stripped_path, callback);
-      return;
-    }
-  }
-#endif  // !defined(GOOGLE_CHROME_BUILD)
-
   if (stripped_path == kMainHtmlFilename) {
     if (search_config_provider_->DefaultSearchProviderIsGoogle()) {
       InitiatePromoAndOGBRequests();
@@ -1112,14 +1097,6 @@
 
 std::string LocalNtpSource::GetContentSecurityPolicy() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-#if !defined(GOOGLE_CHROME_BUILD)
-  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
-  if (command_line->HasSwitch(switches::kLocalNtpReload)) {
-    // While live-editing the local NTP files, turn off CSP.
-    return "script-src * 'unsafe-inline';";
-  }
-#endif  // !defined(GOOGLE_CHROME_BUILD)
-
   GURL google_base_url = google_util::CommandLineGoogleBaseURL();
 
   // Allow embedding of the most visited iframe, as well as the account
diff --git a/chrome/browser/search/most_visited_iframe_source.cc b/chrome/browser/search/most_visited_iframe_source.cc
index 5aa8a9c..3b414d0 100644
--- a/chrome/browser/search/most_visited_iframe_source.cc
+++ b/chrome/browser/search/most_visited_iframe_source.cc
@@ -7,12 +7,16 @@
 #include "base/command_line.h"
 #include "base/feature_list.h"
 #include "base/memory/ref_counted_memory.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
 #include "build/build_config.h"
-#include "chrome/browser/search/local_files_ntp_source.h"
+#include "chrome/browser/search/instant_io_context.h"
 #include "chrome/browser/search/ntp_features.h"
-#include "chrome/common/chrome_switches.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/grit/local_ntp_resources.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/base/resource/resource_bundle.h"
 #include "url/gurl.h"
 
 namespace {
@@ -61,24 +65,6 @@
   GURL url(chrome::kChromeSearchMostVisitedUrl + path_and_query);
   std::string path(url.path());
 
-#if !defined(GOOGLE_CHROME_BUILD)
-  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
-  if (command_line->HasSwitch(switches::kLocalNtpReload)) {
-    std::string rel_path = "most_visited_" + path.substr(1);
-    if (path == kSingleJSPath) {
-      std::string origin;
-      if (!GetOrigin(wc_getter, &origin)) {
-        callback.Run(nullptr);
-        return;
-      }
-      local_ntp::SendLocalFileResourceWithOrigin(rel_path, origin, callback);
-    } else {
-      local_ntp::SendLocalFileResource(rel_path, callback);
-    }
-    return;
-  }
-#endif
-
   if (path == kSingleHTMLPath) {
     ui::TemplateReplacements replacements;
     bool disable_fade = base::FeatureList::IsEnabled(
@@ -124,6 +110,40 @@
   }
 }
 
+std::string MostVisitedIframeSource::GetMimeType(
+    const std::string& path_and_query) {
+  std::string path(GURL("chrome-search://host/" + path_and_query).path());
+  if (base::EndsWith(path, ".js", base::CompareCase::INSENSITIVE_ASCII))
+    return "application/javascript";
+  if (base::EndsWith(path, ".png", base::CompareCase::INSENSITIVE_ASCII))
+    return "image/png";
+  if (base::EndsWith(path, ".css", base::CompareCase::INSENSITIVE_ASCII))
+    return "text/css";
+  if (base::EndsWith(path, ".html", base::CompareCase::INSENSITIVE_ASCII))
+    return "text/html";
+  if (base::EndsWith(path, ".svg", base::CompareCase::INSENSITIVE_ASCII))
+    return "image/svg+xml";
+  return std::string();
+}
+
+bool MostVisitedIframeSource::AllowCaching() {
+  return false;
+}
+
+bool MostVisitedIframeSource::ShouldServiceRequest(
+    const GURL& url,
+    content::ResourceContext* resource_context,
+    int render_process_id) {
+  return InstantIOContext::ShouldServiceRequest(url, resource_context,
+                                                render_process_id) &&
+         url.SchemeIs(chrome::kChromeSearchScheme) &&
+         url.host_piece() == GetSource() && ServesPath(url.path());
+}
+
+bool MostVisitedIframeSource::ShouldDenyXFrameOptions() {
+  return false;
+}
+
 bool MostVisitedIframeSource::ServesPath(const std::string& path) const {
   return path == kSingleHTMLPath || path == kSingleCSSPath ||
          path == kSingleJSPath || path == kTitleHTMLPath ||
@@ -134,3 +154,51 @@
          path == kLocalNTPCommonCSSPath || path == kAnimationsCSSPath ||
          path == kAnimationsJSPath || path == kLocalNTPUtilsJSPath;
 }
+
+void MostVisitedIframeSource::SendResource(
+    int resource_id,
+    const content::URLDataSource::GotDataCallback& callback,
+    const ui::TemplateReplacements* replacements) {
+  base::StringPiece resource =
+      ui::ResourceBundle::GetSharedInstance().GetRawDataResource(resource_id);
+  std::string response =
+      replacements != nullptr
+          ? ui::ReplaceTemplateExpressions(resource, *replacements)
+          : resource.as_string();
+  callback.Run(base::RefCountedString::TakeString(&response));
+}
+
+void MostVisitedIframeSource::SendJSWithOrigin(
+    int resource_id,
+    const content::ResourceRequestInfo::WebContentsGetter& wc_getter,
+    const content::URLDataSource::GotDataCallback& callback) {
+  std::string origin;
+  if (!GetOrigin(wc_getter, &origin)) {
+    callback.Run(nullptr);
+    return;
+  }
+
+  base::StringPiece template_js =
+      ui::ResourceBundle::GetSharedInstance().GetRawDataResource(resource_id);
+  std::string response(template_js.as_string());
+  base::ReplaceFirstSubstringAfterOffset(&response, 0, "{{ORIGIN}}", origin);
+  callback.Run(base::RefCountedString::TakeString(&response));
+}
+
+bool MostVisitedIframeSource::GetOrigin(
+    const content::ResourceRequestInfo::WebContentsGetter& wc_getter,
+    std::string* origin) const {
+  if (wc_getter.is_null())
+    return false;
+  content::WebContents* contents = wc_getter.Run();
+  if (!contents)
+    return false;
+  content::NavigationEntry* entry = contents->GetController().GetVisibleEntry();
+  if (!entry)
+    return false;
+
+  *origin = entry->GetURL().GetOrigin().spec();
+  // Origin should not include a trailing slash. That is part of the path.
+  base::TrimString(*origin, "/", origin);
+  return true;
+}
diff --git a/chrome/browser/search/most_visited_iframe_source.h b/chrome/browser/search/most_visited_iframe_source.h
index b5f3f3e..e3a7db40 100644
--- a/chrome/browser/search/most_visited_iframe_source.h
+++ b/chrome/browser/search/most_visited_iframe_source.h
@@ -7,7 +7,8 @@
 
 #include "base/macros.h"
 #include "build/build_config.h"
-#include "chrome/browser/search/iframe_source.h"
+#include "content/public/browser/url_data_source.h"
+#include "ui/base/template_expressions.h"
 
 #if defined(OS_ANDROID)
 #error "Instant is only used on desktop";
@@ -15,23 +16,48 @@
 
 // Serves HTML for displaying suggestions using iframes, e.g.
 // chrome-search://most-visited/single.html
-class MostVisitedIframeSource : public IframeSource {
+class MostVisitedIframeSource : public content::URLDataSource {
  public:
   MostVisitedIframeSource();
   ~MostVisitedIframeSource() override;
 
-  // Overridden from IframeSource. Public for testing.
+  // content::URLDataSource:
+  std::string GetSource() override;
   void StartDataRequest(
       const std::string& path_and_query,
       const content::ResourceRequestInfo::WebContentsGetter& wc_getter,
       const content::URLDataSource::GotDataCallback& callback) override;
+  std::string GetMimeType(const std::string& path_and_query) override;
+  bool AllowCaching() override;
+  bool ShouldDenyXFrameOptions() override;
+  bool ShouldServiceRequest(const GURL& url,
+                            content::ResourceContext* resource_context,
+                            int render_process_id) override;
+
+ protected:
+  // Returns whether this source should serve data for a particular path.
+  virtual bool ServesPath(const std::string& path) const;
+
+  // Sends unmodified resource bytes.
+  void SendResource(int resource_id,
+                    const content::URLDataSource::GotDataCallback& callback,
+                    const ui::TemplateReplacements* replacements = nullptr);
+
+  // Sends Javascript with an expected postMessage origin interpolated.
+  void SendJSWithOrigin(
+      int resource_id,
+      const content::ResourceRequestInfo::WebContentsGetter& wc_getter,
+      const content::URLDataSource::GotDataCallback& callback);
+
+  // This is exposed for testing and should not be overridden.
+  // Sets |origin| to the URL of the WebContents identified by |wc_getter|.
+  // Returns true if successful and false if not, for example if the WebContents
+  // does not exist
+  virtual bool GetOrigin(
+      const content::ResourceRequestInfo::WebContentsGetter& wc_getter,
+      std::string* origin) const;
 
  private:
-  // Overridden from IframeSource:
-  std::string GetSource() override;
-
-  bool ServesPath(const std::string& path) const override;
-
   DISALLOW_COPY_AND_ASSIGN(MostVisitedIframeSource);
 };
 
diff --git a/chrome/browser/search/iframe_source_unittest.cc b/chrome/browser/search/most_visited_iframe_source_unittest.cc
similarity index 85%
rename from chrome/browser/search/iframe_source_unittest.cc
rename to chrome/browser/search/most_visited_iframe_source_unittest.cc
index f3514505..ac23d5c4 100644
--- a/chrome/browser/search/iframe_source_unittest.cc
+++ b/chrome/browser/search/most_visited_iframe_source_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/search/iframe_source.h"
+#include "chrome/browser/search/most_visited_iframe_source.h"
 
 #include <memory>
 
@@ -30,12 +30,12 @@
 const char kInstantOrigin[] = "chrome-search://instant";
 const int kInvalidRendererPID = 42;
 
-class TestIframeSource : public IframeSource {
+class TestMostVisitedIframeSource : public MostVisitedIframeSource {
  public:
-  using IframeSource::GetMimeType;
-  using IframeSource::ShouldServiceRequest;
-  using IframeSource::SendResource;
-  using IframeSource::SendJSWithOrigin;
+  using MostVisitedIframeSource::GetMimeType;
+  using MostVisitedIframeSource::SendJSWithOrigin;
+  using MostVisitedIframeSource::SendResource;
+  using MostVisitedIframeSource::ShouldServiceRequest;
 
   void set_origin(std::string origin) { origin_ = origin; }
 
@@ -66,19 +66,18 @@
   std::string origin_;
 };
 
-class IframeSourceTest : public testing::Test {
+class MostVisitedIframeSourceTest : public testing::Test {
  public:
   // net::URLRequest wants to be executed with a message loop that has TYPE_IO.
   // InstantIOContext needs to be created on the UI thread and have everything
   // else happen on the IO thread. This setup is a hacky way to satisfy all
   // those constraints.
-  IframeSourceTest()
+  MostVisitedIframeSourceTest()
       : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
         instant_io_context_(NULL),
-        response_(NULL) {
-  }
+        response_(NULL) {}
 
-  TestIframeSource* source() { return source_.get(); }
+  TestMostVisitedIframeSource* source() { return source_.get(); }
 
   std::string response_string() {
     if (response_.get()) {
@@ -104,8 +103,8 @@
 
  private:
   void SetUp() override {
-    source_.reset(new TestIframeSource());
-    callback_ = base::Bind(&IframeSourceTest::SaveResponse,
+    source_ = std::make_unique<TestMostVisitedIframeSource>();
+    callback_ = base::Bind(&MostVisitedIframeSourceTest::SaveResponse,
                            base::Unretained(this));
     instant_io_context_ = new InstantIOContext;
     InstantIOContext::SetUserDataOnIO(&resource_context_, instant_io_context_);
@@ -125,13 +124,13 @@
 
   net::TestURLRequestContext test_url_request_context_;
   content::MockResourceContext resource_context_;
-  std::unique_ptr<TestIframeSource> source_;
+  std::unique_ptr<TestMostVisitedIframeSource> source_;
   content::URLDataSource::GotDataCallback callback_;
   scoped_refptr<InstantIOContext> instant_io_context_;
   scoped_refptr<base::RefCountedMemory> response_;
 };
 
-TEST_F(IframeSourceTest, ShouldServiceRequest) {
+TEST_F(MostVisitedIframeSourceTest, ShouldServiceRequest) {
   source()->set_origin(kNonInstantOrigin);
   EXPECT_FALSE(ShouldService("http://test/loader.js", kNonInstantRendererPID));
   source()->set_origin(kInstantOrigin);
@@ -151,7 +150,7 @@
       ShouldService("chrome-search://test/valid.js", kInvalidRendererPID));
 }
 
-TEST_F(IframeSourceTest, GetMimeType) {
+TEST_F(MostVisitedIframeSourceTest, GetMimeType) {
   // URLDataManagerBackend does not include / in path_and_query.
   EXPECT_EQ("text/html", source()->GetMimeType("foo.html"));
   EXPECT_EQ("application/javascript", source()->GetMimeType("foo.js"));
@@ -160,12 +159,12 @@
   EXPECT_EQ("", source()->GetMimeType("bogus"));
 }
 
-TEST_F(IframeSourceTest, SendResource) {
+TEST_F(MostVisitedIframeSourceTest, SendResource) {
   SendResource(IDR_MOST_VISITED_TITLE_HTML);
   EXPECT_FALSE(response_string().empty());
 }
 
-TEST_F(IframeSourceTest, SendJSWithOrigin) {
+TEST_F(MostVisitedIframeSourceTest, SendJSWithOrigin) {
   source()->set_origin(kInstantOrigin);
   SendJSWithOrigin(IDR_MOST_VISITED_TITLE_JS);
   EXPECT_FALSE(response_string().empty());
diff --git a/chrome/browser/sync/chrome_sync_client.cc b/chrome/browser/sync/chrome_sync_client.cc
index 69b6b96..961cecf 100644
--- a/chrome/browser/sync/chrome_sync_client.cc
+++ b/chrome/browser/sync/chrome_sync_client.cc
@@ -9,6 +9,7 @@
 
 #include "base/bind.h"
 #include "base/command_line.h"
+#include "base/feature_list.h"
 #include "base/path_service.h"
 #include "base/syslog_logging.h"
 #include "base/task/post_task.h"
@@ -38,9 +39,13 @@
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/browser/themes/theme_syncable_service.h"
 #include "chrome/browser/undo/bookmark_undo_service_factory.h"
+#include "chrome/browser/web_applications/web_app_provider.h"
+#include "chrome/browser/web_applications/web_app_sync_bridge.h"
+#include "chrome/browser/web_applications/web_app_sync_manager.h"
 #include "chrome/browser/web_data_service_factory.h"
 #include "chrome/common/buildflags.h"
 #include "chrome/common/channel_info.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/pref_names.h"
 #include "components/autofill/core/browser/webdata/autocomplete_sync_bridge.h"
@@ -338,6 +343,18 @@
             profile_, syncer::APP_SETTINGS),
         dump_stack, profile_));
   }
+
+  // Web Apps sync is disabled by default.
+  if (base::FeatureList::IsEnabled(features::kDesktopPWAsWithoutExtensions) &&
+      base::FeatureList::IsEnabled(features::kDesktopPWAsUSS) &&
+      web_app::WebAppProvider::Get(profile_)) {
+    if (!disabled_types.Has(syncer::WEB_APPS)) {
+      controllers.push_back(std::make_unique<syncer::ModelTypeController>(
+          syncer::WEB_APPS,
+          std::make_unique<syncer::ForwardingModelTypeControllerDelegate>(
+              GetControllerDelegateForModelType(syncer::WEB_APPS).get())));
+    }
+  }
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS)
 
 #if !defined(OS_ANDROID)
@@ -516,7 +533,19 @@
           ->GetSyncBridge()
           ->change_processor()
           ->GetControllerDelegate();
-
+#if BUILDFLAG(ENABLE_EXTENSIONS)
+    case syncer::WEB_APPS: {
+      DCHECK(base::FeatureList::IsEnabled(
+          features::kDesktopPWAsWithoutExtensions));
+      DCHECK(base::FeatureList::IsEnabled(features::kDesktopPWAsUSS));
+      auto* provider = web_app::WebAppProvider::Get(profile_);
+      DCHECK(provider);
+      return provider->sync_manager()
+          .bridge()
+          .change_processor()
+          ->GetControllerDelegate();
+    }
+#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
     // We don't exercise this function for certain datatypes, because their
     // controllers get the delegate elsewhere.
     case syncer::AUTOFILL:
diff --git a/chrome/browser/sync/profile_sync_service_factory.cc b/chrome/browser/sync/profile_sync_service_factory.cc
index 5059e39e..ecb45aa 100644
--- a/chrome/browser/sync/profile_sync_service_factory.cc
+++ b/chrome/browser/sync/profile_sync_service_factory.cc
@@ -40,6 +40,7 @@
 #include "chrome/browser/sync/user_event_service_factory.h"
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/browser/undo/bookmark_undo_service_factory.h"
+#include "chrome/browser/web_applications/web_app_provider_factory.h"
 #include "chrome/browser/web_data_service_factory.h"
 #include "chrome/common/buildflags.h"
 #include "chrome/common/channel_info.h"
@@ -161,6 +162,7 @@
   DependsOn(
       extensions::ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
   DependsOn(extensions::StorageFrontend::GetFactoryInstance());
+  DependsOn(web_app::WebAppProviderFactory::GetInstance());
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS)
 #if defined(OS_CHROMEOS)
   DependsOn(chromeos::SyncedPrintersManagerFactory::GetInstance());
diff --git a/chrome/browser/sync/profile_sync_service_factory_unittest.cc b/chrome/browser/sync/profile_sync_service_factory_unittest.cc
index fd498da..2cbbd2a 100644
--- a/chrome/browser/sync/profile_sync_service_factory_unittest.cc
+++ b/chrome/browser/sync/profile_sync_service_factory_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/task/thread_pool/thread_pool.h"
 #include "build/build_config.h"
 #include "chrome/common/buildflags.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/browser_sync/browser_sync_switches.h"
 #include "components/sync/base/model_type.h"
@@ -20,6 +21,7 @@
 #include "components/sync/driver/sync_driver_switches.h"
 #include "components/sync/driver/sync_service.h"
 #include "content/public/test/test_browser_thread_bundle.h"
+#include "extensions/buildflags/buildflags.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 #if defined(OS_CHROMEOS)
@@ -42,27 +44,46 @@
 
   // Returns the collection of default datatypes.
   std::vector<syncer::ModelType> DefaultDatatypes() {
-    static_assert(45 == syncer::ModelType::NUM_ENTRIES,
+    static_assert(46 == syncer::ModelType::NUM_ENTRIES,
                   "When adding a new type, you probably want to add it here as "
                   "well (assuming it is already enabled).");
 
     std::vector<syncer::ModelType> datatypes;
 
-    // Desktop types.
-#if !defined(OS_ANDROID)
+    // These preprocessor conditions and their order should be in sync with
+    // preprocessor conditions in ChromeSyncClient::CreateDataTypeControllers:
+
+    // ChromeSyncClient types.
+    datatypes.push_back(syncer::SECURITY_EVENTS);
+
+#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
+    datatypes.push_back(syncer::SUPERVISED_USER_SETTINGS);
+    datatypes.push_back(syncer::SUPERVISED_USER_WHITELISTS);
+#endif  // BUILDFLAG(ENABLE_SUPERVISED_USERS)
+
+#if BUILDFLAG(ENABLE_EXTENSIONS)
     datatypes.push_back(syncer::APPS);
-#if BUILDFLAG(ENABLE_APP_LIST)
-    datatypes.push_back(syncer::APP_LIST);
-#endif
-    datatypes.push_back(syncer::APP_SETTINGS);
-#if defined(OS_LINUX) || defined(OS_WIN) || defined(OS_CHROMEOS)
-    datatypes.push_back(syncer::DICTIONARY);
-#endif
     datatypes.push_back(syncer::EXTENSIONS);
     datatypes.push_back(syncer::EXTENSION_SETTINGS);
-    datatypes.push_back(syncer::SEARCH_ENGINES);
+    datatypes.push_back(syncer::APP_SETTINGS);
+    if (base::FeatureList::IsEnabled(features::kDesktopPWAsWithoutExtensions) &&
+        base::FeatureList::IsEnabled(features::kDesktopPWAsUSS)) {
+      datatypes.push_back(syncer::WEB_APPS);
+    }
+#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
+
+#if !defined(OS_ANDROID)
     datatypes.push_back(syncer::THEMES);
-#endif  // !OS_ANDROID
+    datatypes.push_back(syncer::SEARCH_ENGINES);
+#endif  // !defined(OS_ANDROID)
+
+#if BUILDFLAG(ENABLE_APP_LIST)
+    datatypes.push_back(syncer::APP_LIST);
+#endif  // BUILDFLAG(ENABLE_APP_LIST)
+
+#if defined(OS_LINUX) || defined(OS_WIN)
+    datatypes.push_back(syncer::DICTIONARY);
+#endif
 
 #if defined(OS_CHROMEOS)
     if (arc::IsArcAllowedForProfile(profile()))
@@ -88,9 +109,6 @@
     datatypes.push_back(syncer::PRIORITY_PREFERENCES);
     datatypes.push_back(syncer::SESSIONS);
     datatypes.push_back(syncer::PROXY_TABS);
-    datatypes.push_back(syncer::SECURITY_EVENTS);
-    datatypes.push_back(syncer::SUPERVISED_USER_SETTINGS);
-    datatypes.push_back(syncer::SUPERVISED_USER_WHITELISTS);
     datatypes.push_back(syncer::TYPED_URLS);
     datatypes.push_back(syncer::USER_EVENTS);
     datatypes.push_back(syncer::USER_CONSENTS);
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index dbaae23..98c5e5c 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1216,6 +1216,8 @@
       "webui/management_ui.h",
       "webui/management_ui_handler.cc",
       "webui/management_ui_handler.h",
+      "webui/management_ui_handler_chromeos.cc",
+      "webui/management_ui_handler_chromeos.h",
       "webui/media_router/media_router_internals_ui.cc",
       "webui/media_router/media_router_internals_ui.h",
       "webui/media_router/media_router_internals_webui_message_handler.cc",
@@ -2818,6 +2820,8 @@
       "views/native_file_system/native_file_system_access_icon_view.h",
       "views/native_file_system/native_file_system_permission_view.cc",
       "views/native_file_system/native_file_system_permission_view.h",
+      "views/native_file_system/native_file_system_ui_helpers.cc",
+      "views/native_file_system/native_file_system_ui_helpers.h",
       "views/native_file_system/native_file_system_usage_bubble_view.cc",
       "views/native_file_system/native_file_system_usage_bubble_view.h",
       "views/omnibox/omnibox_match_cell_view.cc",
diff --git a/chrome/browser/ui/android/passwords/manual_filling_view_android.cc b/chrome/browser/ui/android/passwords/manual_filling_view_android.cc
index 329bf916..63bcb79 100644
--- a/chrome/browser/ui/android/passwords/manual_filling_view_android.cc
+++ b/chrome/browser/ui/android/passwords/manual_filling_view_android.cc
@@ -157,7 +157,8 @@
   for (const UserInfo& user_info : tab_data.user_info_list()) {
     ScopedJavaLocalRef<jobject> j_user_info =
         Java_ManualFillingComponentBridge_addUserInfoToAccessorySheetData(
-            env, java_object_, j_tab_data);
+            env, java_object_, j_tab_data,
+            ConvertUTF8ToJavaString(env, user_info.origin()));
     for (const UserInfo::Field& field : user_info.fields()) {
       Java_ManualFillingComponentBridge_addFieldToUserInfo(
           env, java_object_, j_user_info,
diff --git a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
index f45f664..cc21f765 100644
--- a/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
+++ b/chrome/browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc
@@ -313,6 +313,7 @@
         base::CommandLine::ForCurrentProcess(), base::FilePath(), false);
     extension_service_->Init();
 
+    bool flush_app_service_mojo_calls = false;
     if (app_service_proxy_connector_) {
       DCHECK(profile());
       app_service_proxy_impl_.reset(apps::AppServiceProxyImpl::CreateForTesting(
@@ -320,11 +321,17 @@
       old_app_service_proxy_for_testing_ =
           AppServiceAppModelBuilder::SetAppServiceProxyForTesting(
               app_service_proxy_impl_.get());
+      // Flush the App Service Mojo calls, but only after calling
+      // arc_test_.SetUp, as it also pumps the run-loop in general.
+      flush_app_service_mojo_calls = true;
     }
 
     if (auto_start_arc_test_)
       arc_test_.SetUp(profile());
 
+    if (flush_app_service_mojo_calls)
+      app_service_proxy_impl_->FlushMojoCallsForTesting();
+
     // Wait until |extension_system| is signaled as started.
     base::RunLoop run_loop;
     extension_system->ready().Post(FROM_HERE, run_loop.QuitClosure());
@@ -1323,6 +1330,8 @@
   EXPECT_EQ("App2, Fake App 1, Chrome, App1, Fake App 0, Gmail",
             GetPinnedAppStatus());
 
+  if (app_service_proxy_impl_)
+    app_service_proxy_impl_->FlushMojoCallsForTesting();
   copy_sync_list = app_list_syncable_service_->GetAllSyncData(syncer::APP_LIST);
 
   ResetLauncherController();
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index b08b03ac..a9da630 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -1715,6 +1715,9 @@
   ProtocolHandler handler =
       ProtocolHandler::CreateProtocolHandler(protocol, url);
 
+  if (!handler.IsValid())
+    return;
+
   ProtocolHandlerRegistry* registry =
       ProtocolHandlerRegistryFactory::GetForBrowserContext(context);
   if (registry->SilentlyHandleRegisterHandlerRequest(handler))
diff --git a/chrome/browser/ui/browser_ui_prefs.cc b/chrome/browser/ui/browser_ui_prefs.cc
index 6d26c7f..7da71a9 100644
--- a/chrome/browser/ui/browser_ui_prefs.cc
+++ b/chrome/browser/ui/browser_ui_prefs.cc
@@ -125,4 +125,9 @@
                                 false);
   registry->RegisterBooleanPref(prefs::kAllowPopupsDuringPageUnload, false);
   registry->RegisterBooleanPref(prefs::kUserFeedbackAllowed, true);
+
+#if !defined(OS_ANDROID)
+  registry->RegisterBooleanPref(prefs::kShowFirstRunDefaultSearchShortcut,
+                                false);
+#endif  // !defined(OS_ANDROID)
 }
diff --git a/chrome/browser/ui/search/local_ntp_browsertest.cc b/chrome/browser/ui/search/local_ntp_browsertest.cc
index 1ca8e97..aeafb15 100644
--- a/chrome/browser/ui/search/local_ntp_browsertest.cc
+++ b/chrome/browser/ui/search/local_ntp_browsertest.cc
@@ -1163,6 +1163,17 @@
   }
 };
 
+IN_PROC_BROWSER_TEST_F(LocalNTPFRESearchShortcutTest, PRE_SearchShortcutShown) {
+  content::WebContents* active_tab =
+      local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
+  local_ntp_test_utils::NavigateToNTPAndWaitUntilLoaded(browser());
+  ASSERT_TRUE(search::IsInstantNTP(active_tab));
+
+  content::RenderFrameHost* iframe = GetIframe(active_tab, kMostVisitedIframe);
+
+  EXPECT_TRUE(ContainsDefaultSearchTile(iframe));
+}
+
 IN_PROC_BROWSER_TEST_F(LocalNTPFRESearchShortcutTest, SearchShortcutShown) {
   content::WebContents* active_tab =
       local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
@@ -1171,6 +1182,7 @@
 
   content::RenderFrameHost* iframe = GetIframe(active_tab, kMostVisitedIframe);
 
+  // Search shortcut is retained across browser restart
   EXPECT_TRUE(ContainsDefaultSearchTile(iframe));
 }
 
@@ -1213,7 +1225,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(LocalNTPExistingProfileSearchShortcutTest,
-                       FRESearchShortcutNotAddedForExistingUsers) {
+                       PRE_FRESearchShortcutNotAddedForExistingUsers) {
   TestInstantServiceObserver observer(
       InstantServiceFactory::GetForProfile(browser()->profile()));
 
@@ -1234,16 +1246,24 @@
   base::test::ScopedFeatureList scoped_feature_list_;
   scoped_feature_list_.InitAndEnableFeature(
       features::kFirstRunDefaultSearchShortcut);
-  ASSERT_TRUE(
-      base::FeatureList::IsEnabled(features::kFirstRunDefaultSearchShortcut));
+}
 
-  // One new tiles (the non-NTP URL) should be added.
+IN_PROC_BROWSER_TEST_F(LocalNTPExistingProfileSearchShortcutTest,
+                       FRESearchShortcutNotAddedForExistingUsers) {
+  TestInstantServiceObserver observer(
+      InstantServiceFactory::GetForProfile(browser()->profile()));
+
+  // One new tile (the non-NTP URL "/title2.html") should be added.
   observer.WaitForMostVisitedItems(kDefaultMostVisitedItemCount + 1);
 
-  active_tab = local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
+  content::WebContents* active_tab =
+      local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
   local_ntp_test_utils::NavigateToNTPAndWaitUntilLoaded(browser());
   ASSERT_TRUE(search::IsInstantNTP(active_tab));
-  iframe = GetIframe(active_tab, kMostVisitedIframe);
+
+  content::RenderFrameHost* iframe = GetIframe(active_tab, kMostVisitedIframe);
+
+  // Search shortcut is not added after browser restart
   EXPECT_FALSE(ContainsDefaultSearchTile(iframe));
 }
 #endif
diff --git a/chrome/browser/ui/views/native_file_system/native_file_system_permission_view.cc b/chrome/browser/ui/views/native_file_system/native_file_system_permission_view.cc
index 20323174..e542dba 100644
--- a/chrome/browser/ui/views/native_file_system/native_file_system_permission_view.cc
+++ b/chrome/browser/ui/views/native_file_system/native_file_system_permission_view.cc
@@ -8,16 +8,15 @@
 #include "chrome/browser/permissions/permission_util.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/chrome_typography.h"
+#include "chrome/browser/ui/views/native_file_system/native_file_system_ui_helpers.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/constrained_window/constrained_window_views.h"
-#include "components/url_formatter/elide_url.h"
 #include "components/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/strings/grit/ui_strings.h"
 #include "ui/views/controls/button/label_button.h"
 #include "ui/views/controls/label.h"
-#include "ui/views/controls/styled_label.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/window/dialog_client_view.h"
 
@@ -33,26 +32,10 @@
       provider->GetDialogInsetsForContentType(views::TEXT, views::TEXT),
       provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL)));
 
-  base::string16 formatted_origin =
-      url_formatter::FormatOriginForSecurityDisplay(
-          origin, url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC);
-  size_t offset;
-  auto label = std::make_unique<views::StyledLabel>(
-      l10n_util::GetStringFUTF16(
-          is_directory ? IDS_NATIVE_FILE_SYSTEM_WRITE_PERMISSION_DIRECTORY_TEXT
-                       : IDS_NATIVE_FILE_SYSTEM_WRITE_PERMISSION_FILE_TEXT,
-          formatted_origin, &offset),
-      nullptr);
-  label->SetTextContext(CONTEXT_BODY_TEXT_SMALL);
-  label->SetDefaultTextStyle(STYLE_SECONDARY);
-  label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-
-  views::StyledLabel::RangeStyleInfo origin_style;
-  origin_style.text_style = STYLE_EMPHASIZED_SECONDARY;
-  label->AddStyleRange(gfx::Range(offset, offset + formatted_origin.length()),
-                       origin_style);
-
-  AddChildView(std::move(label));
+  AddChildView(native_file_system_ui_helper::CreateOriginLabel(
+      is_directory ? IDS_NATIVE_FILE_SYSTEM_WRITE_PERMISSION_DIRECTORY_TEXT
+                   : IDS_NATIVE_FILE_SYSTEM_WRITE_PERMISSION_FILE_TEXT,
+      origin, CONTEXT_BODY_TEXT_SMALL));
 
   auto file_label_container = std::make_unique<views::View>();
   int indent =
diff --git a/chrome/browser/ui/views/native_file_system/native_file_system_ui_helpers.cc b/chrome/browser/ui/views/native_file_system/native_file_system_ui_helpers.cc
new file mode 100644
index 0000000..9ec7722d
--- /dev/null
+++ b/chrome/browser/ui/views/native_file_system/native_file_system_ui_helpers.cc
@@ -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.
+
+#include "chrome/browser/ui/views/native_file_system/native_file_system_ui_helpers.h"
+
+#include "chrome/browser/ui/views/chrome_typography.h"
+#include "components/url_formatter/elide_url.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/views/controls/styled_label.h"
+
+namespace native_file_system_ui_helper {
+
+std::unique_ptr<views::View> CreateOriginLabel(int message_id,
+                                               const url::Origin& origin,
+                                               int text_context) {
+  base::string16 formatted_origin =
+      url_formatter::FormatOriginForSecurityDisplay(
+          origin, url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC);
+  size_t offset;
+  auto label = std::make_unique<views::StyledLabel>(
+      l10n_util::GetStringFUTF16(message_id, formatted_origin, &offset),
+      nullptr);
+  label->SetTextContext(text_context);
+  label->SetDefaultTextStyle(STYLE_SECONDARY);
+  label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+
+  views::StyledLabel::RangeStyleInfo origin_style;
+  origin_style.text_style = STYLE_EMPHASIZED_SECONDARY;
+  label->AddStyleRange(gfx::Range(offset, offset + formatted_origin.length()),
+                       origin_style);
+  return label;
+}
+
+}  // namespace native_file_system_ui_helper
diff --git a/chrome/browser/ui/views/native_file_system/native_file_system_ui_helpers.h b/chrome/browser/ui/views/native_file_system/native_file_system_ui_helpers.h
new file mode 100644
index 0000000..ee726c1
--- /dev/null
+++ b/chrome/browser/ui/views/native_file_system/native_file_system_ui_helpers.h
@@ -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.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_NATIVE_FILE_SYSTEM_NATIVE_FILE_SYSTEM_UI_HELPERS_H_
+#define CHROME_BROWSER_UI_VIEWS_NATIVE_FILE_SYSTEM_NATIVE_FILE_SYSTEM_UI_HELPERS_H_
+
+#include <memory>
+
+namespace url {
+class Origin;
+}
+
+namespace views {
+class View;
+}
+
+namespace native_file_system_ui_helper {
+
+// Creates and returns a label where the place holder is replaced with |origin|,
+// while formatting the origin as emphasized text.
+std::unique_ptr<views::View> CreateOriginLabel(int message_id,
+                                               const url::Origin& origin,
+                                               int text_context);
+
+}  // namespace native_file_system_ui_helper
+
+#endif  // CHROME_BROWSER_UI_VIEWS_NATIVE_FILE_SYSTEM_NATIVE_FILE_SYSTEM_UI_HELPERS_H_
diff --git a/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.cc b/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.cc
index 014e9e6..d699c73 100644
--- a/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.cc
+++ b/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.cc
@@ -13,16 +13,15 @@
 #include "chrome/browser/ui/views/chrome_typography.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
+#include "chrome/browser/ui/views/native_file_system/native_file_system_ui_helpers.h"
 #include "chrome/browser/ui/views/page_action/omnibox_page_action_icon_container_view.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/strings/grit/components_strings.h"
-#include "components/url_formatter/elide_url.h"
 #include "third_party/icu/source/common/unicode/unistr.h"
 #include "third_party/icu/source/common/unicode/utypes.h"
 #include "third_party/icu/source/i18n/unicode/listformatter.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/views/controls/label.h"
-#include "ui/views/controls/styled_label.h"
 #include "ui/views/layout/box_layout.h"
 
 namespace {
@@ -48,6 +47,42 @@
   return base::i18n::UnicodeStringToString16(formatted);
 }
 
+// Returns the message Id to use as heading text, depending on what types of
+// usage are present (i.e. just writable files, or also readable directories,
+// etc).
+// |need_lifetime_text_at_end| is set to false iff the returned message Id
+// already includes an explanation for how long a website will have access to
+// the listed paths. It is set to true iff a separate label is needed at the end
+// of the dialog to explain lifetime.
+int ComputeHeadingMessageFromUsage(
+    const NativeFileSystemUsageBubbleView::Usage& usage,
+    bool* need_lifetime_text_at_end) {
+  if (!usage.writable_files.empty() && !usage.writable_directories.empty()) {
+    *need_lifetime_text_at_end = true;
+    return IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_FILES_AND_DIRECTORIES_TEXT;
+  }
+  if (!usage.writable_files.empty()) {
+    if (usage.readable_directories.empty()) {
+      *need_lifetime_text_at_end = false;
+      return IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_FILES_TEXT;
+    }
+    *need_lifetime_text_at_end = true;
+    return IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_FILES_NO_LIFETIME_TEXT;
+  }
+  if (!usage.writable_directories.empty()) {
+    if (usage.readable_directories.empty()) {
+      *need_lifetime_text_at_end = false;
+      return IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_DIRECTORIES_TEXT;
+    }
+    *need_lifetime_text_at_end = true;
+    return IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_DIRECTORIES_NO_LIFETIME_TEXT;
+  }
+
+  DCHECK(!usage.readable_directories.empty());
+  *need_lifetime_text_at_end = false;
+  return IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_READABLE_DIRECTORIES_TEXT;
+}
+
 }  // namespace
 
 NativeFileSystemUsageBubbleView::Usage::Usage() = default;
@@ -156,15 +191,44 @@
                       views::DISTANCE_DIALOG_CONTENT_MARGIN_BOTTOM_CONTROL),
                   0));
 
-  AddPathList(IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_FILES_TEXT,
-              IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_FILES_TEXT,
+  bool need_lifetime_text_at_end = false;
+  int heading_message_id =
+      ComputeHeadingMessageFromUsage(usage_, &need_lifetime_text_at_end);
+
+  AddChildView(native_file_system_ui_helper::CreateOriginLabel(
+      heading_message_id, origin_, CONTEXT_BODY_TEXT_LARGE));
+
+  AddPathList(IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_FILES_TEXT,
               usage_.writable_files);
-  AddPathList(IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_WRITABLE_DIRECTORIES_TEXT,
-              IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_DIRECTORIES_TEXT,
+  AddPathList(IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_DIRECTORIES_TEXT,
               usage_.writable_directories);
-  AddPathList(IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_READABLE_DIRECTORIES_TEXT,
-              IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_DIRECTORIES_TEXT,
+
+  // If the header wasn't already the "readable directories" header (i.e. we
+  // had at least one writable file or directory as well) add a secondary header
+  // for the readable directories section.
+  if (heading_message_id !=
+      IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_READABLE_DIRECTORIES_TEXT) {
+    auto directory_label = std::make_unique<views::Label>(
+        l10n_util::GetStringUTF16(
+            IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_ALSO_READABLE_DIRECTORIES_TEXT),
+        CONTEXT_BODY_TEXT_LARGE, STYLE_SECONDARY);
+    directory_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+    directory_label->SetMultiLine(true);
+    AddChildView(std::move(directory_label));
+  }
+
+  AddPathList(IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_DIRECTORIES_TEXT,
               usage_.readable_directories);
+
+  if (need_lifetime_text_at_end) {
+    auto lifetime_label = std::make_unique<views::Label>(
+        l10n_util::GetStringUTF16(
+            IDS_NATIVE_FILE_SYSTEM_USAGE_BUBBLE_LIFETIME_TEXT),
+        CONTEXT_BODY_TEXT_LARGE, STYLE_SECONDARY);
+    lifetime_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+    lifetime_label->SetMultiLine(true);
+    AddChildView(std::move(lifetime_label));
+  }
 }
 
 void NativeFileSystemUsageBubbleView::WindowClosing() {
@@ -189,30 +253,11 @@
 }
 
 void NativeFileSystemUsageBubbleView::AddPathList(
-    int caption_message_id,
     int details_message_id,
     const std::vector<base::FilePath>& paths) {
   if (paths.empty())
     return;
 
-  base::string16 formatted_origin =
-      url_formatter::FormatOriginForSecurityDisplay(
-          origin_, url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC);
-  size_t offset;
-  auto label = std::make_unique<views::StyledLabel>(
-      l10n_util::GetStringFUTF16(caption_message_id, formatted_origin, &offset),
-      nullptr);
-  label->SetTextContext(CONTEXT_BODY_TEXT_LARGE);
-  label->SetDefaultTextStyle(STYLE_SECONDARY);
-  label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-
-  views::StyledLabel::RangeStyleInfo origin_style;
-  origin_style.text_style = STYLE_EMPHASIZED_SECONDARY;
-  label->AddStyleRange(gfx::Range(offset, offset + formatted_origin.length()),
-                       origin_style);
-
-  AddChildView(std::move(label));
-
   std::vector<base::string16> base_names;
   for (const auto& path : paths)
     base_names.push_back(path.BaseName().LossyDisplayName());
diff --git a/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.h b/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.h
index c977915..5fea45729 100644
--- a/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.h
+++ b/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.h
@@ -51,8 +51,7 @@
   void CloseBubble() override;
   gfx::Size CalculatePreferredSize() const override;
 
-  void AddPathList(int caption_message_id,
-                   int details_message_id,
+  void AddPathList(int details_message_id,
                    const std::vector<base::FilePath>& paths);
 
   // Singleton instance of the bubble. The bubble can only be shown on the
diff --git a/chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.cc
index 75ff54b..4b6b59b 100644
--- a/chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/assistant_optin_flow_screen_handler.cc
@@ -203,7 +203,7 @@
   RecordAssistantOptInStatus(VOICE_MATCH_ENROLLMENT_ERROR);
   CallJS("login.AssistantOptInFlowScreen.onVoiceMatchUpdate",
          base::Value("failure"));
-  LOG(ERROR) << "Speaker ID enrollmend failure.";
+  LOG(ERROR) << "Speaker ID enrollment failure.";
 }
 
 void AssistantOptInFlowScreenHandler::SetupAssistantConnection() {
diff --git a/chrome/browser/ui/webui/chromeos/network_ui.cc b/chrome/browser/ui/webui/chromeos/network_ui.cc
index bb71ffd..4c767fef 100644
--- a/chrome/browser/ui/webui/chromeos/network_ui.cc
+++ b/chrome/browser/ui/webui/chromeos/network_ui.cc
@@ -48,6 +48,7 @@
 constexpr char kOpenCellularActivationUi[] = "openCellularActivationUi";
 constexpr char kShowNetworkDetails[] = "showNetworkDetails";
 constexpr char kShowNetworkConfig[] = "showNetworkConfig";
+constexpr char kShowAddNewWifiNetworkDialog[] = "showAddNewWifi";
 
 bool GetServicePathFromGuid(const std::string& guid,
                             std::string* service_path) {
@@ -119,6 +120,10 @@
         kShowNetworkConfig,
         base::BindRepeating(&NetworkConfigMessageHandler::ShowNetworkConfig,
                             base::Unretained(this)));
+    web_ui()->RegisterMessageCallback(
+        kShowAddNewWifiNetworkDialog,
+        base::BindRepeating(&NetworkConfigMessageHandler::ShowAddNewWifi,
+                            base::Unretained(this)));
   }
 
  private:
@@ -220,6 +225,10 @@
     InternetConfigDialog::ShowDialogForNetworkId(guid);
   }
 
+  void ShowAddNewWifi(const base::ListValue* arg_list) {
+    InternetConfigDialog::ShowDialogForNetworkType(::onc::network_type::kWiFi);
+  }
+
   void GetShillDevicePropertiesSuccess(
       const std::string& device_path,
       const base::DictionaryValue& dictionary) {
@@ -329,6 +338,13 @@
   localized_strings->SetString(
       "noCellularErrorText",
       l10n_util::GetStringUTF16(IDS_NETWORK_UI_NO_CELLULAR_ERROR_TEXT));
+
+  localized_strings->SetString(
+      "addNewWifiLabel",
+      l10n_util::GetStringUTF16(IDS_NETWORK_UI_ADD_NEW_WIFI_LABEL));
+  localized_strings->SetString(
+      "addNewWifiButtonText",
+      l10n_util::GetStringUTF16(IDS_NETWORK_UI_ADD_NEW_WIFI_BUTTON_TEXT));
 }
 
 NetworkUI::NetworkUI(content::WebUI* web_ui)
diff --git a/chrome/browser/ui/webui/management_ui_handler.cc b/chrome/browser/ui/webui/management_ui_handler.cc
index 7229fe4..f495672 100644
--- a/chrome/browser/ui/webui/management_ui_handler.cc
+++ b/chrome/browser/ui/webui/management_ui_handler.cc
@@ -44,6 +44,7 @@
 #include "chrome/browser/chromeos/policy/status_uploader.h"
 #include "chrome/browser/chromeos/policy/system_log_uploader.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chrome/browser/ui/webui/management_ui_handler_chromeos.h"
 #include "chrome/grit/chromium_strings.h"
 #include "components/prefs/pref_service.h"
 #include "components/user_manager/user_manager.h"
@@ -568,29 +569,8 @@
       g_browser_process->platform_part()->browser_policy_connector_chromeos();
   const auto url = connector->GetCustomerLogoURL();
   if (!url.empty() && GURL(url) != logo_url_) {
-    net::NetworkTrafficAnnotationTag traffic_annotation =
-        net::DefineNetworkTrafficAnnotation("management_ui_customer_logo", R"(
-          semantics {
-            sender: "Management UI Handler"
-            description:
-              "Download organization logo for visualization on the "
-              "chrome://management page."
-            trigger:
-              "The user managed by organization that provides a company logo "
-              "in their GSuites account loads the chrome://management page."
-            data:
-              "Organization uploaded image URL."
-            destination: GOOGLE_OWNED_SERVICE
-          }
-          policy {
-            cookies_allowed: NO
-            setting:
-              "This feature cannot be disabled by settings, but it is only "
-              "triggered by a user action."
-            policy_exception_justification: "Not implemented."
-          })");
-    icon_fetcher_ =
-        std::make_unique<BitmapFetcher>(GURL(url), this, traffic_annotation);
+    icon_fetcher_ = std::make_unique<BitmapFetcher>(
+        GURL(url), this, GetManagementUICustomerLogoAnnotation());
     icon_fetcher_->Init(
         std::string(), net::URLRequest::NEVER_CLEAR_REFERRER,
         net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES);
diff --git a/chrome/browser/ui/webui/management_ui_handler_chromeos.cc b/chrome/browser/ui/webui/management_ui_handler_chromeos.cc
new file mode 100644
index 0000000..2702f50
--- /dev/null
+++ b/chrome/browser/ui/webui/management_ui_handler_chromeos.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 "chrome/browser/ui/webui/management_ui_handler_chromeos.h"
+
+net::NetworkTrafficAnnotationTag GetManagementUICustomerLogoAnnotation() {
+  return net::DefineNetworkTrafficAnnotation("management_ui_customer_logo", R"(
+      semantics {
+        sender: "Management UI Handler"
+        description:
+          "Download organization logo for visualization on the "
+          "chrome://management page."
+        trigger:
+          "The user managed by organization that provides a company logo "
+          "in their GSuites account loads the chrome://management page."
+        data:
+          "Organization uploaded image URL."
+        destination: GOOGLE_OWNED_SERVICE
+      }
+      policy {
+        cookies_allowed: NO
+        setting:
+          "This feature cannot be disabled by settings, but it is only "
+          "triggered by a user action."
+        policy_exception_justification: "Not implemented."
+      })");
+}
diff --git a/chrome/browser/ui/webui/management_ui_handler_chromeos.h b/chrome/browser/ui/webui/management_ui_handler_chromeos.h
new file mode 100644
index 0000000..b2deb92
--- /dev/null
+++ b/chrome/browser/ui/webui/management_ui_handler_chromeos.h
@@ -0,0 +1,12 @@
+// 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_WEBUI_MANAGEMENT_UI_HANDLER_CHROMEOS_H_
+#define CHROME_BROWSER_UI_WEBUI_MANAGEMENT_UI_HANDLER_CHROMEOS_H_
+
+#include "net/traffic_annotation/network_traffic_annotation.h"
+
+net::NetworkTrafficAnnotationTag GetManagementUICustomerLogoAnnotation();
+
+#endif  // CHROME_BROWSER_UI_WEBUI_MANAGEMENT_UI_HANDLER_CHROMEOS_H_
diff --git a/chrome/browser/ui/webui/settings/chromeos/google_assistant_handler.cc b/chrome/browser/ui/webui/settings/chromeos/google_assistant_handler.cc
index e8e300be2..25814f62 100644
--- a/chrome/browser/ui/webui/settings/chromeos/google_assistant_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/google_assistant_handler.cc
@@ -13,6 +13,7 @@
 #include "base/values.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/webui/chromeos/assistant_optin/assistant_optin_ui.h"
+#include "chromeos/audio/cras_audio_handler.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/services/assistant/public/mojom/constants.mojom.h"
 #include "components/arc/arc_prefs.h"
@@ -25,13 +26,34 @@
 namespace settings {
 
 GoogleAssistantHandler::GoogleAssistantHandler(Profile* profile)
-    : profile_(profile), weak_factory_(this) {}
+    : profile_(profile), weak_factory_(this) {
+  chromeos::CrasAudioHandler::Get()->AddAudioObserver(this);
+}
 
-GoogleAssistantHandler::~GoogleAssistantHandler() {}
+GoogleAssistantHandler::~GoogleAssistantHandler() {
+  chromeos::CrasAudioHandler::Get()->RemoveAudioObserver(this);
+}
 
-void GoogleAssistantHandler::OnJavascriptAllowed() {}
+void GoogleAssistantHandler::OnJavascriptAllowed() {
+  if (pending_hotword_update_) {
+    OnAudioNodesChanged();
+  }
+}
+
 void GoogleAssistantHandler::OnJavascriptDisallowed() {}
 
+void GoogleAssistantHandler::OnAudioNodesChanged() {
+  if (!IsJavascriptAllowed()) {
+    pending_hotword_update_ = true;
+    return;
+  }
+
+  pending_hotword_update_ = false;
+  FireWebUIListener(
+      "hotwordDeviceUpdated",
+      base::Value(chromeos::CrasAudioHandler::Get()->HasHotwordDevice()));
+}
+
 void GoogleAssistantHandler::RegisterMessages() {
   web_ui()->RegisterMessageCallback(
       "showGoogleAssistantSettings",
@@ -46,6 +68,10 @@
       "syncVoiceModelStatus",
       base::BindRepeating(&GoogleAssistantHandler::HandleSyncVoiceModelStatus,
                           base::Unretained(this)));
+  web_ui()->RegisterMessageCallback(
+      "initializeGoogleAssistantPage",
+      base::BindRepeating(&GoogleAssistantHandler::HandleInitialized,
+                          base::Unretained(this)));
 }
 
 void GoogleAssistantHandler::HandleShowGoogleAssistantSettings(
@@ -71,6 +97,11 @@
   settings_manager_->SyncSpeakerIdEnrollmentStatus();
 }
 
+void GoogleAssistantHandler::HandleInitialized(const base::ListValue* args) {
+  CHECK_EQ(0U, args->GetSize());
+  AllowJavascript();
+}
+
 void GoogleAssistantHandler::BindAssistantSettingsManager() {
   DCHECK(!settings_manager_.is_bound());
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/google_assistant_handler.h b/chrome/browser/ui/webui/settings/chromeos/google_assistant_handler.h
index 87d09bc..8891f4e 100644
--- a/chrome/browser/ui/webui/settings/chromeos/google_assistant_handler.h
+++ b/chrome/browser/ui/webui/settings/chromeos/google_assistant_handler.h
@@ -6,7 +6,9 @@
 #define CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_GOOGLE_ASSISTANT_HANDLER_H_
 
 #include "base/macros.h"
+#include "base/optional.h"
 #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
+#include "chromeos/audio/cras_audio_handler.h"
 #include "chromeos/services/assistant/public/mojom/settings.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
 
@@ -15,7 +17,8 @@
 namespace chromeos {
 namespace settings {
 
-class GoogleAssistantHandler : public ::settings::SettingsPageUIHandler {
+class GoogleAssistantHandler : public ::settings::SettingsPageUIHandler,
+                               chromeos::CrasAudioHandler::AudioObserver {
  public:
   explicit GoogleAssistantHandler(Profile* profile);
   ~GoogleAssistantHandler() override;
@@ -24,6 +27,9 @@
   void OnJavascriptAllowed() override;
   void OnJavascriptDisallowed() override;
 
+  // chromeos::CrasAudioHandler::AudioObserver overrides
+  void OnAudioNodesChanged() override;
+
  private:
   // WebUI call to launch into the Google Assistant app settings.
   void HandleShowGoogleAssistantSettings(const base::ListValue* args);
@@ -31,6 +37,8 @@
   void HandleRetrainVoiceModel(const base::ListValue* args);
   // WebUI call to sync Assistant voice model status.
   void HandleSyncVoiceModelStatus(const base::ListValue* args);
+  // WebUI call to signal js side is ready.
+  void HandleInitialized(const base::ListValue* args);
 
   // Bind to assistant settings manager.
   void BindAssistantSettingsManager();
@@ -39,6 +47,8 @@
 
   assistant::mojom::AssistantSettingsManagerPtr settings_manager_;
 
+  bool pending_hotword_update_ = false;
+
   base::WeakPtrFactory<GoogleAssistantHandler> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(GoogleAssistantHandler);
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn
index 70e7bb3..cad27f69 100644
--- a/chrome/browser/web_applications/BUILD.gn
+++ b/chrome/browser/web_applications/BUILD.gn
@@ -34,6 +34,10 @@
     "web_app_install_task.h",
     "web_app_registrar.cc",
     "web_app_registrar.h",
+    "web_app_sync_bridge.cc",
+    "web_app_sync_bridge.h",
+    "web_app_sync_manager.cc",
+    "web_app_sync_manager.h",
     "web_app_tab_helper.cc",
     "web_app_tab_helper.h",
   ]
diff --git a/chrome/browser/web_applications/proto/web_app.proto b/chrome/browser/web_applications/proto/web_app.proto
index 46d5287..fd87aff 100644
--- a/chrome/browser/web_applications/proto/web_app.proto
+++ b/chrome/browser/web_applications/proto/web_app.proto
@@ -8,7 +8,7 @@
 
 package web_app;
 
-// Information about web app icon.
+// Local data: Information about web app icon.
 message WebAppIconInfoProto {
   // The URL of the app icon.
   required string url = 1;
@@ -17,18 +17,18 @@
 }
 
 // WebApp class data.
-// TODO(loyso): Consider moving this proto to components/sync/protocol/
-// crbug.com/902214.
+// This should be a superset for WebAppSpecifics in
+// components/sync/protocol/web_app_specifics.proto
 message WebAppProto {
   // app_id is the client tag for sync system.
   required string app_id = 1;
-  optional string name = 2;
-  optional string description = 3;
-  required string launch_url = 4;
-  optional string scope = 5;
-  optional uint32 theme_color = 6;
+  required string launch_url = 2;
+  required string name = 3;
+  required uint32 theme_color = 4;
 
   // Local data members, not to be synced:
+  optional string description = 5;
+  optional string scope = 6;
   // List of icon infos.
   repeated WebAppIconInfoProto icons = 7;
 }
diff --git a/chrome/browser/web_applications/web_app.cc b/chrome/browser/web_applications/web_app.cc
index 309c22df..69f83bbc 100644
--- a/chrome/browser/web_applications/web_app.cc
+++ b/chrome/browser/web_applications/web_app.cc
@@ -39,7 +39,6 @@
 }
 
 void WebApp::SetIcons(Icons icons) {
-  DCHECK(!icons.empty());
   icons_ = std::move(icons);
 }
 
diff --git a/chrome/browser/web_applications/web_app.h b/chrome/browser/web_applications/web_app.h
index 005fe8c..129c04dc 100644
--- a/chrome/browser/web_applications/web_app.h
+++ b/chrome/browser/web_applications/web_app.h
@@ -28,6 +28,7 @@
   const std::string& description() const { return description_; }
   const GURL& launch_url() const { return launch_url_; }
   const GURL& scope() const { return scope_; }
+  // TODO(loyso): Remove Optional. This is a required field now.
   const base::Optional<SkColor>& theme_color() const { return theme_color_; }
 
   struct IconInfo {
diff --git a/chrome/browser/web_applications/web_app_database.cc b/chrome/browser/web_applications/web_app_database.cc
index 7043237..d96d7679 100644
--- a/chrome/browser/web_applications/web_app_database.cc
+++ b/chrome/browser/web_applications/web_app_database.cc
@@ -63,14 +63,22 @@
     const WebApp& web_app) {
   auto proto = std::make_unique<WebAppProto>();
 
+  // Required fields:
+  DCHECK(!web_app.app_id().empty());
   proto->set_app_id(web_app.app_id());
-  proto->set_name(web_app.name());
-  proto->set_description(web_app.description());
+
+  DCHECK(!web_app.launch_url().is_empty() && web_app.launch_url().is_valid());
   proto->set_launch_url(web_app.launch_url().spec());
+
+  proto->set_name(web_app.name());
+
+  DCHECK(web_app.theme_color());
+  proto->set_theme_color(web_app.theme_color().value());
+
+  // Optional fields:
+  proto->set_description(web_app.description());
   if (!web_app.scope().is_empty())
     proto->set_scope(web_app.scope().spec());
-  if (web_app.theme_color())
-    proto->set_theme_color(web_app.theme_color().value());
 
   for (const WebApp::IconInfo& icon : web_app.icons()) {
     WebAppIconInfoProto* icon_proto = proto->add_icons();
@@ -85,43 +93,49 @@
 std::unique_ptr<WebApp> WebAppDatabase::CreateWebApp(const WebAppProto& proto) {
   auto web_app = std::make_unique<WebApp>(proto.app_id());
 
+  // Required fields:
   GURL launch_url(proto.launch_url());
   if (launch_url.is_empty() || !launch_url.is_valid()) {
-    LOG(ERROR) << "WebApp proto launch_url parse error: "
-               << launch_url.possibly_invalid_spec();
+    DLOG(ERROR) << "WebApp proto launch_url parse error: "
+                << launch_url.possibly_invalid_spec();
     return nullptr;
   }
-
   web_app->SetLaunchUrl(launch_url);
+
+  if (!proto.has_name()) {
+    DLOG(ERROR) << "WebApp proto parse error: no name field";
+    return nullptr;
+  }
   web_app->SetName(proto.name());
-  web_app->SetDescription(proto.description());
+
+  if (!proto.has_theme_color()) {
+    DLOG(ERROR) << "WebApp proto parse error: no theme_color field";
+    return nullptr;
+  }
+  web_app->SetThemeColor(proto.theme_color());
+
+  // Optional fields:
+  if (proto.has_description())
+    web_app->SetDescription(proto.description());
 
   if (proto.has_scope()) {
     GURL scope(proto.scope());
     if (scope.is_empty() || !scope.is_valid()) {
-      LOG(ERROR) << "WebApp proto scope parse error: "
-                 << scope.possibly_invalid_spec();
+      DLOG(ERROR) << "WebApp proto scope parse error: "
+                  << scope.possibly_invalid_spec();
       return nullptr;
     }
     web_app->SetScope(scope);
   }
 
-  if (proto.has_theme_color())
-    web_app->SetThemeColor(proto.theme_color());
-
-  if (proto.icons_size() == 0) {
-    LOG(ERROR) << "WebApp proto parse icons error: no icons";
-    return nullptr;
-  }
-
   WebApp::Icons icons;
   for (int i = 0; i < proto.icons_size(); ++i) {
     const WebAppIconInfoProto& icon_proto = proto.icons(i);
 
     GURL icon_url(icon_proto.url());
     if (icon_url.is_empty() || !icon_url.is_valid()) {
-      LOG(ERROR) << "WebApp IconInfo proto url parse error: "
-                 << icon_url.possibly_invalid_spec();
+      DLOG(ERROR) << "WebApp IconInfo proto url parse error: "
+                  << icon_url.possibly_invalid_spec();
       return nullptr;
     }
 
@@ -145,11 +159,8 @@
     base::OnceClosure closure) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  // For now we use syncer:APPS prefix within local isolated LevelDB, no sync.
-  // TODO(loyso): Create separate ModelType::WEB_APPS before implementing sync.
-  // Otherwise it may interfere with existing APPS data.
   std::move(store_factory)
-      .Run(syncer::APPS,
+      .Run(syncer::WEB_APPS,
            base::BindOnce(&WebAppDatabase::OnStoreCreated,
                           weak_ptr_factory_.GetWeakPtr(), std::move(closure)));
 }
@@ -160,7 +171,7 @@
     std::unique_ptr<syncer::ModelTypeStore> store) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (error) {
-    LOG(ERROR) << "WebApps LevelDB opening error: " << error->ToString();
+    DLOG(ERROR) << "WebApps LevelDB opening error: " << error->ToString();
     return;
   }
 
@@ -174,7 +185,7 @@
     std::unique_ptr<syncer::ModelTypeStore::RecordList> data_records) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (error) {
-    LOG(ERROR) << "WebApps LevelDB read error: " << error->ToString();
+    DLOG(ERROR) << "WebApps LevelDB read error: " << error->ToString();
     return;
   }
 
@@ -194,7 +205,7 @@
     const base::Optional<syncer::ModelError>& error) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (error)
-    LOG(ERROR) << "WebApps LevelDB write error: " << error->ToString();
+    DLOG(ERROR) << "WebApps LevelDB write error: " << error->ToString();
 }
 
 // static
@@ -203,7 +214,7 @@
   WebAppProto proto;
   const bool parsed = proto.ParseFromString(value);
   if (!parsed || proto.app_id() != app_id) {
-    LOG(ERROR) << "WebApps LevelDB parse error (unknown).";
+    DLOG(ERROR) << "WebApps LevelDB parse error (unknown).";
     return nullptr;
   }
 
diff --git a/chrome/browser/web_applications/web_app_database_unittest.cc b/chrome/browser/web_applications/web_app_database_unittest.cc
index 53de77b..f18b545 100644
--- a/chrome/browser/web_applications/web_app_database_unittest.cc
+++ b/chrome/browser/web_applications/web_app_database_unittest.cc
@@ -206,20 +206,18 @@
 
   const auto launch_url = GURL("https://example.com/");
   const AppId app_id = GenerateAppIdFromURL(GURL(launch_url));
+  const std::string name = "Name";
+  const SkColor color = 0xAABBCCDDu;
 
   auto app = std::make_unique<WebApp>(app_id);
-
+  // Required fields:
   app->SetLaunchUrl(launch_url);
-  EXPECT_TRUE(app->name().empty());
+  app->SetName(name);
+  app->SetThemeColor(base::Optional<SkColor>(color));
+  // Let optional fields be empty:
   EXPECT_TRUE(app->description().empty());
   EXPECT_TRUE(app->scope().is_empty());
-  EXPECT_FALSE(app->theme_color().has_value());
-
-  // |icons| is mandatory data member for a representation in DB. If no icons,
-  // WebAppDatabase::CreateWebApp(from_proto) returns nullptr.
-  WebApp::Icons icons;
-  icons.push_back({GURL("https://example.com/icon"), 512});
-  app->SetIcons(std::move(icons));
+  EXPECT_TRUE(app->icons().empty());
   registrar_->RegisterApp(std::move(app));
 
   Registry registry = ReadRegistry();
@@ -227,15 +225,16 @@
 
   std::unique_ptr<WebApp>& app_copy = registry.at(app_id);
 
-  // Mandatory members.
+  // Required fields were serialized:
   EXPECT_EQ(app_id, app_copy->app_id());
   EXPECT_EQ(launch_url, app_copy->launch_url());
+  EXPECT_EQ(name, app_copy->name());
+  EXPECT_EQ(color, app_copy->theme_color().value());
 
-  // No optional members.
-  EXPECT_TRUE(app_copy->name().empty());
+  // No optional fields.
   EXPECT_TRUE(app_copy->description().empty());
   EXPECT_TRUE(app_copy->scope().is_empty());
-  EXPECT_FALSE(app_copy->theme_color().has_value());
+  EXPECT_TRUE(app_copy->icons().empty());
 }
 
 TEST_F(WebAppDatabaseTest, WebAppWithManyIcons) {
diff --git a/chrome/browser/web_applications/web_app_provider.cc b/chrome/browser/web_applications/web_app_provider.cc
index 69d29151..c54214d 100644
--- a/chrome/browser/web_applications/web_app_provider.cc
+++ b/chrome/browser/web_applications/web_app_provider.cc
@@ -36,6 +36,7 @@
 #include "chrome/browser/web_applications/web_app_install_manager.h"
 #include "chrome/browser/web_applications/web_app_provider_factory.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
+#include "chrome/browser/web_applications/web_app_sync_manager.h"
 #include "chrome/browser/web_applications/web_app_tab_helper.h"
 #include "chrome/common/chrome_features.h"
 #include "components/pref_registry/pref_registry_syncable.h"
@@ -154,6 +155,8 @@
       web_app_registrar.get(), icon_manager_.get());
   install_manager_ = std::make_unique<WebAppInstallManager>(profile);
 
+  sync_manager_ = std::make_unique<WebAppSyncManager>();
+
   registrar_ = std::move(web_app_registrar);
 }
 
diff --git a/chrome/browser/web_applications/web_app_provider.h b/chrome/browser/web_applications/web_app_provider.h
index b37b815..850d1a0c 100644
--- a/chrome/browser/web_applications/web_app_provider.h
+++ b/chrome/browser/web_applications/web_app_provider.h
@@ -44,6 +44,7 @@
 class WebAppDatabase;
 class WebAppDatabaseFactory;
 class WebAppIconManager;
+class WebAppSyncManager;
 
 // Forward declarations for legacy extension-based subsystems.
 class WebAppPolicyManager;
@@ -80,6 +81,9 @@
   WebAppPolicyManager* policy_manager() override;
   WebAppUiDelegate& ui_delegate() override;
 
+  WebAppDatabaseFactory& database_factory() { return *database_factory_; }
+  WebAppSyncManager& sync_manager() { return *sync_manager_; }
+
   // KeyedService:
   void Shutdown() override;
 
@@ -126,6 +130,7 @@
   std::unique_ptr<WebAppDatabaseFactory> database_factory_;
   std::unique_ptr<WebAppDatabase> database_;
   std::unique_ptr<WebAppIconManager> icon_manager_;
+  std::unique_ptr<WebAppSyncManager> sync_manager_;
   WebAppUiDelegate* ui_delegate_ = nullptr;
 
   // New generalized subsystems:
diff --git a/chrome/browser/web_applications/web_app_sync_bridge.cc b/chrome/browser/web_applications/web_app_sync_bridge.cc
new file mode 100644
index 0000000..b238617
--- /dev/null
+++ b/chrome/browser/web_applications/web_app_sync_bridge.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 "chrome/browser/web_applications/web_app_sync_bridge.h"
+
+#include "base/logging.h"
+#include "base/optional.h"
+#include "components/sync/model/metadata_change_list.h"
+
+namespace web_app {
+
+WebAppSyncBridge::WebAppSyncBridge(
+    std::unique_ptr<syncer::ModelTypeChangeProcessor> change_processor)
+    : syncer::ModelTypeSyncBridge(std::move(change_processor)) {}
+
+WebAppSyncBridge::~WebAppSyncBridge() = default;
+
+std::unique_ptr<syncer::MetadataChangeList>
+WebAppSyncBridge::CreateMetadataChangeList() {
+  NOTIMPLEMENTED();
+  return nullptr;
+}
+
+base::Optional<syncer::ModelError> WebAppSyncBridge::MergeSyncData(
+    std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
+    syncer::EntityChangeList entity_data) {
+  NOTIMPLEMENTED();
+  return base::nullopt;
+}
+
+base::Optional<syncer::ModelError> WebAppSyncBridge::ApplySyncChanges(
+    std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
+    syncer::EntityChangeList entity_changes) {
+  NOTIMPLEMENTED();
+  return base::nullopt;
+}
+
+void WebAppSyncBridge::GetData(StorageKeyList storage_keys,
+                               DataCallback callback) {
+  NOTIMPLEMENTED();
+}
+
+void WebAppSyncBridge::GetAllDataForDebugging(DataCallback callback) {
+  NOTIMPLEMENTED();
+}
+
+std::string WebAppSyncBridge::GetClientTag(
+    const syncer::EntityData& entity_data) {
+  NOTIMPLEMENTED();
+  return std::string();
+}
+
+std::string WebAppSyncBridge::GetStorageKey(
+    const syncer::EntityData& entity_data) {
+  NOTIMPLEMENTED();
+  return std::string();
+}
+
+}  // namespace web_app
diff --git a/chrome/browser/web_applications/web_app_sync_bridge.h b/chrome/browser/web_applications/web_app_sync_bridge.h
new file mode 100644
index 0000000..4306435
--- /dev/null
+++ b/chrome/browser/web_applications/web_app_sync_bridge.h
@@ -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.
+
+#ifndef CHROME_BROWSER_WEB_APPLICATIONS_WEB_APP_SYNC_BRIDGE_H_
+#define CHROME_BROWSER_WEB_APPLICATIONS_WEB_APP_SYNC_BRIDGE_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "components/sync/model/model_type_sync_bridge.h"
+
+namespace syncer {
+class ModelTypeChangeProcessor;
+}
+
+namespace web_app {
+
+class WebAppSyncBridge : public syncer::ModelTypeSyncBridge {
+ public:
+  explicit WebAppSyncBridge(
+      std::unique_ptr<syncer::ModelTypeChangeProcessor> change_processor);
+  ~WebAppSyncBridge() override;
+
+  // syncer::ModelTypeSyncBridge:
+  std::unique_ptr<syncer::MetadataChangeList> CreateMetadataChangeList()
+      override;
+  base::Optional<syncer::ModelError> MergeSyncData(
+      std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
+      syncer::EntityChangeList entity_data) override;
+  base::Optional<syncer::ModelError> ApplySyncChanges(
+      std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
+      syncer::EntityChangeList entity_changes) override;
+  void GetData(StorageKeyList storage_keys, DataCallback callback) override;
+  void GetAllDataForDebugging(DataCallback callback) override;
+  std::string GetClientTag(const syncer::EntityData& entity_data) override;
+  std::string GetStorageKey(const syncer::EntityData& entity_data) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(WebAppSyncBridge);
+};
+
+}  // namespace web_app
+
+#endif  // CHROME_BROWSER_WEB_APPLICATIONS_WEB_APP_SYNC_BRIDGE_H_
diff --git a/chrome/browser/web_applications/web_app_sync_manager.cc b/chrome/browser/web_applications/web_app_sync_manager.cc
new file mode 100644
index 0000000..6b82c4a
--- /dev/null
+++ b/chrome/browser/web_applications/web_app_sync_manager.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 "chrome/browser/web_applications/web_app_sync_manager.h"
+
+#include "base/bind.h"
+#include "chrome/browser/web_applications/web_app_sync_bridge.h"
+#include "chrome/common/channel_info.h"
+#include "components/sync/base/model_type.h"
+#include "components/sync/base/report_unrecoverable_error.h"
+#include "components/sync/model_impl/client_tag_based_model_type_processor.h"
+
+namespace web_app {
+
+WebAppSyncManager::WebAppSyncManager() {
+  auto change_processor =
+      std::make_unique<syncer::ClientTagBasedModelTypeProcessor>(
+          syncer::WEB_APPS,
+          base::BindRepeating(&syncer::ReportUnrecoverableError,
+                              chrome::GetChannel()));
+
+  bridge_ = std::make_unique<WebAppSyncBridge>(std::move(change_processor));
+}
+
+WebAppSyncManager::~WebAppSyncManager() = default;
+
+}  // namespace web_app
diff --git a/chrome/browser/web_applications/web_app_sync_manager.h b/chrome/browser/web_applications/web_app_sync_manager.h
new file mode 100644
index 0000000..08a1bc04
--- /dev/null
+++ b/chrome/browser/web_applications/web_app_sync_manager.h
@@ -0,0 +1,32 @@
+// 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_WEB_APPLICATIONS_WEB_APP_SYNC_MANAGER_H_
+#define CHROME_BROWSER_WEB_APPLICATIONS_WEB_APP_SYNC_MANAGER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+
+namespace web_app {
+
+class WebAppSyncBridge;
+
+// Exclusively used from the UI thread.
+class WebAppSyncManager {
+ public:
+  WebAppSyncManager();
+  ~WebAppSyncManager();
+
+  WebAppSyncBridge& bridge() { return *bridge_; }
+
+ private:
+  std::unique_ptr<WebAppSyncBridge> bridge_;
+
+  DISALLOW_COPY_AND_ASSIGN(WebAppSyncManager);
+};
+
+}  // namespace web_app
+
+#endif  // CHROME_BROWSER_WEB_APPLICATIONS_WEB_APP_SYNC_MANAGER_H_
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 7fd74d5..fa89d6d2 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -232,6 +232,12 @@
 const base::Feature kDesktopPWAsUnifiedInstall{
     "DesktopPWAsUnifiedInstall", base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Enables or disables new Desktop PWAs Unified Sync and Storage (USS)
+// implementation that does not use extensions. Requires
+// kDesktopPWAsWithoutExtensions to be enabled.
+const base::Feature kDesktopPWAsUSS{"DesktopPWAsUSS",
+                                    base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables or disables the ability to install PWAs from the omnibox.
 const base::Feature kDesktopPWAsOmniboxInstall{
     "DesktopPWAsOmniboxInstall", base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 289ab87a..b8d830e 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -139,6 +139,9 @@
 extern const base::Feature kDesktopPWAsUnifiedInstall;
 
 COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kDesktopPWAsUSS;
+
+COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kDesktopPWAsOmniboxInstall;
 
 COMPONENT_EXPORT(CHROME_FEATURES)
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 1a473b18..db9e27ee 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -667,12 +667,6 @@
 // resulted in a browser startup.
 const char kWinJumplistAction[]             = "win-jumplist-action";
 
-#if !defined(GOOGLE_CHROME_BUILD)
-// Enables a live-reload for local NTP resources. This only works when Chrome
-// is running from a Chrome source directory.
-const char kLocalNtpReload[]                = "local-ntp-reload";
-#endif
-
 #if defined(OS_ANDROID)
 // Android authentication account type for SPNEGO authentication
 const char kAuthAndroidNegotiateAccountType[] = "auth-spnego-account-type";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index c7b4ed7..942b698 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -193,10 +193,6 @@
 extern const char kWinHttpProxyResolver[];
 extern const char kWinJumplistAction[];
 
-#if !defined(GOOGLE_CHROME_BUILD)
-extern const char kLocalNtpReload[];
-#endif
-
 #if defined(OS_ANDROID)
 extern const char kAuthAndroidNegotiateAccountType[];
 extern const char kEnableAccessibilityTabSwitcher[];
diff --git a/chrome/common/custom_handlers/protocol_handler.cc b/chrome/common/custom_handlers/protocol_handler.cc
index 19649d5..24c0b875 100644
--- a/chrome/common/custom_handlers/protocol_handler.cc
+++ b/chrome/common/custom_handlers/protocol_handler.cc
@@ -9,6 +9,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/value_conversions.h"
 #include "chrome/grit/generated_resources.h"
+#include "content/public/common/origin_util.h"
 #include "net/base/escape.h"
 #include "ui/base/l10n/l10n_util.h"
 
@@ -34,6 +35,35 @@
   return value->HasKey("protocol") && value->HasKey("url");
 }
 
+bool ProtocolHandler::IsValid() const {
+  // TODO(https://crbug.com/977083): Consider limiting to secure contexts.
+
+  // This matches SupportedSchemes() in blink's NavigatorContentUtils.
+
+  // Although not enforced in the spec the spec gives freedom to do additional
+  // security checks. Bugs have arisen from allowing non-http/https URLs, e.g.
+  // https://crbug.com/971917 so we check this here.
+  if (!url_.SchemeIsHTTPOrHTTPS()) {
+    return false;
+  }
+
+  // From:
+  // https://html.spec.whatwg.org/multipage/system-state.html#safelisted-scheme
+  static constexpr const char* const kProtocolSafelist[] = {
+      "bitcoin", "geo",  "im",   "irc",         "ircs", "magnet", "mailto",
+      "mms",     "news", "nntp", "openpgp4fpr", "sip",  "sms",    "smsto",
+      "ssh",     "tel",  "urn",  "webcal",      "wtai", "xmpp"};
+  static constexpr const char kWebPrefix[] = "web+";
+
+  bool has_web_prefix =
+      base::StartsWith(protocol_, kWebPrefix,
+                       base::CompareCase::INSENSITIVE_ASCII) &&
+      protocol_ != kWebPrefix;
+
+  return has_web_prefix ||
+         base::Contains(kProtocolSafelist, base::ToLowerASCII(protocol_));
+}
+
 bool ProtocolHandler::IsSameOrigin(
     const ProtocolHandler& handler) const {
   return handler.url().GetOrigin() == url_.GetOrigin();
diff --git a/chrome/common/custom_handlers/protocol_handler.h b/chrome/common/custom_handlers/protocol_handler.h
index 36b56799..d538705 100644
--- a/chrome/common/custom_handlers/protocol_handler.h
+++ b/chrome/common/custom_handlers/protocol_handler.h
@@ -34,6 +34,9 @@
   // define a ProtocolHandler.
   static bool IsValidDict(const base::DictionaryValue* value);
 
+  // Return true if the protocol handler meets security constraints.
+  bool IsValid() const;
+
   // Returns true if this handler's url has the same origin as the given one.
   bool IsSameOrigin(const ProtocolHandler& handler) const;
 
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 12db442..6e0de1f 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -1409,6 +1409,11 @@
 #if !defined(OS_ANDROID)
 // Whether or not this profile has been shown the Welcome page.
 const char kHasSeenWelcomePage[] = "browser.has_seen_welcome_page";
+
+// A boolean specifying whether the default search shortcut should be shown on
+// the New Tab Page after it has been initialized during first run.
+const char kShowFirstRunDefaultSearchShortcut[] =
+    "profile.show_first_run_default_search_shortcut";
 #endif
 
 #if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index a4f7619a..8560cdee 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -462,6 +462,7 @@
 
 #if !defined(OS_ANDROID)
 extern const char kHasSeenWelcomePage[];
+extern const char kShowFirstRunDefaultSearchShortcut[];
 #endif
 
 #if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)
diff --git a/chrome/services/cups_proxy/BUILD.gn b/chrome/services/cups_proxy/BUILD.gn
index 031c02a..f163e54 100644
--- a/chrome/services/cups_proxy/BUILD.gn
+++ b/chrome/services/cups_proxy/BUILD.gn
@@ -74,6 +74,7 @@
       ":test_support",
       "//base",
       "//chrome/services/cups_proxy/public/cpp",
+      "//chrome/services/cups_proxy/public/cpp:unit_tests",
       "//testing/gtest",
     ]
 
diff --git a/chrome/services/cups_proxy/ipp_validator.cc b/chrome/services/cups_proxy/ipp_validator.cc
index 18deadd..5485fd75 100644
--- a/chrome/services/cups_proxy/ipp_validator.cc
+++ b/chrome/services/cups_proxy/ipp_validator.cc
@@ -18,6 +18,7 @@
 #include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "chrome/services/cups_proxy/ipp_attribute_validator.h"
+#include "chrome/services/cups_proxy/public/cpp/cups_util.h"
 #include "net/http/http_util.h"
 #include "printing/backend/cups_ipp_util.h"
 
@@ -95,9 +96,20 @@
     return base::nullopt;
   }
 
-  // Ensure endpoint is either default('/') or known printer
-  auto printer = delegate_->GetPrinter(endpoint.as_string());
-  if (endpoint != "/" && !printer) {
+  // Empty endpoint is allowed.
+  if (endpoint == "/") {
+    return HttpRequestLine{method.as_string(), endpoint.as_string(),
+                           http_version.as_string()};
+  }
+
+  // Ensure endpoint is a known printer.
+  auto printer_id = ParseEndpointForPrinterId(endpoint.as_string());
+  if (!printer_id.has_value()) {
+    return base::nullopt;
+  }
+
+  auto printer = delegate_->GetPrinter(*printer_id);
+  if (!printer.has_value()) {
     return base::nullopt;
   }
 
diff --git a/chrome/services/cups_proxy/ipp_validator_unittest.cc b/chrome/services/cups_proxy/ipp_validator_unittest.cc
index fea795a..76bd154 100644
--- a/chrome/services/cups_proxy/ipp_validator_unittest.cc
+++ b/chrome/services/cups_proxy/ipp_validator_unittest.cc
@@ -27,6 +27,10 @@
 
 using Printer = chromeos::Printer;
 
+std::string EncodeEndpointForPrinterId(std::string printer_id) {
+  return "/printers/" + printer_id;
+}
+
 // Fake backend for CupsProxyServiceDelegate.
 class FakeServiceDelegate
     : public chromeos::printing::FakeCupsProxyServiceDelegate {
@@ -137,7 +141,7 @@
   auto request = GetBasicIppRequest();
   std::string printer_id = "abc";
 
-  request->endpoint = printer_id;
+  request->endpoint = EncodeEndpointForPrinterId(printer_id);
   EXPECT_FALSE(RunValidateIppRequest(request));
 
   // Should succeed now that |delegate_| knows about the printer.
diff --git a/chrome/services/cups_proxy/public/cpp/BUILD.gn b/chrome/services/cups_proxy/public/cpp/BUILD.gn
index 0c35dc6..ac042cf 100644
--- a/chrome/services/cups_proxy/public/cpp/BUILD.gn
+++ b/chrome/services/cups_proxy/public/cpp/BUILD.gn
@@ -32,6 +32,20 @@
   }
 }
 
+source_set("unit_tests") {
+  testonly = true
+
+  sources = [
+    "cups_util_unittest.cc",
+  ]
+  deps = [
+    ":cpp",
+    "//base",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
+}
+
 source_set("manifest") {
   sources = [
     "manifest.cc",
diff --git a/chrome/services/cups_proxy/public/cpp/cups_util.cc b/chrome/services/cups_proxy/public/cpp/cups_util.cc
index a7712ce9..11a5959f 100644
--- a/chrome/services/cups_proxy/public/cpp/cups_util.cc
+++ b/chrome/services/cups_proxy/public/cpp/cups_util.cc
@@ -35,4 +35,16 @@
   return uuid.substr(uuid_start + 1).as_string();
 }
 
+base::Optional<std::string> ParseEndpointForPrinterId(
+    base::StringPiece endpoint) {
+  size_t last_path = endpoint.find_last_of('/');
+  if (last_path == base::StringPiece::npos ||
+      last_path + 1 >= endpoint.size()) {
+    return base::nullopt;
+  }
+
+  endpoint.remove_prefix(last_path + 1);
+  return endpoint.as_string();
+}
+
 }  // namespace cups_proxy
diff --git a/chrome/services/cups_proxy/public/cpp/cups_util.h b/chrome/services/cups_proxy/public/cpp/cups_util.h
index 55d393f..ffac05b 100644
--- a/chrome/services/cups_proxy/public/cpp/cups_util.h
+++ b/chrome/services/cups_proxy/public/cpp/cups_util.h
@@ -23,9 +23,14 @@
 // If |ipp| refers to a printer, we return the associated printer_id.
 // Note: Expects the printer id to be embedded in the resource field of the
 // 'printer-uri' IPP attribute.
-// TODO(crbug.com/945409): Add testing suite.
+// TODO(crbug.com/945409): Expand testing suite.
 base::Optional<std::string> GetPrinterId(ipp_t* ipp);
 
+// Expects |endpoint| to be of the form '/printers/{printer_id}'.
+// Returns an empty Optional if parsing fails or yields an empty printer_id.
+base::Optional<std::string> ParseEndpointForPrinterId(
+    base::StringPiece endpoint);
+
 }  // namespace cups_proxy
 
 #endif  // CHROME_SERVICES_CUPS_PROXY_PUBLIC_CPP_CUPS_UTIL_H_
diff --git a/chrome/services/cups_proxy/public/cpp/cups_util_unittest.cc b/chrome/services/cups_proxy/public/cpp/cups_util_unittest.cc
new file mode 100644
index 0000000..fe1c384e
--- /dev/null
+++ b/chrome/services/cups_proxy/public/cpp/cups_util_unittest.cc
@@ -0,0 +1,45 @@
+// 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/services/cups_proxy/public/cpp/cups_util.h"
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cups_proxy {
+namespace {
+
+using ::testing::IsEmpty;
+using ::testing::Not;
+
+const char kPrinterIdPrefix[] = "/printers/";
+
+// Generated via base::GenerateGUID.
+const char kDefaultPrinterId[] = "fd4c5f2e-7549-43d5-b931-9bf4e4f1bf51";
+
+TEST(ParseEndpointForPrinterIdTest, SimpleSanityTest) {
+  base::Optional<std::string> printer_id = ParseEndpointForPrinterId(
+      std::string(kPrinterIdPrefix) + kDefaultPrinterId);
+
+  EXPECT_TRUE(printer_id.has_value());
+  EXPECT_THAT(*printer_id, Not(IsEmpty()));
+}
+
+// PrinterId's must be non-empty.
+TEST(ParseEndpointForPrinterIdTest, EmptyPrinterId) {
+  EXPECT_FALSE(ParseEndpointForPrinterId(kPrinterIdPrefix));
+}
+
+// Endpoints must contain a '/'.
+TEST(ParseEndpointForPrinterIdTest, MissingPathDelimeter) {
+  EXPECT_FALSE(ParseEndpointForPrinterId(kDefaultPrinterId));
+}
+
+}  // namespace
+}  // namespace cups_proxy
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index b9fbf43..57af7a48 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3616,10 +3616,10 @@
       "../browser/safe_browsing/chrome_cleaner/srt_field_trial_win_unittest.cc",
       "../browser/search/background/ntp_background_service_unittest.cc",
       "../browser/search/chrome_colors/chrome_colors_service_unittest.cc",
-      "../browser/search/iframe_source_unittest.cc",
       "../browser/search/instant_service_unittest.cc",
       "../browser/search/instant_unittest_base.cc",
       "../browser/search/instant_unittest_base.h",
+      "../browser/search/most_visited_iframe_source_unittest.cc",
       "../browser/search/one_google_bar/one_google_bar_loader_impl_unittest.cc",
       "../browser/search/one_google_bar/one_google_bar_service_unittest.cc",
       "../browser/search/promos/promo_service_unittest.cc",
diff --git a/chrome/test/base/browser_with_test_window_test.cc b/chrome/test/base/browser_with_test_window_test.cc
index 9e2350a..30f54cd 100644
--- a/chrome/test/base/browser_with_test_window_test.cc
+++ b/chrome/test/base/browser_with_test_window_test.cc
@@ -51,7 +51,8 @@
 void BrowserWithTestWindowTest::SetUp() {
   testing::Test::SetUp();
 #if defined(OS_CHROMEOS)
-  ash_test_helper_.SetUp(true);
+  ash::AshTestHelper::InitParams init_params;
+  ash_test_helper_.SetUp(init_params);
 #elif defined(TOOLKIT_VIEWS)
   views_test_helper_.reset(new views::ScopedViewsTestHelper());
 #endif
diff --git a/chrome/test/data/custom_handler_foo.html b/chrome/test/data/custom_handler.html
similarity index 100%
rename from chrome/test/data/custom_handler_foo.html
rename to chrome/test/data/custom_handler.html
diff --git a/chrome/test/data/webui/welcome/app_chooser_test.js b/chrome/test/data/webui/welcome/app_chooser_test.js
index 3871ed4..4bd0318 100644
--- a/chrome/test/data/webui/welcome/app_chooser_test.js
+++ b/chrome/test/data/webui/welcome/app_chooser_test.js
@@ -37,13 +37,13 @@
       },
     ];
 
-    /** @type {nux.NuxAppProxy} */
+    /** @type {welcome.NuxAppProxy} */
     let testAppBrowserProxy;
 
-    /** @type {nux.ModuleMetricsProxy} */
+    /** @type {welcome.ModuleMetricsProxy} */
     let testAppMetricsProxy;
 
-    /** @type {nux.BookmarkProxy} */
+    /** @type {welcome.BookmarkProxy} */
     let testBookmarkBrowserProxy;
 
     /** @type {AppChooserElement} */
@@ -54,10 +54,10 @@
       testAppMetricsProxy = new TestMetricsProxy();
       testBookmarkBrowserProxy = new TestBookmarkProxy();
 
-      nux.GoogleAppProxyImpl.instance_ = testAppBrowserProxy;
-      nux.GoogleAppsMetricsProxyImpl.instance_ = testAppMetricsProxy;
-      nux.BookmarkProxyImpl.instance_ = testBookmarkBrowserProxy;
-      nux.BookmarkBarManager.instance_ = new nux.BookmarkBarManager();
+      welcome.GoogleAppProxyImpl.instance_ = testAppBrowserProxy;
+      welcome.GoogleAppsMetricsProxyImpl.instance_ = testAppMetricsProxy;
+      welcome.BookmarkProxyImpl.instance_ = testBookmarkBrowserProxy;
+      welcome.BookmarkBarManager.instance_ = new welcome.BookmarkBarManager();
 
       testAppBrowserProxy.setAppList(apps);
 
diff --git a/chrome/test/data/webui/welcome/module_metrics_test.js b/chrome/test/data/webui/welcome/module_metrics_test.js
index 9ca564b8..4c729898 100644
--- a/chrome/test/data/webui/welcome/module_metrics_test.js
+++ b/chrome/test/data/webui/welcome/module_metrics_test.js
@@ -4,15 +4,15 @@
 
 cr.define('onboarding_welcome_module_metrics', function() {
   suite('ModuleMetricsTest', function() {
-    /** @type {nux.ModuleMetricsProxy} */
+    /** @type {welcome.ModuleMetricsProxy} */
     let testMetricsProxy;
 
-    /** @type {nux.ModuleMetricsManager} */
+    /** @type {welcome.ModuleMetricsManager} */
     let testMetricsManager;
 
     setup(function() {
       testMetricsProxy = new TestMetricsProxy();
-      testMetricsManager = new nux.ModuleMetricsManager(testMetricsProxy);
+      testMetricsManager = new welcome.ModuleMetricsManager(testMetricsProxy);
 
       testMetricsManager.recordPageInitialized();
 
diff --git a/chrome/test/data/webui/welcome/nux_ntp_background_test.js b/chrome/test/data/webui/welcome/nux_ntp_background_test.js
index 0dd420fe..4798026 100644
--- a/chrome/test/data/webui/welcome/nux_ntp_background_test.js
+++ b/chrome/test/data/webui/welcome/nux_ntp_background_test.js
@@ -4,7 +4,7 @@
 
 cr.define('onboarding_ntp_background_test', function() {
   suite('NuxNtpBackgroundTest', function() {
-    /** @type {!Array<!nux.NtpBackgroundData} */
+    /** @type {!Array<!welcome.NtpBackgroundData} */
     let backgrounds = [
       {
         id: 0,
@@ -25,10 +25,10 @@
     /** @type {NuxNtpBackgroundElement} */
     let testElement;
 
-    /** @type {nux.ModuleMetricsProxy} */
+    /** @type {welcome.ModuleMetricsProxy} */
     let testMetricsProxy;
 
-    /** @type {nux.NtpBackgroundProxy} */
+    /** @type {welcome.NtpBackgroundProxy} */
     let testNtpBackgroundProxy;
 
     setup(function() {
@@ -37,9 +37,9 @@
       });
 
       testMetricsProxy = new TestMetricsProxy();
-      nux.NtpBackgroundMetricsProxyImpl.instance_ = testMetricsProxy;
+      welcome.NtpBackgroundMetricsProxyImpl.instance_ = testMetricsProxy;
       testNtpBackgroundProxy = new TestNtpBackgroundProxy();
-      nux.NtpBackgroundProxyImpl.instance_ = testNtpBackgroundProxy;
+      welcome.NtpBackgroundProxyImpl.instance_ = testNtpBackgroundProxy;
       testNtpBackgroundProxy.setBackgroundsList(backgrounds);
 
       PolymerTest.clearBody();
diff --git a/chrome/test/data/webui/welcome/nux_set_as_default_test.js b/chrome/test/data/webui/welcome/nux_set_as_default_test.js
index 2f470fb4..aedb067 100644
--- a/chrome/test/data/webui/welcome/nux_set_as_default_test.js
+++ b/chrome/test/data/webui/welcome/nux_set_as_default_test.js
@@ -7,7 +7,7 @@
     /** @type {NuxSetAsDefaultElement} */
     let testElement;
 
-    /** @type {nux.NuxSetAsDefaultProxy} */
+    /** @type {welcome.NuxSetAsDefaultProxy} */
     let testSetAsDefaultProxy;
 
     /** @type {!Promise} */
@@ -15,7 +15,7 @@
 
     setup(function() {
       testSetAsDefaultProxy = new TestNuxSetAsDefaultProxy();
-      nux.NuxSetAsDefaultProxyImpl.instance_ = testSetAsDefaultProxy;
+      welcome.NuxSetAsDefaultProxyImpl.instance_ = testSetAsDefaultProxy;
 
       navigatedPromise = new Promise(resolve => {
         // Spy on navigational function to make sure it's called.
diff --git a/chrome/test/data/webui/welcome/test_bookmark_proxy.js b/chrome/test/data/webui/welcome/test_bookmark_proxy.js
index 9ba70638..911722f 100644
--- a/chrome/test/data/webui/welcome/test_bookmark_proxy.js
+++ b/chrome/test/data/webui/welcome/test_bookmark_proxy.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-/** @implements {nux.BookmarkProxy} */
+/** @implements {welcome.BookmarkProxy} */
 class TestBookmarkProxy extends TestBrowserProxy {
   constructor() {
     super([
diff --git a/chrome/test/data/webui/welcome/test_google_app_proxy.js b/chrome/test/data/webui/welcome/test_google_app_proxy.js
index 6b0af9e..c352083 100644
--- a/chrome/test/data/webui/welcome/test_google_app_proxy.js
+++ b/chrome/test/data/webui/welcome/test_google_app_proxy.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-/** @implements {nux.GoogleAppProxy} */
+/** @implements {welcome.GoogleAppProxy} */
 class TestGoogleAppProxy extends TestBrowserProxy {
   constructor() {
     super([
@@ -13,7 +13,7 @@
 
     this.providerSelectedCount = 0;
 
-    /** @private {!Array<!nux.BookmarkListItem>} */
+    /** @private {!Array<!welcome.BookmarkListItem>} */
     this.appList_ = [];
   }
 
@@ -34,7 +34,7 @@
     this.providerSelectedCount++;
   }
 
-  /** @param {!Array<!nux.BookmarkListItem>} appList */
+  /** @param {!Array<!welcome.BookmarkListItem>} appList */
   setAppList(appList) {
     this.appList_ = appList;
   }
diff --git a/chrome/test/data/webui/welcome/test_metrics_proxy.js b/chrome/test/data/webui/welcome/test_metrics_proxy.js
index ef5d4fc..87ec0156 100644
--- a/chrome/test/data/webui/welcome/test_metrics_proxy.js
+++ b/chrome/test/data/webui/welcome/test_metrics_proxy.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-/** @implements {nux.ModuleMetricsProxy} */
+/** @implements {welcome.ModuleMetricsProxy} */
 class TestMetricsProxy extends TestBrowserProxy {
   constructor() {
     super([
diff --git a/chrome/test/data/webui/welcome/test_ntp_background_proxy.js b/chrome/test/data/webui/welcome/test_ntp_background_proxy.js
index cae93b8..223b6f4 100644
--- a/chrome/test/data/webui/welcome/test_ntp_background_proxy.js
+++ b/chrome/test/data/webui/welcome/test_ntp_background_proxy.js
@@ -14,7 +14,7 @@
       'setBackground',
     ]);
 
-    /** @private {!Array<!nux.NtpBackgroundData} */
+    /** @private {!Array<!welcome.NtpBackgroundData} */
     this.backgroundsList_ = [];
 
     /** @private {boolean} */
@@ -58,7 +58,7 @@
     this.preloadImageSuccess_ = success;
   }
 
-  /** @param {!Array<!nux.NtpBackgroundData>} backgroundsList */
+  /** @param {!Array<!welcome.NtpBackgroundData>} backgroundsList */
   setBackgroundsList(backgroundsList) {
     this.backgroundsList_ = backgroundsList;
   }
diff --git a/chrome/test/data/webui/welcome/test_nux_set_as_default_proxy.js b/chrome/test/data/webui/welcome/test_nux_set_as_default_proxy.js
index a91e168..c6ab9262 100644
--- a/chrome/test/data/webui/welcome/test_nux_set_as_default_proxy.js
+++ b/chrome/test/data/webui/welcome/test_nux_set_as_default_proxy.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-/** @implements {nux.NuxSetAsDefaultProxy} */
+/** @implements {welcome.NuxSetAsDefaultProxy} */
 class TestNuxSetAsDefaultProxy extends TestBrowserProxy {
   constructor() {
     super([
@@ -30,7 +30,7 @@
     this.methodCalled('setAsDefault');
   }
 
-  /** @param {!nux.DefaultBrowserInfo} status */
+  /** @param {!welcome.DefaultBrowserInfo} status */
   setDefaultStatus(status) {
     this.defaultStatus_ = status;
   }
diff --git a/chrome/test/data/webui/welcome/welcome_app_test.js b/chrome/test/data/webui/welcome/welcome_app_test.js
index cd17f3d..2a5112f 100644
--- a/chrome/test/data/webui/welcome/welcome_app_test.js
+++ b/chrome/test/data/webui/welcome/welcome_app_test.js
@@ -11,7 +11,7 @@
     /** @type {welcome.WelcomeBrowserProxy} */
     let testWelcomeBrowserProxy;
 
-    /** @type {nux.NuxSetAsDefaultProxy} */
+    /** @type {welcome.NuxSetAsDefaultProxy} */
     let testSetAsDefaultProxy;
 
     function resetTestElement() {
@@ -48,11 +48,11 @@
       welcome.WelcomeBrowserProxyImpl.instance_ = testWelcomeBrowserProxy;
 
       testSetAsDefaultProxy = new TestNuxSetAsDefaultProxy();
-      nux.NuxSetAsDefaultProxyImpl.instance_ = testSetAsDefaultProxy;
+      welcome.NuxSetAsDefaultProxyImpl.instance_ = testSetAsDefaultProxy;
 
       // Not used in test, but setting to test proxy anyway, in order to prevent
       // calls to backend.
-      nux.BookmarkProxyImpl.instance_ = new TestBookmarkProxy();
+      welcome.BookmarkProxyImpl.instance_ = new TestBookmarkProxy();
 
       resetTestElement();
     });
@@ -123,7 +123,7 @@
 
     test('default-status check resolves with correct value', function() {
       /**
-       * @param {!nux.DefaultBrowserInfo} status
+       * @param {!welcome.DefaultBrowserInfo} status
        * @param {boolean} expectedDefaultExists
        * @return {!Promise}
        */
diff --git a/chromecast/media/audio/BUILD.gn b/chromecast/media/audio/BUILD.gn
index 308ad35..2919478 100644
--- a/chromecast/media/audio/BUILD.gn
+++ b/chromecast/media/audio/BUILD.gn
@@ -15,6 +15,8 @@
 
 cast_source_set("audio") {
   sources = [
+    "cast_audio_input_stream.cc",
+    "cast_audio_input_stream.h",
     "cast_audio_manager.cc",
     "cast_audio_manager.h",
     "cast_audio_mixer.cc",
diff --git a/chromecast/media/audio/cast_audio_input_stream.cc b/chromecast/media/audio/cast_audio_input_stream.cc
new file mode 100644
index 0000000..32790704
--- /dev/null
+++ b/chromecast/media/audio/cast_audio_input_stream.cc
@@ -0,0 +1,100 @@
+// 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 "chromecast/media/audio/cast_audio_input_stream.h"
+
+#include "base/logging.h"
+#include "chromecast/media/audio/capture_service/capture_service_receiver.h"
+
+namespace chromecast {
+namespace media {
+
+CastAudioInputStream::CastAudioInputStream(
+    const ::media::AudioParameters& audio_params,
+    const std::string& device_id)
+    : audio_params_(audio_params) {
+  DETACH_FROM_THREAD(audio_thread_checker_);
+  LOG(INFO) << __func__ << " " << this
+            << " created from device_id = " << device_id
+            << " with audio_params = {" << audio_params_.AsHumanReadableString()
+            << "}.";
+}
+
+CastAudioInputStream::~CastAudioInputStream() {
+  DCHECK_CALLED_ON_VALID_THREAD(audio_thread_checker_);
+}
+
+bool CastAudioInputStream::Open() {
+  DCHECK_CALLED_ON_VALID_THREAD(audio_thread_checker_);
+  DCHECK(!capture_service_receiver_);
+  LOG(INFO) << __func__ << " " << this << ".";
+
+  // Sanity check the audio parameters.
+  ::media::AudioParameters::Format format = audio_params_.format();
+  DCHECK((format == ::media::AudioParameters::AUDIO_PCM_LINEAR) ||
+         (format == ::media::AudioParameters::AUDIO_PCM_LOW_LATENCY));
+  ::media::ChannelLayout channel_layout = audio_params_.channel_layout();
+  if ((channel_layout != ::media::CHANNEL_LAYOUT_MONO) &&
+      (channel_layout != ::media::CHANNEL_LAYOUT_STEREO)) {
+    LOG(WARNING) << "Unsupported channel layout: " << channel_layout;
+    return false;
+  }
+  DCHECK_GE(audio_params_.channels(), 1);
+  DCHECK_LE(audio_params_.channels(), 2);
+
+  capture_service_receiver_ =
+      std::make_unique<CaptureServiceReceiver>(audio_params_);
+  return true;
+}
+
+void CastAudioInputStream::Start(AudioInputCallback* input_callback) {
+  DCHECK_CALLED_ON_VALID_THREAD(audio_thread_checker_);
+  DCHECK(capture_service_receiver_);
+  DCHECK(input_callback);
+  LOG(INFO) << __func__ << " " << this << ".";
+  capture_service_receiver_->Start(input_callback);
+}
+
+void CastAudioInputStream::Stop() {
+  DCHECK_CALLED_ON_VALID_THREAD(audio_thread_checker_);
+  DCHECK(capture_service_receiver_);
+  LOG(INFO) << __func__ << " " << this << ".";
+  capture_service_receiver_->Stop();
+}
+
+void CastAudioInputStream::Close() {
+  DCHECK_CALLED_ON_VALID_THREAD(audio_thread_checker_);
+  LOG(INFO) << __func__ << " " << this << ".";
+  capture_service_receiver_.reset();
+}
+
+double CastAudioInputStream::GetMaxVolume() {
+  return 1.0;
+}
+
+void CastAudioInputStream::SetVolume(double volume) {}
+
+double CastAudioInputStream::GetVolume() {
+  return 1.0;
+}
+
+bool CastAudioInputStream::SetAutomaticGainControl(bool enabled) {
+  return false;
+}
+
+bool CastAudioInputStream::GetAutomaticGainControl() {
+  return false;
+}
+
+bool CastAudioInputStream::IsMuted() {
+  return false;
+}
+
+void CastAudioInputStream::SetOutputDeviceForAec(
+    const std::string& output_device_id) {
+  // Not supported. Do nothing.
+}
+
+}  // namespace media
+}  // namespace chromecast
diff --git a/chromecast/media/audio/cast_audio_input_stream.h b/chromecast/media/audio/cast_audio_input_stream.h
new file mode 100644
index 0000000..dc54bac
--- /dev/null
+++ b/chromecast/media/audio/cast_audio_input_stream.h
@@ -0,0 +1,53 @@
+// 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 CHROMECAST_MEDIA_AUDIO_CAST_AUDIO_INPUT_STREAM_H_
+#define CHROMECAST_MEDIA_AUDIO_CAST_AUDIO_INPUT_STREAM_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/threading/thread_checker.h"
+#include "media/audio/audio_io.h"
+#include "media/base/audio_parameters.h"
+
+namespace chromecast {
+namespace media {
+
+class CaptureServiceReceiver;
+class CastAudioManager;
+
+class CastAudioInputStream : public ::media::AudioInputStream {
+ public:
+  CastAudioInputStream(const ::media::AudioParameters& audio_params,
+                       const std::string& device_id);
+  ~CastAudioInputStream() override;
+
+  // ::media::AudioInputStream implementation:
+  bool Open() override;
+  void Start(AudioInputCallback* source_callback) override;
+  void Stop() override;
+  void Close() override;
+  double GetMaxVolume() override;
+  void SetVolume(double volume) override;
+  double GetVolume() override;
+  bool SetAutomaticGainControl(bool enabled) override;
+  bool GetAutomaticGainControl() override;
+  bool IsMuted() override;
+  void SetOutputDeviceForAec(const std::string& output_device_id) override;
+
+ private:
+  const ::media::AudioParameters audio_params_;
+  std::unique_ptr<CaptureServiceReceiver> capture_service_receiver_;
+
+  THREAD_CHECKER(audio_thread_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(CastAudioInputStream);
+};
+
+}  // namespace media
+}  // namespace chromecast
+
+#endif  // CHROMECAST_MEDIA_AUDIO_CAST_AUDIO_INPUT_STREAM_H_
diff --git a/chromecast/media/audio/cast_audio_manager_alsa.cc b/chromecast/media/audio/cast_audio_manager_alsa.cc
index 6112900..fb22185 100644
--- a/chromecast/media/audio/cast_audio_manager_alsa.cc
+++ b/chromecast/media/audio/cast_audio_manager_alsa.cc
@@ -4,12 +4,13 @@
 
 #include "chromecast/media/audio/cast_audio_manager_alsa.h"
 
-#include <string>
 #include <utility>
 
 #include "base/memory/free_deleter.h"
 #include "base/stl_util.h"
+#include "base/strings/string_piece.h"
 #include "chromecast/media/audio/audio_buildflags.h"
+#include "chromecast/media/audio/cast_audio_input_stream.h"
 #include "chromecast/media/cma/backend/cma_backend_factory.h"
 #include "media/audio/alsa/alsa_input.h"
 #include "media/audio/alsa/alsa_wrapper.h"
@@ -18,18 +19,62 @@
 namespace media {
 
 namespace {
+
 // TODO(alokp): Query the preferred value from media backend.
 const int kDefaultSampleRate = BUILDFLAG(AUDIO_INPUT_SAMPLE_RATE);
 
 // TODO(jyw): Query the preferred value from media backend.
-static const int kDefaultInputBufferSize = 1024;
+const int kDefaultInputBufferSize = 1024;
+
+const int kCommunicationsSampleRate = 16000;
+const int kCommunicationsInputBufferSize = 160;  // 10 ms.
 
 // Since "default" and "dmix" devices are virtual devices mapped to real
 // devices, we remove them from the list to avoiding duplicate counting.
-static const char* kInvalidAudioInputDevices[] = {
-    "default", "dmix", "null",
+constexpr base::StringPiece kInvalidAudioInputDevices[] = {
+    "default",
+    "dmix",
+    "null",
+    "communications",
 };
 
+// Constants specified by the ALSA API for device hints.
+constexpr char kPcmInterfaceName[] = "pcm";
+constexpr char kIoHintName[] = "IOID";
+constexpr char kNameHintName[] = "NAME";
+constexpr char kDescriptionHintName[] = "DESC";
+
+bool IsAlsaDeviceAvailable(CastAudioManagerAlsa::StreamType type,
+                           const char* device_name) {
+  if (!device_name)
+    return false;
+
+  // We do prefix matches on the device name to see whether to include
+  // it or not.
+  if (type == CastAudioManagerAlsa::kStreamCapture) {
+    // Check if the device is in the list of invalid devices.
+    for (size_t i = 0; i < base::size(kInvalidAudioInputDevices); ++i) {
+      if (kInvalidAudioInputDevices[i] == device_name)
+        return false;
+    }
+    return true;
+  } else {
+    DCHECK_EQ(CastAudioManagerAlsa::kStreamPlayback, type);
+    // We prefer the device type that maps straight to hardware but
+    // goes through software conversion if needed (e.g. incompatible
+    // sample rate).
+    // TODO(joi): Should we prefer "hw" instead?
+    const std::string kDeviceTypeDesired = "plughw";
+    return kDeviceTypeDesired == device_name;
+  }
+}
+
+std::string UnwantedDeviceTypeWhenEnumerating(
+    CastAudioManagerAlsa::StreamType wanted_type) {
+  return wanted_type == CastAudioManagerAlsa::kStreamPlayback ? "Input"
+                                                              : "Output";
+}
+
 }  // namespace
 
 CastAudioManagerAlsa::CastAudioManagerAlsa(
@@ -60,11 +105,23 @@
 void CastAudioManagerAlsa::GetAudioInputDeviceNames(
     ::media::AudioDeviceNames* device_names) {
   DCHECK(device_names->empty());
+  // Prepend the default device since we always want it to be on the top of the
+  // list for all platforms. Note, pulse has exclusively opened the default
+  // device, so we must open the device via the "default" moniker.
+  device_names->push_front(::media::AudioDeviceName::CreateDefault());
+  device_names->push_back(::media::AudioDeviceName::CreateCommunications());
+
   GetAlsaAudioDevices(kStreamCapture, device_names);
 }
 
 ::media::AudioParameters CastAudioManagerAlsa::GetInputStreamParameters(
     const std::string& device_id) {
+  if (device_id == ::media::AudioDeviceDescription::kCommunicationsDeviceId) {
+    return ::media::AudioParameters(::media::AudioParameters::AUDIO_PCM_LINEAR,
+                                    ::media::CHANNEL_LAYOUT_MONO,
+                                    kCommunicationsSampleRate,
+                                    kCommunicationsInputBufferSize);
+  }
   // TODO(jyw): Be smarter about sample rate instead of hardcoding it.
   // Need to send a valid AudioParameters object even when it will be unused.
   return ::media::AudioParameters(
@@ -96,6 +153,9 @@
       (device_id == ::media::AudioDeviceDescription::kDefaultDeviceId)
           ? ::media::AlsaPcmInputStream::kAutoSelectDevice
           : device_id;
+  if (device_name == ::media::AudioDeviceDescription::kCommunicationsDeviceId) {
+    return new CastAudioInputStream(params, device_name);
+  }
   return new ::media::AlsaPcmInputStream(this, device_name, params,
                                          wrapper_.get());
 }
@@ -103,8 +163,6 @@
 void CastAudioManagerAlsa::GetAlsaAudioDevices(
     StreamType type,
     ::media::AudioDeviceNames* device_names) {
-  // Constants specified by the ALSA API for device hints.
-  static const char kPcmInterfaceName[] = "pcm";
   int card = -1;
 
   // Loop through the sound cards to get ALSA device hints.
@@ -127,28 +185,17 @@
     StreamType type,
     void** hints,
     ::media::AudioDeviceNames* device_names) {
-  static const char kIoHintName[] = "IOID";
-  static const char kNameHintName[] = "NAME";
-  static const char kDescriptionHintName[] = "DESC";
-
-  const char* unwanted_device_type = UnwantedDeviceTypeWhenEnumerating(type);
+  const std::string unwanted_device_type =
+      UnwantedDeviceTypeWhenEnumerating(type);
 
   for (void** hint_iter = hints; *hint_iter != NULL; hint_iter++) {
     // Only examine devices of the right type.  Valid values are
     // "Input", "Output", and NULL which means both input and output.
     std::unique_ptr<char, base::FreeDeleter> io(
         wrapper_->DeviceNameGetHint(*hint_iter, kIoHintName));
-    if (io != NULL && strcmp(unwanted_device_type, io.get()) == 0)
+    if (io && unwanted_device_type == io.get())
       continue;
 
-    // Found a device, prepend the default device since we always want
-    // it to be on the top of the list for all platforms. And there is
-    // no duplicate counting here since it is only done if the list is
-    // still empty.  Note, pulse has exclusively opened the default
-    // device, so we must open the device via the "default" moniker.
-    if (device_names->empty())
-      device_names->push_front(::media::AudioDeviceName::CreateDefault());
-
     // Get the unique device name for the device.
     std::unique_ptr<char, base::FreeDeleter> unique_device_name(
         wrapper_->DeviceNameGetHint(*hint_iter, kNameHintName));
@@ -162,12 +209,10 @@
       ::media::AudioDeviceName name;
       name.unique_id = unique_device_name.get();
       if (desc) {
+        name.device_name = desc.get();
         // Use the more user friendly description as name.
         // Replace '\n' with '-'.
-        char* pret = strchr(desc.get(), '\n');
-        if (pret)
-          *pret = '-';
-        name.device_name = desc.get();
+        name.device_name.replace(name.device_name.find('\n'), 1, 1, '-');
       } else {
         // Virtual devices don't necessarily have descriptions.
         // Use their names instead.
@@ -180,39 +225,5 @@
   }
 }
 
-// static
-bool CastAudioManagerAlsa::IsAlsaDeviceAvailable(StreamType type,
-                                                 const char* device_name) {
-  if (!device_name)
-    return false;
-
-  // We do prefix matches on the device name to see whether to include
-  // it or not.
-  if (type == kStreamCapture) {
-    // Check if the device is in the list of invalid devices.
-    for (size_t i = 0; i < base::size(kInvalidAudioInputDevices); ++i) {
-      if (strncmp(kInvalidAudioInputDevices[i], device_name,
-                  strlen(kInvalidAudioInputDevices[i])) == 0)
-        return false;
-    }
-    return true;
-  } else {
-    DCHECK_EQ(kStreamPlayback, type);
-    // We prefer the device type that maps straight to hardware but
-    // goes through software conversion if needed (e.g. incompatible
-    // sample rate).
-    // TODO(joi): Should we prefer "hw" instead?
-    static const char kDeviceTypeDesired[] = "plughw";
-    return strncmp(kDeviceTypeDesired, device_name,
-                   base::size(kDeviceTypeDesired) - 1) == 0;
-  }
-}
-
-// static
-const char* CastAudioManagerAlsa::UnwantedDeviceTypeWhenEnumerating(
-    StreamType wanted_type) {
-  return wanted_type == kStreamPlayback ? "Input" : "Output";
-}
-
 }  // namespace media
 }  // namespace chromecast
diff --git a/chromecast/media/audio/cast_audio_manager_alsa.h b/chromecast/media/audio/cast_audio_manager_alsa.h
index 5a0c49f108..d0fa9a4 100644
--- a/chromecast/media/audio/cast_audio_manager_alsa.h
+++ b/chromecast/media/audio/cast_audio_manager_alsa.h
@@ -22,6 +22,11 @@
 
 class CastAudioManagerAlsa : public CastAudioManager {
  public:
+  enum StreamType {
+    kStreamPlayback = 0,
+    kStreamCapture,
+  };
+
   CastAudioManagerAlsa(
       std::unique_ptr<::media::AudioThread> audio_thread,
       ::media::AudioLogFactory* audio_log_factory,
@@ -41,11 +46,6 @@
       const std::string& device_id) override;
 
  private:
-  enum StreamType {
-    kStreamPlayback = 0,
-    kStreamCapture,
-  };
-
   // CastAudioManager implementation.
   ::media::AudioInputStream* MakeLinearInputStream(
       const ::media::AudioParameters& params,
@@ -70,11 +70,6 @@
                           void** hint,
                           ::media::AudioDeviceNames* device_names);
 
-  // Checks if the specific ALSA device is available.
-  static bool IsAlsaDeviceAvailable(StreamType type, const char* device_name);
-
-  static const char* UnwantedDeviceTypeWhenEnumerating(StreamType wanted_type);
-
   std::unique_ptr<::media::AlsaWrapper> wrapper_;
 
   DISALLOW_COPY_AND_ASSIGN(CastAudioManagerAlsa);
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 625ee5ff..30098ec 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -40,6 +40,10 @@
 const base::Feature kCrostiniUsbAllowUnsupported{
     "CrostiniUsbAllowUnsupported", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables or disables the new WebUI Crostini installer.
+const base::Feature kCrostiniWebUIInstaller{"CrostiniWebUIInstaller",
+                                            base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables or disables the CryptAuth v2 Enrollment flow.
 const base::Feature kCryptAuthV2Enrollment{"CryptAuthV2Enrollment",
                                            base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chromeos/constants/chromeos_features.h b/chromeos/constants/chromeos_features.h
index 7903111..128f9b0 100644
--- a/chromeos/constants/chromeos_features.h
+++ b/chromeos/constants/chromeos_features.h
@@ -31,6 +31,8 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kCrostiniUsbAllowUnsupported;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
+extern const base::Feature kCrostiniWebUIInstaller;
+COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const base::Feature kCryptAuthV2Enrollment;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const base::Feature kDiscoverApp;
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const base::Feature kDriveFs;
diff --git a/components/autofill/core/browser/ui/accessory_sheet_data.cc b/components/autofill/core/browser/ui/accessory_sheet_data.cc
index 36eb0fb..3f9740d 100644
--- a/components/autofill/core/browser/ui/accessory_sheet_data.cc
+++ b/components/autofill/core/browser/ui/accessory_sheet_data.cc
@@ -56,6 +56,8 @@
 
 UserInfo::UserInfo() = default;
 
+UserInfo::UserInfo(std::string origin) : origin_(std::move(origin)) {}
+
 UserInfo::UserInfo(const UserInfo& user_info) = default;
 
 UserInfo::UserInfo(UserInfo&& field) = default;
@@ -67,11 +69,12 @@
 UserInfo& UserInfo::operator=(UserInfo&& user_info) = default;
 
 bool UserInfo::operator==(const UserInfo& user_info) const {
-  return fields_ == user_info.fields_;
+  return fields_ == user_info.fields_ && origin_ == user_info.origin_;
 }
 
 std::ostream& operator<<(std::ostream& os, const UserInfo& user_info) {
-  os << "[\n";
+  os << "origin: \"" << user_info.origin() << "\", \n"
+     << "fields: [\n";
   for (const UserInfo::Field& field : user_info.fields()) {
     os << field << ", \n";
   }
@@ -164,13 +167,15 @@
 
 AccessorySheetData::Builder::~Builder() = default;
 
-AccessorySheetData::Builder&& AccessorySheetData::Builder::AddUserInfo() && {
+AccessorySheetData::Builder&& AccessorySheetData::Builder::AddUserInfo(
+    std::string origin) && {
   // Calls AddUserInfo()& since |this| is an lvalue.
-  return std::move(AddUserInfo());
+  return std::move(AddUserInfo(std::move(origin)));
 }
 
-AccessorySheetData::Builder& AccessorySheetData::Builder::AddUserInfo() & {
-  accessory_sheet_data_.add_user_info(UserInfo());
+AccessorySheetData::Builder& AccessorySheetData::Builder::AddUserInfo(
+    std::string origin) & {
+  accessory_sheet_data_.add_user_info(UserInfo(std::move(origin)));
   return *this;
 }
 
diff --git a/components/autofill/core/browser/ui/accessory_sheet_data.h b/components/autofill/core/browser/ui/accessory_sheet_data.h
index c4fc354d..1c1fa830 100644
--- a/components/autofill/core/browser/ui/accessory_sheet_data.h
+++ b/components/autofill/core/browser/ui/accessory_sheet_data.h
@@ -59,6 +59,7 @@
   };
 
   UserInfo();
+  explicit UserInfo(std::string origin);
   UserInfo(const UserInfo& user_info);
   UserInfo(UserInfo&& field);
 
@@ -70,10 +71,12 @@
   void add_field(Field field) { fields_.push_back(std::move(field)); }
 
   const std::vector<Field>& fields() const { return fields_; }
+  const std::string& origin() const { return origin_; }
 
   bool operator==(const UserInfo& user_info) const;
 
  private:
+  std::string origin_;
   std::vector<Field> fields_;
 };
 
@@ -175,8 +178,8 @@
   ~Builder();
 
   // Adds a new UserInfo object to |accessory_sheet_data_|.
-  Builder&& AddUserInfo() &&;
-  Builder& AddUserInfo() &;
+  Builder&& AddUserInfo(std::string origin = std::string()) &&;
+  Builder& AddUserInfo(std::string origin = std::string()) &;
 
   // Appends a selectable, non-obfuscated field to the last UserInfo object.
   Builder&& AppendSimpleField(base::string16 text) &&;
diff --git a/components/exo/wayland/clients/test/wayland_client_test_helper.cc b/components/exo/wayland/clients/test/wayland_client_test_helper.cc
index 3f5a2cf..ad65264c 100644
--- a/components/exo/wayland/clients/test/wayland_client_test_helper.cc
+++ b/components/exo/wayland/clients/test/wayland_client_test_helper.cc
@@ -107,9 +107,8 @@
   command_line->AppendSwitch(wm::switches::kWindowAnimationsDisabled);
 
   ash_test_helper_ = std::make_unique<ash::AshTestHelper>();
-
-  ash_test_helper_->SetUp(/*start_session=*/true,
-                          /*provide_local_state=*/true);
+  ash::AshTestHelper::InitParams init_params;
+  ash_test_helper_->SetUp(init_params);
   ash::Shell::GetPrimaryRootWindow()->Show();
   ash::Shell::GetPrimaryRootWindow()->GetHost()->Show();
   ash::Shell::GetPrimaryRootWindow()->MoveCursorTo(gfx::Point(-1000, -1000));
diff --git a/components/favicon/core/large_icon_service_impl.cc b/components/favicon/core/large_icon_service_impl.cc
index f4f37fe..3a894cf 100644
--- a/components/favicon/core/large_icon_service_impl.cc
+++ b/components/favicon/core/large_icon_service_impl.cc
@@ -13,7 +13,6 @@
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/singleton.h"
-#include "base/metrics/field_trial_params.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/stringprintf.h"
@@ -34,11 +33,6 @@
 
 namespace favicon {
 
-// This feature is only used for accessing field trial parameters, not for
-// switching on/off the code.
-const base::Feature kLargeIconServiceFetchingFeature{
-    "LargeIconServiceFetching", base::FEATURE_ENABLED_BY_DEFAULT};
-
 namespace {
 
 using favicon_base::GoogleFaviconServerRequestStatus;
@@ -48,22 +42,16 @@
 const char kGoogleServerV2RequestFormat[] =
     "https://t0.gstatic.com/faviconV2?client=chrome&nfrp=2&%s"
     "size=%d&min_size=%d&max_size=%d&fallback_opts=TYPE,SIZE,URL&url=%s";
-const char kGoogleServerV2RequestFormatParam[] = "request_format";
 
 const char kClientParam[] = "client=chrome";
 
 const char kCheckSeenParam[] = "check_seen=true&";
 
 const int kGoogleServerV2EnforcedMinSizeInPixel = 16;
-const char kGoogleServerV2EnforcedMinSizeInPixelParam[] =
-    "enforced_min_size_in_pixel";
 
 const double kGoogleServerV2DesiredToMaxSizeFactor = 2.0;
-const char kGoogleServerV2DesiredToMaxSizeFactorParam[] =
-    "desired_to_max_size_factor";
 
-const double kGoogleServerV2MinimumMaxSizeInPixel = 256.0;
-const char kGoogleServerV2MinimumMaxSizeInPixelParam[] = "minimum_max_size";
+const int kGoogleServerV2MinimumMaxSizeInPixel = 256;
 
 const int kInvalidOrganizationId = -1;
 
@@ -88,30 +76,17 @@
     int min_source_size_in_pixel,
     int desired_size_in_pixel,
     bool may_page_url_be_private) {
-  std::string url_format = base::GetFieldTrialParamValueByFeature(
-      kLargeIconServiceFetchingFeature, kGoogleServerV2RequestFormatParam);
-  double desired_to_max_size_factor = base::GetFieldTrialParamByFeatureAsDouble(
-      kLargeIconServiceFetchingFeature,
-      kGoogleServerV2DesiredToMaxSizeFactorParam,
-      kGoogleServerV2DesiredToMaxSizeFactor);
-  int minimum_max_size_in_pixel = base::GetFieldTrialParamByFeatureAsInt(
-      kLargeIconServiceFetchingFeature,
-      kGoogleServerV2MinimumMaxSizeInPixelParam,
-      kGoogleServerV2MinimumMaxSizeInPixel);
-
-  min_source_size_in_pixel = std::max(
-      min_source_size_in_pixel, base::GetFieldTrialParamByFeatureAsInt(
-                                    kLargeIconServiceFetchingFeature,
-                                    kGoogleServerV2EnforcedMinSizeInPixelParam,
-                                    kGoogleServerV2EnforcedMinSizeInPixel));
+  min_source_size_in_pixel =
+      std::max(min_source_size_in_pixel, kGoogleServerV2EnforcedMinSizeInPixel);
   desired_size_in_pixel =
       std::max(desired_size_in_pixel, min_source_size_in_pixel);
-  int max_size_in_pixel =
-      static_cast<int>(desired_size_in_pixel * desired_to_max_size_factor);
-  max_size_in_pixel = std::max(max_size_in_pixel, minimum_max_size_in_pixel);
+  int max_size_in_pixel = static_cast<int>(
+      desired_size_in_pixel * kGoogleServerV2DesiredToMaxSizeFactor);
+  max_size_in_pixel =
+      std::max(max_size_in_pixel, kGoogleServerV2MinimumMaxSizeInPixel);
 
   std::string request_url = base::StringPrintf(
-      url_format.empty() ? kGoogleServerV2RequestFormat : url_format.c_str(),
+      kGoogleServerV2RequestFormat,
       may_page_url_be_private ? kCheckSeenParam : "", desired_size_in_pixel,
       min_source_size_in_pixel, max_size_in_pixel, page_url.spec().c_str());
   base::ReplaceFirstSubstringAfterOffset(
diff --git a/components/favicon/core/large_icon_service_impl_unittest.cc b/components/favicon/core/large_icon_service_impl_unittest.cc
index 55243a9..c7f4d728 100644
--- a/components/favicon/core/large_icon_service_impl_unittest.cc
+++ b/components/favicon/core/large_icon_service_impl_unittest.cc
@@ -225,56 +225,6 @@
       "Favicons.LargeIconService.DownloadedSize", 32, /*expected_count=*/1);
 }
 
-TEST_F(LargeIconServiceTest, ShouldGetFromGoogleServerWithCustomUrl) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeatureWithParameters(
-      kLargeIconServiceFetchingFeature,
-      {{"request_format",
-        "https://t0.gstatic.com/"
-        "faviconV2?%ssize=%d&min_size=%d&max_size=%d&url=%s"},
-       {"enforced_min_size_in_pixel", "43"},
-       {"desired_to_max_size_factor", "6.5"}});
-  const GURL kExpectedServerUrl(
-      "https://t0.gstatic.com/faviconV2?check_seen=true&"
-      "size=61&min_size=43&max_size=396&url=http://www.example.com/");
-
-  EXPECT_CALL(mock_favicon_service_, UnableToDownloadFavicon(_)).Times(0);
-  EXPECT_CALL(mock_favicon_service_,
-              CanSetOnDemandFavicons(GURL(kDummyUrl),
-                                     favicon_base::IconType::kTouchIcon, _))
-      .WillOnce([](auto, auto, base::OnceCallback<void(bool)> callback) {
-        return base::ThreadTaskRunnerHandle::Get()->PostTask(
-            FROM_HERE, base::BindOnce(std::move(callback), true));
-      });
-
-  base::MockCallback<favicon_base::GoogleFaviconServerCallback> callback;
-  EXPECT_CALL(*mock_image_fetcher_,
-              FetchImageAndData_(kExpectedServerUrl, _, _, _))
-      .WillOnce(PostFetchReply(gfx::Image::CreateFrom1xBitmap(
-          CreateTestSkBitmap(64, 64, kTestColor))));
-  EXPECT_CALL(mock_favicon_service_,
-              SetOnDemandFavicons(GURL(kDummyUrl), kExpectedServerUrl,
-                                  favicon_base::IconType::kTouchIcon, _, _))
-      .WillOnce(
-          [](auto, auto, auto, auto, base::OnceCallback<void(bool)> callback) {
-            return base::ThreadTaskRunnerHandle::Get()->PostTask(
-                FROM_HERE, base::BindOnce(std::move(callback), true));
-          });
-
-  large_icon_service_
-      .GetLargeIconOrFallbackStyleFromGoogleServerSkippingLocalCache(
-          favicon::FaviconServerFetcherParams::CreateForMobile(
-              GURL(kDummyUrl),
-              /*min_source_size_in_pixel=*/42,
-              /*desired_size_in_pixel=*/61),
-          /*may_page_url_be_private=*/true, /*should_trim_page_url_path=*/false,
-          TRAFFIC_ANNOTATION_FOR_TESTS, callback.Get());
-
-  EXPECT_CALL(callback,
-              Run(favicon_base::GoogleFaviconServerRequestStatus::SUCCESS));
-  scoped_task_environment_.RunUntilIdle();
-}
-
 TEST_F(LargeIconServiceTest, ShouldGetFromGoogleServerWithOriginalUrl) {
   const GURL kExpectedServerUrl(
       "https://t0.gstatic.com/faviconV2?client=chrome&nfrp=2"
diff --git a/components/gcm_driver/resources/gcm_internals.html b/components/gcm_driver/resources/gcm_internals.html
index 54d5874..92068078 100644
--- a/components/gcm_driver/resources/gcm_internals.html
+++ b/components/gcm_driver/resources/gcm_internals.html
@@ -1,5 +1,5 @@
 <!doctype html>
-<html dir="$i18n{textdirection}" lang="$i18n{language}">
+<html dir="ltr" lang="en">
 <head>
   <meta charset="utf-8">
   <title>GCM Internals</title>
diff --git a/components/network_hints/browser/network_hints_message_filter.cc b/components/network_hints/browser/network_hints_message_filter.cc
index 0a4f9107..998bad3 100644
--- a/components/network_hints/browser/network_hints_message_filter.cc
+++ b/components/network_hints/browser/network_hints_message_filter.cc
@@ -83,7 +83,7 @@
       int result,
       const base::Optional<net::AddressList>& resolved_addresses) override {
     VLOG(2) << __FUNCTION__ << ": " << hostname_ << ", result=" << result;
-    std::move(request_);
+    request_.reset();
   }
 
   mojo::Binding<network::mojom::ResolveHostClient> binding_;
diff --git a/components/password_manager/core/browser/login_database.cc b/components/password_manager/core/browser/login_database.cc
index 3c6b171f..b99abea2 100644
--- a/components/password_manager/core/browser/login_database.cc
+++ b/components/password_manager/core/browser/login_database.cc
@@ -1215,19 +1215,14 @@
   return true;
 }
 
-bool LoginDatabase::GetAutoSignInLogins(
-    std::vector<std::unique_ptr<PasswordForm>>* forms) {
-  DCHECK(forms);
+bool LoginDatabase::GetAutoSignInLogins(PrimaryKeyToFormMap* key_to_form_map) {
+  DCHECK(key_to_form_map);
   DCHECK(!autosignin_statement_.empty());
-  forms->clear();
+  key_to_form_map->clear();
 
   sql::Statement s(
       db_.GetCachedStatement(SQL_FROM_HERE, autosignin_statement_.c_str()));
-  PrimaryKeyToFormMap key_to_form_map;
-  FormRetrievalResult result = StatementToForms(&s, nullptr, &key_to_form_map);
-  for (auto& pair : key_to_form_map) {
-    forms->push_back(std::move(pair.second));
-  }
+  FormRetrievalResult result = StatementToForms(&s, nullptr, key_to_form_map);
   return result == FormRetrievalResult::kSuccess;
 }
 
diff --git a/components/password_manager/core/browser/login_database.h b/components/password_manager/core/browser/login_database.h
index 5b07dcf..303210a 100644
--- a/components/password_manager/core/browser/login_database.h
+++ b/components/password_manager/core/browser/login_database.h
@@ -142,8 +142,8 @@
                               forms) WARN_UNUSED_RESULT;
 
   // Gets the list of auto-sign-inable credentials.
-  bool GetAutoSignInLogins(std::vector<std::unique_ptr<autofill::PasswordForm>>*
-                               forms) WARN_UNUSED_RESULT;
+  bool GetAutoSignInLogins(PrimaryKeyToFormMap* key_to_form_map)
+      WARN_UNUSED_RESULT;
 
   // Deletes the login database file on disk, and creates a new, empty database.
   // This can be used after migrating passwords to some other store, to ensure
diff --git a/components/password_manager/core/browser/login_database_unittest.cc b/components/password_manager/core/browser/login_database_unittest.cc
index 37762be..22609bf 100644
--- a/components/password_manager/core/browser/login_database_unittest.cc
+++ b/components/password_manager/core/browser/login_database_unittest.cc
@@ -1011,7 +1011,7 @@
 }
 
 TEST_F(LoginDatabaseTest, GetAutoSignInLogins) {
-  std::vector<std::unique_ptr<PasswordForm>> result;
+  PrimaryKeyToFormMap key_to_form_map;
 
   GURL origin("https://example.com");
   EXPECT_TRUE(AddZeroClickableLogin(&db(), "foo1", origin));
@@ -1019,14 +1019,14 @@
   EXPECT_TRUE(AddZeroClickableLogin(&db(), "foo3", origin));
   EXPECT_TRUE(AddZeroClickableLogin(&db(), "foo4", origin));
 
-  EXPECT_TRUE(db().GetAutoSignInLogins(&result));
-  EXPECT_EQ(4U, result.size());
-  for (const auto& form : result)
-    EXPECT_FALSE(form->skip_zero_click);
+  EXPECT_TRUE(db().GetAutoSignInLogins(&key_to_form_map));
+  EXPECT_EQ(4U, key_to_form_map.size());
+  for (const auto& pair : key_to_form_map)
+    EXPECT_FALSE(pair.second->skip_zero_click);
 
   EXPECT_TRUE(db().DisableAutoSignInForOrigin(origin));
-  EXPECT_TRUE(db().GetAutoSignInLogins(&result));
-  EXPECT_EQ(0U, result.size());
+  EXPECT_TRUE(db().GetAutoSignInLogins(&key_to_form_map));
+  EXPECT_EQ(0U, key_to_form_map.size());
 }
 
 TEST_F(LoginDatabaseTest, DisableAutoSignInForOrigin) {
diff --git a/components/password_manager/core/browser/password_store_default.cc b/components/password_manager/core/browser/password_store_default.cc
index 67266c62..b34eff7 100644
--- a/components/password_manager/core/browser/password_store_default.cc
+++ b/components/password_manager/core/browser/password_store_default.cc
@@ -124,15 +124,15 @@
 
 PasswordStoreChangeList PasswordStoreDefault::DisableAutoSignInForOriginsImpl(
     const base::Callback<bool(const GURL&)>& origin_filter) {
-  std::vector<std::unique_ptr<PasswordForm>> forms;
+  PrimaryKeyToFormMap key_to_form_map;
   PasswordStoreChangeList changes;
-  if (!login_db_ || !login_db_->GetAutoSignInLogins(&forms))
+  if (!login_db_ || !login_db_->GetAutoSignInLogins(&key_to_form_map))
     return changes;
 
   std::set<GURL> origins_to_update;
-  for (const auto& form : forms) {
-    if (origin_filter.Run(form->origin))
-      origins_to_update.insert(form->origin);
+  for (const auto& pair : key_to_form_map) {
+    if (origin_filter.Run(pair.second->origin))
+      origins_to_update.insert(pair.second->origin);
   }
 
   std::set<GURL> origins_updated;
@@ -141,10 +141,10 @@
       origins_updated.insert(origin);
   }
 
-  for (const auto& form : forms) {
-    if (origins_updated.count(form->origin)) {
-      changes.push_back(
-          PasswordStoreChange(PasswordStoreChange::UPDATE, *form));
+  for (const auto& pair : key_to_form_map) {
+    if (origins_updated.count(pair.second->origin)) {
+      changes.emplace_back(PasswordStoreChange::UPDATE, *pair.second,
+                           /*primary_key=*/pair.first);
     }
   }
 
diff --git a/components/signin/core/browser/resources/signin_index.html b/components/signin/core/browser/resources/signin_index.html
index 64d6602..b54d72bbf 100644
--- a/components/signin/core/browser/resources/signin_index.html
+++ b/components/signin/core/browser/resources/signin_index.html
@@ -1,15 +1,15 @@
 <!doctype html>
-<html dir="$i18n{textdirection}" lang="$i18n{language}">
+<html dir="ltr" lang="en">
 <head>
   <meta charset="utf-8">
   <script src="chrome://resources/js/cr.js"></script>
   <script src="chrome://resources/js/util.js"></script>
   <script src="chrome://resources/js/load_time_data.js"></script>
   <script src="strings.js"></script>
-  <if expr="is_ios">
-    <!-- TODO(crbug.com/487000): Remove this once injected by the web layer. -->
-    <script src="chrome://resources/js/ios/web_ui.js"></script>
-  </if>
+<if expr="is_ios">
+  <!-- TODO(crbug.com/487000): Remove this once injected by the web layer. -->
+  <script src="chrome://resources/js/ios/web_ui.js"></script>
+</if>
   <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
   <link rel="stylesheet" type="text/css" href="signin_index.css">
 </head>
diff --git a/components/sync/base/data_type_histogram.h b/components/sync/base/data_type_histogram.h
index 5814eee2..4c5131b 100644
--- a/components/sync/base/data_type_histogram.h
+++ b/components/sync/base/data_type_histogram.h
@@ -170,6 +170,9 @@
       case ::syncer::SECURITY_EVENTS:                            \
         PER_DATA_TYPE_MACRO("SecurityEvents");                   \
         break;                                                   \
+      case ::syncer::WEB_APPS:                                   \
+        PER_DATA_TYPE_MACRO("WebApps");                          \
+        break;                                                   \
       case ::syncer::WIFI_CONFIGURATIONS:                        \
         PER_DATA_TYPE_MACRO("WifiConfigurations");               \
         break;                                                   \
diff --git a/components/sync/base/model_type.cc b/components/sync/base/model_type.cc
index 518410a..50bd0dc 100644
--- a/components/sync/base/model_type.cc
+++ b/components/sync/base/model_type.cc
@@ -155,6 +155,8 @@
     {WIFI_CONFIGURATIONS, "WIFI_CONFIGURATION", "wifi_configurations",
      "Wifi Configurations",
      sync_pb::EntitySpecifics::kWifiConfigurationFieldNumber, 44},
+    {WEB_APPS, "WEB_APP", "web_apps", "Web Apps",
+     sync_pb::EntitySpecifics::kWebAppFieldNumber, 45},
     // ---- Proxy types ----
     {PROXY_TABS, "", "", "Tabs", -1, 25},
     // ---- Control Types ----
@@ -167,11 +169,11 @@
 static_assert(base::size(kModelTypeInfoMap) == ModelType::NUM_ENTRIES,
               "kModelTypeInfoMap should have ModelType::NUM_ENTRIES elements");
 
-static_assert(45 == syncer::ModelType::NUM_ENTRIES,
+static_assert(46 == syncer::ModelType::NUM_ENTRIES,
               "When adding a new type, update enum SyncModelTypes in enums.xml "
               "and suffix SyncModelType in histograms.xml.");
 
-static_assert(45 == syncer::ModelType::NUM_ENTRIES,
+static_assert(46 == syncer::ModelType::NUM_ENTRIES,
               "When adding a new type, update kAllocatorDumpNameWhitelist in "
               "base/trace_event/memory_infra_background_whitelist.cc.");
 
@@ -307,6 +309,9 @@
     case DEPRECATED_EXPERIMENTS:
       specifics->mutable_experiments();
       break;
+    case WEB_APPS:
+      specifics->mutable_web_app();
+      break;
     case WIFI_CONFIGURATIONS:
       specifics->mutable_wifi_configuration();
       break;
@@ -360,7 +365,7 @@
 }
 
 ModelType GetModelTypeFromSpecifics(const sync_pb::EntitySpecifics& specifics) {
-  static_assert(45 == ModelType::NUM_ENTRIES,
+  static_assert(46 == ModelType::NUM_ENTRIES,
                 "When adding new protocol types, the following type lookup "
                 "logic must be updated.");
   if (specifics.has_bookmark())
@@ -445,6 +450,8 @@
     return SEND_TAB_TO_SELF;
   if (specifics.has_security_event())
     return SECURITY_EVENTS;
+  if (specifics.has_web_app())
+    return WEB_APPS;
   if (specifics.has_wifi_configuration())
     return WIFI_CONFIGURATIONS;
 
@@ -452,7 +459,7 @@
 }
 
 ModelTypeSet EncryptableUserTypes() {
-  static_assert(45 == ModelType::NUM_ENTRIES,
+  static_assert(46 == ModelType::NUM_ENTRIES,
                 "If adding an unencryptable type, remove from "
                 "encryptable_user_types below.");
   ModelTypeSet encryptable_user_types = UserTypes();
diff --git a/components/sync/base/model_type.h b/components/sync/base/model_type.h
index d751ce7..794f038f 100644
--- a/components/sync/base/model_type.h
+++ b/components/sync/base/model_type.h
@@ -139,6 +139,8 @@
   SECURITY_EVENTS,
   // Wi-Fi network configurations + credentials
   WIFI_CONFIGURATIONS,
+  // A web app object.
+  WEB_APPS,
 
   // ---- Proxy types ----
   // Proxy types are excluded from the sync protocol, but are still considered
@@ -203,7 +205,7 @@
       DEPRECATED_WIFI_CREDENTIALS, SUPERVISED_USER_WHITELISTS, ARC_PACKAGE,
       PRINTERS, READING_LIST, USER_EVENTS, NIGORI, DEPRECATED_EXPERIMENTS,
       MOUNTAIN_SHARES, USER_CONSENTS, SEND_TAB_TO_SELF, SECURITY_EVENTS,
-      WIFI_CONFIGURATIONS);
+      WEB_APPS, WIFI_CONFIGURATIONS);
 }
 
 // These are the normal user-controlled types. This is to distinguish from
diff --git a/components/sync/base/user_selectable_type.cc b/components/sync/base/user_selectable_type.cc
index 766f582..04f7c34 100644
--- a/components/sync/base/user_selectable_type.cc
+++ b/components/sync/base/user_selectable_type.cc
@@ -45,7 +45,8 @@
     case UserSelectableType::kExtensions:
       return {"extensions", EXTENSIONS, {EXTENSIONS, EXTENSION_SETTINGS}};
     case UserSelectableType::kApps:
-      return {"apps", APPS, {APPS, APP_SETTINGS, APP_LIST, ARC_PACKAGE}};
+      return {
+          "apps", APPS, {APPS, APP_SETTINGS, APP_LIST, ARC_PACKAGE, WEB_APPS}};
 #if BUILDFLAG(ENABLE_READING_LIST)
     case UserSelectableType::kReadingList:
       return {"readingList", READING_LIST, {READING_LIST}};
diff --git a/components/sync/driver/model_association_manager.cc b/components/sync/driver/model_association_manager.cc
index 5d5ce05..735efe1 100644
--- a/components/sync/driver/model_association_manager.cc
+++ b/components/sync/driver/model_association_manager.cc
@@ -47,7 +47,7 @@
     SUPERVISED_USER_WHITELISTS, DEPRECATED_WIFI_CREDENTIALS,
     DEPRECATED_SUPERVISED_USERS, MOUNTAIN_SHARES,
     DEPRECATED_SUPERVISED_USER_SHARED_SETTINGS, DEPRECATED_ARTICLES,
-    SEND_TAB_TO_SELF, SECURITY_EVENTS, WIFI_CONFIGURATIONS};
+    SEND_TAB_TO_SELF, SECURITY_EVENTS, WEB_APPS, WIFI_CONFIGURATIONS};
 
 static_assert(base::size(kStartOrder) ==
                   ModelType::NUM_ENTRIES - FIRST_REAL_MODEL_TYPE,
diff --git a/components/sync/driver/resources/index.html b/components/sync/driver/resources/index.html
index d03c356..58b52b4 100644
--- a/components/sync/driver/resources/index.html
+++ b/components/sync/driver/resources/index.html
@@ -1,5 +1,5 @@
 <!doctype html>
-<html dir="$i18n{textdirection}" lang="$i18n{language}">
+<html dir="ltr" lang="en">
 <head>
 <!-- If you change the title, make sure you also update
 chrome/test/functional/special_tabs.py. -->
diff --git a/components/sync/driver/sync_user_settings_impl.cc b/components/sync/driver/sync_user_settings_impl.cc
index ab2aa5702..7782c57 100644
--- a/components/sync/driver/sync_user_settings_impl.cc
+++ b/components/sync/driver/sync_user_settings_impl.cc
@@ -182,7 +182,7 @@
     types.RetainAll(registered_model_types_);
   }
 
-  static_assert(45 == ModelType::NUM_ENTRIES,
+  static_assert(46 == ModelType::NUM_ENTRIES,
                 "If adding a new sync data type, update the list below below if"
                 " you want to disable the new data type for local sync.");
   types.PutAll(ControlTypes());
diff --git a/components/sync/driver/sync_user_settings_unittest.cc b/components/sync/driver/sync_user_settings_unittest.cc
index 1d5f017..6a54f3e 100644
--- a/components/sync/driver/sync_user_settings_unittest.cc
+++ b/components/sync/driver/sync_user_settings_unittest.cc
@@ -141,6 +141,7 @@
       expected_preferred_types.Put(APP_LIST);
       expected_preferred_types.Put(APP_SETTINGS);
       expected_preferred_types.Put(ARC_PACKAGE);
+      expected_preferred_types.Put(WEB_APPS);
     }
     if (type == UserSelectableType::kExtensions) {
       expected_preferred_types.Put(EXTENSION_SETTINGS);
diff --git a/components/sync/nigori/nigori_sync_bridge_impl.cc b/components/sync/nigori/nigori_sync_bridge_impl.cc
index 681c379d..31df65a 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl.cc
+++ b/components/sync/nigori/nigori_sync_bridge_impl.cc
@@ -306,7 +306,7 @@
 void UpdateNigoriSpecificsFromEncryptedTypes(
     ModelTypeSet encrypted_types,
     sync_pb::NigoriSpecifics* specifics) {
-  static_assert(45 == ModelType::NUM_ENTRIES,
+  static_assert(46 == ModelType::NUM_ENTRIES,
                 "If adding an encryptable type, update handling below.");
   specifics->set_encrypt_bookmarks(encrypted_types.Has(BOOKMARKS));
   specifics->set_encrypt_preferences(encrypted_types.Has(PREFERENCES));
@@ -338,6 +338,7 @@
   specifics->set_encrypt_mountain_shares(encrypted_types.Has(MOUNTAIN_SHARES));
   specifics->set_encrypt_send_tab_to_self(
       encrypted_types.Has(SEND_TAB_TO_SELF));
+  specifics->set_encrypt_web_apps(encrypted_types.Has(WEB_APPS));
 }
 
 }  // namespace
diff --git a/components/sync/protocol/nigori_specifics.proto b/components/sync/protocol/nigori_specifics.proto
index cee80f1..757059a4 100644
--- a/components/sync/protocol/nigori_specifics.proto
+++ b/components/sync/protocol/nigori_specifics.proto
@@ -179,4 +179,7 @@
 
   // Boolean corresponding to whether send tab should be encrypted.
   optional bool encrypt_send_tab_to_self = 47;
+
+  // Boolean corresponding to whether Web Apps data should be encrypted.
+  optional bool encrypt_web_apps = 48;
 }
diff --git a/components/sync/protocol/proto_value_conversions.cc b/components/sync/protocol/proto_value_conversions.cc
index 089f7d8..e0d1bb5 100644
--- a/components/sync/protocol/proto_value_conversions.cc
+++ b/components/sync/protocol/proto_value_conversions.cc
@@ -362,6 +362,7 @@
 IMPLEMENT_PROTO_TO_VALUE(WalletMaskedCreditCard)
 IMPLEMENT_PROTO_TO_VALUE(WalletMetadataSpecifics)
 IMPLEMENT_PROTO_TO_VALUE(WalletPostalAddress)
+IMPLEMENT_PROTO_TO_VALUE(WebAppSpecifics)
 IMPLEMENT_PROTO_TO_VALUE(WifiConfigurationSpecifics)
 IMPLEMENT_PROTO_TO_VALUE(WifiCredentialSpecifics)
 
diff --git a/components/sync/protocol/proto_value_conversions.h b/components/sync/protocol/proto_value_conversions.h
index ea0c4c4c..c8ef6867 100644
--- a/components/sync/protocol/proto_value_conversions.h
+++ b/components/sync/protocol/proto_value_conversions.h
@@ -78,6 +78,7 @@
 class WalletMaskedCreditCard;
 class WalletMetadataSpecifics;
 class WalletPostalAddress;
+class WebAppSpecifics;
 class WifiConfigurationSpecifics;
 class WifiCredentialSpecifics;
 }  // namespace sync_pb
@@ -285,6 +286,9 @@
 std::unique_ptr<base::DictionaryValue> WalletPostalAddressToValue(
     const sync_pb::WalletPostalAddress& wallet_postal_address);
 
+std::unique_ptr<base::DictionaryValue> WebAppSpecificsToValue(
+    const sync_pb::WebAppSpecifics& web_app_specifics);
+
 std::unique_ptr<base::DictionaryValue> WifiConfigurationSpecificsToValue(
     const sync_pb::WifiConfigurationSpecifics& wifi_configuration_specifics);
 
diff --git a/components/sync/protocol/proto_value_conversions_unittest.cc b/components/sync/protocol/proto_value_conversions_unittest.cc
index 57106a6..72b3320 100644
--- a/components/sync/protocol/proto_value_conversions_unittest.cc
+++ b/components/sync/protocol/proto_value_conversions_unittest.cc
@@ -60,7 +60,7 @@
 
 DEFINE_SPECIFICS_TO_VALUE_TEST(encrypted)
 
-static_assert(45 == syncer::ModelType::NUM_ENTRIES,
+static_assert(46 == syncer::ModelType::NUM_ENTRIES,
               "When adding a new field, add a DEFINE_SPECIFICS_TO_VALUE_TEST "
               "for your field below, and optionally a test for the specific "
               "conversions.");
@@ -105,6 +105,7 @@
 DEFINE_SPECIFICS_TO_VALUE_TEST(user_consent)
 DEFINE_SPECIFICS_TO_VALUE_TEST(user_event)
 DEFINE_SPECIFICS_TO_VALUE_TEST(wallet_metadata)
+DEFINE_SPECIFICS_TO_VALUE_TEST(web_app)
 DEFINE_SPECIFICS_TO_VALUE_TEST(wifi_configuration)
 DEFINE_SPECIFICS_TO_VALUE_TEST(wifi_credential)
 
diff --git a/components/sync/protocol/proto_visitors.h b/components/sync/protocol/proto_visitors.h
index a86bdd4..c6c2278 100644
--- a/components/sync/protocol/proto_visitors.h
+++ b/components/sync/protocol/proto_visitors.h
@@ -41,6 +41,7 @@
 #include "components/sync/protocol/unique_position.pb.h"
 #include "components/sync/protocol/user_consent_specifics.pb.h"
 #include "components/sync/protocol/user_event_specifics.pb.h"
+#include "components/sync/protocol/web_app_specifics.pb.h"
 
 // This file implements VisitProtoFields() functions for sync protos.
 //
@@ -371,7 +372,7 @@
 }
 
 VISIT_PROTO_FIELDS(const sync_pb::EntitySpecifics& proto) {
-  static_assert(45 == ModelType::NUM_ENTRIES,
+  static_assert(46 == ModelType::NUM_ENTRIES,
                 "When adding a new protocol type, you will likely need to add "
                 "it here as well.");
   VISIT(encrypted);
@@ -415,6 +416,7 @@
   VISIT(user_consent);
   VISIT(user_event);
   VISIT(wallet_metadata);
+  VISIT(web_app);
   VISIT(wifi_configuration);
   VISIT(wifi_credential);
 }
@@ -1087,6 +1089,13 @@
   VISIT(enabled);
 }
 
+VISIT_PROTO_FIELDS(const sync_pb::WebAppSpecifics& proto) {
+  VISIT(app_id);
+  VISIT(launch_url);
+  VISIT(name);
+  VISIT(theme_color);
+}
+
 VISIT_PROTO_FIELDS(const sync_pb::WifiCredentialSpecifics& proto) {
   VISIT_BYTES(ssid);
   VISIT_ENUM(security_class);
diff --git a/components/sync/protocol/protocol_sources.gni b/components/sync/protocol/protocol_sources.gni
index 561c05cd..975c0ca 100644
--- a/components/sync/protocol/protocol_sources.gni
+++ b/components/sync/protocol/protocol_sources.gni
@@ -58,6 +58,7 @@
   "user_consent_specifics",
   "user_consent_types",
   "user_event_specifics",
+  "web_app_specifics",
   "wifi_configuration_specifics",
   "wifi_credential_specifics",
 ]
diff --git a/components/sync/protocol/sync.proto b/components/sync/protocol/sync.proto
index 56734f6..da6f094e 100644
--- a/components/sync/protocol/sync.proto
+++ b/components/sync/protocol/sync.proto
@@ -57,6 +57,7 @@
 import "unique_position.proto";
 import "user_consent_specifics.proto";
 import "user_event_specifics.proto";
+import "web_app_specifics.proto";
 import "wifi_configuration_specifics.proto";
 import "wifi_credential_specifics.proto";
 
@@ -150,6 +151,7 @@
     MountainShareSpecifics mountain_share = 545005;
     SendTabToSelfSpecifics send_tab_to_self = 601980;
     SecurityEventSpecifics security_event = 600372;
+    WebAppSpecifics web_app = 673225;
     WifiConfigurationSpecifics wifi_configuration = 662827;
   }
 }
diff --git a/components/sync/protocol/web_app_specifics.proto b/components/sync/protocol/web_app_specifics.proto
new file mode 100644
index 0000000..e7d87bd3
--- /dev/null
+++ b/components/sync/protocol/web_app_specifics.proto
@@ -0,0 +1,19 @@
+// 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.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package sync_pb;
+
+// WebApp data. This should be a subset of WebAppProto in
+// chrome/browser/web_applications/proto/web_app.proto
+message WebAppSpecifics {
+  // app_id is the client tag for sync system.
+  optional string app_id = 1;
+  optional string launch_url = 2;
+  optional string name = 3;
+  optional uint32 theme_color = 4;
+}
diff --git a/components/sync/syncable/nigori_util.cc b/components/sync/syncable/nigori_util.cc
index 8229e22..c591696 100644
--- a/components/sync/syncable/nigori_util.cc
+++ b/components/sync/syncable/nigori_util.cc
@@ -250,7 +250,7 @@
                                     bool encrypt_everything,
                                     sync_pb::NigoriSpecifics* nigori) {
   nigori->set_encrypt_everything(encrypt_everything);
-  static_assert(45 == ModelType::NUM_ENTRIES,
+  static_assert(46 == ModelType::NUM_ENTRIES,
                 "If adding an encryptable type, update handling below.");
   nigori->set_encrypt_bookmarks(encrypted_types.Has(BOOKMARKS));
   nigori->set_encrypt_preferences(encrypted_types.Has(PREFERENCES));
@@ -279,6 +279,7 @@
   nigori->set_encrypt_reading_list(encrypted_types.Has(READING_LIST));
   nigori->set_encrypt_mountain_shares(encrypted_types.Has(MOUNTAIN_SHARES));
   nigori->set_encrypt_send_tab_to_self(encrypted_types.Has(SEND_TAB_TO_SELF));
+  nigori->set_encrypt_web_apps(encrypted_types.Has(WEB_APPS));
 }
 
 ModelTypeSet GetEncryptedTypesFromNigori(
@@ -287,7 +288,7 @@
     return ModelTypeSet::All();
 
   ModelTypeSet encrypted_types;
-  static_assert(45 == ModelType::NUM_ENTRIES,
+  static_assert(46 == ModelType::NUM_ENTRIES,
                 "If adding an encryptable type, update handling below.");
   if (nigori.encrypt_bookmarks())
     encrypted_types.Put(BOOKMARKS);
@@ -337,6 +338,8 @@
     encrypted_types.Put(MOUNTAIN_SHARES);
   if (nigori.encrypt_send_tab_to_self())
     encrypted_types.Put(SEND_TAB_TO_SELF);
+  if (nigori.encrypt_web_apps())
+    encrypted_types.Put(WEB_APPS);
   return encrypted_types;
 }
 
diff --git a/components/translate/translate_internals/translate_internals.html b/components/translate/translate_internals/translate_internals.html
index 061cbcd..d5d896a 100644
--- a/components/translate/translate_internals/translate_internals.html
+++ b/components/translate/translate_internals/translate_internals.html
@@ -1,5 +1,5 @@
 <!doctype html>
-<html dir="$i18n{textdirection}" lang="$i18n{language}">
+<html dir="ltr" lang="en">
 <!--
 Copyright 2013 The Chromium Authors. All rights reserved.
 Use of this source code is governed by a BSD-style license that can be
@@ -12,8 +12,8 @@
     <link rel="stylesheet" href="chrome://resources/css/tabs.css">
     <link rel="stylesheet" href="./translate_internals.css">
 <if expr="is_ios">
-  <!-- TODO(crbug.com/487000): Remove this once injected by web. -->
-  <script src="chrome://resources/js/ios/web_ui.js"></script>
+    <!-- TODO(crbug.com/487000): Remove this once injected by web. -->
+    <script src="chrome://resources/js/ios/web_ui.js"></script>
 </if>
     <script src="chrome://resources/js/util.js"></script>
     <script src="chrome://resources/js/cr.js"></script>
diff --git a/components/viz/test/test_context_provider.cc b/components/viz/test/test_context_provider.cc
index 7dd29e0..0e1378c4 100644
--- a/components/viz/test/test_context_provider.cc
+++ b/components/viz/test/test_context_provider.cc
@@ -277,8 +277,19 @@
     std::unique_ptr<TestContextSupport> support,
     std::unique_ptr<TestGLES2Interface> gl,
     bool support_locking)
+    : TestContextProvider(std::move(support),
+                          std::move(gl),
+                          /*raster=*/nullptr,
+                          support_locking) {}
+
+TestContextProvider::TestContextProvider(
+    std::unique_ptr<TestContextSupport> support,
+    std::unique_ptr<TestGLES2Interface> gl,
+    std::unique_ptr<gpu::raster::RasterInterface> raster,
+    bool support_locking)
     : support_(std::move(support)),
       context_gl_(std::move(gl)),
+      raster_context_(std::move(raster)),
       shared_image_interface_(std::make_unique<TestSharedImageInterface>()),
       support_locking_(support_locking),
       weak_ptr_factory_(this) {
@@ -286,8 +297,10 @@
   DCHECK(context_gl_);
   context_thread_checker_.DetachFromThread();
   context_gl_->set_test_support(support_.get());
-  raster_context_ = std::make_unique<gpu::raster::RasterImplementationGLES>(
-      context_gl_.get());
+  if (!raster_context_) {
+    raster_context_ = std::make_unique<gpu::raster::RasterImplementationGLES>(
+        context_gl_.get());
+  }
   // Just pass nullptr to the ContextCacheController for its task runner.
   // Idle handling is tested directly in ContextCacheController's
   // unittests, and isn't needed here.
diff --git a/components/viz/test/test_context_provider.h b/components/viz/test/test_context_provider.h
index cc2d5e26..fbbd098 100644
--- a/components/viz/test/test_context_provider.h
+++ b/components/viz/test/test_context_provider.h
@@ -119,6 +119,11 @@
       std::unique_ptr<TestContextSupport> support,
       std::unique_ptr<TestGLES2Interface> gl,
       bool support_locking);
+  explicit TestContextProvider(
+      std::unique_ptr<TestContextSupport> support,
+      std::unique_ptr<TestGLES2Interface> gl,
+      std::unique_ptr<gpu::raster::RasterInterface> raster,
+      bool support_locking);
 
   // ContextProvider / RasterContextProvider implementation.
   void AddRef() const override;
diff --git a/content/browser/accessibility/browser_accessibility_manager_win.cc b/content/browser/accessibility/browser_accessibility_manager_win.cc
index 59214415..f832e1bee 100644
--- a/content/browser/accessibility/browser_accessibility_manager_win.cc
+++ b/content/browser/accessibility/browser_accessibility_manager_win.cc
@@ -252,6 +252,7 @@
         FireWinAccessibilityEvent(EVENT_OBJECT_SHOW, node);
         FireUiaStructureChangedEvent(StructureChangeType_ChildAdded, node);
       }
+      aria_properties_events_.insert(node);
       break;
     case ui::AXEventGenerator::Event::IMAGE_ANNOTATION_CHANGED:
       FireWinAccessibilityEvent(EVENT_OBJECT_NAMECHANGE, node);
@@ -463,10 +464,16 @@
     return;
   if (!ShouldFireEventForNode(node))
     return;
-  // Suppress events when |IGNORED_CHANGED|
+
+  // Suppress events when |IGNORED_CHANGED| with the exception for firing
+  // UIA_AriaPropertiesPropertyId-hidden event on non-text node marked as
+  // ignored.
   if (node->HasState(ax::mojom::State::kIgnored) ||
-      base::Contains(ignored_changed_nodes_, node))
-    return;
+      base::Contains(ignored_changed_nodes_, node)) {
+    if (uia_property != UIA_AriaPropertiesPropertyId ||
+        node->IsTextOnlyObject())
+      return;
+  }
 
   // The old value is not used by the system
   VARIANT old_value = {};
diff --git a/content/browser/accessibility/dump_accessibility_events_browsertest.cc b/content/browser/accessibility/dump_accessibility_events_browsertest.cc
index e67dd7dd..5c99258 100644
--- a/content/browser/accessibility/dump_accessibility_events_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_events_browsertest.cc
@@ -283,6 +283,11 @@
 }
 
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest,
+                       AccessibilityEventsAriaHiddenChanged) {
+  RunEventTest(FILE_PATH_LITERAL("aria-hidden-changed.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest,
                        AccessibilityEventsAriaInvalidChanged) {
   RunEventTest(FILE_PATH_LITERAL("aria-invalid-changed.html"));
 }
@@ -721,6 +726,11 @@
   RunEventTest(FILE_PATH_LITERAL("tbody-focus.html"));
 }
 
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityEventsTest,
+                       AccessibilityEventsVisibilityHiddenChanged) {
+  RunEventTest(FILE_PATH_LITERAL("visibility-hidden-changed.html"));
+}
+
 // Even with the deflaking in WaitForAccessibilityTreeToContainNodeWithName,
 // this test is still flaky on Windows.
 // TODO(aboxhall, dmazzoni, meredithl): re-enable with better fix for above.
diff --git a/content/browser/appcache/appcache_database.cc b/content/browser/appcache/appcache_database.cc
index 2f63fdd..b3a1698 100644
--- a/content/browser/appcache/appcache_database.cc
+++ b/content/browser/appcache/appcache_database.cc
@@ -14,7 +14,6 @@
 #include "content/browser/appcache/appcache_backfillers.h"
 #include "content/browser/appcache/appcache_entry.h"
 #include "content/browser/appcache/appcache_histograms.h"
-#include "content/public/common/content_features.h"
 #include "sql/database.h"
 #include "sql/error_delegate_util.h"
 #include "sql/meta_table.h"
@@ -249,15 +248,8 @@
     return 0;
 
   int64_t origin_usage = 0;
-  bool padding_enabled =
-      base::FeatureList::IsEnabled(features::kAppCacheIncludePaddingInQuota);
-  for (const auto& cache : caches) {
-    if (padding_enabled) {
-      origin_usage += cache.cache_size + cache.padding_size;
-    } else {
-      origin_usage += cache.cache_size;
-    }
-  }
+  for (const auto& cache : caches)
+    origin_usage += cache.cache_size + cache.padding_size;
   return origin_usage;
 }
 
diff --git a/content/browser/appcache/appcache_database.h b/content/browser/appcache/appcache_database.h
index 99d5b011..0472d255 100644
--- a/content/browser/appcache/appcache_database.h
+++ b/content/browser/appcache/appcache_database.h
@@ -42,8 +42,7 @@
 FORWARD_DECLARE_TEST(AppCacheDatabaseTest, OnlineWhiteListRecords);
 FORWARD_DECLARE_TEST(AppCacheDatabaseTest, ReCreate);
 FORWARD_DECLARE_TEST(AppCacheDatabaseTest, DeletableResponseIds);
-FORWARD_DECLARE_TEST(AppCacheDatabaseTest, OriginUsageWithPaddingDisabled);
-FORWARD_DECLARE_TEST(AppCacheDatabaseTest, OriginUsageWithPaddingEnabled);
+FORWARD_DECLARE_TEST(AppCacheDatabaseTest, OriginUsage);
 FORWARD_DECLARE_TEST(AppCacheDatabaseTest, FindCachesForOrigin);
 FORWARD_DECLARE_TEST(AppCacheDatabaseTest,
                      UpgradeSchemaForVersionsWithoutSupportedMigrations);
@@ -273,10 +272,7 @@
                            OnlineWhiteListRecords);
   FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest, ReCreate);
   FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest, DeletableResponseIds);
-  FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest,
-                           OriginUsageWithPaddingDisabled);
-  FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest,
-                           OriginUsageWithPaddingEnabled);
+  FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest, OriginUsage);
   FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest, FindCachesForOrigin);
   FRIEND_TEST_ALL_PREFIXES(content::AppCacheDatabaseTest,
                            UpgradeSchemaForVersionsWithoutSupportedMigrations);
diff --git a/content/browser/appcache/appcache_database_unittest.cc b/content/browser/appcache/appcache_database_unittest.cc
index 0960822..037bb647 100644
--- a/content/browser/appcache/appcache_database_unittest.cc
+++ b/content/browser/appcache/appcache_database_unittest.cc
@@ -11,10 +11,8 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/macros.h"
 #include "base/strings/stringprintf.h"
-#include "base/test/scoped_feature_list.h"
 #include "content/browser/appcache/appcache_database.h"
 #include "content/browser/appcache/appcache_entry.h"
-#include "content/public/common/content_features.h"
 #include "sql/database.h"
 #include "sql/meta_table.h"
 #include "sql/statement.h"
@@ -767,75 +765,7 @@
   ASSERT_TRUE(expecter.SawExpectedErrors());
 }
 
-TEST(AppCacheDatabaseTest, OriginUsageWithPaddingDisabled) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndDisableFeature(features::kAppCacheIncludePaddingInQuota);
-
-  const GURL kManifestUrl("http://blah/manifest");
-  const GURL kManifestUrl2("http://blah/manifest2");
-  const url::Origin kOrigin = url::Origin::Create(kManifestUrl);
-  const GURL kOtherOriginManifestUrl("http://other/manifest");
-  const url::Origin kOtherOrigin = url::Origin::Create(kOtherOriginManifestUrl);
-
-  const base::FilePath kEmptyPath;
-  AppCacheDatabase db(kEmptyPath);
-  EXPECT_TRUE(db.LazyOpen(true));
-
-  std::vector<AppCacheDatabase::CacheRecord> cache_records;
-  EXPECT_EQ(0, db.GetOriginUsage(kOrigin));
-
-  AppCacheDatabase::GroupRecord group_record;
-  group_record.group_id = 1;
-  group_record.manifest_url = kManifestUrl;
-  group_record.origin = kOrigin;
-  EXPECT_TRUE(db.InsertGroup(&group_record));
-  AppCacheDatabase::CacheRecord cache_record;
-  cache_record.cache_id = 1;
-  cache_record.group_id = 1;
-  cache_record.online_wildcard = true;
-  cache_record.update_time = kZeroTime;
-  cache_record.cache_size = 100;
-  cache_record.padding_size = 1;
-  EXPECT_TRUE(db.InsertCache(&cache_record));
-
-  EXPECT_EQ(100, db.GetOriginUsage(kOrigin));
-
-  group_record.group_id = 2;
-  group_record.manifest_url = kManifestUrl2;
-  group_record.origin = kOrigin;
-  EXPECT_TRUE(db.InsertGroup(&group_record));
-  cache_record.cache_id = 2;
-  cache_record.group_id = 2;
-  cache_record.online_wildcard = true;
-  cache_record.update_time = kZeroTime;
-  cache_record.cache_size = 1000;
-  cache_record.padding_size = 1;
-  EXPECT_TRUE(db.InsertCache(&cache_record));
-
-  EXPECT_EQ(1100, db.GetOriginUsage(kOrigin));
-
-  group_record.group_id = 3;
-  group_record.manifest_url = kOtherOriginManifestUrl;
-  group_record.origin = kOtherOrigin;
-  EXPECT_TRUE(db.InsertGroup(&group_record));
-  cache_record.cache_id = 3;
-  cache_record.group_id = 3;
-  cache_record.online_wildcard = true;
-  cache_record.update_time = kZeroTime;
-  cache_record.cache_size = 5000;
-  cache_record.padding_size = 1;
-  EXPECT_TRUE(db.InsertCache(&cache_record));
-
-  EXPECT_EQ(5000, db.GetOriginUsage(kOtherOrigin));
-
-  std::map<url::Origin, int64_t> usage_map;
-  EXPECT_TRUE(db.GetAllOriginUsage(&usage_map));
-  EXPECT_EQ(2U, usage_map.size());
-  EXPECT_EQ(1100, usage_map[kOrigin]);
-  EXPECT_EQ(5000, usage_map[kOtherOrigin]);
-}
-
-TEST(AppCacheDatabaseTest, OriginUsageWithPaddingEnabled) {
+TEST(AppCacheDatabaseTest, OriginUsage) {
   const GURL kManifestUrl("http://blah/manifest");
   const GURL kManifestUrl2("http://blah/manifest2");
   const url::Origin kOrigin = url::Origin::Create(kManifestUrl);
diff --git a/content/browser/appcache/appcache_storage.h b/content/browser/appcache/appcache_storage.h
index 73f8fc4..7d6ad74 100644
--- a/content/browser/appcache/appcache_storage.h
+++ b/content/browser/appcache/appcache_storage.h
@@ -328,7 +328,7 @@
   int64_t last_group_id_;
   int64_t last_response_id_;
 
-  // Maps origin to usage (includes padding, unless padding feature is disabled)
+  // Maps origin to padded usage.
   std::map<url::Origin, int64_t> usage_map_;
   AppCacheWorkingSet working_set_;
   AppCacheServiceImpl* service_;
diff --git a/content/browser/appcache/appcache_storage_impl_unittest.cc b/content/browser/appcache/appcache_storage_impl_unittest.cc
index 038244f..64123ec 100644
--- a/content/browser/appcache/appcache_storage_impl_unittest.cc
+++ b/content/browser/appcache/appcache_storage_impl_unittest.cc
@@ -37,7 +37,6 @@
 #include "content/browser/appcache/appcache_url_loader_request.h"
 #include "content/browser/child_process_security_policy_impl.h"
 #include "content/public/browser/browser_task_traits.h"
-#include "content/public/common/content_features.h"
 #include "content/public/test/test_browser_context.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "net/base/net_errors.h"
@@ -566,34 +565,6 @@
     storage()->StoreGroupAndNewestCache(group_.get(), cache_.get(), delegate());
   }
 
-  void StoreNewGroup_PaddingDisabled() {
-    // Store a group and its newest cache. Should complete asynchronously.
-    PushNextTask(base::BindOnce(&AppCacheStorageImplTest::Verify_StoreNewGroup,
-                                base::Unretained(this)));
-
-    // Set up some preconditions. Create a group and newest cache that
-    // appear to be "unstored" and big enough to exceed the 5M limit.
-    const int64_t kTooBig = 10 * 1024 * 1024;  // 10M
-    group_ = base::MakeRefCounted<AppCacheGroup>(storage(), kManifestUrl,
-                                                 storage()->NewGroupId());
-    cache_ = base::MakeRefCounted<AppCache>(storage(), storage()->NewCacheId());
-    cache_->AddEntry(kEntryUrl,
-                     AppCacheEntry(AppCacheEntry::EXPLICIT,
-                                   /*response_id=*/1,
-                                   /*response_size=*/kDefaultEntrySize,
-                                   /*padding_size=*/kTooBig));
-    // Hold a ref to the cache to simulate the UpdateJob holding that ref,
-    // and hold a ref to the group to simulate the CacheHost holding that ref.
-
-    // Have the quota manager return asynchronously for this test.
-    mock_quota_manager_proxy_->mock_manager_->async_ = true;
-
-    // Conduct the store test. The records should commit successfully even
-    // though the padding size exceeds the limit since the test disabled the
-    // padding feature flag.
-    storage()->StoreGroupAndNewestCache(group_.get(), cache_.get(), delegate());
-  }
-
   void Verify_StoreNewGroup() {
     EXPECT_TRUE(delegate()->stored_group_success_);
     EXPECT_EQ(group_.get(), delegate()->stored_group_.get());
@@ -1945,12 +1916,6 @@
   RunTestOnIOThread(&AppCacheStorageImplTest::StoreNewGroup);
 }
 
-TEST_F(AppCacheStorageImplTest, StoreNewGroup_PaddingDisabled) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndDisableFeature(features::kAppCacheIncludePaddingInQuota);
-  RunTestOnIOThread(&AppCacheStorageImplTest::StoreNewGroup_PaddingDisabled);
-}
-
 TEST_F(AppCacheStorageImplTest, StoreExistingGroup) {
   RunTestOnIOThread(&AppCacheStorageImplTest::StoreExistingGroup);
 }
diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h
index 8514757..315d5813 100644
--- a/content/browser/bad_message.h
+++ b/content/browser/bad_message.h
@@ -243,6 +243,7 @@
   RFHI_BEGIN_NAVIGATION_NON_WEBBY_TRANSITION = 215,
   RFH_NO_MATCHING_NAVIGATION_REQUEST_ON_COMMIT = 216,
   AUTH_INVALID_ICON_URL = 217,
+  REGISTER_PROTOCOL_HANDLER_INVALID_URL = 218,
 
   // Please add new elements here. The naming convention is abbreviated class
   // name (e.g. RenderFrameHost becomes RFH) plus a unique description of the
diff --git a/content/browser/child_process_security_policy_impl.cc b/content/browser/child_process_security_policy_impl.cc
index 7978e38..36e2898 100644
--- a/content/browser/child_process_security_policy_impl.cc
+++ b/content/browser/child_process_security_policy_impl.cc
@@ -1626,10 +1626,10 @@
       if (matches_browsing_instance_id &&
           IsolatedOriginUtil::DoesOriginMatchIsolatedOrigin(
               origin, isolated_origin_entry.origin())) {
-        // If a match has been found that requires all subdomains to be
-        // isolated then return immediately. |origin| is returned to ensure
-        // proper process isolation, e.g. https://a.b.c.isolated.com matches
-        // an IsolatedOriginEntry constructed from http://**.isolated.com, so
+        // If a match has been found that requires all subdomains to be isolated
+        // then return immediately. |origin| is returned to ensure proper
+        // process isolation, e.g. https://a.b.c.isolated.com matches an
+        // IsolatedOriginEntry constructed from http://[*.]isolated.com, so
         // https://a.b.c.isolated.com must be returned.
         if (isolated_origin_entry.isolate_all_subdomains()) {
           *result = origin;
diff --git a/content/browser/child_process_security_policy_unittest.cc b/content/browser/child_process_security_policy_unittest.cc
index 1a701a3..263e804 100644
--- a/content/browser/child_process_security_policy_unittest.cc
+++ b/content/browser/child_process_security_policy_unittest.cc
@@ -1439,8 +1439,8 @@
 
 TEST_F(ChildProcessSecurityPolicyTest, IsolateAllSuborigins) {
   url::Origin qux = url::Origin::Create(GURL("https://qux.com/"));
-  IsolatedOriginPattern etld1_wild("https://**.foo.com");
-  IsolatedOriginPattern etld2_wild("https://**.bar.foo.com");
+  IsolatedOriginPattern etld1_wild("https://[*.]foo.com");
+  IsolatedOriginPattern etld2_wild("https://[*.]bar.foo.com");
   url::Origin etld1 = url::Origin::Create(GURL("https://foo.com"));
   url::Origin etld2 = url::Origin::Create(GURL("https://bar.foo.com"));
 
@@ -1498,8 +1498,8 @@
   // Construct a simple case, a single isolated origin.
   //  IsolatedOriginPattern isolated("https://isolated.com");
   IsolatedOriginPattern inner_isolated("https://inner.isolated.com");
-  IsolatedOriginPattern wildcard("https://**.wildcard.com");
-  IsolatedOriginPattern inner_wildcard("https://**.inner.wildcard.com");
+  IsolatedOriginPattern wildcard("https://[*.]wildcard.com");
+  IsolatedOriginPattern inner_wildcard("https://[*.]inner.wildcard.com");
 
   GURL isolated_url("https://isolated.com");
   GURL inner_isolated_url("https://inner.isolated.com");
@@ -1565,7 +1565,8 @@
     // isolated origin. Removing the isolated origin should have no effect on
     // the wildcard origin.
     IsolatedOriginPattern isolated("https://isolated.com");
-    IsolatedOriginPattern wildcard_isolated("https://**.wildcard.isolated.com");
+    IsolatedOriginPattern wildcard_isolated(
+        "https://[*.]wildcard.isolated.com");
 
     GURL isolated_url("https://isolated.com");
     GURL a_isolated_url("https://a.isolated.com");
@@ -1594,7 +1595,7 @@
   {
     // A single isolated origin is nested within a wildcard origin. In this
     // scenario the wildcard origin supersedes isolated origins.
-    IsolatedOriginPattern wildcard("https://**.wildcard.com");
+    IsolatedOriginPattern wildcard("https://[*.]wildcard.com");
     IsolatedOriginPattern isolated_wildcard("https://isolated.wildcard.com");
 
     GURL wildcard_url("https://wildcard.com");
@@ -1623,8 +1624,8 @@
   {
     // Nest wildcard isolated origins within each other. Verify that removing
     // the outer wildcard origin doesn't affect the inner one.
-    IsolatedOriginPattern outer("https://**.outer.com");
-    IsolatedOriginPattern inner("https://**.inner.outer.com");
+    IsolatedOriginPattern outer("https://[*.]outer.com");
+    IsolatedOriginPattern inner("https://[*.]inner.outer.com");
 
     GURL outer_url("https://outer.com");
     GURL a_outer_url("https://a.outer.com");
@@ -1652,7 +1653,7 @@
   // doesn't affect the isolating behavior of the wildcard, i.e. whichever
   // isolated domain is added entered 'wins'.
   {
-    IsolatedOriginPattern wild("https://**.bar.foo.com");
+    IsolatedOriginPattern wild("https://[*.]bar.foo.com");
     IsolatedOriginPattern single("https://bar.foo.com");
 
     GURL host_url("https://host.bar.foo.com");
@@ -1678,7 +1679,7 @@
   // Verify the first domain added remains dominant in the case of differing
   // wildcard and non-wildcard statuses.
   {
-    IsolatedOriginPattern wild("https://**.bar.foo.com");
+    IsolatedOriginPattern wild("https://[*.]bar.foo.com");
     IsolatedOriginPattern single("https://bar.foo.com");
 
     GURL host_url("https://host.bar.foo.com");
@@ -2118,14 +2119,14 @@
 }
 
 TEST_F(ChildProcessSecurityPolicyTest, IsolatedOriginPattern) {
-  const base::StringPiece etld1_wild("https://**.foo.com");
+  const base::StringPiece etld1_wild("https://[*.]foo.com");
   url::Origin etld1_wild_origin = url::Origin::Create(GURL("https://foo.com"));
   IsolatedOriginPattern p(etld1_wild);
   EXPECT_TRUE(p.isolate_all_subdomains());
   EXPECT_TRUE(p.is_valid());
   EXPECT_EQ(p.origin(), etld1_wild_origin);
 
-  const base::StringPiece etld2_wild("https://**.bar.foo.com");
+  const base::StringPiece etld2_wild("https://[*.]bar.foo.com");
   url::Origin etld2_wild_origin =
       url::Origin::Create(GURL("https://bar.foo.com"));
   bool result = p.Parse(etld2_wild);
@@ -2181,7 +2182,7 @@
   EXPECT_TRUE(p.is_valid());
   EXPECT_EQ(p.origin(), ip_origin);
 
-  const base::StringPiece wild_ip_addr("https://**.10.20.30.40");
+  const base::StringPiece wild_ip_addr("https://[*.]10.20.30.40");
   result = p.Parse(wild_ip_addr);
   EXPECT_FALSE(result);
   EXPECT_FALSE(p.isolate_all_subdomains());
@@ -2304,9 +2305,9 @@
   EXPECT_EQ(IsolatedOriginPattern(foo), IsolatedOriginPattern(foo_port));
   EXPECT_EQ(IsolatedOriginPattern(foo), IsolatedOriginPattern(foo_path));
 
-  std::string wild_foo("https://**.foo.com");
-  std::string wild_foo_port("https://**.foo.com:8000");
-  std::string wild_foo_path("https://**.foo.com/some/path");
+  std::string wild_foo("https://[*.]foo.com");
+  std::string wild_foo_port("https://[*.]foo.com:8000");
+  std::string wild_foo_path("https://[*.]foo.com/some/path");
 
   EXPECT_EQ(IsolatedOriginPattern(wild_foo),
             IsolatedOriginPattern(wild_foo_port));
@@ -2347,14 +2348,14 @@
   // A single wildcard origin.
   EXPECT_THAT(
       ChildProcessSecurityPolicyImpl::ParseIsolatedOrigins(
-          "https://**.wild.foo.com"),
-      testing::ElementsAre(IsolatedOriginPattern("https://**.wild.foo.com")));
+          "https://[*.]wild.foo.com"),
+      testing::ElementsAre(IsolatedOriginPattern("https://[*.]wild.foo.com")));
 
   // A mixture of wildcard and non-wildcard origins.
   EXPECT_THAT(
       ChildProcessSecurityPolicyImpl::ParseIsolatedOrigins(
-          "https://**.wild.foo.com,https://isolated.foo.com"),
-      testing::ElementsAre(IsolatedOriginPattern("https://**.wild.foo.com"),
+          "https://[*.]wild.foo.com,https://isolated.foo.com"),
+      testing::ElementsAre(IsolatedOriginPattern("https://[*.]wild.foo.com"),
                            IsolatedOriginPattern("https://isolated.foo.com")));
 }
 
@@ -2373,7 +2374,7 @@
   url::Origin wild_with_port =
       url::Origin::Create(GURL("https://a.wild.com:5678"));
   url::Origin wild_origin = url::Origin::Create(GURL("https://a.wild.com"));
-  IsolatedOriginPattern wild_pattern("https://**.wild.com:5678");
+  IsolatedOriginPattern wild_pattern("https://[*.]wild.com:5678");
 
   p->AddIsolatedOrigins({isolated_origin_with_port},
                         IsolatedOriginSource::TEST);
diff --git a/content/browser/download/mhtml_generation_browsertest.cc b/content/browser/download/mhtml_generation_browsertest.cc
index 0f10768..7c3693b 100644
--- a/content/browser/download/mhtml_generation_browsertest.cc
+++ b/content/browser/download/mhtml_generation_browsertest.cc
@@ -141,7 +141,9 @@
 
 // This Mock injects our overwritten interface, running the callback
 // SerializeAsMHTMLResponse and immediately disconnecting the message pipe.
-class RespondAndDisconnectMockWriter : public MockWriterBase {
+class RespondAndDisconnectMockWriter
+    : public MockWriterBase,
+      public base::RefCountedThreadSafe<RespondAndDisconnectMockWriter> {
  public:
   RespondAndDisconnectMockWriter() {}
 
@@ -232,26 +234,32 @@
     // as there can be at most two watcher invocations to write a block of
     // data smaller than the data pipe buffer to file.
     download::GetDownloadTaskRunner()->PostTask(
-        FROM_HERE, base::BindOnce(&RespondAndDisconnectMockWriter::TaskX,
-                                  base::Unretained(this)));
+        FROM_HERE,
+        base::BindOnce(&RespondAndDisconnectMockWriter::TaskX,
+                       scoped_refptr<RespondAndDisconnectMockWriter>(this)));
   }
 
   void TaskX() {
     download::GetDownloadTaskRunner()->PostTask(
-        FROM_HERE, base::BindOnce(&RespondAndDisconnectMockWriter::TaskY,
-                                  base::Unretained(this)));
+        FROM_HERE,
+        base::BindOnce(&RespondAndDisconnectMockWriter::TaskY,
+                       scoped_refptr<RespondAndDisconnectMockWriter>(this)));
   }
 
   void TaskY() {
     base::PostTaskWithTraits(
         FROM_HERE, {BrowserThread::UI},
         base::BindOnce(&RespondAndDisconnectMockWriter::TaskZ,
-                       base::Unretained(this)));
+                       scoped_refptr<RespondAndDisconnectMockWriter>(this)));
   }
 
   void TaskZ() { binding_.Unbind(); }
 
  private:
+  friend base::RefCountedThreadSafe<RespondAndDisconnectMockWriter>;
+
+  ~RespondAndDisconnectMockWriter() override = default;
+
   DISALLOW_COPY_AND_ASSIGN(RespondAndDisconnectMockWriter);
 };
 
@@ -467,13 +475,14 @@
 #endif
 IN_PROC_BROWSER_TEST_P(MHTMLGenerationTest,
                        MAYBE_GenerateMHTMLAndCloseConnection) {
-  RespondAndDisconnectMockWriter mock_writer;
+  scoped_refptr<RespondAndDisconnectMockWriter> mock_writer =
+      base::MakeRefCounted<RespondAndDisconnectMockWriter>();
 
   NavigateToURL(shell(), embedded_test_server()->GetURL("/simple_page.html"));
   base::FilePath path(temp_dir_.GetPath());
   path = path.Append(FILE_PATH_LITERAL("test.mht"));
 
-  OverrideInterface(&mock_writer);
+  OverrideInterface(mock_writer.get());
   DisableWellformednessCheck();
 
   MHTMLGenerationParams params(path);
diff --git a/content/browser/isolated_origin_browsertest.cc b/content/browser/isolated_origin_browsertest.cc
index 8872b1e..7b662fb 100644
--- a/content/browser/isolated_origin_browsertest.cc
+++ b/content/browser/isolated_origin_browsertest.cc
@@ -2715,11 +2715,11 @@
   }
 
  private:
-  const char* kAllSubdomainWildcard = "**.";
+  const char* kAllSubdomainWildcard = "[*.]";
 
   // Calling GetURL() on the embedded test server will escape any '*' characters
   // into '%2A', so to create a wildcard origin they must be post-processed to
-  // have the string '**.' inserted at the correct point.
+  // have the string '[*.]' inserted at the correct point.
   std::string MakeWildcard(GURL url) {
     DCHECK(url.is_valid());
     return url.scheme() + url::kStandardSchemeSeparator +
diff --git a/content/browser/isolated_origin_util.cc b/content/browser/isolated_origin_util.cc
index 07cfb127..3d6770e8 100644
--- a/content/browser/isolated_origin_util.cc
+++ b/content/browser/isolated_origin_util.cc
@@ -10,7 +10,7 @@
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "url/gurl.h"
 
-const char* kAllSubdomainsWildcard = "**.";
+const char* kAllSubdomainsWildcard = "[*.]";
 
 namespace content {
 
diff --git a/content/browser/isolated_origin_util.h b/content/browser/isolated_origin_util.h
index 7721104..9486db2 100644
--- a/content/browser/isolated_origin_util.h
+++ b/content/browser/isolated_origin_util.h
@@ -14,7 +14,7 @@
 namespace content {
 
 // This class holds isolated origin patterns, providing support for double
-// wildcard origins, e.g. https://**.foo.com indicates that all domains under
+// wildcard origins, e.g. https://[*.]foo.com indicates that all domains under
 // foo.com are to be treated as if they are distinct isolated
 // origins. Non-wildcard origins to be isolated are also supported, e.g.
 // https://bar.com.
@@ -47,8 +47,8 @@
   // this oriqin will be opaque.
   const url::Origin& origin() const { return origin_; }
 
-  // True if the supplied pattern was of the form https://**.foo.com, indicating
-  // all subdomains of foo.com are to be isolated.
+  // True if the supplied pattern was of the form https://[*.]foo.com,
+  // indicating all subdomains of foo.com are to be isolated.
   bool isolate_all_subdomains() const { return isolate_all_subdomains_; }
 
   // Return the original pattern used to construct this instance.
diff --git a/content/browser/renderer_host/render_widget_targeter.cc b/content/browser/renderer_host/render_widget_targeter.cc
index 0749033..50bc7544d 100644
--- a/content/browser/renderer_host/render_widget_targeter.cc
+++ b/content/browser/renderer_host/render_widget_targeter.cc
@@ -5,6 +5,8 @@
 #include "content/browser/renderer_host/render_widget_targeter.h"
 
 #include "base/bind.h"
+#include "base/debug/crash_logging.h"
+#include "base/debug/dump_without_crashing.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/rand_util.h"
@@ -12,8 +14,10 @@
 #include "components/viz/host/host_frame_sink_manager.h"
 #include "content/browser/compositor/surface_utils.h"
 #include "content/browser/renderer_host/input/one_shot_timeout_monitor.h"
+#include "content/browser/renderer_host/render_view_host_impl.h"
 #include "content/browser/renderer_host/render_widget_host_impl.h"
 #include "content/browser/renderer_host/render_widget_host_view_base.h"
+#include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/site_isolation_policy.h"
 #include "third_party/blink/public/platform/web_input_event.h"
 #include "ui/events/blink/blink_event_util.h"
@@ -51,6 +55,29 @@
 
 constexpr const char kTracingCategory[] = "input,latency";
 
+// This function helps with debugging the reasons of viz hit testing mismatch.
+void DumpWithoutCrashing(RenderWidgetHostViewBase* root_view,
+                         RenderWidgetHostViewBase* target,
+                         const base::Optional<gfx::PointF>& target_location) {
+  RenderViewHostImpl* rvh =
+      RenderViewHostImpl::From(root_view->GetRenderWidgetHost());
+  if (!rvh || !rvh->GetMainFrame())
+    return;
+  static auto* crash_key = base::debug::AllocateCrashKeyString(
+      "vizhittest-mismatch-v2-url", base::debug::CrashKeySize::Size256);
+  const std::string& url =
+      rvh->GetMainFrame()->GetLastCommittedURL().GetOrigin().spec();
+  base::debug::SetCrashKeyString(crash_key, url);
+
+  crash_key = base::debug::AllocateCrashKeyString(
+      "vizhittest-mismatch-v2-coordinate", base::debug::CrashKeySize::Size32);
+  const std::string& global_coordinate =
+      target->TransformPointToRootCoordSpaceF(target_location.value())
+          .ToString();
+  base::debug::SetCrashKeyString(crash_key, global_coordinate);
+  base::debug::DumpWithoutCrashing();
+}
+
 }  // namespace
 
 class TracingUmaTracker {
@@ -455,6 +482,7 @@
         // If the result did not change, it is likely that viz hit test finds
         // the wrong target.
         match_result = HitTestResultsMatch::kDoNotMatch;
+        DumpWithoutCrashing(root_view, target, target_location);
       } else {
         // Hit test data changed, so the result is no longer reliable.
         match_result = HitTestResultsMatch::kHitTestResultChanged;
diff --git a/content/browser/resources/appcache/appcache_internals.html b/content/browser/resources/appcache/appcache_internals.html
index 7dfd489..d3c3d36 100644
--- a/content/browser/resources/appcache/appcache_internals.html
+++ b/content/browser/resources/appcache/appcache_internals.html
@@ -4,7 +4,7 @@
   found in the LICENSE file.
 -->
 <!DOCTYPE html>
-<html dir="$i18n{textdirection}" lang="$i18n{language}">
+<html dir="ltr" lang="en">
   <head>
     <meta charset="utf-8">
     <title>AppCache</title>
diff --git a/content/browser/resources/gpu/gpu_internals.html b/content/browser/resources/gpu/gpu_internals.html
index 731ee86c..7995d2b 100644
--- a/content/browser/resources/gpu/gpu_internals.html
+++ b/content/browser/resources/gpu/gpu_internals.html
@@ -1,5 +1,5 @@
 <!doctype html>
-<html dir="$i18n{textdirection}" lang="$i18n{language}">
+<html dir="ltr" lang="en">
 <!--
 Copyright (c) 2012 The Chromium Authors. All rights reserved.
 Use of this source code is governed by a BSD-style license that can be
diff --git a/content/browser/resources/histograms/histograms_internals.html b/content/browser/resources/histograms/histograms_internals.html
index 16a22436..7a9c448c 100644
--- a/content/browser/resources/histograms/histograms_internals.html
+++ b/content/browser/resources/histograms/histograms_internals.html
@@ -2,7 +2,7 @@
    Use of this source code is governed by a BSD-style license that can be
    found in the LICENSE file.-->
 <!doctype html>
-<html dir="$i18n{textdirection}" lang="$i18n{language}">
+<html dir="ltr" lang="en">
 <head>
   <meta charset="utf-8">
   <script src="chrome://resources/js/cr.js"></script>
diff --git a/content/browser/resources/indexed_db/indexeddb_internals.html b/content/browser/resources/indexed_db/indexeddb_internals.html
index 0b66ea2..30e8a96d 100644
--- a/content/browser/resources/indexed_db/indexeddb_internals.html
+++ b/content/browser/resources/indexed_db/indexeddb_internals.html
@@ -1,5 +1,5 @@
 <!doctype html>
-<html dir="$i18n{textdirection}" lang="$i18n{language}">
+<html dir="ltr" lang="en">
 <head>
   <meta charset="utf-8">
   <title>IndexedDB</title>
diff --git a/content/browser/resources/media/media_internals.html b/content/browser/resources/media/media_internals.html
index 57c8433..abd3244 100644
--- a/content/browser/resources/media/media_internals.html
+++ b/content/browser/resources/media/media_internals.html
@@ -4,7 +4,7 @@
 found in the LICENSE file.
 -->
 <!DOCTYPE html>
-<html dir="$i18n{textdirection}" lang="$i18n{language}">
+<html dir="ltr" lang="en">
 <head>
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=1">
diff --git a/content/browser/resources/process/process_internals.html b/content/browser/resources/process/process_internals.html
index 07d458c3..25a70ac0 100644
--- a/content/browser/resources/process/process_internals.html
+++ b/content/browser/resources/process/process_internals.html
@@ -2,7 +2,7 @@
    Use of this source code is governed by a BSD-style license that can be
    found in the LICENSE file.-->
 <!doctype html>
-<html dir="$i18n{textdirection}" lang="$i18n{language}">
+<html dir="ltr" lang="en">
 <head>
   <meta charset="utf-8">
   <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
diff --git a/content/browser/resources/service_worker/serviceworker_internals.html b/content/browser/resources/service_worker/serviceworker_internals.html
index fc3f6e5..4842288 100644
--- a/content/browser/resources/service_worker/serviceworker_internals.html
+++ b/content/browser/resources/service_worker/serviceworker_internals.html
@@ -1,5 +1,5 @@
 <!doctype html>
-<html dir="$i18n{textdirection}" lang="$i18n{language}">
+<html dir="ltr" lang="en">
 <head>
   <meta charset="utf-8">
   <title>chrome://serviceworker-internals</title>
diff --git a/content/browser/service_worker/service_worker_context_core.h b/content/browser/service_worker/service_worker_context_core.h
index b06af8e..295dced 100644
--- a/content/browser/service_worker/service_worker_context_core.h
+++ b/content/browser/service_worker/service_worker_context_core.h
@@ -161,8 +161,8 @@
 
   // Returns a ProviderHost iterator for all service worker clients for the
   // |origin|. If |include_reserved_clients| is false, this only returns clients
-  // that are execution ready (i.e., for windows, the navigation has been
-  // committed and for workers, the final response after redirects has been
+  // that are execution ready (i.e., for windows, the document has been
+  // created and for workers, the final response after redirects has been
   // delivered).
   std::unique_ptr<ProviderHostIterator> GetClientProviderHostIterator(
       const GURL& origin,
diff --git a/content/browser/service_worker/service_worker_controllee_request_handler.cc b/content/browser/service_worker/service_worker_controllee_request_handler.cc
index 2ac8aa45..92d4d956 100644
--- a/content/browser/service_worker/service_worker_controllee_request_handler.cc
+++ b/content/browser/service_worker/service_worker_controllee_request_handler.cc
@@ -60,34 +60,6 @@
 
 }  // namespace
 
-// RAII class that disallows calling SetControllerRegistration() on a provider
-// host.
-class ServiceWorkerControlleeRequestHandler::
-    ScopedDisallowSetControllerRegistration {
- public:
-  explicit ScopedDisallowSetControllerRegistration(
-      base::WeakPtr<ServiceWorkerProviderHost> provider_host)
-      : provider_host_(std::move(provider_host)) {
-    DCHECK(provider_host_->IsSetControllerRegistrationAllowed())
-        << "The host already disallows using a registration; nested disallow "
-           "is not supported.";
-    provider_host_->AllowSetControllerRegistration(false);
-  }
-
-  ~ScopedDisallowSetControllerRegistration() {
-    if (!provider_host_)
-      return;
-    DCHECK(!provider_host_->IsSetControllerRegistrationAllowed())
-        << "Failed to disallow using a registration.";
-    provider_host_->AllowSetControllerRegistration(true);
-  }
-
- private:
-  base::WeakPtr<ServiceWorkerProviderHost> provider_host_;
-
-  DISALLOW_COPY_AND_ASSIGN(ScopedDisallowSetControllerRegistration);
-};
-
 ServiceWorkerControlleeRequestHandler::ServiceWorkerControlleeRequestHandler(
     base::WeakPtr<ServiceWorkerContextCore> context,
     base::WeakPtr<ServiceWorkerProviderHost> provider_host,
@@ -155,34 +127,36 @@
   }
 #endif  // BUILDFLAG(ENABLE_OFFLINE_PAGES)
 
-  loader_wrapper_ = std::make_unique<ServiceWorkerNavigationLoaderWrapper>(
-      std::make_unique<ServiceWorkerNavigationLoader>(
-          std::move(callback), std::move(fallback_callback), this,
-          tentative_resource_request, provider_host_,
-          base::WrapRefCounted(context_->loader_factory_getter())));
-
   resource_context_ = resource_context;
 
-  PrepareForMainResource(tentative_resource_request.url,
-                         tentative_resource_request.site_for_cookies);
+  TRACE_EVENT_ASYNC_BEGIN1(
+      "ServiceWorker",
+      "ServiceWorkerControlleeRequestHandler::MaybeCreateLoader", this, "URL",
+      tentative_resource_request.url.spec());
+  // The provider host may already have set a controller in redirect case,
+  // unset it now.
+  provider_host_->SetControllerRegistration(
+      nullptr, false /* notify_controllerchange */);
 
-  if (loader()->ShouldFallbackToNetwork()) {
-    // The job already fell back to network. Clear the job now.
-    ClearJob();
-    return;
-  }
+  loader_callback_ = std::move(callback);
+  fallback_callback_ = std::move(fallback_callback);
+  stripped_url_ = net::SimplifyUrlForRequest(tentative_resource_request.url);
+  provider_host_->UpdateUrls(stripped_url_,
+                             tentative_resource_request.site_for_cookies);
+  registration_lookup_start_time_ = base::TimeTicks::Now();
 
-  // We will asynchronously continue on DidLookupRegistrationForMainResource.
+  // Look up a registration.
+  context_->storage()->FindRegistrationForDocument(
+      stripped_url_,
+      base::BindOnce(
+          &ServiceWorkerControlleeRequestHandler::ContinueWithRegistration,
+          weak_factory_.GetWeakPtr()));
 }
 
 base::Optional<SubresourceLoaderParams>
 ServiceWorkerControlleeRequestHandler::MaybeCreateSubresourceLoaderParams() {
-  // We didn't create URLLoader for this request.
-  if (!loader())
-    return base::nullopt;
-
-  // DidLookupRegistrationForMainResource() for the request didn't find
-  // a matching service worker for this request, and
+  // ContinueWithRegistration() for the request didn't find a matching service
+  // worker for this request, and
   // ServiceWorkerProviderHost::SetControllerRegistration() was not called.
   if (!provider_host_ || !provider_host_->controller())
     return base::nullopt;
@@ -218,97 +192,60 @@
   return base::Optional<SubresourceLoaderParams>(std::move(params));
 }
 
-void ServiceWorkerControlleeRequestHandler::PrepareForMainResource(
-    const GURL& url,
-    const GURL& site_for_cookies) {
-  DCHECK(loader());
-  DCHECK(context_);
-  DCHECK(provider_host_);
-  TRACE_EVENT_ASYNC_BEGIN1(
-      "ServiceWorker",
-      "ServiceWorkerControlleeRequestHandler::PrepareForMainResource", this,
-      "URL", url.spec());
-  // The provider host may already have set a controller in redirect case,
-  // unset it now.
-  provider_host_->SetControllerRegistration(
-      nullptr, false /* notify_controllerchange */);
-
-  // Also prevent a registration from claiming this host while it's not
-  // yet execution ready.
-  auto disallow_controller =
-      std::make_unique<ScopedDisallowSetControllerRegistration>(provider_host_);
-
-  stripped_url_ = net::SimplifyUrlForRequest(url);
-  provider_host_->UpdateUrls(stripped_url_, site_for_cookies);
-  registration_lookup_start_time_ = base::TimeTicks::Now();
-  context_->storage()->FindRegistrationForDocument(
-      stripped_url_, base::BindOnce(&ServiceWorkerControlleeRequestHandler::
-                                        DidLookupRegistrationForMainResource,
-                                    weak_factory_.GetWeakPtr(),
-                                    std::move(disallow_controller)));
-}
-
-void ServiceWorkerControlleeRequestHandler::
-    DidLookupRegistrationForMainResource(
-        std::unique_ptr<ScopedDisallowSetControllerRegistration>
-            disallow_controller,
-        blink::ServiceWorkerStatusCode status,
-        scoped_refptr<ServiceWorkerRegistration> registration) {
-  // The job may have been destroyed before this was invoked.
-  if (!loader())
-    return;
-
+void ServiceWorkerControlleeRequestHandler::ContinueWithRegistration(
+    blink::ServiceWorkerStatusCode status,
+    scoped_refptr<ServiceWorkerRegistration> registration) {
   ServiceWorkerMetrics::RecordLookupRegistrationTime(
       status, base::TimeTicks::Now() - registration_lookup_start_time_);
 
   if (status != blink::ServiceWorkerStatusCode::kOk) {
-    loader()->FallbackToNetwork();
     TRACE_EVENT_ASYNC_END1(
         "ServiceWorker",
-        "ServiceWorkerControlleeRequestHandler::PrepareForMainResource", this,
+        "ServiceWorkerControlleeRequestHandler::MaybeCreateLoader", this,
         "Status", blink::ServiceWorkerStatusToString(status));
+    CompleteWithoutLoader();
     return;
   }
   DCHECK(registration);
 
   if (!provider_host_) {
-    loader()->FallbackToNetwork();
     TRACE_EVENT_ASYNC_END1(
         "ServiceWorker",
-        "ServiceWorkerControlleeRequestHandler::PrepareForMainResource", this,
+        "ServiceWorkerControlleeRequestHandler::MaybeCreateLoader", this,
         "Info", "No Provider");
+    CompleteWithoutLoader();
     return;
   }
   provider_host_->AddMatchingRegistration(registration.get());
 
   if (!context_) {
-    loader()->FallbackToNetwork();
     TRACE_EVENT_ASYNC_END1(
         "ServiceWorker",
-        "ServiceWorkerControlleeRequestHandler::PrepareForMainResource", this,
+        "ServiceWorkerControlleeRequestHandler::MaybeCreateLoader", this,
         "Info", "No Context");
+    CompleteWithoutLoader();
     return;
   }
 
   if (!GetContentClient()->browser()->AllowServiceWorker(
           registration->scope(), provider_host_->site_for_cookies(), GURL(),
           resource_context_, provider_host_->web_contents_getter())) {
-    loader()->FallbackToNetwork();
     TRACE_EVENT_ASYNC_END1(
         "ServiceWorker",
-        "ServiceWorkerControlleeRequestHandler::PrepareForMainResource", this,
+        "ServiceWorkerControlleeRequestHandler::MaybeCreateLoader", this,
         "Info", "ServiceWorker is blocked");
+    CompleteWithoutLoader();
     return;
   }
 
   if (!provider_host_->IsContextSecureForServiceWorker()) {
     // TODO(falken): Figure out a way to surface in the page's DevTools
     // console that the service worker was blocked for security.
-    loader()->FallbackToNetwork();
     TRACE_EVENT_ASYNC_END1(
         "ServiceWorker",
-        "ServiceWorkerControlleeRequestHandler::PrepareForMainResource", this,
+        "ServiceWorkerControlleeRequestHandler::MaybeCreateLoader", this,
         "Info", "Insecure context");
+    CompleteWithoutLoader();
     return;
   }
 
@@ -321,8 +258,7 @@
         true /* skip_script_comparison */,
         base::BindOnce(
             &ServiceWorkerControlleeRequestHandler::DidUpdateRegistration,
-            weak_factory_.GetWeakPtr(), registration,
-            std::move(disallow_controller)));
+            weak_factory_.GetWeakPtr(), registration));
     return;
   }
 
@@ -335,11 +271,11 @@
   scoped_refptr<ServiceWorkerVersion> active_version =
       registration->active_version();
   if (!active_version) {
-    loader()->FallbackToNetwork();
     TRACE_EVENT_ASYNC_END1(
         "ServiceWorker",
-        "ServiceWorkerControlleeRequestHandler::PrepareForMainResource", this,
+        "ServiceWorkerControlleeRequestHandler::MaybeCreateLoader", this,
         "Info", "No active version, so falling back to network");
+    CompleteWithoutLoader();
     return;
   }
 
@@ -348,45 +284,30 @@
       << ServiceWorkerVersion::VersionStatusToString(active_version->status());
   // Wait until it's activated before firing fetch events.
   if (active_version->status() == ServiceWorkerVersion::ACTIVATING) {
-    registration->active_version()->RegisterStatusChangeCallback(
-        base::BindOnce(&ServiceWorkerControlleeRequestHandler::
-                           ContinueWithInScopeMainResourceRequest,
-                       weak_factory_.GetWeakPtr(), registration, active_version,
-                       std::move(disallow_controller)));
+    registration->active_version()->RegisterStatusChangeCallback(base::BindOnce(
+        &ServiceWorkerControlleeRequestHandler::ContinueWithActivatedVersion,
+        weak_factory_.GetWeakPtr(), registration, active_version));
     TRACE_EVENT_ASYNC_END1(
         "ServiceWorker",
-        "ServiceWorkerControlleeRequestHandler::PrepareForMainResource", this,
+        "ServiceWorkerControlleeRequestHandler::MaybeCreateLoader", this,
         "Info", "Wait until finished SW activation");
     return;
   }
 
-  ContinueWithInScopeMainResourceRequest(std::move(registration),
-                                         std::move(active_version),
-                                         std::move(disallow_controller));
+  ContinueWithActivatedVersion(std::move(registration),
+                               std::move(active_version));
 }
 
-void ServiceWorkerControlleeRequestHandler::
-    ContinueWithInScopeMainResourceRequest(
-        scoped_refptr<ServiceWorkerRegistration> registration,
-        scoped_refptr<ServiceWorkerVersion> active_version,
-        std::unique_ptr<ScopedDisallowSetControllerRegistration>
-            disallow_controller) {
-  // The job may have been destroyed before this was invoked. In that
-  // case, |loader()| can't be used, so return.
-  if (!loader()) {
+void ServiceWorkerControlleeRequestHandler::ContinueWithActivatedVersion(
+    scoped_refptr<ServiceWorkerRegistration> registration,
+    scoped_refptr<ServiceWorkerVersion> active_version) {
+  if (!context_ || !provider_host_) {
     TRACE_EVENT_ASYNC_END1(
         "ServiceWorker",
-        "ServiceWorkerControlleeRequestHandler::PrepareForMainResource", this,
-        "Info", "The job was destroyed");
-    return;
-  }
-
-  if (!provider_host_) {
-    loader()->FallbackToNetwork();
-    TRACE_EVENT_ASYNC_END1(
-        "ServiceWorker",
-        "ServiceWorkerControlleeRequestHandler::PrepareForMainResource", this,
-        "Info", "The provider host is gone, so falling back to network");
+        "ServiceWorkerControlleeRequestHandler::MaybeCreateLoader", this,
+        "Info",
+        "The context or provider host is gone, so falling back to network");
+    CompleteWithoutLoader();
     return;
   }
 
@@ -409,19 +330,18 @@
     //      retries.
     //   3) If the provider host does not have an active version, just fail the
     //      load.
-    loader()->FallbackToNetwork();
     TRACE_EVENT_ASYNC_END2(
         "ServiceWorker",
-        "ServiceWorkerControlleeRequestHandler::PrepareForMainResource", this,
+        "ServiceWorkerControlleeRequestHandler::MaybeCreateLoader", this,
         "Info",
         "The expected active version is not ACTIVATED, so falling back to "
         "network",
         "Status",
         ServiceWorkerVersion::VersionStatusToString(active_version->status()));
+    CompleteWithoutLoader();
     return;
   }
 
-  disallow_controller.reset();
   provider_host_->SetControllerRegistration(
       registration, false /* notify_controllerchange */);
 
@@ -436,37 +356,45 @@
   if (IsResourceTypeFrame(resource_type_))
     provider_host_->AddServiceWorkerToUpdate(active_version);
 
-  bool should_forward = active_version->fetch_handler_existence() ==
-                        ServiceWorkerVersion::FetchHandlerExistence::EXISTS;
-  if (should_forward)
-    loader()->ForwardToServiceWorker();
-  else
-    loader()->FallbackToNetwork();
+  if (active_version->fetch_handler_existence() !=
+      ServiceWorkerVersion::FetchHandlerExistence::EXISTS) {
+    TRACE_EVENT_ASYNC_END1(
+        "ServiceWorker",
+        "ServiceWorkerControlleeRequestHandler::MaybeCreateLoader", this,
+        "Info", "Skipped the ServiceWorker which has no fetch handler");
+    CompleteWithoutLoader();
+    return;
+  }
+
+  // Finally, we want to forward to the service worker! Make a
+  // ServiceWorkerNavigationLoader which does that work.
+  loader_wrapper_ = std::make_unique<ServiceWorkerNavigationLoaderWrapper>(
+      std::make_unique<ServiceWorkerNavigationLoader>(
+          std::move(fallback_callback_), this, provider_host_,
+          base::WrapRefCounted(context_->loader_factory_getter())));
 
   TRACE_EVENT_ASYNC_END1(
       "ServiceWorker",
-      "ServiceWorkerControlleeRequestHandler::PrepareForMainResource", this,
-      "Info",
-      (should_forward)
-          ? "Forwarded to the ServiceWorker"
-          : "Skipped the ServiceWorker which has no fetch handler");
+      "ServiceWorkerControlleeRequestHandler::MaybeCreateLoader", this, "Info",
+      "Forwarded to the ServiceWorker");
+  std::move(loader_callback_)
+      .Run(base::BindOnce(&ServiceWorkerNavigationLoader::StartRequest,
+                          loader_wrapper_->get()->AsWeakPtr()));
 }
 
 void ServiceWorkerControlleeRequestHandler::DidUpdateRegistration(
     scoped_refptr<ServiceWorkerRegistration> original_registration,
-    std::unique_ptr<ScopedDisallowSetControllerRegistration>
-        disallow_controller,
     blink::ServiceWorkerStatusCode status,
     const std::string& status_message,
     int64_t registration_id) {
   DCHECK(force_update_started_);
 
-  // The job may have been destroyed before this was invoked.
-  if (!loader())
-    return;
-
   if (!context_) {
-    loader()->FallbackToNetwork();
+    TRACE_EVENT_ASYNC_END1(
+        "ServiceWorker",
+        "ServiceWorkerControlleeRequestHandler::MaybeCreateLoader", this,
+        "Info", "The context is gone in DidUpdateRegistration");
+    CompleteWithoutLoader();
     return;
   }
   if (status != blink::ServiceWorkerStatusCode::kOk ||
@@ -474,10 +402,10 @@
     // Update failed. Look up the registration again since the original
     // registration was possibly unregistered in the meantime.
     context_->storage()->FindRegistrationForDocument(
-        stripped_url_, base::BindOnce(&ServiceWorkerControlleeRequestHandler::
-                                          DidLookupRegistrationForMainResource,
-                                      weak_factory_.GetWeakPtr(),
-                                      std::move(disallow_controller)));
+        stripped_url_,
+        base::BindOnce(
+            &ServiceWorkerControlleeRequestHandler::ContinueWithRegistration,
+            weak_factory_.GetWeakPtr()));
     return;
   }
   DCHECK_EQ(original_registration->id(), registration_id);
@@ -488,20 +416,18 @@
   new_version->RegisterStatusChangeCallback(base::BindOnce(
       &ServiceWorkerControlleeRequestHandler::OnUpdatedVersionStatusChanged,
       weak_factory_.GetWeakPtr(), std::move(original_registration),
-      base::WrapRefCounted(new_version), std::move(disallow_controller)));
+      base::WrapRefCounted(new_version)));
 }
 
 void ServiceWorkerControlleeRequestHandler::OnUpdatedVersionStatusChanged(
     scoped_refptr<ServiceWorkerRegistration> registration,
-    scoped_refptr<ServiceWorkerVersion> version,
-    std::unique_ptr<ScopedDisallowSetControllerRegistration>
-        disallow_controller) {
-  // The job may have been destroyed before this was invoked.
-  if (!loader())
-    return;
-
+    scoped_refptr<ServiceWorkerVersion> version) {
   if (!context_) {
-    loader()->FallbackToNetwork();
+    TRACE_EVENT_ASYNC_END1(
+        "ServiceWorker",
+        "ServiceWorkerControlleeRequestHandler::MaybeCreateLoader", this,
+        "Info", "The context is gone in OnUpdatedVersionStatusChanged");
+    CompleteWithoutLoader();
     return;
   }
   if (version->status() == ServiceWorkerVersion::ACTIVATED ||
@@ -510,16 +436,15 @@
     // continue with the incumbent version.
     // In case unregister job may have run, look up the registration again.
     context_->storage()->FindRegistrationForDocument(
-        stripped_url_, base::BindOnce(&ServiceWorkerControlleeRequestHandler::
-                                          DidLookupRegistrationForMainResource,
-                                      weak_factory_.GetWeakPtr(),
-                                      std::move(disallow_controller)));
+        stripped_url_,
+        base::BindOnce(
+            &ServiceWorkerControlleeRequestHandler::ContinueWithRegistration,
+            weak_factory_.GetWeakPtr()));
     return;
   }
   version->RegisterStatusChangeCallback(base::BindOnce(
       &ServiceWorkerControlleeRequestHandler::OnUpdatedVersionStatusChanged,
-      weak_factory_.GetWeakPtr(), std::move(registration), version,
-      std::move(disallow_controller)));
+      weak_factory_.GetWeakPtr(), std::move(registration), version));
 }
 
 ServiceWorkerVersion*
@@ -551,4 +476,8 @@
   loader_wrapper_.reset();
 }
 
+void ServiceWorkerControlleeRequestHandler::CompleteWithoutLoader() {
+  std::move(loader_callback_).Run({});
+}
+
 }  // namespace content
diff --git a/content/browser/service_worker/service_worker_controllee_request_handler.h b/content/browser/service_worker/service_worker_controllee_request_handler.h
index 3d819e6..5b53366 100644
--- a/content/browser/service_worker/service_worker_controllee_request_handler.h
+++ b/content/browser/service_worker/service_worker_controllee_request_handler.h
@@ -6,6 +6,7 @@
 #define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_CONTROLLEE_REQUEST_HANDLER_H_
 
 #include <stdint.h>
+
 #include <memory>
 #include <string>
 
@@ -18,6 +19,7 @@
 #include "content/common/content_export.h"
 #include "content/common/service_worker/service_worker_types.h"
 #include "content/public/common/resource_type.h"
+#include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/mojom/fetch_api.mojom.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
 #include "url/gurl.h"
@@ -66,35 +68,23 @@
  private:
   FRIEND_TEST_ALL_PREFIXES(ServiceWorkerControlleeRequestHandlerTest,
                            ActivateWaitingVersion);
-  class ScopedDisallowSetControllerRegistration;
 
-  // TODO(falken): Remove the "MainResource" names, they are redundant as this
-  // handler is for main resources only.
-  void PrepareForMainResource(const GURL& url, const GURL& site_for_cookies);
-  void DidLookupRegistrationForMainResource(
-      std::unique_ptr<ScopedDisallowSetControllerRegistration>
-          disallow_controller,
+  void ContinueWithRegistration(
       blink::ServiceWorkerStatusCode status,
       scoped_refptr<ServiceWorkerRegistration> registration);
-  void ContinueWithInScopeMainResourceRequest(
+  void ContinueWithActivatedVersion(
       scoped_refptr<ServiceWorkerRegistration> registration,
-      scoped_refptr<ServiceWorkerVersion> version,
-      std::unique_ptr<ScopedDisallowSetControllerRegistration>
-          disallow_controller);
+      scoped_refptr<ServiceWorkerVersion> version);
 
   // For forced update.
   void DidUpdateRegistration(
       scoped_refptr<ServiceWorkerRegistration> original_registration,
-      std::unique_ptr<ScopedDisallowSetControllerRegistration>
-          disallow_controller,
       blink::ServiceWorkerStatusCode status,
       const std::string& status_message,
       int64_t registration_id);
   void OnUpdatedVersionStatusChanged(
       scoped_refptr<ServiceWorkerRegistration> registration,
-      scoped_refptr<ServiceWorkerVersion> version,
-      std::unique_ptr<ScopedDisallowSetControllerRegistration>
-          disallow_controller);
+      scoped_refptr<ServiceWorkerVersion> version);
 
   // ServiceWorkerNavigationLoader::Delegate implementation:
   ServiceWorkerVersion* GetServiceWorkerVersion() override;
@@ -105,6 +95,8 @@
   // that job, except for timing information.
   void ClearJob();
 
+  void CompleteWithoutLoader();
+
   // Schedules a service worker update to occur shortly after the page and its
   // initial subresources load, if this handler was for a navigation.
   void MaybeScheduleUpdate();
@@ -118,6 +110,9 @@
   bool force_update_started_;
   base::TimeTicks registration_lookup_start_time_;
 
+  LoaderCallback loader_callback_;
+  FallbackCallback fallback_callback_;
+
   base::WeakPtrFactory<ServiceWorkerControlleeRequestHandler> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerControlleeRequestHandler);
diff --git a/content/browser/service_worker/service_worker_controllee_request_handler_unittest.cc b/content/browser/service_worker/service_worker_controllee_request_handler_unittest.cc
index 1baf2c31..b38c8e23 100644
--- a/content/browser/service_worker/service_worker_controllee_request_handler_unittest.cc
+++ b/content/browser/service_worker/service_worker_controllee_request_handler_unittest.cc
@@ -19,7 +19,6 @@
 #include "content/browser/service_worker/service_worker_context_core.h"
 #include "content/browser/service_worker/service_worker_provider_host.h"
 #include "content/browser/service_worker/service_worker_registration.h"
-#include "content/browser/service_worker/service_worker_response_type.h"
 #include "content/browser/service_worker/service_worker_test_utils.h"
 #include "content/common/service_worker/service_worker_types.h"
 #include "content/public/browser/resource_context.h"
@@ -58,16 +57,17 @@
               test->provider_host_,
               type)) {}
 
-    ServiceWorkerNavigationLoader* MaybeCreateLoader() {
+    void MaybeCreateLoader() {
       network::ResourceRequest resource_request;
       resource_request.url = request_->url();
       resource_request.resource_type = static_cast<int>(resource_type_);
       resource_request.headers = request()->extra_request_headers();
       handler_->MaybeCreateLoader(resource_request, nullptr, nullptr,
                                   base::DoNothing(), base::DoNothing());
-      return handler_->loader();
     }
 
+    ServiceWorkerNavigationLoader* loader() { return handler_->loader(); }
+
     void ResetHandler() { handler_.reset(nullptr); }
 
     net::URLRequest* request() const { return request_.get(); }
@@ -185,16 +185,12 @@
   // Conduct a main resource load.
   ServiceWorkerRequestTestResources test_resources(
       this, GURL("https://host/scope/doc"), ResourceType::kMainFrame);
-  ServiceWorkerNavigationLoader* loader = test_resources.MaybeCreateLoader();
-
-  EXPECT_FALSE(loader->ShouldFallbackToNetwork());
-  EXPECT_FALSE(loader->ShouldForwardToServiceWorker());
-  EXPECT_FALSE(version_->HasControllee());
+  test_resources.MaybeCreateLoader();
+  EXPECT_FALSE(test_resources.loader());
 
   base::RunLoop().RunUntilIdle();
 
-  EXPECT_FALSE(loader->ShouldFallbackToNetwork());
-  EXPECT_TRUE(loader->ShouldForwardToServiceWorker());
+  EXPECT_TRUE(test_resources.loader());
   EXPECT_TRUE(version_->HasControllee());
   histogram_tester.ExpectTotalCount(
       "ServiceWorker.LookupRegistration.MainResource.Time.Exists", 1);
@@ -212,8 +208,8 @@
   // Conduct a main resource load.
   ServiceWorkerRequestTestResources test_resources(
       this, GURL("https://host/scope/doc"), ResourceType::kMainFrame);
-  ServiceWorkerNavigationLoader* loader = test_resources.MaybeCreateLoader();
-  EXPECT_FALSE(loader);
+  test_resources.MaybeCreateLoader();
+  EXPECT_FALSE(test_resources.loader());
 
   histogram_tester.ExpectTotalCount(
       "ServiceWorker.LookupRegistration.MainResource.Time.DoesNotExist", 1);
@@ -233,8 +229,8 @@
   // Conduct a main resource load.
   ServiceWorkerRequestTestResources test_resources(
       this, GURL("https://host/scope/doc"), ResourceType::kMainFrame);
-  ServiceWorkerNavigationLoader* loader = test_resources.MaybeCreateLoader();
-  EXPECT_FALSE(loader);
+  test_resources.MaybeCreateLoader();
+  EXPECT_FALSE(test_resources.loader());
 
   histogram_tester.ExpectTotalCount(
       "ServiceWorker.LookupRegistration.MainResource.Time.Error", 1);
@@ -259,17 +255,13 @@
   // Conduct a main resource load.
   ServiceWorkerRequestTestResources test_resources(
       this, GURL("https://host/scope/doc"), ResourceType::kMainFrame);
-  ServiceWorkerNavigationLoader* loader = test_resources.MaybeCreateLoader();
-  ASSERT_TRUE(loader);
+  test_resources.MaybeCreateLoader();
+  EXPECT_FALSE(test_resources.loader());
 
-  EXPECT_FALSE(loader->ShouldFallbackToNetwork());
-  EXPECT_FALSE(loader->ShouldForwardToServiceWorker());
-  EXPECT_FALSE(version_->HasControllee());
   base::RunLoop().RunUntilIdle();
 
   // Verify we did not use the worker.
-  EXPECT_TRUE(loader->ShouldFallbackToNetwork());
-  EXPECT_FALSE(loader->ShouldForwardToServiceWorker());
+  EXPECT_FALSE(test_resources.loader());
   EXPECT_FALSE(version_->HasControllee());
 
   SetBrowserClientForTesting(old_browser_client);
@@ -290,17 +282,13 @@
   // Conduct a main resource load.
   ServiceWorkerRequestTestResources test_resources(
       this, GURL("https://host/scope/doc"), ResourceType::kMainFrame);
-  ServiceWorkerNavigationLoader* loader = test_resources.MaybeCreateLoader();
-  ASSERT_TRUE(loader);
-
-  EXPECT_FALSE(loader->ShouldFallbackToNetwork());
-  EXPECT_FALSE(loader->ShouldForwardToServiceWorker());
+  test_resources.MaybeCreateLoader();
+  EXPECT_FALSE(test_resources.loader());
   EXPECT_FALSE(version_->HasControllee());
   base::RunLoop().RunUntilIdle();
 
   // Verify we did not use the worker.
-  EXPECT_TRUE(loader->ShouldFallbackToNetwork());
-  EXPECT_FALSE(loader->ShouldForwardToServiceWorker());
+  EXPECT_FALSE(test_resources.loader());
   EXPECT_FALSE(version_->HasControllee());
 }
 
@@ -317,18 +305,14 @@
   // Conduct a main resource load.
   ServiceWorkerRequestTestResources test_resources(
       this, GURL("https://host/scope/doc"), ResourceType::kMainFrame);
-  ServiceWorkerNavigationLoader* loader = test_resources.MaybeCreateLoader();
-  ASSERT_TRUE(loader);
-
-  EXPECT_FALSE(loader->ShouldFallbackToNetwork());
-  EXPECT_FALSE(loader->ShouldForwardToServiceWorker());
+  test_resources.MaybeCreateLoader();
+  EXPECT_FALSE(test_resources.loader());
   EXPECT_FALSE(version_->HasControllee());
 
   base::RunLoop().RunUntilIdle();
 
   EXPECT_EQ(ServiceWorkerVersion::ACTIVATED, version_->status());
-  EXPECT_FALSE(loader->ShouldFallbackToNetwork());
-  EXPECT_TRUE(loader->ShouldForwardToServiceWorker());
+  EXPECT_TRUE(test_resources.loader());
   EXPECT_TRUE(version_->HasControllee());
 
   test_resources.ResetHandler();
@@ -346,7 +330,7 @@
   // Conduct a main resource load.
   ServiceWorkerRequestTestResources test_resources(
       this, GURL("https://host/scope/doc"), ResourceType::kMainFrame);
-  ServiceWorkerNavigationLoader* job = test_resources.MaybeCreateLoader();
+  test_resources.MaybeCreateLoader();
 
   base::RunLoop().RunUntilIdle();
 
@@ -354,7 +338,7 @@
   // provider host should not be controlled. However it should add the
   // registration as a matching registration so it can be used for .ready and
   // claim().
-  EXPECT_FALSE(job);
+  EXPECT_FALSE(test_resources.loader());
   EXPECT_FALSE(version_->HasControllee());
   EXPECT_FALSE(provider_host_->controller());
   EXPECT_EQ(registration_.get(), provider_host_->MatchRegistration());
@@ -377,19 +361,15 @@
   // Conduct a main resource load.
   ServiceWorkerRequestTestResources test_resources(
       this, GURL("https://host/scope/doc"), ResourceType::kMainFrame);
-  ServiceWorkerNavigationLoader* loader = test_resources.MaybeCreateLoader();
-  ASSERT_TRUE(loader);
-
-  EXPECT_FALSE(loader->ShouldFallbackToNetwork());
-  EXPECT_FALSE(loader->ShouldForwardToServiceWorker());
+  test_resources.MaybeCreateLoader();
+  EXPECT_FALSE(test_resources.loader());
 
   // Shouldn't crash if the ProviderHost is deleted prior to completion of
   // the database lookup.
   context()->RemoveProviderHost(provider_host_->provider_id());
   EXPECT_FALSE(provider_host_.get());
   base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(loader->ShouldFallbackToNetwork());
-  EXPECT_FALSE(loader->ShouldForwardToServiceWorker());
+  EXPECT_FALSE(test_resources.loader());
 }
 
 #if BUILDFLAG(ENABLE_OFFLINE_PAGES)
@@ -409,9 +389,9 @@
   // Sets an offline header to indicate force loading offline page.
   test_resources.request()->SetExtraRequestHeaderByName(
       "X-Chrome-offline", "reason=download", true);
-  ServiceWorkerNavigationLoader* loader = test_resources.MaybeCreateLoader();
-
-  EXPECT_FALSE(loader);
+  test_resources.MaybeCreateLoader();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(test_resources.loader());
 }
 
 TEST_F(ServiceWorkerControlleeRequestHandlerTest, FallbackWithNoOfflineHeader) {
@@ -430,9 +410,9 @@
   // Empty offline header value should not cause fallback.
   test_resources.request()->SetExtraRequestHeaderByName("X-Chrome-offline", "",
                                                         true);
-  ServiceWorkerNavigationLoader* loader = test_resources.MaybeCreateLoader();
-
-  EXPECT_TRUE(loader);
+  test_resources.MaybeCreateLoader();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(test_resources.loader());
 }
 #endif  // BUILDFLAG(ENABLE_OFFLINE_PAGE)
 
diff --git a/content/browser/service_worker/service_worker_navigation_loader.cc b/content/browser/service_worker/service_worker_navigation_loader.cc
index 4a1699b..d74ca80 100644
--- a/content/browser/service_worker/service_worker_navigation_loader.cc
+++ b/content/browser/service_worker/service_worker_navigation_loader.cc
@@ -78,34 +78,25 @@
 };
 
 ServiceWorkerNavigationLoader::ServiceWorkerNavigationLoader(
-    NavigationLoaderInterceptor::LoaderCallback callback,
     NavigationLoaderInterceptor::FallbackCallback fallback_callback,
     Delegate* delegate,
-    const network::ResourceRequest& tentative_resource_request,
     base::WeakPtr<ServiceWorkerProviderHost> provider_host,
     scoped_refptr<URLLoaderFactoryGetter> url_loader_factory_getter)
-    : loader_callback_(std::move(callback)),
-      fallback_callback_(std::move(fallback_callback)),
+    : fallback_callback_(std::move(fallback_callback)),
       delegate_(delegate),
       provider_host_(std::move(provider_host)),
       url_loader_factory_getter_(std::move(url_loader_factory_getter)),
       binding_(this),
       weak_factory_(this) {
-  TRACE_EVENT_WITH_FLOW1(
+  TRACE_EVENT_WITH_FLOW0(
       "ServiceWorker",
-      "ServiceWorkerNavigationLoader::ServiceWorkerNavigationloader", this,
-      TRACE_EVENT_FLAG_FLOW_OUT, "url", tentative_resource_request.url.spec());
+      "ServiceWorkerNavigationLoader::ServiceWorkerNavigationLoader", this,
+      TRACE_EVENT_FLAG_FLOW_OUT);
 
   DCHECK(delegate_);
-  DCHECK(ServiceWorkerUtils::IsMainResourceType(
-      static_cast<ResourceType>(tentative_resource_request.resource_type)));
 
   response_head_.load_timing.request_start = base::TimeTicks::Now();
   response_head_.load_timing.request_start_time = base::Time::Now();
-
-  // Beware that the final resource request may change due to throttles, so
-  // don't save |tentative_resource_request| here. We'll get the real one in
-  // StartRequest.
 }
 
 ServiceWorkerNavigationLoader::~ServiceWorkerNavigationLoader() {
@@ -115,44 +106,6 @@
       TRACE_EVENT_FLAG_FLOW_IN);
 }
 
-void ServiceWorkerNavigationLoader::FallbackToNetwork() {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  TRACE_EVENT_WITH_FLOW0(
-      "ServiceWorker", "ServiceWorkerNavigationLoader::FallbackToNetwork", this,
-      TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
-  // ServiceWorkerControlleeRequestHandler only calls this if this loader never
-  // intercepted the request. Fallback to network after interception uses
-  // |fallback_callback_| instead.
-  DCHECK_EQ(response_type_, ResponseType::NOT_DETERMINED);
-  response_type_ = ResponseType::FALLBACK_TO_NETWORK;
-
-  TransitionToStatus(Status::kCompleted);
-
-  std::move(loader_callback_).Run({});
-}
-
-void ServiceWorkerNavigationLoader::ForwardToServiceWorker() {
-  TRACE_EVENT_WITH_FLOW0(
-      "ServiceWorker", "ServiceWorkerNavigationLoader::ForwardToServiceWorker",
-      this, TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
-  DCHECK_EQ(status_, Status::kNotStarted);
-  DCHECK_EQ(response_type_, ResponseType::NOT_DETERMINED);
-
-  response_type_ = ResponseType::FORWARD_TO_SERVICE_WORKER;
-
-  std::move(loader_callback_)
-      .Run(base::BindOnce(&ServiceWorkerNavigationLoader::StartRequest,
-                          weak_factory_.GetWeakPtr()));
-}
-
-bool ServiceWorkerNavigationLoader::ShouldFallbackToNetwork() {
-  return response_type_ == ResponseType::FALLBACK_TO_NETWORK;
-}
-
-bool ServiceWorkerNavigationLoader::ShouldForwardToServiceWorker() {
-  return response_type_ == ResponseType::FORWARD_TO_SERVICE_WORKER;
-}
-
 void ServiceWorkerNavigationLoader::DetachedFromRequest() {
   delegate_ = nullptr;
   DeleteIfNeeded();
@@ -167,6 +120,13 @@
     const network::ResourceRequest& resource_request,
     network::mojom::URLLoaderRequest request,
     network::mojom::URLLoaderClientPtr client) {
+  TRACE_EVENT_WITH_FLOW1("ServiceWorker",
+                         "ServiceWorkerNavigationLoader::StartRequest", this,
+                         TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
+                         "url", resource_request.url.spec());
+  DCHECK(ServiceWorkerUtils::IsMainResourceType(
+      static_cast<ResourceType>(resource_request.resource_type)));
+
   resource_request_ = resource_request;
   if (provider_host_ && provider_host_->fetch_request_window_id()) {
     resource_request_.fetch_window_id =
@@ -182,13 +142,8 @@
                      base::Unretained(this)));
   url_loader_client_ = std::move(client);
 
-  DCHECK_EQ(ResponseType::FORWARD_TO_SERVICE_WORKER, response_type_);
   TransitionToStatus(Status::kStarted);
 
-  TRACE_EVENT_WITH_FLOW0("ServiceWorker",
-                         "ServiceWorkerNavigationLoader::StartRequest", this,
-                         TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
-
   ServiceWorkerVersion* active_worker = delegate_->GetServiceWorkerVersion();
   if (!active_worker) {
     CommitCompleted(net::ERR_FAILED, "No active worker");
diff --git a/content/browser/service_worker/service_worker_navigation_loader.h b/content/browser/service_worker/service_worker_navigation_loader.h
index 9b31b990..af871ed 100644
--- a/content/browser/service_worker/service_worker_navigation_loader.h
+++ b/content/browser/service_worker/service_worker_navigation_loader.h
@@ -15,7 +15,6 @@
 #include "content/browser/service_worker/embedded_worker_status.h"
 #include "content/browser/service_worker/service_worker_fetch_dispatcher.h"
 #include "content/browser/service_worker/service_worker_metrics.h"
-#include "content/browser/service_worker/service_worker_response_type.h"
 #include "content/browser/url_loader_factory_getter.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "mojo/public/cpp/system/data_pipe.h"
@@ -42,8 +41,6 @@
 class CONTENT_EXPORT ServiceWorkerNavigationLoader
     : public network::mojom::URLLoader {
  public:
-  using ResponseType = ServiceWorkerResponseType;
-
   class CONTENT_EXPORT Delegate {
    public:
     virtual ~Delegate() {}
@@ -63,26 +60,20 @@
     virtual void MainResourceLoadFailed() = 0;
   };
 
-  // Created by ServiceWorkerControlleeRequestHandler::MaybeCreateLoader
-  // when starting to load a main resource.
+  // Created by ServiceWorkerControlleeRequestHandler
+  // after it determines the load should go through a service worker.
   //
   // For the navigation case, this job typically works in the following order:
-  // 1. One of the FallbackTo* or ForwardTo* methods are called by
-  //    ServiceWorkerControlleeRequestHandler, which determines how the request
-  //    should be served (e.g. should fallback to network or should be sent to
-  //    the SW). If it decides to fallback to the network this will call
-  //    |loader_callback| with a null RequestHandler, which will be then handled
-  //    by NavigationURLLoaderImpl.
-  // 2. If it is decided that the request should be sent to the SW,
-  //    this job calls |loader_callback|, passing StartRequest as the
+  // 1. ServiceWorkerControlleeRequestHandler::MaybeCreateLoader() creates the
+  //    ServiceWorkerNavigationLoader, passing StartRequest() as the
   //    RequestHandler.
-  // 3. At this point, the NavigationURLLoaderImpl can throttle the request,
+  // 2. At this point, the NavigationURLLoaderImpl can throttle the request,
   //    and invoke the RequestHandler later with a possibly modified request.
-  // 4. StartRequest is invoked. This dispatches a FetchEvent.
-  // 5. DidDispatchFetchEvent() determines the request's final destination. If
+  // 3. StartRequest is invoked. This dispatches a FetchEvent.
+  // 4. DidDispatchFetchEvent() determines the request's final destination. If
   //    it turns out we need to fallback to network, it calls
   //    |fallback_callback|.
-  // 6. Otherwise if the SW returned a stream or blob as a response
+  // 5. Otherwise if the SW returned a stream or blob as a response
   //    this job passes the response to the network::mojom::URLLoaderClientPtr
   //    connected to NavigationURLLoaderImpl (for resource loading for
   //    navigation), that was given to StartRequest. This forwards the
@@ -91,20 +82,18 @@
   // Loads for shared workers work similarly, except SharedWorkerScriptLoader
   // is used instead of NavigationURLLoaderImpl.
   ServiceWorkerNavigationLoader(
-      NavigationLoaderInterceptor::LoaderCallback loader_callback,
       NavigationLoaderInterceptor::FallbackCallback fallback_callback,
       Delegate* delegate,
-      const network::ResourceRequest& tentative_resource_request,
       base::WeakPtr<ServiceWorkerProviderHost> provider_host,
       scoped_refptr<URLLoaderFactoryGetter> url_loader_factory_getter);
 
   ~ServiceWorkerNavigationLoader() override;
 
-  // Called via ServiceWorkerControlleeRequestHandler.
-  void FallbackToNetwork();
-  void ForwardToServiceWorker();
-  bool ShouldFallbackToNetwork();
-  bool ShouldForwardToServiceWorker();
+  // Passed as the RequestHandler for
+  // NavigationLoaderInterceptor::MaybeCreateLoader.
+  void StartRequest(const network::ResourceRequest& resource_request,
+                    network::mojom::URLLoaderRequest request,
+                    network::mojom::URLLoaderClientPtr client);
 
   // The navigation request that was holding this job is
   // going away. Calling this internally calls |DeleteIfNeeded()|
@@ -132,10 +121,6 @@
     kCompleted,
   };
 
-  // For FORWARD_TO_SERVICE_WORKER case.
-  void StartRequest(const network::ResourceRequest& resource_request,
-                    network::mojom::URLLoaderRequest request,
-                    network::mojom::URLLoaderClientPtr client);
   void DidPrepareFetchEvent(scoped_refptr<ServiceWorkerVersion> version,
                             EmbeddedWorkerStatus initial_worker_status);
   void DidDispatchFetchEvent(
@@ -187,8 +172,6 @@
 
   void TransitionToStatus(Status new_status);
 
-  ResponseType response_type_ = ResponseType::NOT_DETERMINED;
-  NavigationLoaderInterceptor::LoaderCallback loader_callback_;
   NavigationLoaderInterceptor::FallbackCallback fallback_callback_;
 
   // |delegate_| is non-null and owns |this| until DetachedFromRequest() is
diff --git a/content/browser/service_worker/service_worker_navigation_loader_unittest.cc b/content/browser/service_worker/service_worker_navigation_loader_unittest.cc
index 0b84ae8..72e82bc5 100644
--- a/content/browser/service_worker/service_worker_navigation_loader_unittest.cc
+++ b/content/browser/service_worker/service_worker_navigation_loader_unittest.cc
@@ -387,41 +387,24 @@
 
   ServiceWorkerStorage* storage() { return helper_->context()->storage(); }
 
-  // Indicates whether ServiceWorkerNavigationLoader decided to handle a
-  // request, i.e., it returned a non-null RequestHandler for the request.
-  enum class LoaderResult {
-    kHandledRequest,
-    kDidNotHandleRequest,
-  };
-
-  // Starts a request. Returns whether ServiceWorkerNavigationLoader handled the
-  // request. If kHandledRequest was returned, the request is ongoing and the
+  // Starts a request. After calling this, the request is ongoing and the
   // caller can use functions like client_.RunUntilComplete() to wait for
   // completion.
-  LoaderResult StartRequest(std::unique_ptr<network::ResourceRequest> request) {
+  void StartRequest(std::unique_ptr<network::ResourceRequest> request) {
     provider_host_ = CreateProviderHostForWindow(
         helper_->mock_render_process_id(), true /* is_parent_frame_secure */,
         helper_->context()->AsWeakPtr(), &provider_endpoints_);
-    // Start a ServiceWorkerNavigationLoader. It should return a
-    // RequestHandler.
-    SingleRequestURLLoaderFactory::RequestHandler handler;
+    // Create a ServiceWorkerNavigationLoader.
     loader_ = std::make_unique<ServiceWorkerNavigationLoader>(
-        base::BindOnce(&ReceiveRequestHandler, &handler),
         base::BindOnce(&ServiceWorkerNavigationLoaderTest::Fallback,
                        base::Unretained(this)),
-        this, *request, provider_host_,
+        this, provider_host_,
         base::WrapRefCounted<URLLoaderFactoryGetter>(
             helper_->context()->loader_factory_getter()));
-    loader_->ForwardToServiceWorker();
-    base::RunLoop().RunUntilIdle();
-    if (!handler)
-      return LoaderResult::kDidNotHandleRequest;
 
-    // Run the handler. It will load |request.url|.
-    std::move(handler).Run(*request, mojo::MakeRequest(&loader_ptr_),
-                           client_.CreateInterfacePtr());
-
-    return LoaderResult::kHandledRequest;
+    // Load |request.url|.
+    loader_->StartRequest(*request, mojo::MakeRequest(&loader_ptr_),
+                          client_.CreateInterfacePtr());
   }
 
   // The |fallback_callback| passed to the ServiceWorkerNavigationLoader in
@@ -514,9 +497,8 @@
 TEST_F(ServiceWorkerNavigationLoaderTest, Basic) {
   base::HistogramTester histogram_tester;
 
-  // Perform the request
-  LoaderResult result = StartRequest(CreateRequest());
-  EXPECT_EQ(LoaderResult::kHandledRequest, result);
+  // Perform the request.
+  StartRequest(CreateRequest());
   client_.RunUntilComplete();
 
   EXPECT_EQ(net::OK, client_.completion_status().error_code);
@@ -543,9 +525,7 @@
   version_ = nullptr;
 
   // Perform the request.
-  LoaderResult result = StartRequest(CreateRequest());
-  EXPECT_EQ(LoaderResult::kHandledRequest, result);
-
+  StartRequest(CreateRequest());
   client_.RunUntilComplete();
   EXPECT_EQ(net::ERR_FAILED, client_.completion_status().error_code);
 
@@ -597,8 +577,7 @@
   service_worker_->RespondWithBlob(std::move(blob));
 
   // Perform the request.
-  LoaderResult result = StartRequest(CreateRequest());
-  EXPECT_EQ(LoaderResult::kHandledRequest, result);
+  StartRequest(CreateRequest());
   client_.RunUntilComplete();
 
   const network::ResourceResponseHead& info = client_.response_head();
@@ -638,8 +617,7 @@
   service_worker_->RespondWithBlob(std::move(blob));
 
   // Perform the request.
-  LoaderResult result = StartRequest(CreateRequest());
-  EXPECT_EQ(LoaderResult::kHandledRequest, result);
+  StartRequest(CreateRequest());
 
   // We should get a valid response once the headers arrive.
   client_.RunUntilResponseReceived();
@@ -674,8 +652,7 @@
                                      std::move(data_pipe.consumer_handle));
 
   // Perform the request.
-  LoaderResult result = StartRequest(CreateRequest());
-  EXPECT_EQ(LoaderResult::kHandledRequest, result);
+  StartRequest(CreateRequest());
   client_.RunUntilResponseReceived();
 
   const network::ResourceResponseHead& info = client_.response_head();
@@ -722,8 +699,7 @@
                                      std::move(data_pipe.consumer_handle));
 
   // Perform the request.
-  LoaderResult result = StartRequest(CreateRequest());
-  EXPECT_EQ(LoaderResult::kHandledRequest, result);
+  StartRequest(CreateRequest());
   client_.RunUntilResponseReceived();
 
   const network::ResourceResponseHead& info = client_.response_head();
@@ -772,8 +748,7 @@
                                      std::move(data_pipe.consumer_handle));
 
   // Perform the request.
-  LoaderResult result = StartRequest(CreateRequest());
-  EXPECT_EQ(LoaderResult::kHandledRequest, result);
+  StartRequest(CreateRequest());
   client_.RunUntilResponseReceived();
 
   const network::ResourceResponseHead& info = client_.response_head();
@@ -822,8 +797,7 @@
   service_worker_->RespondWithFallback();
 
   // Perform the request.
-  LoaderResult result = StartRequest(CreateRequest());
-  EXPECT_EQ(LoaderResult::kHandledRequest, result);
+  StartRequest(CreateRequest());
 
   // The fallback callback should be called.
   RunUntilFallbackCallback();
@@ -849,9 +823,7 @@
   service_worker_->RespondWithError();
 
   // Perform the request.
-  LoaderResult result = StartRequest(CreateRequest());
-  EXPECT_EQ(LoaderResult::kHandledRequest, result);
-
+  StartRequest(CreateRequest());
   client_.RunUntilComplete();
   EXPECT_EQ(net::ERR_FAILED, client_.completion_status().error_code);
 
@@ -871,8 +843,7 @@
   service_worker_->FailToDispatchFetchEvent();
 
   // Perform the request.
-  LoaderResult result = StartRequest(CreateRequest());
-  EXPECT_EQ(LoaderResult::kHandledRequest, result);
+  StartRequest(CreateRequest());
 
   // The fallback callback should be called.
   RunUntilFallbackCallback();
@@ -895,8 +866,7 @@
   service_worker_->RespondEarly();
 
   // Perform the request.
-  LoaderResult result = StartRequest(CreateRequest());
-  EXPECT_EQ(LoaderResult::kHandledRequest, result);
+  StartRequest(CreateRequest());
   client_.RunUntilComplete();
 
   const network::ResourceResponseHead& info = client_.response_head();
@@ -910,41 +880,6 @@
   EXPECT_FALSE(HasWorkInBrowser(version_.get()));
 }
 
-// Test asking the loader to fallback to network. In production code, this
-// happens when there is no active service worker for the URL, or it must be
-// skipped, etc.
-TEST_F(ServiceWorkerNavigationLoaderTest, FallbackToNetwork) {
-  base::HistogramTester histogram_tester;
-
-  network::ResourceRequest request;
-  request.url = GURL("https://www.example.com/");
-  request.method = "GET";
-  request.mode = network::mojom::RequestMode::kNavigate;
-  request.credentials_mode = network::mojom::CredentialsMode::kInclude;
-  request.redirect_mode = network::mojom::RedirectMode::kManual;
-
-  SingleRequestURLLoaderFactory::RequestHandler handler;
-  auto loader = std::make_unique<ServiceWorkerNavigationLoader>(
-      base::BindOnce(&ReceiveRequestHandler, &handler),
-      base::BindOnce(&ServiceWorkerNavigationLoaderTest::Fallback,
-                     base::Unretained(this)),
-      this, request, nullptr /* provider_host */,
-      base::WrapRefCounted<URLLoaderFactoryGetter>(
-          helper_->context()->loader_factory_getter()));
-  // Ask the loader to fallback to network. In production code,
-  // ServiceWorkerControlleeRequestHandler calls FallbackToNetwork() to do this.
-  loader->FallbackToNetwork();
-  base::RunLoop().RunUntilIdle();
-  EXPECT_FALSE(handler);
-
-  // No fetch event was dispatched.
-  histogram_tester.ExpectTotalCount(kHistogramMainResourceFetchEvent, 0);
-  histogram_tester.ExpectTotalCount(
-      "ServiceWorker.LoadTiming.MainFrame.MainResource."
-      "StartToForwardServiceWorker",
-      0);
-}
-
 // Test responding to the fetch event with a redirect response.
 TEST_F(ServiceWorkerNavigationLoaderTest, Redirect) {
   base::HistogramTester histogram_tester;
@@ -952,8 +887,7 @@
   service_worker_->RespondWithRedirectResponse(new_url);
 
   // Perform the request.
-  LoaderResult result = StartRequest(CreateRequest());
-  EXPECT_EQ(LoaderResult::kHandledRequest, result);
+  StartRequest(CreateRequest());
   client_.RunUntilRedirectReceived();
 
   const network::ResourceResponseHead& info = client_.response_head();
@@ -969,9 +903,8 @@
                                       blink::ServiceWorkerStatusCode::kOk, 1);
 }
 
-TEST_F(ServiceWorkerNavigationLoaderTest, LifetimeAfterForwardToServiceWorker) {
-  LoaderResult result = StartRequest(CreateRequest());
-  EXPECT_EQ(LoaderResult::kHandledRequest, result);
+TEST_F(ServiceWorkerNavigationLoaderTest, Lifetime) {
+  StartRequest(CreateRequest());
   base::WeakPtr<ServiceWorkerNavigationLoader> loader = loader_->AsWeakPtr();
   ASSERT_TRUE(loader);
 
@@ -992,40 +925,9 @@
   // |loader_| is deleted here. LSan test will alert if it leaks.
 }
 
-TEST_F(ServiceWorkerNavigationLoaderTest, LifetimeAfterFallbackToNetwork) {
-  network::ResourceRequest request;
-  request.url = GURL("https://www.example.com/");
-  request.method = "GET";
-  request.mode = network::mojom::RequestMode::kNavigate;
-  request.credentials_mode = network::mojom::CredentialsMode::kInclude;
-  request.redirect_mode = network::mojom::RedirectMode::kManual;
-
-  SingleRequestURLLoaderFactory::RequestHandler handler;
-  auto loader = std::make_unique<ServiceWorkerNavigationLoader>(
-      base::BindOnce(&ReceiveRequestHandler, &handler),
-      base::BindOnce(&ServiceWorkerNavigationLoaderTest::Fallback,
-                     base::Unretained(this)),
-      this, request, nullptr /* provider_host */,
-      base::WrapRefCounted<URLLoaderFactoryGetter>(
-          helper_->context()->loader_factory_getter()));
-  base::WeakPtr<ServiceWorkerNavigationLoader> loader_weakptr =
-      loader->AsWeakPtr();
-  // Ask the loader to fallback to network. In production code,
-  // ServiceWorkerControlleeRequestHandler calls FallbackToNetwork() to do this.
-  loader->FallbackToNetwork();
-  base::RunLoop().RunUntilIdle();
-  EXPECT_FALSE(handler);
-  EXPECT_TRUE(loader_weakptr);
-
-  // DetachedFromRequest() deletes |loader_|.
-  loader.release()->DetachedFromRequest();
-  EXPECT_FALSE(loader_weakptr);
-}
-
 TEST_F(ServiceWorkerNavigationLoaderTest, ConnectionErrorDuringFetchEvent) {
   service_worker_->DeferResponse();
-  LoaderResult result = StartRequest(CreateRequest());
-  EXPECT_EQ(LoaderResult::kHandledRequest, result);
+  StartRequest(CreateRequest());
 
   // Wait for the fetch event to be dispatched.
   service_worker_->RunUntilFetchEvent();
@@ -1045,8 +947,7 @@
 }
 
 TEST_F(ServiceWorkerNavigationLoaderTest, DetachedDuringFetchEvent) {
-  LoaderResult result = StartRequest(CreateRequest());
-  EXPECT_EQ(LoaderResult::kHandledRequest, result);
+  StartRequest(CreateRequest());
 
   // Detach the loader immediately after it started. This results in
   // DidDispatchFetchEvent() being invoked later with null |delegate_|.
diff --git a/content/browser/service_worker/service_worker_provider_host.cc b/content/browser/service_worker/service_worker_provider_host.cc
index 18eff1ad..036d2619 100644
--- a/content/browser/service_worker/service_worker_provider_host.cc
+++ b/content/browser/service_worker/service_worker_provider_host.cc
@@ -689,6 +689,8 @@
 void ServiceWorkerProviderHost::ClaimedByRegistration(
     scoped_refptr<ServiceWorkerRegistration> registration) {
   DCHECK(registration->active_version());
+  DCHECK(is_execution_ready());
+
   // TODO(falken): This should just early return, or DCHECK. claim() should have
   // no effect on a page that's already using the registration.
   if (registration == controller_registration_) {
@@ -696,11 +698,7 @@
     return;
   }
 
-  // TODO(crbug.com/866353): It shouldn't be necesary to check
-  // |allow_set_controller_registration_|. See the comment for
-  // AllowSetControllerRegistration().
-  if (allow_set_controller_registration_)
-    SetControllerRegistration(registration, true /* notify_controllerchange */);
+  SetControllerRegistration(registration, true /* notify_controllerchange */);
 }
 
 void ServiceWorkerProviderHost::OnBeginNavigationCommit(int render_process_id,
diff --git a/content/browser/service_worker/service_worker_provider_host.h b/content/browser/service_worker/service_worker_provider_host.h
index 4f18627..c050626f 100644
--- a/content/browser/service_worker/service_worker_provider_host.h
+++ b/content/browser/service_worker/service_worker_provider_host.h
@@ -272,22 +272,6 @@
       scoped_refptr<ServiceWorkerRegistration> controller_registration,
       bool notify_controllerchange);
 
-  // For use by the ServiceWorkerControlleeRequestHandler to disallow a
-  // registration claiming this host while its main resource request is
-  // occurring.
-  //
-  // TODO(crbug.com/866353): This should be unneccessary: registration code
-  // already avoids claiming clients that are not execution ready. However
-  // there may be edge cases with shared workers (pre-NetS13nServiceWorker) and
-  // about:blank iframes, since |is_execution_ready()| is initialized true for
-  // them. Try to remove this after S13nServiceWorker.
-  void AllowSetControllerRegistration(bool allow) {
-    allow_set_controller_registration_ = allow;
-  }
-  bool IsSetControllerRegistrationAllowed() {
-    return allow_set_controller_registration_;
-  }
-
   // Returns an interceptor for a main resource request. May return nullptr if
   // the request doesn't require interception.
   std::unique_ptr<NavigationLoaderInterceptor> CreateLoaderInterceptor(
@@ -410,8 +394,8 @@
   bool is_response_committed() const;
 
   // For service worker clients. True if the client is execution ready and
-  // therefore can be exposed to JavaScript. Execution ready implies connected
-  // to renderer.
+  // therefore can be exposed to JavaScript. Execution ready implies response
+  // committed.
   // https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-execution-ready-flag
   bool is_execution_ready() const;
 
@@ -677,7 +661,6 @@
   // the provider host's controller is updated to match it.
   scoped_refptr<ServiceWorkerVersion> controller_;
   scoped_refptr<ServiceWorkerRegistration> controller_registration_;
-  bool allow_set_controller_registration_ = true;
 
   // For service worker execution contexts. The ServiceWorkerVersion of the
   // service worker this is a provider for.
diff --git a/content/browser/service_worker/service_worker_registration.cc b/content/browser/service_worker/service_worker_registration.cc
index 290d6e3..afe28f1 100644
--- a/content/browser/service_worker/service_worker_registration.cc
+++ b/content/browser/service_worker/service_worker_registration.cc
@@ -250,17 +250,40 @@
   DCHECK(context_);
   DCHECK(active_version());
 
+  // https://w3c.github.io/ServiceWorker/#clients-claim
+  //
+  // "For each service worker client client whose origin is the same as the
+  //  service worker's origin:
+  const bool include_reserved_clients = false;
   for (std::unique_ptr<ServiceWorkerContextCore::ProviderHostIterator> it =
-           context_->GetClientProviderHostIterator(
-               scope_.GetOrigin(), false /* include_reserved_clients */);
+           context_->GetClientProviderHostIterator(scope_.GetOrigin(),
+                                                   include_reserved_clients);
        !it->IsAtEnd(); it->Advance()) {
     ServiceWorkerProviderHost* host = it->GetProviderHost();
+    // "1. If client’s execution ready flag is unset or client’s discarded flag
+    //     is set, continue."
+    // |include_reserved_clients| ensures only execution ready clients are
+    // returned.
+    DCHECK(host->is_execution_ready());
+
+    // This is part of step 5 but performed here as an optimization. Do nothing
+    // if this version is already the controller.
     if (host->controller() == active_version())
       continue;
+
+    // "2. If client is not a secure context, continue."
     if (!host->IsContextSecureForServiceWorker())
       continue;
-    if (host->MatchRegistration() == this)
-      host->ClaimedByRegistration(this);
+
+    // "3. Let registration be the result of running Match Service Worker
+    //     Registration algorithm passing client’s creation URL as the argument.
+    //  4. If registration is not the service worker's containing service worker
+    //     registration, continue."
+    if (host->MatchRegistration() != this)
+      continue;
+
+    // The remaining steps are performed here:
+    host->ClaimedByRegistration(this);
   }
 }
 
diff --git a/content/browser/service_worker/service_worker_response_type.h b/content/browser/service_worker/service_worker_response_type.h
deleted file mode 100644
index 0d37b88..0000000
--- a/content/browser/service_worker/service_worker_response_type.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_RESPONSE_TYPE_H_
-#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_RESPONSE_TYPE_H_
-
-namespace content {
-
-// Response handling type, used in URL {request,loader} jobs.
-enum class ServiceWorkerResponseType {
-  NOT_DETERMINED,
-  FAIL_DUE_TO_LOST_CONTROLLER,
-  FALLBACK_TO_NETWORK,
-  FALLBACK_TO_RENDERER,  // Use this when falling back with CORS check
-  FORWARD_TO_SERVICE_WORKER
-};
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_RESPONSE_TYPE_H_
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 24ea4bd..3cc697a 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -302,6 +302,24 @@
   return a->frame_tree_node()->depth() < b->frame_tree_node()->depth();
 }
 
+bool AreValidRegisterProtocolHandlerArguments(const std::string& protocol,
+                                              const GURL& url,
+                                              const url::Origin& origin) {
+  ChildProcessSecurityPolicyImpl* policy =
+      ChildProcessSecurityPolicyImpl::GetInstance();
+  if (policy->IsPseudoScheme(protocol))
+    return false;
+
+  if (!url.SchemeIsHTTPOrHTTPS())
+    return false;
+
+  url::Origin url_origin = url::Origin::Create(url);
+  if (!url_origin.IsSameOriginWith(origin))
+    return false;
+
+  return true;
+}
+
 }  // namespace
 
 std::unique_ptr<WebContents> WebContents::Create(
@@ -4832,10 +4850,12 @@
   if (!delegate_)
     return;
 
-  ChildProcessSecurityPolicyImpl* policy =
-      ChildProcessSecurityPolicyImpl::GetInstance();
-  if (policy->IsPseudoScheme(protocol))
+  if (!AreValidRegisterProtocolHandlerArguments(
+          protocol, url, source->GetLastCommittedOrigin())) {
+    ReceivedBadMessage(source->GetProcess(),
+                       bad_message::REGISTER_PROTOCOL_HANDLER_INVALID_URL);
     return;
+  }
 
   delegate_->RegisterProtocolHandler(this, protocol, url, user_gesture);
 }
@@ -4849,10 +4869,12 @@
   if (!delegate_)
     return;
 
-  ChildProcessSecurityPolicyImpl* policy =
-      ChildProcessSecurityPolicyImpl::GetInstance();
-  if (policy->IsPseudoScheme(protocol))
+  if (!AreValidRegisterProtocolHandlerArguments(
+          protocol, url, source->GetLastCommittedOrigin())) {
+    ReceivedBadMessage(source->GetProcess(),
+                       bad_message::REGISTER_PROTOCOL_HANDLER_INVALID_URL);
     return;
+  }
 
   delegate_->UnregisterProtocolHandler(this, protocol, url, user_gesture);
 }
diff --git a/content/browser/web_contents/web_contents_impl_unittest.cc b/content/browser/web_contents/web_contents_impl_unittest.cc
index 07a2e13..387385e 100644
--- a/content/browser/web_contents/web_contents_impl_unittest.cc
+++ b/content/browser/web_contents/web_contents_impl_unittest.cc
@@ -3343,6 +3343,8 @@
  public:
   MOCK_METHOD2(HandleContextMenu,
                bool(RenderFrameHost*, const ContextMenuParams&));
+  MOCK_METHOD4(RegisterProtocolHandler,
+               void(WebContents*, const std::string&, const GURL&, bool));
 };
 
 }  // namespace
@@ -3361,4 +3363,86 @@
   contents()->SetDelegate(nullptr);
 }
 
+TEST_F(WebContentsImplTest, RegisterProtocolHandlerDifferentOrigin) {
+  MockWebContentsDelegate delegate;
+  contents()->SetDelegate(&delegate);
+
+  GURL url("https://www.google.com");
+  GURL handler_url1("https://www.google.com/handler/%s");
+  GURL handler_url2("https://www.example.com/handler/%s");
+
+  contents()->NavigateAndCommit(url);
+
+  // Only the first call to RegisterProtocolHandler should register because the
+  // other call has a handler from a different origin.
+  EXPECT_CALL(delegate,
+              RegisterProtocolHandler(contents(), "mailto", handler_url1, true))
+      .Times(1);
+
+  {
+    FrameHostMsg_RegisterProtocolHandler message(
+        main_test_rfh()->GetRoutingID(), "mailto", handler_url1,
+        base::string16(), /*user_gesture=*/true);
+    contents()->OnMessageReceived(main_test_rfh(), message);
+  }
+
+  {
+    FrameHostMsg_RegisterProtocolHandler message(
+        main_test_rfh()->GetRoutingID(), "mailto", handler_url2,
+        base::string16(), /*user_gesture=*/true);
+    contents()->OnMessageReceived(main_test_rfh(), message);
+  }
+
+  contents()->SetDelegate(nullptr);
+}
+
+TEST_F(WebContentsImplTest, RegisterProtocolHandlerDataURL) {
+  MockWebContentsDelegate delegate;
+  contents()->SetDelegate(&delegate);
+
+  GURL data("data:text/html,<html><body><b>hello world</b></body></html>");
+  GURL data_handler(data.spec() + "%s");
+
+  contents()->NavigateAndCommit(data);
+
+  // Data URLs should fail.
+  EXPECT_CALL(delegate,
+              RegisterProtocolHandler(contents(), "mailto", data_handler, true))
+      .Times(0);
+
+  {
+    FrameHostMsg_RegisterProtocolHandler message(
+        main_test_rfh()->GetRoutingID(), "mailto", data_handler,
+        base::string16(), /*user_gesture=*/true);
+    contents()->OnMessageReceived(main_test_rfh(), message);
+  }
+
+  contents()->SetDelegate(nullptr);
+}
+
+TEST_F(WebContentsImplTest, RegisterProtocolHandlerBlobURL) {
+  MockWebContentsDelegate delegate;
+  contents()->SetDelegate(&delegate);
+
+  GURL url("https://www.google.com");
+  GURL blob_handler(
+      "blob:https://www.google.com/f2d8c47d-17d0-4bf5-8f0a-76e42cbed3bf/%s");
+
+  contents()->NavigateAndCommit(url);
+
+  // Blob URLs should fail.
+  EXPECT_CALL(delegate,
+              RegisterProtocolHandler(contents(), "mailto", blob_handler, true))
+      .Times(0);
+
+  {
+    FrameHostMsg_RegisterProtocolHandler message(
+        main_test_rfh()->GetRoutingID(), "mailto", blob_handler,
+        base::string16(), /*user_gesture=*/true);
+    contents()->OnMessageReceived(main_test_rfh(), message);
+  }
+
+  contents()->SetDelegate(nullptr);
+}
+
 }  // namespace content
diff --git a/content/child/child_thread_impl.cc b/content/child/child_thread_impl.cc
index 979c895..31ee3932 100644
--- a/content/child/child_thread_impl.cc
+++ b/content/child/child_thread_impl.cc
@@ -22,7 +22,6 @@
 #include "base/message_loop/timer_slack.h"
 #include "base/metrics/field_trial.h"
 #include "base/metrics/histogram_macros.h"
-#include "base/optional.h"
 #include "base/power_monitor/power_monitor.h"
 #include "base/process/process.h"
 #include "base/process/process_handle.h"
@@ -179,7 +178,7 @@
 
 #endif  // OS(POSIX)
 
-base::Optional<mojo::IncomingInvitation> InitializeMojoIPCChannel() {
+mojo::IncomingInvitation InitializeMojoIPCChannel() {
   TRACE_EVENT0("startup", "InitializeMojoIPCChannel");
   mojo::PlatformChannelEndpoint endpoint;
 #if defined(OS_WIN)
@@ -200,12 +199,12 @@
   auto* client = base::MachPortRendezvousClient::GetInstance();
   if (!client) {
     LOG(ERROR) << "Mach rendezvous failed.";
-    return base::nullopt;
+    return {};
   }
   auto receive = client->TakeReceiveRight('mojo');
   if (!receive.is_valid()) {
     LOG(ERROR) << "Invalid PlatformChannel receive right";
-    return base::nullopt;
+    return {};
   }
   endpoint =
       mojo::PlatformChannelEndpoint(mojo::PlatformHandle(std::move(receive)));
@@ -214,10 +213,6 @@
       base::ScopedFD(base::GlobalDescriptors::GetInstance()->Get(
           service_manager::kMojoIPCChannel))));
 #endif
-  // Mojo isn't supported on all child process types.
-  // TODO(crbug.com/604282): Support Mojo in the remaining processes.
-  if (!endpoint.is_valid())
-    return base::nullopt;
 
   return mojo::IncomingInvitation::Accept(std::move(endpoint));
 }
@@ -425,15 +420,14 @@
   if (!IsInBrowserProcess()) {
     mojo_ipc_support_.reset(new mojo::core::ScopedIPCSupport(
         GetIOTaskRunner(), mojo::core::ScopedIPCSupport::ShutdownPolicy::FAST));
-    base::Optional<mojo::IncomingInvitation> invitation =
-        InitializeMojoIPCChannel();
+    mojo::IncomingInvitation invitation = InitializeMojoIPCChannel();
 
     std::string service_request_token =
         base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
             service_manager::switches::kServiceRequestChannelToken);
-    if (!service_request_token.empty() && invitation) {
+    if (!service_request_token.empty()) {
       service_request_pipe =
-          invitation->ExtractMessagePipe(service_request_token);
+          invitation.ExtractMessagePipe(service_request_token);
     }
   } else {
     service_request_pipe = options.mojo_invitation->ExtractMessagePipe(
diff --git a/content/public/browser/child_process_security_policy.h b/content/public/browser/child_process_security_policy.h
index e413d5b..bc256489 100644
--- a/content/public/browser/child_process_security_policy.h
+++ b/content/public/browser/child_process_security_policy.h
@@ -293,11 +293,11 @@
 
   // Semantically identical to the above, but accepts a string of comma
   // separated origins. |origins_to_add| can contain both wildcard and
-  // non-wildcard origins, e.g. "https://**.foo.com,https://bar.com".
+  // non-wildcard origins, e.g. "https://[*.]foo.com,https://bar.com".
   //
   // Wildcard origins provide a way to treat all subdomains under the specified
   // host and scheme as distinct isolated origins. For example,
-  // https://**.foo.com would isolate https://foo.com, https://bar.foo.com and
+  // https://[*.]foo.com would isolate https://foo.com, https://bar.foo.com and
   // https://qux.baz.foo.com all in separate processes. Adding a wildcard origin
   // implies breaking document.domain for all of its subdomains.
   //
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index e38f65d..9b8fcc28 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -31,10 +31,6 @@
     "AllowSignedHTTPExchangeCertsWithoutExtension",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Accounts for resource padding when reporting AppCache usage in the quota API.
-const base::Feature kAppCacheIncludePaddingInQuota{
-    "AppCacheIncludePaddingInQuota", base::FEATURE_ENABLED_BY_DEFAULT};
-
 // Creates audio output and input streams using the audio service.
 const base::Feature kAudioServiceAudioStreams{"AudioServiceAudioStreams",
                                               base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 1766d6f3..9c15a01f 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -21,7 +21,6 @@
     kAllowContentInitiatedDataUrlNavigations;
 CONTENT_EXPORT extern const base::Feature
     kAllowSignedHTTPExchangeCertsWithoutExtension;
-CONTENT_EXPORT extern const base::Feature kAppCacheIncludePaddingInQuota;
 CONTENT_EXPORT extern const base::Feature kAudioServiceAudioStreams;
 CONTENT_EXPORT extern const base::Feature kAudioServiceLaunchOnStartup;
 CONTENT_EXPORT extern const base::Feature kAudioServiceOutOfProcess;
diff --git a/content/test/data/accessibility/event/aria-combo-box-delay-show-list-expected-win.txt b/content/test/data/accessibility/event/aria-combo-box-delay-show-list-expected-win.txt
index 55aef2f..c00b1f6 100644
--- a/content/test/data/accessibility/event/aria-combo-box-delay-show-list-expected-win.txt
+++ b/content/test/data/accessibility/event/aria-combo-box-delay-show-list-expected-win.txt
@@ -1,5 +1,5 @@
 EVENT_OBJECT_FOCUS on <li#op1> role=ROLE_SYSTEM_LISTITEM name="Apple" SELECTED,FOCUSED,FOCUSABLE,SELECTABLE PosInSet=1 SetSize=1
-EVENT_OBJECT_HIDE on <body> role=BODY
+EVENT_OBJECT_HIDE on <body> role=BODY INVISIBLE
 IA2_EVENT_ACTIVE_DESCENDANT_CHANGED on <input> role=ROLE_SYSTEM_COMBOBOX EXPANDED,FOCUSABLE,HASPOPUP IA2_STATE_EDITABLE,IA2_STATE_SELECTABLE_TEXT,IA2_STATE_SINGLE_LINE,IA2_STATE_SUPPORTS_AUTOCOMPLETION
 IA2_EVENT_TEXT_INSERTED on <#document> role=ROLE_SYSTEM_DOCUMENT value~=[doc-url] FOCUSABLE new_text={'<obj><obj>' start=0 end=2}
 IA2_EVENT_TEXT_REMOVED on <#document> role=ROLE_SYSTEM_DOCUMENT value~=[doc-url] FOCUSABLE old_text={'<obj>' start=0 end=1}
diff --git a/content/test/data/accessibility/event/aria-hidden-changed-expected-uia-win.txt b/content/test/data/accessibility/event/aria-hidden-changed-expected-uia-win.txt
new file mode 100644
index 0000000..073c1f8
--- /dev/null
+++ b/content/test/data/accessibility/event/aria-hidden-changed-expected-uia-win.txt
@@ -0,0 +1,3 @@
+AriaProperties changed on role=heading, name=Item2
+AriaProperties changed on role=heading, name=Item3
+AriaProperties changed on role=heading, name=Item4
diff --git a/content/test/data/accessibility/event/aria-hidden-changed.html b/content/test/data/accessibility/event/aria-hidden-changed.html
new file mode 100644
index 0000000..dd97f6c
--- /dev/null
+++ b/content/test/data/accessibility/event/aria-hidden-changed.html
@@ -0,0 +1,28 @@
+<!--
+@UIA-WIN-DENY:*
+@UIA-WIN-ALLOW:AriaProperties*
+-->
+<!DOCTYPE html>
+<html>
+<body>
+  <h4 id="d1">Item1</h4>
+  <h4 id="d2" aria-hidden="true">Item2</h4>
+  <h4 id="d3" aria-hidden="false">Item3</h4>
+  <h4 id="d4" aria-hidden="true">Item4</h4>
+  <script>
+    function go() {
+      // Set aria-hidden from [removed]->false; should not fire an event.
+      document.getElementById('d1').setAttribute('aria-hidden', 'false');
+
+      // Set aria-hidden from true->false; should fire an event.
+      document.getElementById('d2').setAttribute('aria-hidden', 'false');
+
+      // Set aria-hidden from false->true; should fire an event.
+      document.getElementById('d3').setAttribute('aria-hidden', 'true');
+
+      // Set aria-hidden from true->[removed]; should fire an event.
+      document.getElementById('d4').removeAttribute('aria-hidden');
+    }
+  </script>
+</body>
+</html>
diff --git a/content/test/data/accessibility/event/css-visibility-expected-win.txt b/content/test/data/accessibility/event/css-visibility-expected-win.txt
index a267f80..d522a9a 100644
--- a/content/test/data/accessibility/event/css-visibility-expected-win.txt
+++ b/content/test/data/accessibility/event/css-visibility-expected-win.txt
@@ -1,4 +1,4 @@
-EVENT_OBJECT_HIDE on <div.a> role=DIV level=2
+EVENT_OBJECT_HIDE on <div.a> role=DIV INVISIBLE level=2
 EVENT_OBJECT_REORDER on <div> role=ROLE_SYSTEM_TOOLBAR IA2_STATE_HORIZONTAL
 EVENT_OBJECT_SHOW on <div.b> role=ROLE_SYSTEM_GROUPING name="Banner"
 IA2_EVENT_TEXT_INSERTED on <div> role=ROLE_SYSTEM_TOOLBAR IA2_STATE_HORIZONTAL new_text={'<obj>' start=0 end=1}
diff --git a/content/test/data/accessibility/event/expanded-change-expected-uia-win.txt b/content/test/data/accessibility/event/expanded-change-expected-uia-win.txt
index be21082..2bfcb1d 100644
--- a/content/test/data/accessibility/event/expanded-change-expected-uia-win.txt
+++ b/content/test/data/accessibility/event/expanded-change-expected-uia-win.txt
@@ -1,2 +1,7 @@
 AriaProperties changed on role=link, name=Toggle
+AriaProperties changed on role=list
+AriaProperties changed on role=listitem
+AriaProperties changed on role=listitem
+AriaProperties changed on role=listitem
+AriaProperties changed on role=listitem
 ExpandCollapseExpandCollapseState changed on role=link, name=Toggle
diff --git a/content/test/data/accessibility/event/visibility-hidden-changed-expected-uia-win.txt b/content/test/data/accessibility/event/visibility-hidden-changed-expected-uia-win.txt
new file mode 100644
index 0000000..b9eeffe
--- /dev/null
+++ b/content/test/data/accessibility/event/visibility-hidden-changed-expected-uia-win.txt
@@ -0,0 +1,4 @@
+AriaProperties changed on role=heading
+AriaProperties changed on role=heading
+AriaProperties changed on role=heading, name=Item2
+AriaProperties changed on role=heading, name=Item4
diff --git a/content/test/data/accessibility/event/visibility-hidden-changed.html b/content/test/data/accessibility/event/visibility-hidden-changed.html
new file mode 100644
index 0000000..5084bb5
--- /dev/null
+++ b/content/test/data/accessibility/event/visibility-hidden-changed.html
@@ -0,0 +1,28 @@
+<!--
+@UIA-WIN-DENY:*
+@UIA-WIN-ALLOW:AriaProperties*
+-->
+<!DOCTYPE html>
+<html>
+<body>
+  <h4 id="d1">Item1</h4>
+  <h4 id="d2" style="visibility: hidden">Item2</h4>
+  <h4 id="d3" style="visibility: visible">Item3</h4>
+  <h4 id="d4" style="visibility: hidden">Item4</h4>
+  <script>
+    function go() {
+      // Set style from [none]->visibility: hidden; should fire an event.
+      document.getElementById('d1').setAttribute('style', 'visibility: hidden');
+
+      // Set style from visibility: hidden->visibility: visible; should fire an event.
+      document.getElementById('d2').setAttribute('style', 'visibility: visible');
+
+      // Set style from visibility: visible->visibility: hidden; should fire an event.
+      document.getElementById('d3').setAttribute('style', 'visibility: hidden');
+
+      // Remove style visibility; should fire an event.
+      document.getElementById('d4').removeAttribute('style');
+    }
+  </script>
+</body>
+</html>
diff --git a/content/test/gpu/gpu_tests/gpu_helper.py b/content/test/gpu/gpu_tests/gpu_helper.py
index d5a6d422..6123985 100644
--- a/content/test/gpu/gpu_tests/gpu_helper.py
+++ b/content/test/gpu/gpu_tests/gpu_helper.py
@@ -111,8 +111,19 @@
     for o in extra_browser_args:
       if "UseSkiaRenderer" in o:
         return 'skia-renderer'
+      if "--disable-vulkan-fallback-to-gl-for-testing" in o:
+        return 'skia-renderer'
   return 'no-skia-renderer'
 
+# Used to parse additional options sent to the browser instance via
+# '--extra-browser-args', looking for '--use-vulkan='.
+def GetVulkan(extra_browser_args):
+  if extra_browser_args:
+    for o in extra_browser_args:
+      if "--use-vulkan=" in o:
+        return 'use-vulkan'
+  return 'no-use-vulkan'
+
 # used by unittests to create a mock arguments object
 def GetMockArgs(is_asan=False, webgl_version='1.0.0'):
   args = mock.MagicMock()
diff --git a/content/test/gpu/gpu_tests/gpu_integration_test.py b/content/test/gpu/gpu_tests/gpu_integration_test.py
index a38886bf..5f0e15b7a 100644
--- a/content/test/gpu/gpu_tests/gpu_integration_test.py
+++ b/content/test/gpu/gpu_tests/gpu_integration_test.py
@@ -315,6 +315,9 @@
       skia_renderer = gpu_helper.GetSkiaRenderer(\
           browser._browser_backend.browser_options.extra_browser_args)
       tags.extend([skia_renderer])
+      use_vulkan = gpu_helper.GetVulkan(\
+          browser._browser_backend.browser_options.extra_browser_args)
+      tags.extend([use_vulkan])
     return tags
 
   @classmethod
diff --git a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
index 2229070..d193d56 100644
--- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -1,6 +1,7 @@
 # tags: [ android chromeos highsierra linux mac mojave win ]
 # tags: [ android-chromium android-webview-instrumentation debug ]
 # tags: [ skia-renderer no-skia-renderer ]
+# tags: [ use-vulkan no-use-vulkan ]
 # tags: [ amd amd-0x6613 amd-0x679e amd-0x6821 intel intel-0xa2e intel-0x5912
 #         nvidia nvidia-0xfe9 qualcomm-adreno-(tm)-330 qualcomm-adreno-(tm)-418
 #         qualcomm-adreno-(tm)-420 qualcomm-adreno-(tm)-430
@@ -194,5 +195,27 @@
 # Fails when the browser feature SkiaRenderer is enabled
 crbug.com/976370 [ skia-renderer ] Pixel_CSS3DBlueBox [ Skip ]
 
+# Fails when the browser features SkiaRenderer & Vulkan are enabled on Android
+crbug.com/969864 [ android skia-renderer use-vulkan ] Pixel_2DCanvasWebGL [ Failure ]
+crbug.com/969864 [ android skia-renderer use-vulkan ] Pixel_BackgroundImage [ Skip ]
+crbug.com/969864 [ android skia-renderer use-vulkan ] Pixel_Canvas2DRedBox [ Skip ]
+crbug.com/969864 [ android skia-renderer use-vulkan ] Pixel_Canvas2DUntagged [ Skip ]
+crbug.com/969864 [ android skia-renderer use-vulkan ] Pixel_CanvasDisplayLinearRGBAccelerated2D [ Skip ]
+crbug.com/969864 [ android skia-renderer use-vulkan ] Pixel_OffscreenCanvas2DResizeOnWorker [ Skip ]
+crbug.com/969864 [ android skia-renderer use-vulkan ] Pixel_OffscreenCanvasAccelerated2D [ Skip ]
+crbug.com/969864 [ android skia-renderer use-vulkan ] Pixel_OffscreenCanvasAccelerated2DWorker [ Skip ]
+crbug.com/969864 [ android skia-renderer use-vulkan ] Pixel_OffscreenCanvasTransferAfterStyleResize [ Skip ]
+crbug.com/969864 [ android skia-renderer use-vulkan ] Pixel_OffscreenCanvasTransferBeforeStyleResize [ Skip ]
+crbug.com/969864 [ android skia-renderer use-vulkan ] Pixel_OffscreenCanvasTransferToImageBitmap [ Skip ]
+crbug.com/969864 [ android skia-renderer use-vulkan ] Pixel_OffscreenCanvasTransferToImageBitmapWorker [ Skip ]
+crbug.com/969864 [ android skia-renderer use-vulkan ] Pixel_OffscreenCanvasWebGLDefault [ Skip ]
+crbug.com/969864 [ android skia-renderer use-vulkan ] Pixel_OffscreenCanvasWebGLDefaultWorker [ Skip ]
+crbug.com/969864 [ android skia-renderer use-vulkan ] Pixel_OffscreenCanvasWebglResizeOnWorker [ Skip ]
+crbug.com/969864 [ android skia-renderer use-vulkan ] Pixel_WebGLGreenTriangle_AA_Alpha [ Skip ]
+crbug.com/969864 [ android skia-renderer use-vulkan ] Pixel_WebGLGreenTriangle_AA_NoAlpha [ Skip ]
+crbug.com/969864 [ android skia-renderer use-vulkan ] Pixel_WebGLGreenTriangle_NoAA_Alpha [ Skip ]
+crbug.com/969864 [ android skia-renderer use-vulkan ] Pixel_WebGLGreenTriangle_NoAA_NoAlpha [ Skip ]
+crbug.com/969864 [ android skia-renderer use-vulkan ] Pixel_WebGLTransparentGreenTriangle_NoAlpha_ImplicitClear [ Skip ]
+
 # Produces blank images on Intel HD 630 w/ Mesa 19.0.2
 crbug.com/976861 [ linux intel-0x5912 ] Pixel_OffscreenCanvasTransferToImageBitmap [ Skip ]
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
index 9454cee..80a8ef4 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
@@ -451,7 +451,8 @@
 crbug.com/352645 [ android android-webview-instrumentation no-angle ] conformance/textures/misc/texture-npot-video.html [ Skip ]
 
 # These video tests appear to be flaky.
-crbug.com/907512 [ android release ] conformance/textures/video/tex-2d-rgba-rgba-unsigned_short_5_5_5_1.html [ RetryOnFailure ]
+crbug.com/834933 [ android android-chromium ] conformance/textures/video/tex-2d-rgba-rgba-unsigned_short_4_4_4_4.html [ RetryOnFailure ]
+crbug.com/907512 [ android android-chromium ] conformance/textures/video/tex-2d-rgba-rgba-unsigned_short_5_5_5_1.html [ RetryOnFailure ]
 crbug.com/733599 [ android ] conformance/textures/video/tex-2d-alpha-alpha-unsigned_byte.html [ RetryOnFailure ]
 crbug.com/733599 [ android no-angle ] conformance/textures/video/tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html [ RetryOnFailure ]
 crbug.com/733599 [ android no-angle ] conformance/textures/video/tex-2d-luminance-luminance-unsigned_byte.html [ RetryOnFailure ]
@@ -569,7 +570,6 @@
 crbug.com/891456 [ android nvidia ] conformance/textures/image_bitmap_from_video/tex-2d-rgb-rgb-unsigned_short_5_6_5.html [ RetryOnFailure ]
 crbug.com/891456 [ android nvidia ] conformance/textures/image_bitmap_from_video/tex-2d-rgba-rgba-unsigned_byte.html [ RetryOnFailure ]
 crbug.com/891456 [ android nvidia ] conformance/textures/image_bitmap_from_video/tex-2d-rgba-rgba-unsigned_short_5_5_5_1.html [ RetryOnFailure ]
-crbug.com/891456 [ android nvidia ] conformance/textures/video/tex-2d-rgba-rgba-unsigned_short_4_4_4_4.html [ RetryOnFailure ]
 
 # Flaky timeout on android_n5x_swarming_rel and
 # android-marshmallow-arm64-rel.
diff --git a/docs/README.md b/docs/README.md
index ed8f04fd..e1f2150 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -146,6 +146,8 @@
 *   [WebUI Explainer](webui_explainer.md) - An explanation of C++ and JavaScript
     infrastructural code for Chrome UIs implemented with web technologies (i.e.
     chrome:// URLs).
+*   [Watchlists](infra/watchlists.md) - Use watchlists to get notified of CLs
+    you are interested in.
 
 ### Testing
 *   [Running and Debugging Web Tests](testing/web_tests.md)
diff --git a/docs/contributing.md b/docs/contributing.md
index eb214d21..cf5924b 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -279,6 +279,8 @@
 
 ## Tips
 
+### Review etiquette
+
 During the lifetime of a review, you may want to rebase your change onto a newer
 source revision to minimize merge conflicts. The reviewer-friendly way to do
 this is to first address any unresolved comments and upload those changes as a
@@ -292,6 +294,13 @@
 read these guidelines on [minimizing review lag][review-lag] and take them in
 consideration both when writing reviews and responding to review feedback.
 
+### Watchlists
+
+If you would like to be notified about changes to a set of files covering a
+topic or an area of Chromium, you may use the [watchlists][watchlist-doc]
+feature in order to receive email notifications.
+
+
 [//]: # (the reference link section should be alphabetically sorted)
 [checkout-and-build]: https://chromium.googlesource.com/chromium/src/+/master/docs/#checking-out-and-building
 [cl-footer-syntax]: https://dev.chromium.org/developers/contributing-code/-bug-syntax
@@ -320,3 +329,4 @@
 [skia-dev-guide]: https://skia.org/dev/contrib
 [try-job-access]: https://www.chromium.org/getting-involved/become-a-committer#TOC-Try-job-access
 [v8-dev-guide]: https://v8.dev/docs
+[watchlist-doc]: infra/watchlists.md
diff --git a/docs/infra/watchlists.md b/docs/infra/watchlists.md
new file mode 100644
index 0000000..6542a595
--- /dev/null
+++ b/docs/infra/watchlists.md
@@ -0,0 +1,76 @@
+## What are watchlists?
+
+A watchlist is a mechanism that allows a developer (a "watcher") to watch over
+portions of code that the watcher is interested in. A watcher will be cc-ed on
+changes that modify that portion of code, thereby giving that watcher an
+opportunity to make comments on codereview.chromium.org even before the change
+is committed.
+
+**Important :** As watchlists are processed locally when uploading using `git cl
+upload` it is not possible to define watchlists for Gerrit CLs generated by
+tools such as the [Skia autoroller][skia-autoroller] (see
+[crbug.com/982198][crbug-982198]). In order to be notified of autoroller
+commits, find the [corresponding config file][roller-configs] and add email
+addresses of people to be notified of roller commits in the `sheriff` field of
+the autoroller config.
+
+## Syntax
+
+Watchlists are defined using a `WATCHLISTS` file, which resides at the root of a
+repository. A typical `WATCHLISTS` file looks like:
+
+```
+{
+  'WATCHLIST_DEFINITIONS': {
+    'valgrind': {
+      'filepath': 'tools/valgrind/',
+    },
+    'mac': {
+      'filepath': 'cocoa|\.mm$|(_mac|_posix)\.(cc|h)$',
+    },
+  },
+  'WATCHLISTS': {
+    'valgrind': ['nirnimesh@chromium.org', 'dank@chromium.org'],
+  },
+}
+```
+
+In this case, watchlists named `valgrind` and `mac` are defined in
+`WATCHLIST_DEFINITIONS` and their corresponding watchers declared in
+`WATCHLISTS`.
+
+In the example above, whenever a new changeset is created that refers to any
+file in `tools/valgrind/`, the `'valgrind'` watchlist will be triggered and
+`nirnimesh@chromium.org` & `dank@chromium.org` will be cc-ed to the changeset
+for review. A regular expression can be used as the matching pattern. Matches
+are determined using python's `re.search()` function call, so matching `A_WORD`
+is the same as matching `.*A_WORD.*`.
+
+Each name in `WATCHLISTS` must be defined first in `WATCHLIST_DEFINITIONS`.
+
+Watchlist processing takes place during `git-cl upload` and are non-binding;
+that is, an approval from that watcher is not needed for commit. It merely gives
+the watcher an opportunity to make comments, if any.
+
+## Editing Watchlists
+
+You create new watchlists or add yourself to existing watchlists by editing the
+WATCHLISTS file at the base of the repository.
+
+It's advisable to run `watchlists.py` to verify that your new rules work.
+
+Example (from src):
+
+```
+python ../depot_tools/watchlists.py PATH/TO/FILE1 PATH/TO/FILE2 ....
+```
+
+
+## Override
+
+To override watchlist processing, use `git cl upload` with `--bypass-hooks`.
+
+[//]: # (the reference link section should be alphabetically sorted)
+[skia-autoroller]: https://skia.googlesource.com/buildbot/+/master/autoroll/README.md
+[crbug-982198]: https://bugs.chromium.org/p/chromium/issues/detail?id=982198
+[roller-configs]: https://skia.googlesource.com/buildbot/+/master/autoroll/config
diff --git a/docs/media/gpu/video_decoder_test_usage.md b/docs/media/gpu/video_decoder_test_usage.md
index 6d091737..bfaba76 100644
--- a/docs/media/gpu/video_decoder_test_usage.md
+++ b/docs/media/gpu/video_decoder_test_usage.md
@@ -66,7 +66,7 @@
     --disable_validator  disable frame validation, useful on old
                          platforms that don't support import mode.
     --output_frames      write all decoded video frames to the
-                         "video_frames" folder.
+                         "<testname>" folder.
     --output_folder      overwrite the default output folder used when
                          "--output_frames" is specified.
     --use_vd             use the new VD-based video decoders, instead of
diff --git a/extensions/browser/content_verifier.cc b/extensions/browser/content_verifier.cc
index ea89a51..0ae23ed 100644
--- a/extensions/browser/content_verifier.cc
+++ b/extensions/browser/content_verifier.cc
@@ -417,7 +417,7 @@
   hash_helper_.reset();
 }
 
-ContentVerifyJob* ContentVerifier::CreateJobFor(
+scoped_refptr<ContentVerifyJob> ContentVerifier::CreateAndStartJobFor(
     const std::string& extension_id,
     const base::FilePath& extension_root,
     const base::FilePath& relative_path) {
@@ -446,9 +446,11 @@
 
   // TODO(asargent) - we can probably get some good performance wins by having
   // a cache of ContentHashReader's that we hold onto past the end of each job.
-  return new ContentVerifyJob(
+  scoped_refptr<ContentVerifyJob> job = base::MakeRefCounted<ContentVerifyJob>(
       extension_id, data->version, extension_root, normalized_unix_path,
       base::BindOnce(&ContentVerifier::VerifyFailed, this, extension_id));
+  job->Start(this);
+  return job;
 }
 
 void ContentVerifier::GetContentHash(
diff --git a/extensions/browser/content_verifier.h b/extensions/browser/content_verifier.h
index c2b5b91..1f7c893b 100644
--- a/extensions/browser/content_verifier.h
+++ b/extensions/browser/content_verifier.h
@@ -52,11 +52,13 @@
   void Start();
   void Shutdown();
 
-  // Call this before reading a file within an extension. The caller owns the
-  // returned job.
-  ContentVerifyJob* CreateJobFor(const std::string& extension_id,
-                                 const base::FilePath& extension_root,
-                                 const base::FilePath& relative_path);
+  // Call this before reading a file within an extension. Returns and starts a
+  // content verify job if the specified resource requires content verification,
+  // otherwise returns nullptr.
+  scoped_refptr<ContentVerifyJob> CreateAndStartJobFor(
+      const std::string& extension_id,
+      const base::FilePath& extension_root,
+      const base::FilePath& relative_path);
 
   // ExtensionRegistryObserver interface
   void OnExtensionLoaded(content::BrowserContext* browser_context,
diff --git a/extensions/browser/extension_protocols.cc b/extensions/browser/extension_protocols.cc
index 01c94f01..a9cf702 100644
--- a/extensions/browser/extension_protocols.cc
+++ b/extensions/browser/extension_protocols.cc
@@ -573,11 +573,9 @@
       scoped_refptr<net::HttpResponseHeaders> response_headers) {
     scoped_refptr<ContentVerifyJob> verify_job;
     if (content_verifier) {
-      verify_job = content_verifier->CreateJobFor(resource.extension_id(),
-                                                  resource.extension_root(),
-                                                  resource.relative_path());
-      if (verify_job)
-        verify_job->Start(content_verifier.get());
+      verify_job = content_verifier->CreateAndStartJobFor(
+          resource.extension_id(), resource.extension_root(),
+          resource.relative_path());
     }
 
     content::CreateFileURLLoader(
diff --git a/extensions/browser/extension_user_script_loader.cc b/extensions/browser/extension_user_script_loader.cc
index 9d9e9b1b..bd9ab82 100644
--- a/extensions/browser/extension_user_script_loader.cc
+++ b/extensions/browser/extension_user_script_loader.cc
@@ -68,11 +68,9 @@
 void VerifyContent(const VerifyContentInfo& info) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(info.verifier);
-  ContentVerifier* verifier = info.verifier.get();
-  scoped_refptr<ContentVerifyJob> job(verifier->CreateJobFor(
+  scoped_refptr<ContentVerifyJob> job(info.verifier->CreateAndStartJobFor(
       info.extension_id, info.extension_root, info.relative_path));
   if (job.get()) {
-    job->Start(verifier);
     job->BytesRead(info.content.size(), info.content.data());
     job->DoneReading();
   }
diff --git a/gpu/BUILD.gn b/gpu/BUILD.gn
index d6aefe5..a2b2219 100644
--- a/gpu/BUILD.gn
+++ b/gpu/BUILD.gn
@@ -545,6 +545,7 @@
     "ipc/service/gpu_channel_test_common.cc",
     "ipc/service/gpu_channel_test_common.h",
     "ipc/service/gpu_channel_unittest.cc",
+    "ipc/service/gpu_watchdog_thread_unittest.cc",
   ]
 
   if (is_chromeos) {
diff --git a/gpu/ipc/service/gpu_watchdog_thread_unittest.cc b/gpu/ipc/service/gpu_watchdog_thread_unittest.cc
new file mode 100644
index 0000000..d27ed1f4
--- /dev/null
+++ b/gpu/ipc/service/gpu_watchdog_thread_unittest.cc
@@ -0,0 +1,107 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "gpu/ipc/service/gpu_watchdog_thread_v2.h"
+
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gpu {
+
+namespace {
+constexpr auto kGpuWatchdogTimeout = base::TimeDelta::FromMilliseconds(1000);
+
+// This task will run for duration_ms milliseconds.
+void SimpleTask(base::TimeDelta duration) {
+  base::PlatformThread::Sleep(duration);
+}
+}  // namespace
+
+class GpuWatchdogTest : public testing::Test {
+ public:
+  GpuWatchdogTest() {}
+
+  void LongTaskWithReportProgress(base::TimeDelta duration,
+                                  base::TimeDelta report_delta);
+
+  // Implements testing::Test
+  void SetUp() override;
+
+ protected:
+  ~GpuWatchdogTest() override = default;
+  base::MessageLoop main_loop;
+  base::RunLoop run_loop;
+  std::unique_ptr<gpu::GpuWatchdogThread> watchdog_thread_;
+};
+
+void GpuWatchdogTest::SetUp() {
+  ASSERT_TRUE(base::ThreadTaskRunnerHandle::IsSet());
+  ASSERT_TRUE(base::MessageLoopCurrent::IsSet());
+
+  // Set watchdog timeout to 1000 milliseconds
+  watchdog_thread_ = gpu::GpuWatchdogThreadImplV2::Create(
+      /*start_backgrounded*/ false,
+      /*timeout*/ kGpuWatchdogTimeout,
+      /*test_mode*/ true);
+}
+
+// This task will run for duration_ms milliseconds. It will also call watchdog
+// ReportProgress() every report_delta_ms milliseconds.
+void GpuWatchdogTest::LongTaskWithReportProgress(base::TimeDelta duration,
+                                                 base::TimeDelta report_delta) {
+  base::TimeTicks start = base::TimeTicks::Now();
+  base::TimeTicks end;
+
+  do {
+    base::PlatformThread::Sleep(report_delta);
+    watchdog_thread_->ReportProgress();
+    end = base::TimeTicks::Now();
+  } while (end - start <= duration);
+}
+
+// GPU Hang In Initialization
+TEST_F(GpuWatchdogTest, GpuInitializationHang) {
+  // Gpu init (2000 ms) takes longer than timeout (1000 ms).
+  SimpleTask(kGpuWatchdogTimeout + base::TimeDelta::FromMilliseconds(1000));
+
+  // Gpu hangs. OnInitComplete() is not called
+
+  bool result = watchdog_thread_->IsGpuHangDetected();
+  EXPECT_TRUE(result);
+}
+
+// Normal GPU Initialization and Running Task
+TEST_F(GpuWatchdogTest, GpuInitializationAndRunningTasks) {
+  // Assume GPU initialization takes 300 milliseconds.
+  SimpleTask(base::TimeDelta::FromMilliseconds(300));
+  watchdog_thread_->OnInitComplete();
+
+  // Start running GPU tasks. Watchdog function WillProcessTask(),
+  // DidProcessTask() and ReportProgress() are tested.
+  main_loop.task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&SimpleTask, base::TimeDelta::FromMilliseconds(500)));
+  main_loop.task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&SimpleTask, base::TimeDelta::FromMilliseconds(500)));
+
+  // This long task takes 2000 milliseconds to finish, longer than timeout.
+  // But it reports progress every 500 milliseconds
+  main_loop.task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &GpuWatchdogTest::LongTaskWithReportProgress, base::Unretained(this),
+          kGpuWatchdogTimeout + base::TimeDelta::FromMilliseconds(1000),
+          base::TimeDelta::FromMilliseconds(500)));
+
+  main_loop.task_runner()->PostTask(FROM_HERE, run_loop.QuitClosure());
+  run_loop.Run();
+
+  // Everything should be fine. No GPU hang detected.
+  bool result = watchdog_thread_->IsGpuHangDetected();
+  EXPECT_FALSE(result);
+}
+
+}  // namespace gpu
diff --git a/gpu/ipc/service/image_decode_accelerator_stub.cc b/gpu/ipc/service/image_decode_accelerator_stub.cc
index e2701764..78fe94ce 100644
--- a/gpu/ipc/service/image_decode_accelerator_stub.cc
+++ b/gpu/ipc/service/image_decode_accelerator_stub.cc
@@ -6,6 +6,7 @@
 
 #include <stddef.h>
 
+#include <algorithm>
 #include <new>
 #include <utility>
 #include <vector>
@@ -17,6 +18,8 @@
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/numerics/checked_math.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/optional.h"
 #include "base/single_thread_task_runner.h"
 #include "build/build_config.h"
 #include "gpu/command_buffer/common/constants.h"
@@ -26,6 +29,7 @@
 #include "gpu/command_buffer/common/sync_token.h"
 #include "gpu/command_buffer/service/context_group.h"
 #include "gpu/command_buffer/service/decoder_context.h"
+#include "gpu/command_buffer/service/gr_shader_cache.h"
 #include "gpu/command_buffer/service/image_factory.h"
 #include "gpu/command_buffer/service/scheduler.h"
 #include "gpu/command_buffer/service/service_transfer_cache.h"
@@ -235,13 +239,18 @@
   }
 
   std::vector<sk_sp<SkImage>> plane_sk_images;
+  base::Optional<base::ScopedClosureRunner> notify_gl_state_changed;
 #if defined(OS_CHROMEOS)
-  // Right now, we only support YUV 4:2:0 for the output of the decoder.
+  // Right now, we only support YUV 4:2:0 for the output of the decoder. Note
+  // that the we get the planes in YVU order, but we need them in YUV order to
+  // create the transfer cache entry.
   //
   // TODO(andrescj): change to gfx::BufferFormat::YUV_420 once
   // https://crrev.com/c/1573718 lands.
   DCHECK_EQ(gfx::BufferFormat::YVU_420, completed_decode->buffer_format);
   DCHECK_EQ(3u, completed_decode->handle.native_pixmap_handle.planes.size());
+  std::swap(completed_decode->handle.native_pixmap_handle.planes[1],
+            completed_decode->handle.native_pixmap_handle.planes[2]);
 
   // Calculate the dimensions of each of the planes.
   const gfx::Size y_plane_size = completed_decode->visible_size;
@@ -261,6 +270,18 @@
   }
   gfx::Size uv_plane_size = gfx::Size(uv_width, uv_height);
 
+  // We should notify the SharedContextState that we or Skia may have modified
+  // the driver's GL state. We should also notify Skia that we may have modified
+  // the graphics API state outside of Skia. We put this in a
+  // ScopedClosureRunner so that if we return early, both the SharedContextState
+  // and Skia end up in a consistent state.
+  notify_gl_state_changed.emplace(base::BindOnce(
+      [](scoped_refptr<SharedContextState> scs) {
+        scs->set_need_context_state_reset(true);
+        scs->PessimisticallyResetGrContext();
+      },
+      shared_context_state));
+
   // Create a gl::GLImage for each plane and attach it to a texture.
   plane_sk_images.resize(3u);
   for (size_t plane = 0u; plane < 3u; plane++) {
@@ -318,6 +339,9 @@
       return;
     }
 
+    // Notify Skia that we have changed the driver's GL state outside of Skia.
+    shared_context_state->PessimisticallyResetGrContext();
+
     // Create a SkImage using the texture.
     const GrBackendTexture plane_backend_texture(
         plane_size.width(), plane_size.height(), GrMipMapped::kNo,
@@ -368,22 +392,31 @@
     OnError();
     return;
   }
-  DCHECK(shared_context_state->transfer_cache());
-  if (!shared_context_state->transfer_cache()
-           ->CreateLockedHardwareDecodedImageEntry(
-               command_buffer->decoder_context()->GetRasterDecoderId(),
-               params.transfer_cache_entry_id,
-               ServiceDiscardableHandle(std::move(handle_buffer),
-                                        params.discardable_handle_shm_offset,
-                                        params.discardable_handle_shm_id),
-               shared_context_state->gr_context(), std::move(plane_sk_images),
-               completed_decode->buffer_byte_size, params.needs_mips,
-               params.target_color_space.ToSkColorSpace())) {
-    DLOG(ERROR) << "Could not create and insert the transfer cache entry";
-    OnError();
-    return;
+
+  {
+    auto* gr_shader_cache = channel_->gpu_channel_manager()->gr_shader_cache();
+    base::Optional<raster::GrShaderCache::ScopedCacheUse> cache_use;
+    if (gr_shader_cache)
+      cache_use.emplace(gr_shader_cache,
+                        base::strict_cast<int32_t>(channel_->client_id()));
+    DCHECK(shared_context_state->transfer_cache());
+    if (!shared_context_state->transfer_cache()
+             ->CreateLockedHardwareDecodedImageEntry(
+                 command_buffer->decoder_context()->GetRasterDecoderId(),
+                 params.transfer_cache_entry_id,
+                 ServiceDiscardableHandle(std::move(handle_buffer),
+                                          params.discardable_handle_shm_offset,
+                                          params.discardable_handle_shm_id),
+                 shared_context_state->gr_context(), std::move(plane_sk_images),
+                 completed_decode->buffer_byte_size, params.needs_mips,
+                 params.target_color_space.ToSkColorSpace())) {
+      DLOG(ERROR) << "Could not create and insert the transfer cache entry";
+      OnError();
+      return;
+    }
   }
-  shared_context_state->set_need_context_state_reset(true);
+  DCHECK(notify_gl_state_changed);
+  notify_gl_state_changed->RunAndReset();
 
   // All done! The decoded image can now be used for rasterization, so we can
   // release the decode sync token.
diff --git a/ios/build/bots/chromium.clang/ToTiOSDevice.json b/ios/build/bots/chromium.clang/ToTiOSDevice.json
index 7ee7722..78a5a90 100644
--- a/ios/build/bots/chromium.clang/ToTiOSDevice.json
+++ b/ios/build/bots/chromium.clang/ToTiOSDevice.json
@@ -37,87 +37,104 @@
     {
       "app": "base_unittests",
       "device type": "iPhone 6s",
-      "os": "11.4.1"
+      "xcode build version": "10e1001",
+      "os": "12.3.1"
     },
     {
       "app": "boringssl_crypto_tests",
       "device type": "iPhone 6s",
-      "os": "11.4.1"
+      "xcode build version": "10e1001",
+      "os": "12.3.1"
     },
     {
       "app": "boringssl_ssl_tests",
       "device type": "iPhone 6s",
-      "os": "11.4.1"
+      "xcode build version": "10e1001",
+      "os": "12.3.1"
     },
     {
       "app": "components_unittests",
       "device type": "iPhone 6s",
-      "os": "11.4.1"
+      "xcode build version": "10e1001",
+      "os": "12.3.1"
     },
     {
       "app": "crypto_unittests",
       "device type": "iPhone 6s",
-      "os": "11.4.1"
+      "xcode build version": "10e1001",
+      "os": "12.3.1"
     },
     {
       "app": "gfx_unittests",
       "device type": "iPhone 6s",
-      "os": "11.4.1"
+      "xcode build version": "10e1001",
+      "os": "12.3.1"
     },
     {
       "app": "google_apis_unittests",
       "device type": "iPhone 6s",
-      "os": "11.4.1"
+      "xcode build version": "10e1001",
+      "os": "12.3.1"
     },
     {
       "app": "ios_chrome_unittests",
       "device type": "iPhone 6s",
-      "os": "11.4.1"
+      "xcode build version": "10e1001",
+      "os": "12.3.1"
     },
     {
       "app": "ios_net_unittests",
       "device type": "iPhone 6s",
-      "os": "11.4.1"
+      "xcode build version": "10e1001",
+      "os": "12.3.1"
     },
     {
       "app": "ios_web_inttests",
       "device type": "iPhone 6s",
-      "os": "11.4.1"
+      "xcode build version": "10e1001",
+      "os": "12.3.1"
     },
     {
       "app": "ios_web_unittests",
       "device type": "iPhone 6s",
-      "os": "11.4.1"
+      "xcode build version": "10e1001",
+      "os": "12.3.1"
     },
     {
       "app": "ios_web_view_inttests",
       "device type": "iPhone 6s",
-      "os": "11.4.1"
+      "xcode build version": "10e1001",
+      "os": "12.3.1"
     },
     {
       "app": "net_unittests",
       "device type": "iPhone 6s",
-      "os": "11.4.1"
+      "xcode build version": "10e1001",
+      "os": "12.3.1"
     },
     {
       "app": "skia_unittests",
       "device type": "iPhone 6s",
-      "os": "11.4.1"
+      "xcode build version": "10e1001",
+      "os": "12.3.1"
     },
     {
       "app": "sql_unittests",
       "device type": "iPhone 6s",
-      "os": "11.4.1"
+      "xcode build version": "10e1001",
+      "os": "12.3.1"
     },
     {
       "app": "ui_base_unittests",
       "device type": "iPhone 6s",
-      "os": "11.4.1"
+      "xcode build version": "10e1001",
+      "os": "12.3.1"
     },
     {
       "app": "url_unittests",
       "device type": "iPhone 6s",
-      "os": "11.4.1"
+      "xcode build version": "10e1001",
+      "os": "12.3.1"
     }
   ],
   "expiration_time": 10800
diff --git a/ios/chrome/browser/sync/profile_sync_service_factory_unittest.cc b/ios/chrome/browser/sync/profile_sync_service_factory_unittest.cc
index 8c0b776..74c734de 100644
--- a/ios/chrome/browser/sync/profile_sync_service_factory_unittest.cc
+++ b/ios/chrome/browser/sync/profile_sync_service_factory_unittest.cc
@@ -43,7 +43,7 @@
  protected:
   // Returns the collection of default datatypes.
   std::vector<syncer::ModelType> DefaultDatatypes() {
-    static_assert(45 == syncer::ModelType::NUM_ENTRIES,
+    static_assert(46 == syncer::ModelType::NUM_ENTRIES,
                   "When adding a new type, you probably want to add it here as "
                   "well (assuming it is already enabled).");
 
diff --git a/ios/chrome/browser/ui/authentication/cells/account_control_item.mm b/ios/chrome/browser/ui/authentication/cells/account_control_item.mm
index 1048a4c4..9fd731b 100644
--- a/ios/chrome/browser/ui/authentication/cells/account_control_item.mm
+++ b/ios/chrome/browser/ui/authentication/cells/account_control_item.mm
@@ -7,6 +7,7 @@
 #include "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
 #import "ios/chrome/common/colors/UIColor+cr_semantic_colors.h"
+#import "ios/chrome/common/colors/semantic_color_names.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -35,9 +36,9 @@
   cell.textLabel.textColor = UIColor.cr_labelColor;
 
   cell.detailTextLabel.text = self.detailText;
-  cell.detailTextLabel.textColor = self.shouldDisplayError
-                                       ? UIColor.redColor
-                                       : UIColor.cr_secondaryLabelColor;
+  cell.detailTextLabel.textColor =
+      self.shouldDisplayError ? [UIColor colorNamed:kDestructiveTintColor]
+                              : UIColor.cr_secondaryLabelColor;
 }
 
 #pragma mark - Helper methods
diff --git a/ios/chrome/browser/ui/authentication/cells/account_control_item_unittest.mm b/ios/chrome/browser/ui/authentication/cells/account_control_item_unittest.mm
index f10dcbb4..6288a33b 100644
--- a/ios/chrome/browser/ui/authentication/cells/account_control_item_unittest.mm
+++ b/ios/chrome/browser/ui/authentication/cells/account_control_item_unittest.mm
@@ -8,6 +8,7 @@
 #import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
 #import "ios/chrome/common/colors/UIColor+cr_semantic_colors.h"
+#import "ios/chrome/common/colors/semantic_color_names.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #import "testing/gtest_mac.h"
 #include "testing/platform_test.h"
@@ -82,5 +83,6 @@
   EXPECT_NSEQ(mainText, accountCell.textLabel.text);
   EXPECT_NSEQ(detailText, accountCell.detailTextLabel.text);
   EXPECT_EQ(UITableViewCellAccessoryCheckmark, accountCell.accessoryType);
-  EXPECT_NSEQ(UIColor.redColor, accountCell.detailTextLabel.textColor);
+  EXPECT_NSEQ([UIColor colorNamed:kDestructiveTintColor],
+              accountCell.detailTextLabel.textColor);
 }
diff --git a/ios/chrome/browser/ui/authentication/cells/table_view_account_item.mm b/ios/chrome/browser/ui/authentication/cells/table_view_account_item.mm
index 31fe0bb..3c6080f7 100644
--- a/ios/chrome/browser/ui/authentication/cells/table_view_account_item.mm
+++ b/ios/chrome/browser/ui/authentication/cells/table_view_account_item.mm
@@ -9,6 +9,7 @@
 #include "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
 #import "ios/chrome/common/colors/UIColor+cr_semantic_colors.h"
+#import "ios/chrome/common/colors/semantic_color_names.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -50,8 +51,10 @@
   cell.textLabel.text = self.text;
   cell.detailTextLabel.text = self.detailText;
   if (self.shouldDisplayError) {
-    cell.errorIcon.image = [UIImage imageNamed:@"settings_error"];
-    cell.detailTextLabel.textColor = UIColor.redColor;
+    cell.errorIcon.image = [[UIImage imageNamed:@"settings_error"]
+        imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
+    cell.errorIcon.tintColor = [UIColor colorNamed:kDestructiveTintColor];
+    cell.detailTextLabel.textColor = [UIColor colorNamed:kDestructiveTintColor];
   } else {
     cell.errorIcon.image = nil;
     cell.detailTextLabel.textColor = UIColor.cr_secondaryLabelColor;
diff --git a/ios/chrome/browser/ui/authentication/unified_consent/BUILD.gn b/ios/chrome/browser/ui/authentication/unified_consent/BUILD.gn
index 028423b..61cfa0a5d 100644
--- a/ios/chrome/browser/ui/authentication/unified_consent/BUILD.gn
+++ b/ios/chrome/browser/ui/authentication/unified_consent/BUILD.gn
@@ -44,6 +44,7 @@
     "//ios/chrome/browser/ui/util",
     "//ios/chrome/browser/ui/util",
     "//ios/chrome/common",
+    "//ios/chrome/common/colors",
     "//ios/chrome/common/ui_util",
     "//ios/third_party/material_components_ios",
     "//ui/base",
diff --git a/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/BUILD.gn b/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/BUILD.gn
index 79b99548..14769fb4 100644
--- a/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/BUILD.gn
+++ b/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/BUILD.gn
@@ -62,6 +62,7 @@
     "//ios/chrome/browser/ui/util",
     "//ios/chrome/browser/ui/util",
     "//ios/chrome/common",
+    "//ios/chrome/common/colors",
     "//ios/chrome/common/ui_util",
     "//ios/third_party/material_components_ios",
     "//ui/base",
diff --git a/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_header_item.mm b/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_header_item.mm
index b224d024..50ded925 100644
--- a/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_header_item.mm
+++ b/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_header_item.mm
@@ -4,6 +4,7 @@
 
 #import "ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_header_item.h"
 
+#import "ios/chrome/common/colors/UIColor+cr_semantic_colors.h"
 #import "ios/chrome/common/ui_util/constraints_ui_util.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ui/base/l10n/l10n_util.h"
@@ -18,8 +19,6 @@
 CGFloat kBottomMargin = 15;
 // Leading margin for the label.
 CGFloat kLeadingMargin = 24.;
-// Label font color alpha.
-CGFloat kFontAlpha = .87;
 }  // namespace
 
 @interface IdentityChooserHeaderView : UITableViewHeaderFooterView
@@ -36,7 +35,7 @@
     label.text =
         l10n_util::GetNSString(IDS_IOS_ACCOUNT_IDENTITY_CHOOSER_CHOOSE_ACCOUNT);
     label.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
-    label.textColor = [UIColor colorWithWhite:0. alpha:kFontAlpha];
+    label.textColor = UIColor.cr_labelColor;
     [self.contentView addSubview:label];
     NSDictionary* views = @{
       @"label" : label,
diff --git a/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_view_controller.mm b/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_view_controller.mm
index 48da05d..96cf42c 100644
--- a/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_view_controller.mm
+++ b/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_chooser_view_controller.mm
@@ -57,6 +57,7 @@
 }
 
 - (void)viewDidDisappear:(BOOL)animated {
+  [super viewDidDisappear:animated];
   [self.presentationDelegate identityChooserViewControllerDidDisappear:self];
 }
 
diff --git a/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_view.mm b/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_view.mm
index 99dcebaf..8d5c670 100644
--- a/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_view.mm
+++ b/ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_view.mm
@@ -6,6 +6,7 @@
 
 #include "base/logging.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
+#import "ios/chrome/common/colors/UIColor+cr_semantic_colors.h"
 #import "ios/chrome/common/ui_util/constraints_ui_util.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -20,9 +21,6 @@
 const CGFloat kTitleOffset = 4;
 const CGFloat kHorizontalAvatarMargin = 16.;
 const CGFloat kVerticalMargin = 12.;
-// Colors
-const CGFloat kTitleTextColorAlpha = .87;
-const CGFloat kSubtitleTextColorAlpha = .54;
 
 }  // namespace
 
@@ -65,7 +63,7 @@
     _title = [[UILabel alloc] init];
     _title.translatesAutoresizingMaskIntoConstraints = NO;
     _title.numberOfLines = 0;
-    _title.textColor = [UIColor colorWithWhite:0 alpha:kTitleTextColorAlpha];
+    _title.textColor = UIColor.cr_labelColor;
     _title.font = [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline];
     [self addSubview:_title];
 
@@ -73,8 +71,7 @@
     _subtitle = [[UILabel alloc] init];
     _subtitle.translatesAutoresizingMaskIntoConstraints = NO;
     _subtitle.numberOfLines = 0;
-    _subtitle.textColor =
-        [UIColor colorWithWhite:0 alpha:kSubtitleTextColorAlpha];
+    _subtitle.textColor = UIColor.cr_secondaryLabelColor;
     _subtitle.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption1];
     [self addSubview:_subtitle];
 
diff --git a/ios/chrome/browser/ui/authentication/unified_consent/identity_picker_view.mm b/ios/chrome/browser/ui/authentication/unified_consent/identity_picker_view.mm
index af1c51f..6a4dbcaf 100644
--- a/ios/chrome/browser/ui/authentication/unified_consent/identity_picker_view.mm
+++ b/ios/chrome/browser/ui/authentication/unified_consent/identity_picker_view.mm
@@ -8,6 +8,7 @@
 #import "ios/chrome/browser/ui/authentication/authentication_constants.h"
 #import "ios/chrome/browser/ui/authentication/unified_consent/identity_chooser/identity_view.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
+#import "ios/chrome/common/colors/UIColor+cr_semantic_colors.h"
 #import "ios/chrome/common/ui_util/constraints_ui_util.h"
 #import "ios/third_party/material_components_ios/src/components/Ink/src/MaterialInk.h"
 
@@ -25,8 +26,6 @@
 const CGFloat kArrowDownSize = 24.;
 // Distances/margins.
 const CGFloat kArrowDownMargin = 12.;
-// Colors
-const int kHeaderBackgroundColor = 0xf1f3f4;
 
 }  // namespace
 
@@ -51,7 +50,7 @@
   if (self) {
     self.accessibilityIdentifier = kIdentityPickerViewIdentifier;
     self.layer.cornerRadius = kIdentityPickerViewRadius;
-    self.backgroundColor = UIColorFromRGB(kHeaderBackgroundColor);
+    self.backgroundColor = UIColor.cr_secondarySystemBackgroundColor;
     // Adding view elements inside.
     // Ink view.
     _inkView = [[MDCInkView alloc] initWithFrame:CGRectZero];
diff --git a/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm b/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm
index e8a28e5..96fe4b2 100644
--- a/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm
+++ b/ios/chrome/browser/ui/autofill/manual_fill/password_view_controller_egtest.mm
@@ -302,10 +302,6 @@
 
 // Tests that the Password View Controller is not present when presenting UI.
 - (void)testPasswordControllerPauses {
-  // For the search bar to appear in password settings at least one password is
-  // needed.
-  SaveExamplePasswordForm();
-
   // Bring up the keyboard.
   [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
       performAction:chrome_test_util::TapWebElement(kFormElementUsername)];
@@ -338,9 +334,6 @@
     return;
   }
 
-  // For this test one password is needed.
-  SaveExamplePasswordForm();
-
   // Bring up the keyboard.
   [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
       performAction:chrome_test_util::TapWebElement(kFormElementUsername)];
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_edit_view_controller.mm b/ios/chrome/browser/ui/bookmarks/bookmark_edit_view_controller.mm
index 8814da3..2ccaeb83 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_edit_view_controller.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_edit_view_controller.mm
@@ -34,6 +34,7 @@
 #include "ios/chrome/browser/ui/util/rtl_geometry.h"
 #include "ios/chrome/browser/ui/util/ui_util.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
+#import "ios/chrome/common/colors/semantic_color_names.h"
 #import "ios/chrome/common/ui_util/constraints_ui_util.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/public/provider/chrome/browser/chrome_browser_provider.h"
@@ -71,9 +72,6 @@
   ItemTypeInvalidURLFooter,
 };
 
-// The text color for the invalid URL label.
-const CGFloat kInvalidURLTextColor = 0xEA4335;
-
 // Estimated Table Row height.
 const CGFloat kEstimatedTableRowHeight = 50;
 // Estimated TableSection Footer height.
@@ -257,7 +255,7 @@
                            target:nil
                            action:nil];
 
-  deleteButton.tintColor = UIColor.redColor;
+  deleteButton.tintColor = [UIColor colorNamed:kDestructiveTintColor];
   // Setting the image to nil will cause the default shadowImage to be used,
   // we need to create a new one.
   [self.navigationController.toolbar setShadowImage:[UIImage new]
@@ -541,7 +539,8 @@
         base::mac::ObjCCastStrict<UITableViewHeaderFooterView>(footerView);
     headerFooterView.textLabel.font =
         [UIFont preferredFontForTextStyle:UIFontTextStyleCaption1];
-    headerFooterView.textLabel.textColor = UIColorFromRGB(kInvalidURLTextColor);
+    headerFooterView.textLabel.textColor =
+        [UIColor colorNamed:kDestructiveTintColor];
   }
   return footerView;
 }
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_folder_editor_view_controller.mm b/ios/chrome/browser/ui/bookmarks/bookmark_folder_editor_view_controller.mm
index 20f10703..53429862 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_folder_editor_view_controller.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_folder_editor_view_controller.mm
@@ -26,6 +26,7 @@
 #import "ios/chrome/browser/ui/material_components/utils.h"
 #import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h"
 #include "ios/chrome/browser/ui/util/rtl_geometry.h"
+#import "ios/chrome/common/colors/semantic_color_names.h"
 #import "ios/chrome/common/ui_util/constraints_ui_util.h"
 #include "ios/chrome/grit/ios_strings.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -472,7 +473,7 @@
       initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
                            target:nil
                            action:nil];
-  deleteButton.tintColor = UIColor.redColor;
+  deleteButton.tintColor = [UIColor colorNamed:kDestructiveTintColor];
   [self.navigationController.toolbar setShadowImage:[UIImage new]
                                  forToolbarPosition:UIBarPositionAny];
   [self setToolbarItems:@[ spaceButton, deleteButton, spaceButton ]
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.mm b/ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.mm
index 8adb5fa7..3584db3 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_home_view_controller.mm
@@ -50,6 +50,7 @@
 #import "ios/chrome/browser/url_loading/url_loading_params.h"
 #import "ios/chrome/browser/url_loading/url_loading_service.h"
 #import "ios/chrome/browser/url_loading/url_loading_service_factory.h"
+#import "ios/chrome/common/colors/semantic_color_names.h"
 #import "ios/chrome/common/favicon/favicon_attributes.h"
 #import "ios/chrome/common/favicon/favicon_view.h"
 #import "ios/chrome/common/ui_util/constraints_ui_util.h"
@@ -1510,7 +1511,7 @@
                                        style:UIBarButtonItemStylePlain
                                       target:self
                                       action:@selector(leadingButtonClicked)];
-  self.deleteButton.tintColor = UIColor.redColor;
+  self.deleteButton.tintColor = [UIColor colorNamed:kDestructiveTintColor];
   self.deleteButton.enabled = NO;
   self.deleteButton.accessibilityIdentifier =
       kBookmarkHomeLeadingButtonIdentifier;
diff --git a/ios/chrome/browser/ui/infobars/presentation/infobar_expand_banner_animator.mm b/ios/chrome/browser/ui/infobars/presentation/infobar_expand_banner_animator.mm
index bf59fde7..22a4dad 100644
--- a/ios/chrome/browser/ui/infobars/presentation/infobar_expand_banner_animator.mm
+++ b/ios/chrome/browser/ui/infobars/presentation/infobar_expand_banner_animator.mm
@@ -54,9 +54,9 @@
     presentedViewFinalFrame =
         [transitionContext finalFrameForViewController:presentedViewController];
 
-    CGRect initialFrame = [presentingViewController.view
-        convertRect:presentingViewController.view.frame
-             toView:nil];
+    CGRect bannerFrame = presentingViewController.view.frame;
+    CGRect initialFrame = presentedView.frame;
+    initialFrame.size.height = bannerFrame.size.height;
     presentedView.frame = initialFrame;
   }
 
diff --git a/ios/chrome/browser/ui/settings/password/BUILD.gn b/ios/chrome/browser/ui/settings/password/BUILD.gn
index 68636df..7d67fba 100644
--- a/ios/chrome/browser/ui/settings/password/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/password/BUILD.gn
@@ -39,6 +39,7 @@
     "//ios/chrome/browser/ui/table_view",
     "//ios/chrome/browser/ui/table_view/cells",
     "//ios/chrome/browser/ui/util",
+    "//ios/chrome/common/colors",
     "//ios/chrome/common/ui_util",
     "//ios/third_party/material_components_ios",
     "//ui/base",
@@ -86,6 +87,7 @@
     "//ios/chrome/browser/ui/table_view/cells",
     "//ios/chrome/browser/ui/util",
     "//ios/chrome/browser/web:test_support",
+    "//ios/chrome/common/colors",
     "//ios/chrome/test/app:test_support",
     "//ios/web/public/test",
     "//ios/web/public/test",
diff --git a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
index 0830a407..1bbdb22d 100644
--- a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
@@ -72,6 +72,7 @@
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
 #include "ios/chrome/browser/voice/speech_input_locale_config.h"
 #import "ios/chrome/common/colors/UIColor+cr_semantic_colors.h"
+#import "ios/chrome/common/colors/semantic_color_names.h"
 #include "ios/chrome/grit/ios_chromium_strings.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/public/provider/chrome/browser/chrome_browser_provider.h"
@@ -1039,7 +1040,8 @@
     googleServicesItem.image =
         [UIImage imageNamed:kSyncAndGoogleServicesSyncOnImageName];
   } else if (!IsTransientSyncError(syncSetupService->GetSyncServiceState())) {
-    googleServicesItem.detailTextColor = UIColor.redColor;
+    googleServicesItem.detailTextColor =
+        [UIColor colorNamed:kDestructiveTintColor];
     googleServicesItem.detailText =
         GetSyncErrorDescriptionForSyncSetupService(syncSetupService);
     googleServicesItem.image =
diff --git a/ios/chrome/browser/ui/settings/table_cell_catalog_view_controller.mm b/ios/chrome/browser/ui/settings/table_cell_catalog_view_controller.mm
index 7ebddf1..925ecaa 100644
--- a/ios/chrome/browser/ui/settings/table_cell_catalog_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/table_cell_catalog_view_controller.mm
@@ -29,6 +29,7 @@
 #include "ios/chrome/browser/ui/ui_feature_flags.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
 #import "ios/chrome/common/colors/UIColor+cr_semantic_colors.h"
+#import "ios/chrome/common/colors/semantic_color_names.h"
 #import "ios/public/provider/chrome/browser/chrome_browser_provider.h"
 #import "ios/public/provider/chrome/browser/signin/signin_resources_provider.h"
 #include "url/gurl.h"
@@ -133,10 +134,10 @@
   TableViewImageItem* textImageItem2 =
       [[TableViewImageItem alloc] initWithType:ItemTypeTextAccessoryImage];
   textImageItem2.title = @"Image item without image, and disabled";
-  textImageItem2.textColor = UIColor.redColor;
+  textImageItem2.textColor = [UIColor colorNamed:kDestructiveTintColor];
   textImageItem2.detailText =
       @"Very very very long detail text for the image cell without image";
-  textImageItem2.detailTextColor = UIColor.redColor;
+  textImageItem2.detailTextColor = [UIColor colorNamed:kDestructiveTintColor];
   textImageItem2.enabled = NO;
   [model addItem:textImageItem2 toSectionWithIdentifier:SectionIdentifierText];
 
diff --git a/ios/chrome/browser/ui/signin_interaction/signin_interaction_controller_egtest.mm b/ios/chrome/browser/ui/signin_interaction/signin_interaction_controller_egtest.mm
index 673294d..8473165 100644
--- a/ios/chrome/browser/ui/signin_interaction/signin_interaction_controller_egtest.mm
+++ b/ios/chrome/browser/ui/signin_interaction/signin_interaction_controller_egtest.mm
@@ -70,8 +70,28 @@
   [interaction performAction:grey_tap()];
 }
 
+// Removes all browsing data.
+void RemoveBrowsingData() {
+  __block BOOL browsing_data_removed = NO;
+  [chrome_test_util::GetMainController()
+      removeBrowsingDataForBrowserState:chrome_test_util::
+                                            GetOriginalBrowserState()
+                             timePeriod:browsing_data::TimePeriod::ALL_TIME
+                             removeMask:BrowsingDataRemoveMask::REMOVE_ALL
+                        completionBlock:^{
+                          browsing_data_removed = YES;
+                        }];
+  GREYCondition* condition =
+      [GREYCondition conditionWithName:@"Wait for removing browsing data."
+                                 block:^BOOL {
+                                   return browsing_data_removed;
+                                 }];
+  GREYAssert([condition waitWithTimeout:base::test::ios::kWaitForActionTimeout],
+             @"Browsing data was not removed.");
 }
 
+}  // namespace
+
 // Sign-in interaction tests that work both with Unified Consent enabled or
 // disabled.
 @interface SigninInteractionControllerTestCase : ChromeTestCase
@@ -79,6 +99,13 @@
 
 @implementation SigninInteractionControllerTestCase
 
+- (void)setUp {
+  [super setUp];
+  // Remove closed tab history to make sure the sign-in promo is always visible
+  // in recent tabs.
+  RemoveBrowsingData();
+}
+
 // Tests that opening the sign-in screen from the Settings and signing in works
 // correctly when there is already an identity on the device.
 - (void)testSignInOneUser {
@@ -391,9 +418,7 @@
 // Tests to dismiss sign-in by opening an URL from another app.
 // Sign-in opened from: tab switcher.
 // Interrupted at: user consent.
-// TODO(crbug.com/976828): Test flaky based on the screen size and the number
-// of recently closed tabs from the previous tests.
-- (void)DISABLED_testDismissSigninFromTabSwitcher {
+- (void)testDismissSigninFromTabSwitcher {
   [self assertOpenURLWhenSigninFromView:OpenSigninMethodFromTabSwitcher
                         tapSettingsLink:NO];
 }
@@ -401,9 +426,7 @@
 // Tests to dismiss sign-in by opening an URL from another app.
 // Sign-in opened from: tab switcher.
 // Interrupted at: advanced sign-in.
-// TODO(crbug.com/976828): Test flaky based on the screen size and the number
-// of recently closed tabs from the previous tests.
-- (void)DISABLED_testDismissSigninFromTabSwitcherFromAdvancedSigninSettings {
+- (void)testDismissSigninFromTabSwitcherFromAdvancedSigninSettings {
   [self assertOpenURLWhenSigninFromView:OpenSigninMethodFromTabSwitcher
                         tapSettingsLink:YES];
 }
diff --git a/ios/chrome/common/colors/resources/BUILD.gn b/ios/chrome/common/colors/resources/BUILD.gn
index 348478d..0e373a8 100644
--- a/ios/chrome/common/colors/resources/BUILD.gn
+++ b/ios/chrome/common/colors/resources/BUILD.gn
@@ -6,11 +6,18 @@
 
 group("resources") {
   deps = [
+    ":destructive_tint_color",
     ":solid_button_text_color",
     ":tint_color",
   ]
 }
 
+colorset("destructive_tint_color") {
+  sources = [
+    "destructive_tint_color.colorset/Contents.json",
+  ]
+}
+
 colorset("solid_button_text_color") {
   sources = [
     "solid_button_text_color.colorset/Contents.json",
diff --git a/ios/chrome/common/colors/resources/destructive_tint_color.colorset/Contents.json b/ios/chrome/common/colors/resources/destructive_tint_color.colorset/Contents.json
new file mode 100644
index 0000000..f534a807
--- /dev/null
+++ b/ios/chrome/common/colors/resources/destructive_tint_color.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  },
+  "colors" : [
+    {
+      "idiom" : "universal",
+      "color" : {
+        "color-space" : "display-p3",
+        "components" : {
+          "red" : "0xD9",
+          "alpha" : "1.000",
+          "blue" : "0x25",
+          "green" : "0x30"
+        }
+      }
+    },
+    {
+      "idiom" : "universal",
+      "appearances" : [
+        {
+          "appearance" : "luminosity",
+          "value" : "dark"
+        }
+      ],
+      "color" : {
+        "color-space" : "display-p3",
+        "components" : {
+          "red" : "0xF2",
+          "alpha" : "1.000",
+          "blue" : "0x82",
+          "green" : "0x8B"
+        }
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/ios/chrome/common/colors/semantic_color_names.h b/ios/chrome/common/colors/semantic_color_names.h
index e42f80d..b9168a0 100644
--- a/ios/chrome/common/colors/semantic_color_names.h
+++ b/ios/chrome/common/colors/semantic_color_names.h
@@ -7,6 +7,7 @@
 
 #import <UIKit/UIKit.h>
 
+extern NSString* const kDestructiveTintColor;
 extern NSString* const kSolidButtonTextColor;
 extern NSString* const kTintColor;
 
diff --git a/ios/chrome/common/colors/semantic_color_names.mm b/ios/chrome/common/colors/semantic_color_names.mm
index 1ea1587..46d5ebc 100644
--- a/ios/chrome/common/colors/semantic_color_names.mm
+++ b/ios/chrome/common/colors/semantic_color_names.mm
@@ -8,5 +8,6 @@
 #error "This file requires ARC support."
 #endif
 
+NSString* const kDestructiveTintColor = @"destructive_tint_color";
 NSString* const kSolidButtonTextColor = @"solid_button_text_color";
 NSString* const kTintColor = @"tint_color";
diff --git a/ios/chrome/test/wpt/cwt_request_handler.mm b/ios/chrome/test/wpt/cwt_request_handler.mm
index 33ea204..247a307 100644
--- a/ios/chrome/test/wpt/cwt_request_handler.mm
+++ b/ios/chrome/test/wpt/cwt_request_handler.mm
@@ -4,6 +4,7 @@
 
 #import "ios/chrome/test/wpt/cwt_request_handler.h"
 
+#include "base/debug/stack_trace.h"
 #include "base/guid.h"
 #include "base/json/json_reader.h"
 #include "base/json/json_writer.h"
@@ -81,6 +82,7 @@
 const char kWebDriverErrorCodeValueField[] = "error";
 const char kWebDriverErrorMessageValueField[] = "message";
 const char kWebDriverSessionIdValueField[] = "sessionId";
+const char kWebDriverStackTraceValueField[] = "stacktrace";
 
 // Field names for the "capabilities" struct that's included in the response
 // when creating a session.
@@ -105,6 +107,8 @@
   base::Value error_value(base::Value::Type::DICTIONARY);
   error_value.SetStringKey(kWebDriverErrorCodeValueField, error);
   error_value.SetStringKey(kWebDriverErrorMessageValueField, message);
+  error_value.SetStringKey(kWebDriverStackTraceValueField,
+                           base::debug::StackTrace().ToString());
   return error_value;
 }
 
diff --git a/ios/testing/earl_grey/base_earl_grey_test_case.mm b/ios/testing/earl_grey/base_earl_grey_test_case.mm
index 8311c78..416311df 100644
--- a/ios/testing/earl_grey/base_earl_grey_test_case.mm
+++ b/ios/testing/earl_grey/base_earl_grey_test_case.mm
@@ -22,6 +22,15 @@
 GREY_STUB_CLASS_IN_APP_MAIN_QUEUE(BaseEarlGreyTestCaseAppInterface)
 #endif  // defined(CHROME_EARL_GREY_2)
 
+namespace {
+
+// If true, +setUpForTestCase will be called from -setUp.  This flag is used to
+// ensure that +setUpForTestCase is called exactly once per unique XCTestCase
+// and is reset in +tearDown.
+bool g_needs_set_up_for_test_case = true;
+
+}  // namespace
+
 @implementation BaseEarlGreyTestCase
 
 + (void)setUpForTestCase {
@@ -46,11 +55,16 @@
   [self failIfSetUpIsOverridden];
 #endif
 
-  static dispatch_once_t setupToken;
-  dispatch_once(&setupToken, ^{
+  if (g_needs_set_up_for_test_case) {
+    g_needs_set_up_for_test_case = false;
     [CoverageUtils configureCoverageReportPath];
     [[self class] setUpForTestCase];
-  });
+  }
+}
+
++ (void)tearDown {
+  g_needs_set_up_for_test_case = true;
+  [super tearDown];
 }
 
 // Handles system alerts if any are present, closing them to unblock the UI.
diff --git a/ios/web/navigation/crw_web_view_navigation_observer.mm b/ios/web/navigation/crw_web_view_navigation_observer.mm
index 1a0ca1c28..7fbd2d01 100644
--- a/ios/web/navigation/crw_web_view_navigation_observer.mm
+++ b/ios/web/navigation/crw_web_view_navigation_observer.mm
@@ -124,9 +124,7 @@
 
 // Called when WKWebView estimatedProgress has been changed.
 - (void)webViewEstimatedProgressDidChange {
-  if (![self.delegate webViewIsBeingDestroyed:self]) {
-    self.webStateImpl->SendChangeLoadProgress(self.webView.estimatedProgress);
-  }
+  self.webStateImpl->SendChangeLoadProgress(self.webView.estimatedProgress);
 }
 
 // Called when WKWebView loading state has been changed.
diff --git a/ios/web/navigation/crw_web_view_navigation_observer_delegate.h b/ios/web/navigation/crw_web_view_navigation_observer_delegate.h
index 2577345..f70853f 100644
--- a/ios/web/navigation/crw_web_view_navigation_observer_delegate.h
+++ b/ios/web/navigation/crw_web_view_navigation_observer_delegate.h
@@ -16,10 +16,6 @@
 // Delegate for the NavigationObserver.
 @protocol CRWWebViewNavigationObserverDelegate
 
-// Whether the the web view is being closed.
-- (BOOL)webViewIsBeingDestroyed:
-    (CRWWebViewNavigationObserver*)navigationObserver;
-
 // The WebState.
 - (web::WebStateImpl*)webStateImplForNavigationObserver:
     (CRWWebViewNavigationObserver*)navigationObserver;
diff --git a/ios/web/navigation/crw_wk_navigation_handler.h b/ios/web/navigation/crw_wk_navigation_handler.h
index e24823a..0fbf866 100644
--- a/ios/web/navigation/crw_wk_navigation_handler.h
+++ b/ios/web/navigation/crw_wk_navigation_handler.h
@@ -55,10 +55,6 @@
     legacyNativeContentControllerForNavigationHandler:
         (CRWWKNavigationHandler*)navigationHandler;
 
-// Returns YES if WKWebView was deallocated or is being deallocated.
-- (BOOL)navigationHandlerWebViewBeingDestroyed:
-    (CRWWKNavigationHandler*)navigationHandler;
-
 // Returns the actual URL of the document object (i.e., the last committed URL
 // of the main frame).
 - (GURL)navigationHandlerDocumentURL:(CRWWKNavigationHandler*)navigationHandler;
@@ -164,6 +160,9 @@
 // Returns the referrer for the current page.
 @property(nonatomic, readonly, assign) web::Referrer currentReferrer;
 
+// Instructs this handler to close.
+- (void)close;
+
 // Instructs this handler to stop loading.
 - (void)stopLoading;
 
diff --git a/ios/web/navigation/crw_wk_navigation_handler.mm b/ios/web/navigation/crw_wk_navigation_handler.mm
index a63ddbf7..0c71f0b 100644
--- a/ios/web/navigation/crw_wk_navigation_handler.mm
+++ b/ios/web/navigation/crw_wk_navigation_handler.mm
@@ -120,6 +120,9 @@
 @property(nonatomic, readonly, weak)
     CRWLegacyNativeContentController* legacyNativeContentController;
 
+// Set to YES when [self close] is called.
+@property(nonatomic, assign) BOOL beingDestroyed;
+
 @end
 
 @implementation CRWWKNavigationHandler
@@ -149,7 +152,7 @@
   [self didReceiveWKNavigationDelegateCallback];
 
   self.webProcessCrashed = NO;
-  if ([self.delegate navigationHandlerWebViewBeingDestroyed:self]) {
+  if (self.beingDestroyed) {
     decisionHandler(WKNavigationActionPolicyCancel);
     return;
   }
@@ -243,7 +246,7 @@
         (action.navigationType == WKNavigationTypeFormResubmitted)) {
       self.webStateImpl->ShowRepostFormWarningDialog(
           base::BindOnce(^(bool shouldContinue) {
-            if ([self.delegate navigationHandlerWebViewBeingDestroyed:self]) {
+            if (self.beingDestroyed) {
               decisionHandler(WKNavigationActionPolicyCancel);
             } else if (shouldContinue) {
               decisionHandler(WKNavigationActionPolicyAllow);
@@ -340,7 +343,7 @@
     allowLoad =
         self.webStateImpl->ShouldAllowRequest(action.request, requestInfo);
     // The WebState may have been closed in the ShouldAllowRequest callback.
-    if ([self.delegate navigationHandlerWebViewBeingDestroyed:self]) {
+    if (self.beingDestroyed) {
       decisionHandler(WKNavigationActionPolicyCancel);
       return;
     }
@@ -379,7 +382,7 @@
         context->ReleaseItem();
       }
 
-      if (![self.delegate navigationHandlerWebViewBeingDestroyed:self] &&
+      if (!self.beingDestroyed &&
           [self shouldClosePageOnNativeApplicationLoad]) {
         // Loading was started for user initiated navigations and should be
         // stopped because no other WKWebView callbacks are called.
@@ -392,7 +395,7 @@
       }
     }
 
-    if (![self.delegate navigationHandlerWebViewBeingDestroyed:self]) {
+    if (!self.beingDestroyed) {
       // Loading was started for user initiated navigations and should be
       // stopped because no other WKWebView callbacks are called.
       // TODO(crbug.com/767092): Loading should not start until webView.loading
@@ -1214,7 +1217,7 @@
 // this point it's too late for a SafeBrowsing warning to be displayed for the
 // navigation for which the timer was started.
 - (void)didReceiveWKNavigationDelegateCallback {
-  if ([self.delegate navigationHandlerWebViewBeingDestroyed:self]) {
+  if (self.beingDestroyed) {
     UMA_HISTOGRAM_BOOLEAN("Renderer.WKWebViewCallbackAfterDestroy", true);
   }
   _safeBrowsingWarningDetectionTimer.Stop();
@@ -2033,6 +2036,10 @@
 
 #pragma mark - Public methods
 
+- (void)close {
+  self.beingDestroyed = YES;
+}
+
 - (void)stopLoading {
   _stoppedWKNavigation = [self.navigationStates lastAddedNavigation];
   self.pendingNavigationInfo.cancelled = YES;
@@ -2045,7 +2052,7 @@
   // TODO(crbug.com/821995):  Check if this function should be removed.
   if (self.navigationState != web::WKNavigationState::FINISHED) {
     self.navigationState = web::WKNavigationState::FINISHED;
-    if (![self.delegate navigationHandlerWebViewBeingDestroyed:self]) {
+    if (!self.beingDestroyed) {
       self.webStateImpl->SetIsLoading(false);
     }
   }
diff --git a/ios/web/web_state/ui/controller/crw_legacy_native_content_controller.mm b/ios/web/web_state/ui/controller/crw_legacy_native_content_controller.mm
index f5fbf490..bec50847 100644
--- a/ios/web/web_state/ui/controller/crw_legacy_native_content_controller.mm
+++ b/ios/web/web_state/ui/controller/crw_legacy_native_content_controller.mm
@@ -28,6 +28,9 @@
 @property(nonatomic, assign, readonly)
     web::NavigationManagerImpl* navigationManagerImpl;
 @property(nonatomic, assign, readonly) web::NavigationItemImpl* currentNavItem;
+// Set to YES when [self close] is called.
+@property(nonatomic, assign) BOOL beingDestroyed;
+
 @end
 
 @implementation CRWLegacyNativeContentController
@@ -194,6 +197,7 @@
 }
 
 - (void)close {
+  self.beingDestroyed = YES;
   self.nativeProvider = nil;
   if ([self.nativeController respondsToSelector:@selector(close)])
     [self.nativeController close];
@@ -234,7 +238,7 @@
 
 - (void)nativeContent:(id)content
     handleContextMenu:(const web::ContextMenuParams&)params {
-  if ([self.delegate legacyNativeContentControllerIsBeingDestroyed:self]) {
+  if (self.beingDestroyed) {
     return;
   }
   self.webStateImpl->HandleContextMenu(params);
diff --git a/ios/web/web_state/ui/controller/crw_legacy_native_content_controller_delegate.h b/ios/web/web_state/ui/controller/crw_legacy_native_content_controller_delegate.h
index d373f74..30a54a2d 100644
--- a/ios/web/web_state/ui/controller/crw_legacy_native_content_controller_delegate.h
+++ b/ios/web/web_state/ui/controller/crw_legacy_native_content_controller_delegate.h
@@ -22,10 +22,6 @@
 - (BOOL)legacyNativeContentControllerWebUsageEnabled:
     (CRWLegacyNativeContentController*)contentController;
 
-// Whether the delegate is being destroyed.
-- (BOOL)legacyNativeContentControllerIsBeingDestroyed:
-    (CRWLegacyNativeContentController*)contentController;
-
 // Asks the delegate to remove the web view.
 - (void)legacyNativeContentControllerRemoveWebView:
     (CRWLegacyNativeContentController*)contentController;
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index b6db826d..c1f6950 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -416,26 +416,40 @@
   DCHECK_NE(_webView, webView);
 
   // Unwind the old web view.
-  CRWWKScriptMessageRouter* messageRouter =
-      [self webViewConfigurationProvider].GetScriptMessageRouter();
-  web::WebFramesManagerImpl::FromWebState(self.webStateImpl)
-      ->OnWebViewUpdated(_webView, webView, messageRouter);
-  if (_webView) {
-    // TODO(crbug.com/956516): Use removeScriptMessageHandlerForName:webView:
-    // for |kScriptMessageName| and let CRWContextMenuController unregister its
-    // own callback.
-    [messageRouter removeAllScriptMessageHandlersForWebView:_webView];
-  }
+
+  // Remove KVO and WK*Delegate before calling methods on WKWebView so that
+  // handlers won't receive unnecessary callbacks.
   [_webView setNavigationDelegate:nil];
   [_webView setUIDelegate:nil];
   for (NSString* keyPath in self.WKWebViewObservers) {
     [_webView removeObserver:self forKeyPath:keyPath];
   }
 
+  CRWWKScriptMessageRouter* messageRouter =
+      [self webViewConfigurationProvider].GetScriptMessageRouter();
+  web::WebFramesManagerImpl::FromWebState(self.webStateImpl)
+      ->OnWebViewUpdated(_webView, webView, messageRouter);
+
+  if (_webView) {
+    // TODO(crbug.com/956516): Use removeScriptMessageHandlerForName:webView:
+    // for |kScriptMessageName| and let CRWContextMenuController unregister its
+    // own callback.
+    [messageRouter removeAllScriptMessageHandlersForWebView:_webView];
+
+    [_webView stopLoading];
+    [_webView removeFromSuperview];
+  }
+
   // Set up the new web view.
   _webView = webView;
 
   if (_webView) {
+    [_webView setNavigationDelegate:self.navigationHandler];
+    [_webView setUIDelegate:self.UIHandler];
+    for (NSString* keyPath in self.WKWebViewObservers) {
+      [_webView addObserver:self forKeyPath:keyPath options:0 context:nullptr];
+    }
+
     __weak CRWWebController* weakSelf = self;
     [messageRouter
         setScriptMessageHandler:^(WKScriptMessage* message) {
@@ -443,16 +457,13 @@
         }
                            name:kScriptMessageName
                         webView:_webView];
+
+    _webView.allowsBackForwardNavigationGestures =
+        _allowsBackForwardNavigationGestures;
   }
   [_jsInjector setWebView:_webView];
-  [_webView setNavigationDelegate:self.navigationHandler];
-  [_webView setUIDelegate:self.UIHandler];
-  for (NSString* keyPath in self.WKWebViewObservers) {
-    [_webView addObserver:self forKeyPath:keyPath options:0 context:nullptr];
-  }
   self.webViewNavigationObserver.webView = _webView;
-  _webView.allowsBackForwardNavigationGestures =
-      _allowsBackForwardNavigationGestures;
+
   [self setDocumentURL:_defaultURL context:nullptr];
 }
 
@@ -598,8 +609,10 @@
   self.webStateImpl->CancelDialogs();
 
   _SSLStatusUpdater = nil;
+  [self.navigationHandler close];
   [self.UIHandler close];
   [self.JSNavigationHandler close];
+  [self.requestController close];
   _faviconManager.reset();
   _jsWindowErrorManager.reset();
   self.swipeRecognizerProvider = nil;
@@ -1255,11 +1268,6 @@
   return [self webUsageEnabled];
 }
 
-- (BOOL)legacyNativeContentControllerIsBeingDestroyed:
-    (CRWLegacyNativeContentController*)contentController {
-  return _isBeingDestroyed;
-}
-
 - (void)legacyNativeContentControllerRemoveWebView:
     (CRWLegacyNativeContentController*)contentController {
   [self removeWebView];
@@ -1812,11 +1820,9 @@
   self.webStateImpl->CancelDialogs();
   self.navigationManagerImpl->DetachFromWebView();
 
-  [self.webView stopLoading];
-  [self.webView removeFromSuperview];
+  [self setWebView:nil];
   [self.navigationHandler stopLoading];
   [_containerView resetContent];
-  [self setWebView:nil];
 
   // webView:didFailProvisionalNavigation:withError: may never be called after
   // resetting WKWebView, so it is important to clear pending navigations now.
@@ -1986,11 +1992,6 @@
 
 #pragma mark - CRWWebViewNavigationObserverDelegate
 
-- (BOOL)webViewIsBeingDestroyed:
-    (CRWWebViewNavigationObserver*)navigationObserver {
-  return _isBeingDestroyed;
-}
-
 - (web::WebStateImpl*)webStateImplForNavigationObserver:
     (CRWWebViewNavigationObserver*)navigationObserver {
   return self.webStateImpl;
@@ -2149,11 +2150,6 @@
 
 #pragma mark - CRWWKNavigationHandlerDelegate
 
-- (BOOL)navigationHandlerWebViewBeingDestroyed:
-    (CRWWKNavigationHandler*)navigationHandler {
-  return _isBeingDestroyed;
-}
-
 - (web::WebStateImpl*)webStateImplForNavigationHandler:
     (CRWWKNavigationHandler*)navigationHandler {
   return self.webStateImpl;
@@ -2276,11 +2272,6 @@
 
 #pragma mark - CRWWebRequestControllerDelegate
 
-- (BOOL)webRequestControllerIsBeingDestroyed:
-    (CRWWebRequestController*)requestController {
-  return _isBeingDestroyed;
-}
-
 - (void)webRequestControllerStopLoading:
     (CRWWebRequestController*)requestController {
   [self stopLoading];
diff --git a/ios/web/web_state/ui/crw_web_request_controller.h b/ios/web/web_state/ui/crw_web_request_controller.h
index 5895958..fec8ea1c 100644
--- a/ios/web/web_state/ui/crw_web_request_controller.h
+++ b/ios/web/web_state/ui/crw_web_request_controller.h
@@ -28,10 +28,6 @@
 - (void)webRequestControllerStopLoading:
     (CRWWebRequestController*)requestController;
 
-// Whether the delegate is being destroyed.
-- (BOOL)webRequestControllerIsBeingDestroyed:
-    (CRWWebRequestController*)requestController;
-
 // Asks proxy to disconnect scroll proxy if needed.
 - (void)webRequestControllerDisconnectScrollViewProxy:
     (CRWWebRequestController*)requestController;
@@ -75,6 +71,11 @@
 
 - (instancetype)init NS_UNAVAILABLE;
 
+// Instructs the receiver to close. This should be called when the receiver's
+// owner is being destroyed. The state of the receiver will be set to
+// "isBeingDestroyed" after this is called.
+- (void)close;
+
 // Checks if a load request of the current navigation item should proceed. If
 // this returns |YES|, caller should create a webView and call
 // |loadRequestForCurrentNavigationItem|. Otherwise this will set the request
diff --git a/ios/web/web_state/ui/crw_web_request_controller.mm b/ios/web/web_state/ui/crw_web_request_controller.mm
index f9901fa..9cce397 100644
--- a/ios/web/web_state/ui/crw_web_request_controller.mm
+++ b/ios/web/web_state/ui/crw_web_request_controller.mm
@@ -66,6 +66,9 @@
 // Returns The WKNavigationDelegate handler class from delegate.
 @property(nonatomic, readonly) CRWWKNavigationHandler* navigationHandler;
 
+// Set to YES when [self close] is called.
+@property(nonatomic, assign) BOOL beingDestroyed;
+
 @end
 
 @implementation CRWWebRequestController
@@ -78,6 +81,10 @@
   return self;
 }
 
+- (void)close {
+  self.beingDestroyed = YES;
+}
+
 - (BOOL)maybeLoadRequestForCurrentNavigationItem {
   web::NavigationItem* item = self.currentNavItem;
   GURL targetURL = item ? item->GetVirtualURL() : GURL::EmptyGURL();
@@ -275,7 +282,7 @@
   DCHECK(!web::GetWebClient()->IsSlimNavigationManagerEnabled());
   self.webState->ShowRepostFormWarningDialog(
       base::BindOnce(^(bool shouldContinue) {
-        if ([_delegate webRequestControllerIsBeingDestroyed:self])
+        if (self.beingDestroyed)
           return;
 
         if (shouldContinue)
diff --git a/media/audio/BUILD.gn b/media/audio/BUILD.gn
index cb7a5305..d8ae83c 100644
--- a/media/audio/BUILD.gn
+++ b/media/audio/BUILD.gn
@@ -219,8 +219,6 @@
     sources += [
       "android/audio_manager_android.cc",
       "android/audio_manager_android.h",
-      "android/audio_record_input.cc",
-      "android/audio_record_input.h",
       "android/audio_track_output_stream.cc",
       "android/audio_track_output_stream.h",
       "android/muteable_audio_output_stream.h",
diff --git a/media/audio/android/audio_manager_android.cc b/media/audio/android/audio_manager_android.cc
index a9feab6..79bebf1 100644
--- a/media/audio/android/audio_manager_android.cc
+++ b/media/audio/android/audio_manager_android.cc
@@ -13,7 +13,6 @@
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/strings/string_number_conversions.h"
-#include "media/audio/android/audio_record_input.h"
 #include "media/audio/android/audio_track_output_stream.h"
 #include "media/audio/android/opensles_input.h"
 #include "media/audio/android/opensles_output.h"
diff --git a/media/audio/android/audio_record_input.cc b/media/audio/android/audio_record_input.cc
deleted file mode 100644
index 5194301..0000000
--- a/media/audio/android/audio_record_input.cc
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "media/audio/android/audio_record_input.h"
-
-#include "base/logging.h"
-#include "media/audio/android/audio_manager_android.h"
-#include "media/base/android/media_jni_headers/AudioRecordInput_jni.h"
-#include "media/base/audio_bus.h"
-
-using base::android::JavaParamRef;
-
-namespace media {
-
-constexpr SampleFormat kSampleFormat = kSampleFormatS16;
-
-AudioRecordInputStream::AudioRecordInputStream(
-    AudioManagerAndroid* audio_manager,
-    const AudioParameters& params)
-    : audio_manager_(audio_manager),
-      callback_(NULL),
-      direct_buffer_address_(NULL),
-      audio_bus_(media::AudioBus::Create(params)),
-      bytes_per_sample_(SampleFormatToBytesPerChannel(kSampleFormat)) {
-  DVLOG(2) << __PRETTY_FUNCTION__;
-  DCHECK(params.IsValid());
-  j_audio_record_.Reset(Java_AudioRecordInput_createAudioRecordInput(
-      base::android::AttachCurrentThread(), reinterpret_cast<intptr_t>(this),
-      params.sample_rate(), params.channels(), bytes_per_sample_ * 8,
-      params.GetBytesPerBuffer(kSampleFormat),
-      params.effects() & AudioParameters::ECHO_CANCELLER));
-}
-
-AudioRecordInputStream::~AudioRecordInputStream() {
-  DVLOG(2) << __PRETTY_FUNCTION__;
-  DCHECK(thread_checker_.CalledOnValidThread());
-}
-
-void AudioRecordInputStream::CacheDirectBufferAddress(
-    JNIEnv* env,
-    const JavaParamRef<jobject>& obj,
-    const JavaParamRef<jobject>& byte_buffer) {
-  DCHECK(thread_checker_.CalledOnValidThread());
-  direct_buffer_address_ =
-      static_cast<uint8_t*>(env->GetDirectBufferAddress(byte_buffer));
-}
-
-void AudioRecordInputStream::OnData(JNIEnv* env,
-                                    const JavaParamRef<jobject>& obj,
-                                    jint size,
-                                    jint hardware_delay_ms) {
-  DCHECK(direct_buffer_address_);
-  DCHECK_EQ(size,
-            audio_bus_->frames() * audio_bus_->channels() * bytes_per_sample_);
-  // Passing zero as the volume parameter indicates there is no access to a
-  // hardware volume slider.
-  audio_bus_->FromInterleaved(direct_buffer_address_, audio_bus_->frames(),
-                              bytes_per_sample_);
-  callback_->OnData(audio_bus_.get(),
-                    base::TimeTicks::Now() -
-                        base::TimeDelta::FromMilliseconds(hardware_delay_ms),
-                    0.0);
-}
-
-bool AudioRecordInputStream::Open() {
-  DVLOG(2) << __PRETTY_FUNCTION__;
-  DCHECK(thread_checker_.CalledOnValidThread());
-  return Java_AudioRecordInput_open(base::android::AttachCurrentThread(),
-                                    j_audio_record_);
-}
-
-void AudioRecordInputStream::Start(AudioInputCallback* callback) {
-  DVLOG(2) << __PRETTY_FUNCTION__;
-  DCHECK(thread_checker_.CalledOnValidThread());
-  DCHECK(callback);
-
-  if (callback_) {
-    // Start() was already called.
-    DCHECK_EQ(callback_, callback);
-    return;
-  }
-  // The Java thread has not yet started, so we are free to set |callback_|.
-  callback_ = callback;
-
-  Java_AudioRecordInput_start(base::android::AttachCurrentThread(),
-                              j_audio_record_);
-}
-
-void AudioRecordInputStream::Stop() {
-  DVLOG(2) << __PRETTY_FUNCTION__;
-  DCHECK(thread_checker_.CalledOnValidThread());
-  if (!callback_) {
-    // Start() was never called, or Stop() was already called.
-    return;
-  }
-
-  Java_AudioRecordInput_stop(base::android::AttachCurrentThread(),
-                             j_audio_record_);
-
-  // The Java thread must have been stopped at this point, so we are free to
-  // clear |callback_|.
-  callback_ = NULL;
-}
-
-void AudioRecordInputStream::Close() {
-  DVLOG(2) << __PRETTY_FUNCTION__;
-  DCHECK(thread_checker_.CalledOnValidThread());
-  Stop();
-  DCHECK(!callback_);
-  Java_AudioRecordInput_close(base::android::AttachCurrentThread(),
-                              j_audio_record_);
-  audio_manager_->ReleaseInputStream(this);
-}
-
-double AudioRecordInputStream::GetMaxVolume() {
-  return 0.0;
-}
-
-void AudioRecordInputStream::SetVolume(double volume) {
-}
-
-double AudioRecordInputStream::GetVolume() {
-  return 0.0;
-}
-
-bool AudioRecordInputStream::SetAutomaticGainControl(bool enabled) {
-  return false;
-}
-
-bool AudioRecordInputStream::GetAutomaticGainControl() {
-  return false;
-}
-
-bool AudioRecordInputStream::IsMuted() {
-  return false;
-}
-
-void AudioRecordInputStream::SetOutputDeviceForAec(
-    const std::string& output_device_id) {
-  // Do nothing. This is handled at a different layer on Android.
-}
-
-}  // namespace media
diff --git a/media/audio/android/audio_record_input.h b/media/audio/android/audio_record_input.h
deleted file mode 100644
index f5d544b..0000000
--- a/media/audio/android/audio_record_input.h
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef MEDIA_AUDIO_ANDROID_AUDIO_RECORD_INPUT_H_
-#define MEDIA_AUDIO_ANDROID_AUDIO_RECORD_INPUT_H_
-
-#include <stdint.h>
-
-#include <memory>
-
-#include "base/android/jni_android.h"
-#include "base/macros.h"
-#include "base/threading/thread_checker.h"
-#include "media/audio/audio_io.h"
-#include "media/base/audio_parameters.h"
-
-namespace media {
-
-class AudioBus;
-class AudioManagerAndroid;
-
-// Implements PCM audio input support for Android using the Java AudioRecord
-// interface. Most of the work is done by its Java counterpart in
-// AudioRecordInput.java. This class is created and lives on the Audio Manager
-// thread but recorded audio buffers are delivered on a thread managed by
-// the Java class.
-class MEDIA_EXPORT AudioRecordInputStream : public AudioInputStream {
- public:
-  AudioRecordInputStream(AudioManagerAndroid* manager,
-                         const AudioParameters& params);
-
-  ~AudioRecordInputStream() override;
-
-  // Implementation of AudioInputStream.
-  bool Open() override;
-  void Start(AudioInputCallback* callback) override;
-  void Stop() override;
-  void Close() override;
-  double GetMaxVolume() override;
-  void SetVolume(double volume) override;
-  double GetVolume() override;
-  bool SetAutomaticGainControl(bool enabled) override;
-  bool GetAutomaticGainControl() override;
-  bool IsMuted() override;
-  void SetOutputDeviceForAec(const std::string& output_device_id) override;
-
-  // Called from Java when data is available.
-  void OnData(JNIEnv* env,
-              const base::android::JavaParamRef<jobject>& obj,
-              jint size,
-              jint hardware_delay_ms);
-
-  // Called from Java so that we can cache the address of the Java-managed
-  // |byte_buffer| in |direct_buffer_address_|.
-  void CacheDirectBufferAddress(
-      JNIEnv* env,
-      const base::android::JavaParamRef<jobject>& obj,
-      const base::android::JavaParamRef<jobject>& byte_buffer);
-
- private:
-  base::ThreadChecker thread_checker_;
-  AudioManagerAndroid* audio_manager_;
-
-  // Java AudioRecordInput instance.
-  base::android::ScopedJavaGlobalRef<jobject> j_audio_record_;
-
-  // This is the only member accessed by both the Audio Manager and Java
-  // threads. Explanations for why we do not require explicit synchronization
-  // are given in the implementation.
-  AudioInputCallback* callback_;
-
-  // Owned by j_audio_record_.
-  uint8_t* direct_buffer_address_;
-
-  std::unique_ptr<media::AudioBus> audio_bus_;
-  int bytes_per_sample_;
-
-  DISALLOW_COPY_AND_ASSIGN(AudioRecordInputStream);
-};
-
-}  // namespace media
-
-#endif  // MEDIA_AUDIO_ANDROID_AUDIO_RECORD_INPUT_H_
diff --git a/media/base/android/BUILD.gn b/media/base/android/BUILD.gn
index 2eab73a..b0e3a87 100644
--- a/media/base/android/BUILD.gn
+++ b/media/base/android/BUILD.gn
@@ -131,7 +131,6 @@
   generate_jni("media_jni_headers") {
     sources = [
       "java/src/org/chromium/media/AudioManagerAndroid.java",
-      "java/src/org/chromium/media/AudioRecordInput.java",
       "java/src/org/chromium/media/AudioTrackOutputStream.java",
       "java/src/org/chromium/media/CodecProfileLevelList.java",
       "java/src/org/chromium/media/HdrMetadata.java",
@@ -178,7 +177,6 @@
     ]
     java_files = [
       "java/src/org/chromium/media/AudioManagerAndroid.java",
-      "java/src/org/chromium/media/AudioRecordInput.java",
       "java/src/org/chromium/media/AudioTrackOutputStream.java",
       "java/src/org/chromium/media/BitrateAdjuster.java",
       "java/src/org/chromium/media/CodecProfileLevelList.java",
diff --git a/media/base/android/java/src/org/chromium/media/AudioRecordInput.java b/media/base/android/java/src/org/chromium/media/AudioRecordInput.java
deleted file mode 100644
index e2c5c5f..0000000
--- a/media/base/android/java/src/org/chromium/media/AudioRecordInput.java
+++ /dev/null
@@ -1,246 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.media;
-
-import android.annotation.SuppressLint;
-import android.media.AudioFormat;
-import android.media.AudioRecord;
-import android.media.MediaRecorder.AudioSource;
-import android.media.audiofx.AcousticEchoCanceler;
-import android.media.audiofx.AudioEffect;
-import android.media.audiofx.AudioEffect.Descriptor;
-import android.os.Process;
-
-import org.chromium.base.Log;
-import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.annotations.JNINamespace;
-
-import java.nio.ByteBuffer;
-
-// Owned by its native counterpart declared in audio_record_input.h. Refer to
-// that class for general comments.
-@JNINamespace("media")
-class AudioRecordInput {
-    private static final String TAG = "cr.media";
-    // Set to true to enable debug logs. Always check in as false.
-    private static final boolean DEBUG = false;
-    // We are unable to obtain a precise measurement of the hardware delay on
-    // Android. This is a conservative lower-bound based on measurments. It
-    // could surely be tightened with further testing.
-    // TODO(dalecurtis): This should use AudioRecord.getTimestamp() in API 24+.
-    private static final int HARDWARE_DELAY_MS = 100;
-
-    private final long mNativeAudioRecordInputStream;
-    private final int mSampleRate;
-    private final int mChannels;
-    private final int mBitsPerSample;
-    private final boolean mUsePlatformAEC;
-    private ByteBuffer mBuffer;
-    private AudioRecord mAudioRecord;
-    private AudioRecordThread mAudioRecordThread;
-    private AcousticEchoCanceler mAEC;
-
-    private class AudioRecordThread extends Thread {
-        // The "volatile" synchronization technique is discussed here:
-        // http://stackoverflow.com/a/106787/299268
-        // and more generally in this article:
-        // https://www.ibm.com/developerworks/java/library/j-jtp06197/
-        private volatile boolean mKeepAlive = true;
-
-        @Override
-        public void run() {
-            Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
-            try {
-                mAudioRecord.startRecording();
-            } catch (IllegalStateException e) {
-                Log.e(TAG, "startRecording failed", e);
-                return;
-            }
-
-            while (mKeepAlive) {
-                int bytesRead = mAudioRecord.read(mBuffer, mBuffer.capacity());
-                if (bytesRead > 0) {
-                    nativeOnData(mNativeAudioRecordInputStream, bytesRead, HARDWARE_DELAY_MS);
-                } else {
-                    Log.e(TAG, "read failed: %d", bytesRead);
-                    if (bytesRead == AudioRecord.ERROR_INVALID_OPERATION) {
-                        // This can happen if there is already an active
-                        // AudioRecord (e.g. in another tab).
-                        mKeepAlive = false;
-                    }
-                }
-            }
-
-            try {
-                mAudioRecord.stop();
-            } catch (IllegalStateException e) {
-                Log.e(TAG, "stop failed", e);
-            }
-        }
-
-        public void joinRecordThread() {
-            mKeepAlive = false;
-            while (isAlive()) {
-                try {
-                    join();
-                } catch (InterruptedException e) {
-                    // Ignore.
-                }
-            }
-        }
-    }
-
-    @CalledByNative
-    private static AudioRecordInput createAudioRecordInput(long nativeAudioRecordInputStream,
-            int sampleRate, int channels, int bitsPerSample, int bytesPerBuffer,
-            boolean usePlatformAEC) {
-        return new AudioRecordInput(nativeAudioRecordInputStream, sampleRate, channels,
-                                    bitsPerSample, bytesPerBuffer, usePlatformAEC);
-    }
-
-    private AudioRecordInput(long nativeAudioRecordInputStream, int sampleRate, int channels,
-                             int bitsPerSample, int bytesPerBuffer, boolean usePlatformAEC) {
-        mNativeAudioRecordInputStream = nativeAudioRecordInputStream;
-        mSampleRate = sampleRate;
-        mChannels = channels;
-        mBitsPerSample = bitsPerSample;
-        mUsePlatformAEC = usePlatformAEC;
-
-        // We use a direct buffer so that the native class can have access to
-        // the underlying memory address. This avoids the need to copy from a
-        // jbyteArray to native memory. More discussion of this here:
-        // http://developer.android.com/training/articles/perf-jni.html
-        mBuffer = ByteBuffer.allocateDirect(bytesPerBuffer);
-        // Rather than passing the ByteBuffer with every OnData call (requiring
-        // the potentially expensive GetDirectBufferAddress) we simply have the
-        // the native class cache the address to the memory once.
-        //
-        // Unfortunately, profiling with traceview was unable to either confirm
-        // or deny the advantage of this approach, as the values for
-        // nativeOnData() were not stable across runs.
-        nativeCacheDirectBufferAddress(mNativeAudioRecordInputStream, mBuffer);
-    }
-
-    @SuppressLint("NewApi")
-    @CalledByNative
-    private boolean open() {
-        if (mAudioRecord != null) {
-            Log.e(TAG, "open() called twice without a close()");
-            return false;
-        }
-        int channelConfig;
-        if (mChannels == 1) {
-            channelConfig = AudioFormat.CHANNEL_IN_MONO;
-        } else if (mChannels == 2) {
-            channelConfig = AudioFormat.CHANNEL_IN_STEREO;
-        } else {
-            Log.e(TAG, "Unsupported number of channels: %d", mChannels);
-            return false;
-        }
-
-        int audioFormat;
-        if (mBitsPerSample == 8) {
-            audioFormat = AudioFormat.ENCODING_PCM_8BIT;
-        } else if (mBitsPerSample == 16) {
-            audioFormat = AudioFormat.ENCODING_PCM_16BIT;
-        } else {
-            Log.e(TAG, "Unsupported bits per sample: %d", mBitsPerSample);
-            return false;
-        }
-
-        // TODO(ajm): Do we need to make this larger to avoid underruns? The
-        // Android documentation notes "this size doesn't guarantee a smooth
-        // recording under load".
-        int minBufferSize = AudioRecord.getMinBufferSize(mSampleRate, channelConfig, audioFormat);
-        if (minBufferSize < 0) {
-            Log.e(TAG, "getMinBufferSize error: %d", minBufferSize);
-            return false;
-        }
-
-        // We will request mBuffer.capacity() with every read call. The
-        // underlying AudioRecord buffer should be at least this large.
-        int audioRecordBufferSizeInBytes = Math.max(mBuffer.capacity(), minBufferSize);
-        try {
-            // TODO(ajm): Allow other AudioSource types to be requested?
-            mAudioRecord = new AudioRecord(AudioSource.VOICE_COMMUNICATION,
-                                           mSampleRate,
-                                           channelConfig,
-                                           audioFormat,
-                                           audioRecordBufferSizeInBytes);
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "AudioRecord failed", e);
-            return false;
-        }
-
-        if (AcousticEchoCanceler.isAvailable()) {
-            mAEC = AcousticEchoCanceler.create(mAudioRecord.getAudioSessionId());
-            if (mAEC == null) {
-                Log.e(TAG, "AcousticEchoCanceler.create failed");
-                return false;
-            }
-            int ret = mAEC.setEnabled(mUsePlatformAEC);
-            if (ret != AudioEffect.SUCCESS) {
-                Log.e(TAG, "setEnabled error: %d", ret);
-                return false;
-            }
-
-            if (DEBUG) {
-                Descriptor descriptor = mAEC.getDescriptor();
-                Log.d(TAG, "AcousticEchoCanceler name: %s, implementor: %s, uuid: %s",
-                            descriptor.name, descriptor.implementor, descriptor.uuid);
-            }
-        }
-        return true;
-    }
-
-    @CalledByNative
-    private void start() {
-        if (mAudioRecord == null) {
-            Log.e(TAG, "start() called before open().");
-            return;
-        }
-        if (mAudioRecordThread != null) {
-            // start() was already called.
-            return;
-        }
-        mAudioRecordThread = new AudioRecordThread();
-        mAudioRecordThread.start();
-    }
-
-    @CalledByNative
-    private void stop() {
-        if (mAudioRecordThread == null) {
-            // start() was never called, or stop() was already called.
-            return;
-        }
-        mAudioRecordThread.joinRecordThread();
-        mAudioRecordThread = null;
-    }
-
-    @SuppressLint("NewApi")
-    @CalledByNative
-    private void close() {
-        if (mAudioRecordThread != null) {
-            Log.e(TAG, "close() called before stop().");
-            return;
-        }
-        if (mAudioRecord == null) {
-            // open() was not called.
-            return;
-        }
-
-        if (mAEC != null) {
-            mAEC.release();
-            mAEC = null;
-        }
-        mAudioRecord.release();
-        mAudioRecord = null;
-    }
-
-    private native void nativeCacheDirectBufferAddress(long nativeAudioRecordInputStream,
-                                                       ByteBuffer buffer);
-    private native void nativeOnData(
-            long nativeAudioRecordInputStream, int size, int hardwareDelayMs);
-}
diff --git a/media/capture/BUILD.gn b/media/capture/BUILD.gn
index d4e634c..301c581 100644
--- a/media/capture/BUILD.gn
+++ b/media/capture/BUILD.gn
@@ -337,7 +337,10 @@
     deps = [
       "//base",
       "//build/config/linux/libdrm",
+      "//gpu/command_buffer/client",
       "//third_party/minigbm",
+      "//ui/gfx:memory_buffer",
+      "//ui/gfx/geometry",
     ]
   }
 }
diff --git a/media/capture/video/OWNERS b/media/capture/video/OWNERS
index 0448766..0a386e7 100644
--- a/media/capture/video/OWNERS
+++ b/media/capture/video/OWNERS
@@ -1,5 +1,6 @@
 chfremer@chromium.org
 tommi@chromium.org
+guidou@chromium.org
 
 # Original (legacy) owner.
 emircan@chromium.org
diff --git a/media/capture/video/chromeos/local_gpu_memory_buffer_manager.cc b/media/capture/video/chromeos/local_gpu_memory_buffer_manager.cc
index 27a6a7c..7b02bbb7 100644
--- a/media/capture/video/chromeos/local_gpu_memory_buffer_manager.cc
+++ b/media/capture/video/chromeos/local_gpu_memory_buffer_manager.cc
@@ -5,11 +5,18 @@
 #include "media/capture/video/chromeos/local_gpu_memory_buffer_manager.h"
 
 #include <drm_fourcc.h>
+#include <gbm.h>
+#include <stddef.h>
+#include <stdint.h>
 #include <xf86drm.h>
-#include <memory>
 
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
 #include "base/trace_event/memory_allocator_dump_guid.h"
 #include "base/trace_event/process_memory_dump.h"
+#include "ui/gfx/buffer_format_util.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/native_pixmap_handle.h"
 
 namespace media {
 
@@ -56,6 +63,8 @@
   switch (gfx_format) {
     case gfx::BufferFormat::R_8:
       return DRM_FORMAT_R8;
+    case gfx::BufferFormat::YVU_420:
+      return DRM_FORMAT_YVU420;
     case gfx::BufferFormat::YUV_420_BIPLANAR:
       return DRM_FORMAT_NV12;
     // Add more formats when needed.
@@ -251,4 +260,46 @@
     gfx::GpuMemoryBuffer* buffer,
     const gpu::SyncToken& sync_token) {}
 
+std::unique_ptr<gfx::GpuMemoryBuffer> LocalGpuMemoryBufferManager::ImportDmaBuf(
+    const gfx::NativePixmapHandle& handle,
+    const gfx::Size& size,
+    gfx::BufferFormat format) {
+  if (handle.planes.size() != gfx::NumberOfPlanesForBufferFormat(format)) {
+    // This could happen if e.g., we get a compressed RGBA buffer where one
+    // plane is for metadata. We don't support this case.
+    LOG(ERROR) << "Cannot import " << gfx::BufferFormatToString(format)
+               << " with " << handle.planes.size() << " plane(s) (expected "
+               << gfx::NumberOfPlanesForBufferFormat(format) << " plane(s))";
+    return nullptr;
+  }
+  const uint32_t drm_format = GetDrmFormat(format);
+  if (!drm_format) {
+    LOG(ERROR) << "Unsupported format " << gfx::BufferFormatToString(format);
+    return nullptr;
+  }
+  gbm_import_fd_modifier_data import_data{
+      base::checked_cast<uint32_t>(size.width()),
+      base::checked_cast<uint32_t>(size.height()), drm_format,
+      base::checked_cast<uint32_t>(handle.planes.size())};
+  for (size_t plane = 0; plane < handle.planes.size(); plane++) {
+    if (!handle.planes[plane].fd.is_valid()) {
+      LOG(ERROR) << "Invalid file descriptor for plane " << plane;
+      return nullptr;
+    }
+    import_data.fds[plane] = handle.planes[plane].fd.get();
+    import_data.strides[plane] =
+        base::checked_cast<int>(handle.planes[plane].stride);
+    import_data.offsets[plane] =
+        base::checked_cast<int>(handle.planes[plane].offset);
+  }
+  import_data.modifier = handle.modifier;
+  gbm_bo* buffer_object = gbm_bo_import(gbm_device_, GBM_BO_IMPORT_FD_MODIFIER,
+                                        &import_data, GBM_BO_USE_SW_READ_OFTEN);
+  if (!buffer_object) {
+    PLOG(ERROR) << "Could not import the DmaBuf into gbm";
+    return nullptr;
+  }
+  return std::make_unique<GpuMemoryBufferImplGbm>(format, buffer_object);
+}
+
 }  // namespace media
diff --git a/media/capture/video/chromeos/local_gpu_memory_buffer_manager.h b/media/capture/video/chromeos/local_gpu_memory_buffer_manager.h
index 56643f3..b7a1552 100644
--- a/media/capture/video/chromeos/local_gpu_memory_buffer_manager.h
+++ b/media/capture/video/chromeos/local_gpu_memory_buffer_manager.h
@@ -5,10 +5,19 @@
 #ifndef MEDIA_CAPTURE_VIDEO_CHROMEOS_LOCAL_GPU_MEMORY_BUFFER_MANAGER_H_
 #define MEDIA_CAPTURE_VIDEO_CHROMEOS_LOCAL_GPU_MEMORY_BUFFER_MANAGER_H_
 
-#include <gbm.h>
+#include <memory>
 
 #include "gpu/command_buffer/client/gpu_memory_buffer_manager.h"
 #include "media/capture/capture_export.h"
+#include "ui/gfx/buffer_types.h"
+
+struct gbm_device;
+
+namespace gfx {
+class GpuMemoryBuffer;
+struct NativePixmapHandle;
+class Size;
+}  // namespace gfx
 
 namespace media {
 
@@ -16,6 +25,8 @@
 // gfx::GpuMemoryBufferManager which interacts with the DRM render node device
 // directly.  The LocalGpuMemoryBufferManager is only for testing purposes and
 // should not be used in production.
+//
+// TODO(crbug.com/974437): consider moving this to //media/gpu/test.
 class CAPTURE_EXPORT LocalGpuMemoryBufferManager
     : public gpu::GpuMemoryBufferManager {
  public:
@@ -31,6 +42,15 @@
   void SetDestructionSyncToken(gfx::GpuMemoryBuffer* buffer,
                                const gpu::SyncToken& sync_token) override;
 
+  // Imports a DmaBuf as a GpuMemoryBuffer to be able to map it. The
+  // GBM_BO_USE_SW_READ_OFTEN usage is specified so that the user of the
+  // returned GpuMemoryBuffer is guaranteed to have a linear view when mapping
+  // it.
+  std::unique_ptr<gfx::GpuMemoryBuffer> ImportDmaBuf(
+      const gfx::NativePixmapHandle& handle,
+      const gfx::Size& size,
+      gfx::BufferFormat format);
+
  private:
   gbm_device* gbm_device_;
 
diff --git a/media/capture/video/win/video_capture_device_factory_win.cc b/media/capture/video/win/video_capture_device_factory_win.cc
index 0fbac5c..8f899b6e 100644
--- a/media/capture/video/win/video_capture_device_factory_win.cc
+++ b/media/capture/video/win/video_capture_device_factory_win.cc
@@ -92,7 +92,7 @@
     // also https://crbug.com/924528
     "04ca:7047", "04ca:7048",
     // HP Elitebook 840 G1
-    "04f2:b3ed"};
+    "04f2:b3ed", "04f2:b3ca"};
 
 const std::pair<VideoCaptureApi, std::vector<std::pair<GUID, GUID>>>
     kMfAttributes[] = {{VideoCaptureApi::WIN_MEDIA_FOUNDATION,
diff --git a/media/gpu/test/video_player/frame_renderer_thumbnail.cc b/media/gpu/test/video_player/frame_renderer_thumbnail.cc
index 100be1fc..7672654 100644
--- a/media/gpu/test/video_player/frame_renderer_thumbnail.cc
+++ b/media/gpu/test/video_player/frame_renderer_thumbnail.cc
@@ -27,8 +27,8 @@
 // Size of the individual thumbnails that will be rendered.
 constexpr gfx::Size kThumbnailSize(160, 120);
 
-// Default file path used to store the thumbnail image.
-constexpr const base::FilePath::CharType* kDefaultOutputPath =
+// Default filename used to store the thumbnails image.
+constexpr const base::FilePath::CharType* kThumbnailFilename =
     FILE_PATH_LITERAL("thumbnail.png");
 
 // Vertex shader used to render thumbnails.
@@ -110,13 +110,15 @@
 bool FrameRendererThumbnail::gl_initialized_ = false;
 
 FrameRendererThumbnail::FrameRendererThumbnail(
-    const std::vector<std::string>& thumbnail_checksums)
+    const std::vector<std::string>& thumbnail_checksums,
+    const base::FilePath& output_folder)
     : frame_count_(0),
       thumbnail_checksums_(thumbnail_checksums),
       thumbnails_fbo_id_(0),
       thumbnails_texture_id_(0),
       vertex_buffer_(0),
-      program_(0) {
+      program_(0),
+      output_folder_(output_folder) {
   DETACH_FROM_SEQUENCE(renderer_sequence_checker_);
 }
 
@@ -133,25 +135,26 @@
 
 // static
 std::unique_ptr<FrameRendererThumbnail> FrameRendererThumbnail::Create(
-    const std::vector<std::string> thumbnail_checksums) {
-  auto frame_renderer =
-      base::WrapUnique(new FrameRendererThumbnail(thumbnail_checksums));
+    const std::vector<std::string> thumbnail_checksums,
+    const base::FilePath& output_folder) {
+  base::FilePath resolved_output_folder =
+      base::MakeAbsoluteFilePath(output_folder);
+  auto frame_renderer = base::WrapUnique(
+      new FrameRendererThumbnail(thumbnail_checksums, resolved_output_folder));
   frame_renderer->Initialize();
   return frame_renderer;
 }
 
 // static
 std::unique_ptr<FrameRendererThumbnail> FrameRendererThumbnail::Create(
-    const base::FilePath& video_file_path) {
+    const base::FilePath& video_file_path,
+    const base::FilePath& output_folder) {
   // Read thumbnail checksums from file.
   std::vector<std::string> thumbnail_checksums =
       media::test::ReadGoldenThumbnailMD5s(
           video_file_path.AddExtension(FILE_PATH_LITERAL(".md5")));
 
-  auto frame_renderer =
-      base::WrapUnique(new FrameRendererThumbnail(thumbnail_checksums));
-  frame_renderer->Initialize();
-  return frame_renderer;
+  return FrameRendererThumbnail::Create(thumbnail_checksums, output_folder);
 }
 
 void FrameRendererThumbnail::AcquireGLContext() {
@@ -246,6 +249,10 @@
 void FrameRendererThumbnail::SaveThumbnail() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
 
+  // Create the directory tree if it doesn't exist yet.
+  if (!DirectoryExists(output_folder_))
+    base::CreateDirectory(output_folder_);
+
   const std::vector<uint8_t> rgba = ConvertThumbnailToRGBA();
 
   // Convert raw RGBA into PNG for export.
@@ -254,9 +261,11 @@
                         kThumbnailsPageSize, kThumbnailsPageSize.width() * 4,
                         true, std::vector<gfx::PNGCodec::Comment>(), &png);
 
-  base::FilePath filepath(kDefaultOutputPath);
+  base::FilePath filepath = output_folder_.Append(kThumbnailFilename);
+  base::File thumbnail_file(
+      filepath, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
   int num_bytes =
-      base::WriteFile(filepath, reinterpret_cast<char*>(&png[0]), png.size());
+      thumbnail_file.Write(0u, reinterpret_cast<char*>(&png[0]), png.size());
   ASSERT_NE(-1, num_bytes);
   EXPECT_EQ(static_cast<size_t>(num_bytes), png.size());
 }
diff --git a/media/gpu/test/video_player/frame_renderer_thumbnail.h b/media/gpu/test/video_player/frame_renderer_thumbnail.h
index fc70f9ea..d44cf63a 100644
--- a/media/gpu/test/video_player/frame_renderer_thumbnail.h
+++ b/media/gpu/test/video_player/frame_renderer_thumbnail.h
@@ -40,13 +40,15 @@
 
   // Create an instance of the thumbnail frame renderer.
   static std::unique_ptr<FrameRendererThumbnail> Create(
-      const std::vector<std::string> thumbnail_checksums);
+      const std::vector<std::string> thumbnail_checksums,
+      const base::FilePath& output_folder);
 
   // Create an instance of the thumbnail frame renderer. The |video_file_path|
   // should point to a file containing all golden thumbnail hashes for the video
   // being rendered.
   static std::unique_ptr<FrameRendererThumbnail> Create(
-      const base::FilePath& video_file_path);
+      const base::FilePath& video_file_path,
+      const base::FilePath& output_folder);
 
   // FrameRenderer implementation
   // Acquire the active GL context. This will claim |gl_context_lock_|.
@@ -67,7 +69,8 @@
 
  private:
   explicit FrameRendererThumbnail(
-      const std::vector<std::string>& thumbnail_checksums);
+      const std::vector<std::string>& thumbnail_checksums,
+      const base::FilePath& output_folder);
 
   // Initialize the frame renderer, performs all rendering-related setup.
   void Initialize();
@@ -123,6 +126,9 @@
   // Whether GL was initialized, as it should only happen once.
   static bool gl_initialized_;
 
+  // Output folder the thumbnails image will be written to by SaveThumbnail().
+  const base::FilePath output_folder_;
+
   SEQUENCE_CHECKER(client_sequence_checker_);
   SEQUENCE_CHECKER(renderer_sequence_checker_);
 
diff --git a/media/gpu/test/video_player/video_player_test_environment.cc b/media/gpu/test/video_player/video_player_test_environment.cc
index 895b2999..90b14a8 100644
--- a/media/gpu/test/video_player/video_player_test_environment.cc
+++ b/media/gpu/test/video_player/video_player_test_environment.cc
@@ -6,6 +6,11 @@
 
 #include <utility>
 
+#include "base/system/sys_info.h"
+#include "media/base/video_types.h"
+#if defined(OS_CHROMEOS)
+#include "media/gpu/chromeos/platform_video_frame_utils.h"
+#endif  // defined(OS_CHROMEOS)
 #include "media/gpu/test/video_player/video.h"
 
 namespace media {
@@ -49,6 +54,25 @@
 
 VideoPlayerTestEnvironment::~VideoPlayerTestEnvironment() = default;
 
+void VideoPlayerTestEnvironment::SetUp() {
+  VideoTestEnvironment::SetUp();
+
+  // TODO(dstaessens): Remove this check once all platforms support import mode.
+  // Some older platforms do not support importing buffers, but need to allocate
+  // buffers internally in the decoder.
+#if defined(OS_CHROMEOS)
+  constexpr const char* kImportModeBlacklist[] = {"nyan_big", "nyan_blaze",
+                                                  "nyan_kitty"};
+  const std::string board = base::SysInfo::GetLsbReleaseBoard();
+  import_supported_ = (std::find(std::begin(kImportModeBlacklist),
+                                 std::end(kImportModeBlacklist),
+                                 board) == std::end(kImportModeBlacklist));
+#endif  // defined(OS_CHROMEOS)
+
+  // VideoDecoders always require import mode to be supported.
+  DCHECK(!use_vd_ || import_supported_);
+}
+
 const media::test::Video* VideoPlayerTestEnvironment::Video() const {
   return video_.get();
 }
@@ -69,5 +93,9 @@
   return use_vd_;
 }
 
+bool VideoPlayerTestEnvironment::ImportSupported() const {
+  return import_supported_;
+}
+
 }  // namespace test
 }  // namespace media
diff --git a/media/gpu/test/video_player/video_player_test_environment.h b/media/gpu/test/video_player/video_player_test_environment.h
index 5507c8d..746a7b29 100644
--- a/media/gpu/test/video_player/video_player_test_environment.h
+++ b/media/gpu/test/video_player/video_player_test_environment.h
@@ -28,6 +28,9 @@
       bool use_vd);
   ~VideoPlayerTestEnvironment() override;
 
+  // Set up video test environment, called once for entire test run.
+  void SetUp() override;
+
   // Get the video the tests will be ran on.
   const media::test::Video* Video() const;
   // Check whether frame validation is enabled.
@@ -38,6 +41,8 @@
   const base::FilePath& OutputFolder() const;
   // Check whether we should use VD-based video decoders instead of VDA-based.
   bool UseVD() const;
+  // Whether import mode is supported, valid after SetUp() has been called.
+  bool ImportSupported() const;
 
  private:
   VideoPlayerTestEnvironment(std::unique_ptr<media::test::Video> video,
@@ -51,6 +56,8 @@
   const bool output_frames_;
   const base::FilePath output_folder_;
   const bool use_vd_;
+  // TODO(dstaessens): Remove this once all allocate-only platforms reached EOL.
+  bool import_supported_ = false;
 };
 }  // namespace test
 }  // namespace media
diff --git a/media/gpu/vaapi/BUILD.gn b/media/gpu/vaapi/BUILD.gn
index 0e96548..72f7a34 100644
--- a/media/gpu/vaapi/BUILD.gn
+++ b/media/gpu/vaapi/BUILD.gn
@@ -168,9 +168,11 @@
     ":vaapi_utils_unittest",
     "//base",
     "//media:test_support",
+    "//media/capture:chromeos_test_utils",
     "//media/parsers",
     "//testing/gtest",
     "//third_party/libyuv:libyuv",
+    "//ui/gfx:memory_buffer",
     "//ui/gfx/codec",
     "//ui/gfx/geometry",
   ]
diff --git a/media/gpu/vaapi/va.sigs b/media/gpu/vaapi/va.sigs
index 1145ee5..f333cb33 100644
--- a/media/gpu/vaapi/va.sigs
+++ b/media/gpu/vaapi/va.sigs
@@ -20,6 +20,7 @@
 int vaDisplayIsValid(VADisplay dpy);
 VAStatus vaEndPicture(VADisplay dpy, VAContextID context);
 const char *vaErrorStr(VAStatus error_status);
+VAStatus vaExportSurfaceHandle(VADisplay dpy, VASurfaceID surface_id, uint32_t mem_type, uint32_t flags, void *descriptor);
 VAStatus vaGetConfigAttributes(VADisplay dpy, VAProfile profile, VAEntrypoint entrypoint, VAConfigAttrib *attrib_list, int num_attribs);
 VAStatus vaGetImage(VADisplay dpy, VASurfaceID surface, int x, int y, unsigned int width, unsigned int height, VAImageID image);
 VAStatus vaInitialize(VADisplay dpy, int *major_version, int *minor_version);
diff --git a/media/gpu/vaapi/vaapi_image_decoder.cc b/media/gpu/vaapi/vaapi_image_decoder.cc
index 8e35bce..a8f7e7fb 100644
--- a/media/gpu/vaapi/vaapi_image_decoder.cc
+++ b/media/gpu/vaapi/vaapi_image_decoder.cc
@@ -6,7 +6,10 @@
 
 #include "base/logging.h"
 #include "media/gpu/macros.h"
+#include "media/gpu/vaapi/va_surface.h"
 #include "media/gpu/vaapi/vaapi_wrapper.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/linux/native_pixmap_dmabuf.h"
 
 namespace media {
 
@@ -49,4 +52,38 @@
   return profile;
 }
 
+scoped_refptr<gfx::NativePixmapDmaBuf>
+VaapiImageDecoder::ExportAsNativePixmapDmaBuf(VaapiImageDecodeStatus* status) {
+  DCHECK(status);
+
+  // We need to take ownership of the VASurface so that the next decode doesn't
+  // attempt to use the same VASurface. Otherwise, it could overwrite the result
+  // of the current decode before it's used by the caller. This VASurface will
+  // self-clean at the end of this scope, but the underlying buffer should stay
+  // alive because of the exported FDs.
+  scoped_refptr<VASurface> va_surface = ReleaseVASurface();
+  if (!va_surface) {
+    DVLOGF(1) << "No decoded image available";
+    *status = VaapiImageDecodeStatus::kInvalidState;
+    return nullptr;
+  }
+
+  DCHECK_NE(VA_INVALID_ID, va_surface->id());
+  scoped_refptr<gfx::NativePixmapDmaBuf> pixmap =
+      vaapi_wrapper_->ExportVASurfaceAsNativePixmapDmaBuf(va_surface->id());
+  if (!pixmap) {
+    *status = VaapiImageDecodeStatus::kCannotExportSurface;
+    return nullptr;
+  }
+
+  // In Intel's iHD driver the size requested for the surface may be different
+  // than the buffer size of the NativePixmap because of additional alignment.
+  // See https://git.io/fj6nA.
+  DCHECK_LE(va_surface->size().width(), pixmap->GetBufferSize().width());
+  DCHECK_LE(va_surface->size().height(), pixmap->GetBufferSize().height());
+
+  *status = VaapiImageDecodeStatus::kSuccess;
+  return pixmap;
+}
+
 }  // namespace media
diff --git a/media/gpu/vaapi/vaapi_image_decoder.h b/media/gpu/vaapi/vaapi_image_decoder.h
index 18924695..5a073da 100644
--- a/media/gpu/vaapi/vaapi_image_decoder.h
+++ b/media/gpu/vaapi/vaapi_image_decoder.h
@@ -15,6 +15,10 @@
 #include "base/memory/scoped_refptr.h"
 #include "gpu/config/gpu_info.h"
 
+namespace gfx {
+class NativePixmapDmaBuf;
+}  // namespace gfx
+
 namespace media {
 
 class VASurface;
@@ -30,6 +34,7 @@
   kExecuteDecodeFailed,
   kUnsupportedSurfaceFormat,
   kCannotGetImage,
+  kCannotExportSurface,
   kInvalidState,
 };
 
@@ -63,6 +68,13 @@
   // Returns the image profile supported by this decoder.
   gpu::ImageDecodeAcceleratorSupportedProfile GetSupportedProfile() const;
 
+  // Exports the decoded data from the last Decode() call as a
+  // gfx::NativePixmapDmaBuf. Returns nullptr on failure and sets *|status| to
+  // the reason for failure. On success, the image decoder gives up ownership of
+  // the buffer underlying the NativePixmapDmaBuf.
+  scoped_refptr<gfx::NativePixmapDmaBuf> ExportAsNativePixmapDmaBuf(
+      VaapiImageDecodeStatus* status);
+
  protected:
   explicit VaapiImageDecoder(VAProfile va_profile);
 
diff --git a/media/gpu/vaapi/vaapi_jpeg_decoder_unittest.cc b/media/gpu/vaapi/vaapi_jpeg_decoder_unittest.cc
index bbd5e41a..55c59185 100644
--- a/media/gpu/vaapi/vaapi_jpeg_decoder_unittest.cc
+++ b/media/gpu/vaapi/vaapi_jpeg_decoder_unittest.cc
@@ -5,6 +5,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <algorithm>
 #include <memory>
 #include <string>
 #include <vector>
@@ -27,6 +28,7 @@
 #include "base/strings/string_util.h"
 #include "media/base/test_data_util.h"
 #include "media/base/video_types.h"
+#include "media/capture/video/chromeos/local_gpu_memory_buffer_manager.h"
 #include "media/gpu/vaapi/va_surface.h"
 #include "media/gpu/vaapi/vaapi_image_decoder.h"
 #include "media/gpu/vaapi/vaapi_jpeg_decoder.h"
@@ -38,8 +40,13 @@
 #include "third_party/skia/include/core/SkImageInfo.h"
 #include "third_party/skia/include/core/SkPixmap.h"
 #include "third_party/skia/include/encode/SkJpegEncoder.h"
+#include "ui/gfx/buffer_format_util.h"
+#include "ui/gfx/buffer_types.h"
 #include "ui/gfx/codec/jpeg_codec.h"
 #include "ui/gfx/geometry/size.h"
+#include "ui/gfx/gpu_memory_buffer.h"
+#include "ui/gfx/linux/native_pixmap_dmabuf.h"
+#include "ui/gfx/native_pixmap_handle.h"
 
 namespace media {
 namespace {
@@ -138,8 +145,12 @@
       base::strict_cast<int>(parse_result.frame_header.coded_width);
   const int coded_height =
       base::strict_cast<int>(parse_result.frame_header.coded_height);
-  if (coded_width != decoded_image.coded_size.width() ||
-      coded_height != decoded_image.coded_size.height()) {
+  // Note the use of > instead of !=. This is to handle the case in the Intel
+  // iHD driver where the coded size we compute for one of the images is
+  // 1280x720 while the size of the VAAPI surface is 1280x736 because of
+  // additional alignment. See https://git.io/fj6nA.
+  if (coded_width > decoded_image.coded_size.width() ||
+      coded_height > decoded_image.coded_size.height()) {
     DLOG(ERROR) << "Wrong expected decoded JPEG coded size, " << coded_width
                 << "x" << coded_height << " versus VaAPI provided "
                 << decoded_image.coded_size.width() << "x"
@@ -352,6 +363,10 @@
       base::span<const uint8_t> encoded_image,
       VaapiImageDecodeStatus* status = nullptr);
 
+  scoped_refptr<gfx::NativePixmapDmaBuf> DecodeToNativePixmapDmaBuf(
+      base::span<const uint8_t> encoded_image,
+      VaapiImageDecodeStatus* status = nullptr);
+
  protected:
   std::string test_data_path_;
   VaapiJpegDecoder decoder_;
@@ -386,7 +401,7 @@
   scoped_image = decoder_.GetImage(preferred_fourcc, &image_status);
   EXPECT_EQ(!!scoped_image, image_status == VaapiImageDecodeStatus::kSuccess);
 
-  // Record the first fail status.
+  // Return the first fail status.
   if (status) {
     *status = decode_status != VaapiImageDecodeStatus::kSuccess ? decode_status
                                                                 : image_status;
@@ -400,6 +415,29 @@
   return Decode(encoded_image, VA_FOURCC_I420, status);
 }
 
+scoped_refptr<gfx::NativePixmapDmaBuf>
+VaapiJpegDecoderTest::DecodeToNativePixmapDmaBuf(
+    base::span<const uint8_t> encoded_image,
+    VaapiImageDecodeStatus* status) {
+  VaapiImageDecodeStatus decode_status;
+  scoped_refptr<VASurface> surface =
+      decoder_.Decode(encoded_image, &decode_status);
+  EXPECT_EQ(!!surface, decode_status == VaapiImageDecodeStatus::kSuccess);
+
+  // Still try to get the pixmap when decode fails.
+  VaapiImageDecodeStatus pixmap_status;
+  scoped_refptr<gfx::NativePixmapDmaBuf> pixmap =
+      decoder_.ExportAsNativePixmapDmaBuf(&pixmap_status);
+  EXPECT_EQ(!!pixmap, pixmap_status == VaapiImageDecodeStatus::kSuccess);
+
+  // Return the first fail status.
+  if (status) {
+    *status = decode_status != VaapiImageDecodeStatus::kSuccess ? decode_status
+                                                                : pixmap_status;
+  }
+  return pixmap;
+}
+
 // The intention of this test is to ensure that the workarounds added in
 // VaapiWrapper::GetJpegDecodeSuitableImageFourCC() don't result in an
 // unsupported image format.
@@ -588,6 +626,82 @@
   }
 }
 
+// TODO(andrescj): test other JPEG formats besides YUV 4:2:0.
+TEST_F(VaapiJpegDecoderTest, DecodeAndExportAsNativePixmapDmaBuf) {
+  if (base::StartsWith(VaapiWrapper::GetVendorStringForTesting(),
+                       "Mesa Gallium driver", base::CompareCase::SENSITIVE)) {
+    // TODO(crbug.com/974438): until we support surfaces with multiple buffer
+    // objects, the AMD driver fails this test.
+    GTEST_SKIP();
+  }
+  if (base::StartsWith(VaapiWrapper::GetVendorStringForTesting(),
+                       "Intel i965 driver", base::CompareCase::SENSITIVE)) {
+    // TODO(b/135705575): until the correct offsets are exported, the Intel i965
+    // driver fails this test.
+    GTEST_SKIP();
+  }
+
+  base::FilePath input_file = FindTestDataFilePath(kYuv420Filename);
+  std::string jpeg_data;
+  ASSERT_TRUE(base::ReadFileToString(input_file, &jpeg_data))
+      << "failed to read input data from " << input_file.value();
+  const auto encoded_image = base::make_span<const uint8_t>(
+      reinterpret_cast<const uint8_t*>(jpeg_data.data()), jpeg_data.size());
+  VaapiImageDecodeStatus status;
+  scoped_refptr<gfx::NativePixmapDmaBuf> pixmap =
+      DecodeToNativePixmapDmaBuf(encoded_image, &status);
+  ASSERT_EQ(VaapiImageDecodeStatus::kSuccess, status);
+  ASSERT_TRUE(pixmap);
+
+  // After exporting the surface, we should not be able to obtain a VAImage with
+  // the decoded data.
+  VAImageFormat i420_format{};
+  i420_format.fourcc = VA_FOURCC_I420;
+  EXPECT_TRUE(VaapiWrapper::IsImageFormatSupported(i420_format));
+  EXPECT_FALSE(decoder_.GetImage(i420_format.fourcc, &status));
+  EXPECT_EQ(VaapiImageDecodeStatus::kInvalidState, status);
+
+  // Workaround: in order to import and map the pixmap using minigbm when the
+  // format is gfx::BufferFormat::YVU_420, we need to reorder the planes so that
+  // the offsets are in increasing order as assumed in https://bit.ly/2NLubNN.
+  // Otherwise, we get a validation error. In essence, we're making minigbm
+  // think that it is mapping a YVU_420, but it's actually mapping a YUV_420.
+  //
+  // TODO(andrescj): revisit this once crrev.com/c/1573718 lands.
+  gfx::NativePixmapHandle handle = pixmap->ExportHandle();
+  if (pixmap->GetBufferFormat() == gfx::BufferFormat::YVU_420)
+    std::swap(handle.planes[1], handle.planes[2]);
+
+  LocalGpuMemoryBufferManager gpu_memory_buffer_manager;
+  std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer =
+      gpu_memory_buffer_manager.ImportDmaBuf(handle, pixmap->GetBufferSize(),
+                                             pixmap->GetBufferFormat());
+  ASSERT_TRUE(gpu_memory_buffer);
+  ASSERT_TRUE(gpu_memory_buffer->Map());
+  DecodedVAImage decoded_image{};
+  const gfx::BufferFormat format = gpu_memory_buffer->GetFormat();
+  if (format == gfx::BufferFormat::YVU_420) {
+    decoded_image.va_fourcc = VA_FOURCC_I420;
+    decoded_image.number_of_planes = 3u;
+  } else if (format == gfx::BufferFormat::YUV_420_BIPLANAR) {
+    decoded_image.va_fourcc = VA_FOURCC_NV12;
+    decoded_image.number_of_planes = 2u;
+  } else {
+    ASSERT_TRUE(false) << "Unsupported format "
+                       << gfx::BufferFormatToString(format);
+  }
+  decoded_image.coded_size = gpu_memory_buffer->GetSize();
+  for (size_t plane = 0u;
+       plane < base::strict_cast<size_t>(decoded_image.number_of_planes);
+       plane++) {
+    decoded_image.planes[plane].data =
+        static_cast<uint8_t*>(gpu_memory_buffer->memory(plane));
+    decoded_image.planes[plane].stride = gpu_memory_buffer->stride(plane);
+  }
+  EXPECT_TRUE(CompareImages(encoded_image, decoded_image));
+  gpu_memory_buffer->Unmap();
+}
+
 // Make sure that JPEGs whose size is below the supported size range are
 // rejected.
 //
diff --git a/media/gpu/vaapi/vaapi_wrapper.cc b/media/gpu/vaapi/vaapi_wrapper.cc
index b07af1b..bcc5286a 100644
--- a/media/gpu/vaapi/vaapi_wrapper.cc
+++ b/media/gpu/vaapi/vaapi_wrapper.cc
@@ -4,11 +4,13 @@
 
 #include "media/gpu/vaapi/vaapi_wrapper.h"
 
-#include <algorithm>
-#include <type_traits>
-
 #include <dlfcn.h>
 #include <string.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <type_traits>
+#include <utility>
 
 #include <va/va.h>
 #include <va/va_drm.h>
@@ -19,11 +21,14 @@
 #include "base/bind_helpers.h"
 #include "base/callback_helpers.h"
 #include "base/environment.h"
+#include "base/files/scoped_file.h"
 #include "base/logging.h"
 #include "base/macros.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/no_destructor.h"
+#include "base/numerics/checked_math.h"
 #include "base/numerics/safe_conversions.h"
+#include "base/posix/eintr_wrapper.h"
 #include "base/stl_util.h"
 #include "base/strings/string_util.h"
 #include "base/system/sys_info.h"
@@ -41,7 +46,10 @@
 #include "media/gpu/vaapi/vaapi_utils.h"
 #include "third_party/libyuv/include/libyuv.h"
 #include "ui/gfx/buffer_format_util.h"
+#include "ui/gfx/buffer_types.h"
+#include "ui/gfx/linux/native_pixmap_dmabuf.h"
 #include "ui/gfx/native_pixmap.h"
+#include "ui/gfx/native_pixmap_handle.h"
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_implementation.h"
 
@@ -1400,6 +1408,87 @@
                        base::BindOnce(&VaapiWrapper::DestroySurface, this));
 }
 
+scoped_refptr<gfx::NativePixmapDmaBuf>
+VaapiWrapper::ExportVASurfaceAsNativePixmapDmaBuf(VASurfaceID va_surface_id) {
+  VADRMPRIMESurfaceDescriptor descriptor;
+  {
+    base::AutoLock auto_lock(*va_lock_);
+    VAStatus va_res = vaSyncSurface(va_display_, va_surface_id);
+    VA_SUCCESS_OR_RETURN(va_res, "Cannot sync VASurface", nullptr);
+    va_res = vaExportSurfaceHandle(
+        va_display_, va_surface_id, VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
+        VA_EXPORT_SURFACE_READ_ONLY | VA_EXPORT_SURFACE_SEPARATE_LAYERS,
+        &descriptor);
+    VA_SUCCESS_OR_RETURN(va_res, "Failed to export VASurface", nullptr);
+  }
+
+  // We only support one bo containing all the planes. The fd should be owned by
+  // us: per va/va.h, "the exported handles are owned by the caller."
+  //
+  // TODO(crbug.com/974438): support multiple buffer objects so that this can
+  // work in AMD.
+  if (descriptor.num_objects != 1u) {
+    DVLOG(1) << "Only surface descriptors with one bo are supported";
+    return nullptr;
+  }
+  base::ScopedFD bo_fd(descriptor.objects[0].fd);
+  const uint64_t bo_modifier = descriptor.objects[0].drm_format_modifier;
+
+  // Translate the pixel format to a gfx::BufferFormat.
+  gfx::BufferFormat buffer_format;
+  switch (descriptor.fourcc) {
+    case VA_FOURCC_IMC3:
+      // IMC3 is like I420 but all the planes have the same stride. This is used
+      // for decoding 4:2:0 JPEGs in the Intel i965 driver. We don't currently
+      // have a gfx::BufferFormat for YUV420. Instead, we reuse YVU_420 and
+      // later swap the U and V planes.
+      //
+      // TODO(andrescj): revisit this once crrev.com/c/1573718 lands.
+      buffer_format = gfx::BufferFormat::YVU_420;
+      break;
+    case VA_FOURCC_NV12:
+      buffer_format = gfx::BufferFormat::YUV_420_BIPLANAR;
+      break;
+    default:
+      LOG(ERROR) << "Cannot export a surface with FOURCC "
+                 << FourccToString(descriptor.fourcc);
+      return nullptr;
+  }
+
+  gfx::NativePixmapHandle handle{};
+  handle.modifier = bo_modifier;
+  for (uint32_t layer = 0; layer < descriptor.num_layers; layer++) {
+    // According to va/va_drmcommon.h, if VA_EXPORT_SURFACE_SEPARATE_LAYERS is
+    // specified, each layer should contain one plane.
+    DCHECK_EQ(1u, descriptor.layers[layer].num_planes);
+
+    // Strictly speaking, we only have to dup() the fd for the planes after the
+    // first one since we already own the first one, but we dup() regardless for
+    // simplicity: |bo_fd| will be closed at the end of this method anyway.
+    base::ScopedFD plane_fd(HANDLE_EINTR(dup(bo_fd.get())));
+    PCHECK(plane_fd.is_valid());
+    constexpr uint64_t kZeroSizeToPreventMapping = 0u;
+    handle.planes.emplace_back(
+        base::checked_cast<int>(descriptor.layers[layer].pitch[0]),
+        base::checked_cast<int>(descriptor.layers[layer].offset[0]),
+        kZeroSizeToPreventMapping,
+        base::ScopedFD(HANDLE_EINTR(dup(bo_fd.get()))));
+  }
+
+  if (descriptor.fourcc == VA_FOURCC_IMC3) {
+    // Recall that for VA_FOURCC_IMC3, we will return a format of
+    // gfx::BufferFormat::YVU_420, so we need to swap the U and V planes to keep
+    // the semantics.
+    DCHECK_EQ(3u, handle.planes.size());
+    std::swap(handle.planes[1], handle.planes[2]);
+  }
+
+  return base::MakeRefCounted<gfx::NativePixmapDmaBuf>(
+      gfx::Size(base::checked_cast<int>(descriptor.width),
+                base::checked_cast<int>(descriptor.height)),
+      buffer_format, std::move(handle));
+}
+
 bool VaapiWrapper::SubmitBuffer(VABufferType va_buffer_type,
                                 size_t size,
                                 const void* buffer) {
diff --git a/media/gpu/vaapi/vaapi_wrapper.h b/media/gpu/vaapi/vaapi_wrapper.h
index 3d6fe96..8253cfa6 100644
--- a/media/gpu/vaapi/vaapi_wrapper.h
+++ b/media/gpu/vaapi/vaapi_wrapper.h
@@ -23,6 +23,7 @@
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/synchronization/lock.h"
 #include "base/thread_annotations.h"
 #include "media/gpu/media_gpu_export.h"
@@ -36,7 +37,9 @@
 #endif  // USE_X11
 
 namespace gfx {
+enum class BufferFormat;
 class NativePixmap;
+class NativePixmapDmaBuf;
 }
 
 namespace media {
@@ -188,6 +191,25 @@
   scoped_refptr<VASurface> CreateVASurfaceForPixmap(
       const scoped_refptr<gfx::NativePixmap>& pixmap);
 
+  // Syncs and exports the VA surface identified by |va_surface_id| as a
+  // gfx::NativePixmapDmaBuf. Currently, the only VAAPI surface pixel formats
+  // supported are VA_FOURCC_IMC3 and VA_FOURCC_NV12.
+  //
+  // Notes:
+  //
+  // - For VA_FOURCC_IMC3, the format of the returned NativePixmapDmaBuf is
+  //   gfx::BufferFormat::YVU_420 because we don't have a YUV_420 format. The
+  //   planes are flipped accordingly, i.e.,
+  //   gfx::NativePixmapDmaBuf::GetDmaBufOffset(1) refers to the V plane.
+  //   TODO(andrescj): revisit once crrev.com/c/1573718 lands.
+  //
+  // - For VA_FOURCC_NV12, the format of the returned NativePixmapDmaBuf is
+  //   gfx::BufferFormat::YUV_420_BIPLANAR.
+  //
+  // Returns nullptr on failure.
+  scoped_refptr<gfx::NativePixmapDmaBuf> ExportVASurfaceAsNativePixmapDmaBuf(
+      VASurfaceID va_surface_id);
+
   // Submit parameters or slice data of |va_buffer_type|, copying them from
   // |buffer| of size |size|, into HW codec. The data in |buffer| is no
   // longer needed and can be freed after this method returns.
diff --git a/media/gpu/video_decode_accelerator_tests.cc b/media/gpu/video_decode_accelerator_tests.cc
index 265185f..a4dda81 100644
--- a/media/gpu/video_decode_accelerator_tests.cc
+++ b/media/gpu/video_decode_accelerator_tests.cc
@@ -42,7 +42,7 @@
     "  --disable_validator  disable frame validation, useful on old\n"
     "                       platforms that don't support import mode.\n"
     "  --output_frames      write all decoded video frames to the\n"
-    "                       \"video_frames\" folder.\n"
+    "                       \"<testname>\" folder.\n"
     "  --output_folder      overwrite the default output folder used when\n"
     "                       \"--output_frames\" is specified.\n"
     "  --use_vd             use the new VD-based video decoders, instead of\n"
@@ -52,10 +52,6 @@
 
 media::test::VideoPlayerTestEnvironment* g_env;
 
-// Default output folder used to store video frames.
-constexpr const base::FilePath::CharType* kDefaultOutputFolder =
-    FILE_PATH_LITERAL("video_frames");
-
 // Video decode test class. Performs setup and teardown for each single test.
 class VideoDecoderTest : public ::testing::Test {
  public:
@@ -73,7 +69,7 @@
           media::test::VideoFrameValidator::Create(video->FrameChecksums()));
     }
 
-    // Write decoded video frames to the 'video_frames/<test_name/>' folder.
+    // Write decoded video frames to the '<testname>' folder.
     if (g_env->IsFramesOutputEnabled()) {
       base::FilePath output_folder =
           base::FilePath(g_env->OutputFolder())
@@ -85,6 +81,10 @@
     // Use the new VD-based video decoders if requested.
     config.use_vd = g_env->UseVD();
 
+    // Force allocate mode if import mode is not supported.
+    if (!g_env->ImportSupported())
+      config.allocation_mode = AllocationMode::kAllocate;
+
     return VideoPlayer::Create(video, std::move(frame_renderer),
                                std::move(frame_processors), config);
   }
@@ -265,11 +265,16 @@
   if (g_env->IsValidatorEnabled())
     GTEST_SKIP();
 
+  base::FilePath output_folder =
+      base::FilePath(g_env->OutputFolder())
+          .Append(base::FilePath(g_env->GetTestName()));
+
   VideoDecoderClientConfig config;
   config.allocation_mode = AllocationMode::kAllocate;
   auto tvp = CreateVideoPlayer(
       g_env->Video(), config,
-      FrameRendererThumbnail::Create(g_env->Video()->ThumbnailChecksums()));
+      FrameRendererThumbnail::Create(g_env->Video()->ThumbnailChecksums(),
+                                     output_folder));
 
   tvp->Play();
   EXPECT_TRUE(tvp->WaitForFlushDone());
@@ -308,7 +313,7 @@
   // Parse command line arguments.
   bool enable_validator = true;
   bool output_frames = false;
-  base::FilePath::StringType output_folder = media::test::kDefaultOutputFolder;
+  base::FilePath::StringType output_folder = base::FilePath::kCurrentDirectory;
   bool use_vd = false;
   base::CommandLine::SwitchMap switches = cmd_line->GetSwitches();
   for (base::CommandLine::SwitchMap::const_iterator it = switches.begin();
diff --git a/mojo/public/tools/bindings/generators/js_templates/lite/test/BUILD.gn b/mojo/public/tools/bindings/generators/js_templates/lite/test/BUILD.gn
index ec085c5..b2263ccf 100644
--- a/mojo/public/tools/bindings/generators/js_templates/lite/test/BUILD.gn
+++ b/mojo/public/tools/bindings/generators/js_templates/lite/test/BUILD.gn
@@ -10,10 +10,20 @@
   sources = [
     "test.test-mojom",
   ]
+
+  use_old_js_lite_bindings_names = false
+}
+
+mojom("mojo_old_names_bindings") {
+  testonly = true
+  sources = [
+    "test_old_names.test-mojom",
+  ]
 }
 
 js_type_check("closure_compile") {
   deps = [
+    ":old_names_test",
     ":test",
   ]
 }
@@ -21,5 +31,12 @@
 js_library("test") {
   deps = [
     ":mojo_bindings_js_library_for_compile",
+    ":mojo_old_names_bindings_js_library_for_compile",
+  ]
+}
+
+js_library("old_names_test") {
+  deps = [
+    ":mojo_old_names_bindings_js_library_for_compile",
   ]
 }
diff --git a/mojo/public/tools/bindings/generators/js_templates/lite/test/old_names_test.js b/mojo/public/tools/bindings/generators/js_templates/lite/test/old_names_test.js
new file mode 100644
index 0000000..816e733
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/js_templates/lite/test/old_names_test.js
@@ -0,0 +1,43 @@
+// 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.
+
+(() => {
+  async function testFunction() {
+    /** @type {oldNameTest.mojom.TestPageHandlerProxy} */
+    let proxy = oldNameTest.mojom.TestPageHandler.getProxy()
+
+    // Type infers {?{values: !Array<!string>}} from Promise return type.
+    let result = await proxy.method1(' ', 5);
+
+    /** @type {Array<string>} */
+    let values = result.values;
+
+    /** @type {oldNameTest.mojom.TestStruct} */
+    let testStruct = result.ts
+  }
+
+  /** @implements {oldNameTest.mojom.TestPageInterface} */
+  class TestPageImpl {
+    /** @override */
+    onEvent1(s) {
+      /** @type {oldNameTest.mojom.TestStruct} */ let t = s;
+      /** @type {string} */ let id = t.id;
+      /** @type {string|undefined} */ let title = t.title;
+      /** @type {oldNameTest.mojom.TestEnum} */ let enumValue = t.enums[0];
+
+      /** @type {string} */ let numberToStringMapValue = t.numberToStringMap[5];
+
+      /** @type {oldNameTest.mojom.Message} */
+      let messageToMessageArrayValue =
+        t.messageToArrayMap.get({message: 'asdf'})[0];
+
+      /** @type {oldNameTest.mojom.TestEnum} */ let enumToMapMapValue =
+        t.enumToMapMap[oldNameTest.mojom.TestEnum.FIRST]
+                      [oldNameTest.mojom.TestEnum.SECOND];
+      /** @type {oldNameTest.mojom.TestPageInterface} */
+      let handler = t.numberToInterfaceProxyMap[3];
+      handler.onEvent1(t);
+    }
+  }
+})();
diff --git a/mojo/public/tools/bindings/generators/js_templates/lite/test/test.js b/mojo/public/tools/bindings/generators/js_templates/lite/test/test.js
index 0ba0164d..31d12f8 100644
--- a/mojo/public/tools/bindings/generators/js_templates/lite/test/test.js
+++ b/mojo/public/tools/bindings/generators/js_templates/lite/test/test.js
@@ -3,11 +3,11 @@
 // found in the LICENSE file.
 
 async function testFunction() {
-  /** @type {test.mojom.TestPageHandlerProxy} */
-  let proxy = test.mojom.TestPageHandler.getProxy()
+  /** @type {test.mojom.TestPageHandlerRemote} */
+  let remote = test.mojom.TestPageHandler.getRemote()
 
   // Type infers {?{values: !Array<!string>}} from Promise return type.
-  let result = await proxy.method1(' ', 5);
+  let result = await remote.method1(' ', 5);
 
   /** @type {Array<string>} */
   let values = result.values;
diff --git a/mojo/public/tools/bindings/generators/js_templates/lite/test/test_old_names.test-mojom b/mojo/public/tools/bindings/generators/js_templates/lite/test/test_old_names.test-mojom
new file mode 100644
index 0000000..09fc6b08
--- /dev/null
+++ b/mojo/public/tools/bindings/generators/js_templates/lite/test/test_old_names.test-mojom
@@ -0,0 +1,32 @@
+// 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.
+
+module old_name_test.mojom;
+
+enum TestEnum {
+  FIRST,
+  SECOND,
+};
+
+struct Message {
+  string message;
+};
+
+struct TestStruct {
+  string id;
+  string? title;
+  array<TestEnum> enums;
+  map<uint32, string> numberToStringMap;
+  map<Message, array<Message>> messageToArrayMap;
+  map<TestEnum, map<TestEnum, TestEnum>> enumToMapMap;
+  map<uint32, TestPage> numberToInterfaceProxyMap;
+};
+
+interface TestPageHandler {
+  Method1(string p1, int32 p2) => (array<string> values, TestStruct ts);
+};
+
+interface TestPage {
+  OnEvent1(TestStruct s);
+};
diff --git a/net/cert/cert_verify_proc_builtin.cc b/net/cert/cert_verify_proc_builtin.cc
index 4a911e2..b5ef8e7 100644
--- a/net/cert/cert_verify_proc_builtin.cc
+++ b/net/cert/cert_verify_proc_builtin.cc
@@ -40,6 +40,9 @@
 // TODO(https://crbug.com/634470): Make this smaller.
 constexpr uint32_t kPathBuilderIterationLimit = 25000;
 
+constexpr base::TimeDelta kMaxVerificationTime =
+    base::TimeDelta::FromSeconds(60);
+
 DEFINE_CERT_ERROR_ID(kPathLacksEVPolicy, "Path does not have an EV policy");
 
 RevocationPolicy NoRevocationChecking() {
@@ -422,6 +425,7 @@
                   CertIssuerSourceStatic* intermediates,
                   SystemTrustStore* ssl_trust_store,
                   base::Time verification_time,
+                  base::TimeTicks deadline,
                   VerificationType verification_type,
                   SimplePathBuilderDelegate::DigestPolicy digest_policy,
                   int flags,
@@ -475,6 +479,7 @@
   }
 
   path_builder.SetIterationLimit(kPathBuilderIterationLimit);
+  path_builder.SetDeadline(deadline);
 
   path_builder.Run();
 }
@@ -584,6 +589,7 @@
   // VerifyInternal() is expected to carry out verifications using the current
   // time stamp.
   base::Time verification_time = base::Time::Now();
+  base::TimeTicks deadline = base::TimeTicks::Now() + kMaxVerificationTime;
 
   // Parse the target certificate.
   scoped_refptr<ParsedCertificate> target =
@@ -647,12 +653,12 @@
 
     // Run the attempt through the path builder.
     TryBuildPath(target, &intermediates, ssl_trust_store.get(),
-                 verification_time, cur_attempt.verification_type,
+                 verification_time, deadline, cur_attempt.verification_type,
                  cur_attempt.digest_policy, flags, ocsp_response, crl_set,
                  net_fetcher_.get(), ev_metadata, &result,
                  &checked_revocation_for_some_path);
 
-    if (result.HasValidPath())
+    if (result.HasValidPath() || result.exceeded_deadline)
       break;
 
     // If this path building attempt (may have) failed due to the chain using a
diff --git a/net/cert/internal/path_builder.cc b/net/cert/internal/path_builder.cc
index b04dfdb..e81d485 100644
--- a/net/cert/internal/path_builder.cc
+++ b/net/cert/internal/path_builder.cc
@@ -372,6 +372,7 @@
   // returns false.
   bool GetNextPath(ParsedCertificateList* out_certs,
                    CertificateTrust* out_last_cert_trust,
+                   const base::TimeTicks deadline,
                    uint32_t* iteration_count,
                    const uint32_t max_iteration_count);
 
@@ -405,9 +406,13 @@
 
 bool CertPathIter::GetNextPath(ParsedCertificateList* out_certs,
                                CertificateTrust* out_last_cert_trust,
+                               const base::TimeTicks deadline,
                                uint32_t* iteration_count,
                                const uint32_t max_iteration_count) {
   while (true) {
+    if (!deadline.is_null() && base::TimeTicks::Now() > deadline)
+      return false;
+
     if (!next_issuer_.cert) {
       if (cur_path_.Empty()) {
         DVLOG(1) << "CertPathIter exhausted all paths...";
@@ -565,6 +570,10 @@
   max_iteration_count_ = limit;
 }
 
+void CertPathBuilder::SetDeadline(base::TimeTicks deadline) {
+  deadline_ = deadline;
+}
+
 void CertPathBuilder::Run() {
   uint32_t iteration_count = 0;
 
@@ -573,12 +582,15 @@
         std::make_unique<CertPathBuilderResultPath>();
 
     if (!cert_path_iter_->GetNextPath(&result_path->certs,
-                                      &result_path->last_cert_trust,
+                                      &result_path->last_cert_trust, deadline_,
                                       &iteration_count, max_iteration_count_)) {
       // No more paths to check.
       if (max_iteration_count_ > 0 && iteration_count > max_iteration_count_) {
         out_result_->exceeded_iteration_limit = true;
       }
+      if (!deadline_.is_null() && base::TimeTicks::Now() > deadline_) {
+        out_result_->exceeded_deadline = true;
+      }
       return;
     }
 
diff --git a/net/cert/internal/path_builder.h b/net/cert/internal/path_builder.h
index ec09517..5562c4e 100644
--- a/net/cert/internal/path_builder.h
+++ b/net/cert/internal/path_builder.h
@@ -133,6 +133,10 @@
     // configured with |SetIterationLimit|.
     bool exceeded_iteration_limit = false;
 
+    // True if the search stopped because it exceeded the deadline configured
+    // with |SetDeadline|.
+    bool exceeded_deadline = false;
+
    private:
     DISALLOW_COPY_AND_ASSIGN(Result);
   };
@@ -177,6 +181,12 @@
   // new intermediate over all potential paths.
   void SetIterationLimit(uint32_t limit);
 
+  // Sets a deadline for completing path building. If |deadline| has passed and
+  // path building has not completed, path building will stop. Note that this
+  // is not a hard limit, there is no guarantee how far past |deadline| time
+  // will be when path building is aborted.
+  void SetDeadline(base::TimeTicks deadline);
+
   // Executes verification of the target certificate.
   //
   // Upon return results are written to the |result| object passed into the
@@ -196,6 +206,7 @@
   const InitialPolicyMappingInhibit initial_policy_mapping_inhibit_;
   const InitialAnyPolicyInhibit initial_any_policy_inhibit_;
   uint32_t max_iteration_count_ = 0;
+  base::TimeTicks deadline_;
 
   Result* out_result_;
 
diff --git a/net/cert/internal/path_builder_unittest.cc b/net/cert/internal/path_builder_unittest.cc
index a2bba7fd..87840cd 100644
--- a/net/cert/internal/path_builder_unittest.cc
+++ b/net/cert/internal/path_builder_unittest.cc
@@ -488,6 +488,45 @@
   }
 }
 
+TEST_F(PathBuilderMultiRootTest, TestTrivialDeadline) {
+  // C(D) is the trust root.
+  TrustStoreInMemory trust_store;
+  trust_store.AddTrustAnchor(c_by_d_);
+
+  // Cert B(C) is supplied.
+  CertIssuerSourceStatic sync_certs;
+  sync_certs.AddCert(b_by_c_);
+
+  for (const bool insufficient_limit : {true, false}) {
+    SCOPED_TRACE(insufficient_limit);
+
+    CertPathBuilder::Result result;
+    CertPathBuilder path_builder(
+        a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU,
+        initial_explicit_policy_, user_initial_policy_set_,
+        initial_policy_mapping_inhibit_, initial_any_policy_inhibit_, &result);
+    path_builder.AddCertIssuerSource(&sync_certs);
+
+    if (insufficient_limit) {
+      // Set a deadline one millisecond in the past. Path building should fail
+      // since the deadline is already past.
+      path_builder.SetDeadline(base::TimeTicks::Now() -
+                               base::TimeDelta::FromMilliseconds(1));
+    } else {
+      // The other tests in this file exercise the case that |SetDeadline|
+      // isn't called. Therefore set a sufficient limit for the path to be
+      // found.
+      path_builder.SetDeadline(base::TimeTicks::Now() +
+                               base::TimeDelta::FromDays(1));
+    }
+
+    path_builder.Run();
+
+    EXPECT_EQ(!insufficient_limit, result.HasValidPath());
+    EXPECT_EQ(insufficient_limit, result.exceeded_deadline);
+  }
+}
+
 class PathBuilderKeyRolloverTest : public ::testing::Test {
  public:
   PathBuilderKeyRolloverTest()
diff --git a/net/nqe/network_congestion_analyzer.cc b/net/nqe/network_congestion_analyzer.cc
index 0401585..462a7aa 100644
--- a/net/nqe/network_congestion_analyzer.cc
+++ b/net/nqe/network_congestion_analyzer.cc
@@ -2,8 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <algorithm>
+
 #include <net/nqe/network_congestion_analyzer.h>
 
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+
 namespace net {
 
 namespace nqe {
@@ -12,6 +17,7 @@
 
 NetworkCongestionAnalyzer::NetworkCongestionAnalyzer()
     : recent_active_hosts_count_(0u) {}
+
 NetworkCongestionAnalyzer::~NetworkCongestionAnalyzer() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 }
@@ -21,6 +27,51 @@
   return recent_active_hosts_count_;
 }
 
+void NetworkCongestionAnalyzer::NotifyStartTransaction(
+    const URLRequest& request) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Starts tracking the peak queueing delay after |request| starts.
+  TrackPeakQueueingDelayBegin(&request);
+}
+
+void NetworkCongestionAnalyzer::NotifyRequestCompleted(
+    const URLRequest& request) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Ends tracking of the peak queueing delay.
+  base::Optional<base::TimeDelta> peak_observed_delay =
+      TrackPeakQueueingDelayEnd(&request);
+  if (peak_observed_delay.has_value()) {
+    UMA_HISTOGRAM_MEDIUM_TIMES("ResourceScheduler.PeakObservedQueueingDelay",
+                               peak_observed_delay.value());
+  }
+}
+
+void NetworkCongestionAnalyzer::TrackPeakQueueingDelayBegin(
+    const URLRequest* request) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Returns if |request| has already been tracked.
+  if (request_peak_delay_.find(request) != request_peak_delay_.end())
+    return;
+
+  request_peak_delay_[request] = base::nullopt;
+}
+
+base::Optional<base::TimeDelta>
+NetworkCongestionAnalyzer::TrackPeakQueueingDelayEnd(
+    const URLRequest* request) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  auto request_delay = request_peak_delay_.find(request);
+  if (request_delay == request_peak_delay_.end())
+    return base::nullopt;
+
+  base::Optional<base::TimeDelta> peak_delay = request_delay->second;
+  request_peak_delay_.erase(request_delay);
+  return peak_delay;
+}
+
 void NetworkCongestionAnalyzer::ComputeRecentQueueingDelay(
     const std::map<nqe::internal::IPHash, nqe::internal::CanonicalStats>&
         recent_rtt_stats,
@@ -63,6 +114,16 @@
   int32_t delay_ms =
       delay_sample_sum / static_cast<int>(recent_active_hosts_count_);
   recent_queueing_delay_ = base::TimeDelta::FromMilliseconds(delay_ms);
+
+  // Updates the peak queueing delay for all tracked in-flight requests.
+  for (auto& it : request_peak_delay_) {
+    if (it.second.has_value()) {
+      it.second = std::max(it.second.value(), recent_queueing_delay_);
+    } else {
+      it.second = recent_queueing_delay_;
+    }
+  }
+
   if (recent_downlink_per_packet_time_ms_ != base::nullopt) {
     recent_queue_length_ = static_cast<float>(delay_ms) /
                            recent_downlink_per_packet_time_ms_.value();
diff --git a/net/nqe/network_congestion_analyzer.h b/net/nqe/network_congestion_analyzer.h
index f00fbcb..0985baf 100644
--- a/net/nqe/network_congestion_analyzer.h
+++ b/net/nqe/network_congestion_analyzer.h
@@ -17,6 +17,7 @@
 #include "net/nqe/network_quality.h"
 #include "net/nqe/network_quality_estimator_util.h"
 #include "net/nqe/observation_buffer.h"
+#include "net/nqe/throughput_analyzer.h"
 
 namespace net {
 
@@ -36,6 +37,12 @@
   // computing the recent queueing delay. These hosts are recent active hosts.
   size_t GetActiveHostsCount() const;
 
+  // Notifies |this| that the headers of |request| are about to be sent.
+  void NotifyStartTransaction(const URLRequest& request);
+
+  // Notifies |this| that the response body of |request| has been received.
+  void NotifyRequestCompleted(const URLRequest& request);
+
   // Computes the recent queueing delay. Records the observed queueing delay
   // samples for all recent active hosts. The mean queueing delay is recorded in
   // |recent_queueing_delay_|. |recent_rtt_stats| has all canonical statistic
@@ -61,6 +68,15 @@
   }
 
  private:
+  // Starts tracking the peak queueing delay for |request|.
+  void TrackPeakQueueingDelayBegin(const URLRequest* request);
+
+  // Returns the peak queueing delay observed by |request|. Also removes the
+  // record that belongs to |request| in the map. If the result is unavailable,
+  // returns nullopt.
+  base::Optional<base::TimeDelta> TrackPeakQueueingDelayEnd(
+      const URLRequest* request);
+
   // Sets the |recent_downlink_throughput_kbps_| with the estimated downlink
   // throughput in kbps. Also, computes the time frame (in millisecond) to
   // transmit one TCP packet (1500 Bytes) under this downlink throughput.
@@ -83,6 +99,15 @@
   // The estimate of queueing delay induced by packet queue.
   base::TimeDelta recent_queueing_delay_;
 
+  // Mapping between URL requests to the observed queueing delay observations.
+  // The default value is nullopt.
+  typedef std::unordered_map<const URLRequest*, base::Optional<base::TimeDelta>>
+      RequestPeakDelay;
+
+  // This map maintains the mapping from in-flight URL requests to the peak
+  // queueing delay observed by requests.
+  RequestPeakDelay request_peak_delay_;
+
   // Counts the number of hosts involved in the last attempt of computing the
   // recent queueing delay.
   size_t recent_active_hosts_count_;
diff --git a/net/nqe/network_quality_estimator.cc b/net/nqe/network_quality_estimator.cc
index 5ac9dbc..4c2af1d3 100644
--- a/net/nqe/network_quality_estimator.cc
+++ b/net/nqe/network_quality_estimator.cc
@@ -276,6 +276,7 @@
     MaybeComputeEffectiveConnectionType();
   }
   throughput_analyzer_->NotifyStartTransaction(request);
+  network_congestion_analyzer_.NotifyStartTransaction(request);
 }
 
 bool NetworkQualityEstimator::IsHangingRequest(
@@ -390,6 +391,7 @@
     return;
 
   throughput_analyzer_->NotifyRequestCompleted(request);
+  network_congestion_analyzer_.NotifyRequestCompleted(request);
 }
 
 void NetworkQualityEstimator::NotifyURLRequestDestroyed(
diff --git a/net/quic/quic_network_transaction_unittest.cc b/net/quic/quic_network_transaction_unittest.cc
index f5b83e7..810b0f2 100644
--- a/net/quic/quic_network_transaction_unittest.cc
+++ b/net/quic/quic_network_transaction_unittest.cc
@@ -2666,11 +2666,6 @@
 // Verify that if a QUIC connection RTOs, the QuicHttpStream will
 // return QUIC_PROTOCOL_ERROR.
 TEST_P(QuicNetworkTransactionTest, TooManyRtosAfterHandshakeConfirmed) {
-  if (version_.transport_version == quic::QUIC_VERSION_99) {
-    // TODO(rch): Re-enable.
-    return;
-  }
-
   session_params_.retry_without_alt_svc_on_quic_errors = false;
   session_params_.quic_connection_options.push_back(quic::k5RTO);
 
@@ -2688,37 +2683,64 @@
           priority, GetRequestHeaders("GET", "https", "/"), 0, nullptr));
 
   client_maker_.SetEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE);
-  quic_data.AddWrite(SYNCHRONOUS, client_maker_.MakeInitialSettingsPacket(2));
-  // TLP 1
-  quic_data.AddWrite(SYNCHRONOUS,
-                     client_maker_.MakeRetransmissionPacket(1, 3, true));
-  // TLP 2
-  quic_data.AddWrite(SYNCHRONOUS,
-                     client_maker_.MakeRetransmissionPacket(2, 4, true));
-  // RTO 1
-  quic_data.AddWrite(SYNCHRONOUS,
-                     client_maker_.MakeRetransmissionPacket(1, 5, true));
-  quic_data.AddWrite(SYNCHRONOUS,
-                     client_maker_.MakeRetransmissionPacket(2, 6, true));
-  // RTO 2
-  quic_data.AddWrite(SYNCHRONOUS,
-                     client_maker_.MakeRetransmissionPacket(1, 7, true));
-  quic_data.AddWrite(SYNCHRONOUS,
-                     client_maker_.MakeRetransmissionPacket(2, 8, true));
-  // RTO 3
-  quic_data.AddWrite(SYNCHRONOUS,
-                     client_maker_.MakeRetransmissionPacket(1, 9, true));
-  quic_data.AddWrite(SYNCHRONOUS,
-                     client_maker_.MakeRetransmissionPacket(2, 10, true));
-  // RTO 4
-  quic_data.AddWrite(SYNCHRONOUS,
-                     client_maker_.MakeRetransmissionPacket(1, 11, true));
-  quic_data.AddWrite(SYNCHRONOUS,
-                     client_maker_.MakeRetransmissionPacket(2, 12, true));
-  // RTO 5
-  quic_data.AddWrite(SYNCHRONOUS, client_maker_.MakeConnectionClosePacket(
-                                      13, true, quic::QUIC_TOO_MANY_RTOS,
-                                      "5 consecutive retransmission timeouts"));
+  if (version_.transport_version != quic::QUIC_VERSION_99) {
+    quic_data.AddWrite(SYNCHRONOUS, client_maker_.MakeInitialSettingsPacket(2));
+    // TLP 1
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeRetransmissionPacket(1, 3, true));
+    // TLP 2
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeRetransmissionPacket(2, 4, true));
+    // RTO 1
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeRetransmissionPacket(1, 5, true));
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeRetransmissionPacket(2, 6, true));
+    // RTO 2
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeRetransmissionPacket(1, 7, true));
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeRetransmissionPacket(2, 8, true));
+    // RTO 3
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeRetransmissionPacket(1, 9, true));
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeRetransmissionPacket(2, 10, true));
+    // RTO 4
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeRetransmissionPacket(1, 11, true));
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeRetransmissionPacket(2, 12, true));
+    // RTO 5
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeConnectionClosePacket(
+                           13, true, quic::QUIC_TOO_MANY_RTOS,
+                           "5 consecutive retransmission timeouts"));
+  } else {
+    // TLP 1
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeRetransmissionPacket(1, 2, true));
+    // TLP 2
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeRetransmissionPacket(1, 3, true));
+    // RTO 1
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeRetransmissionPacket(1, 4, true));
+    // RTO 2
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeRetransmissionPacket(1, 5, true));
+    // RTO 3
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeRetransmissionPacket(1, 6, true));
+    // RTO 4
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeRetransmissionPacket(1, 7, true));
+    // RTO 5
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeConnectionClosePacket(
+                           8, true, quic::QUIC_TOO_MANY_RTOS,
+                           "5 consecutive retransmission timeouts"));
+  }
 
   quic_data.AddRead(ASYNC, OK);
   quic_data.AddSocketDataToFactory(&socket_factory_);
@@ -2764,11 +2786,6 @@
 // QUIC will not be marked as broken.
 TEST_P(QuicNetworkTransactionTest,
        TooManyRtosAfterHandshakeConfirmedAndStreamReset) {
-  if (version_.transport_version == quic::QUIC_VERSION_99) {
-    // TODO(rch): Re-enable.
-    return;
-  }
-
   session_params_.quic_connection_options.push_back(quic::k5RTO);
 
   // The request will initially go out over QUIC.
@@ -2785,42 +2802,55 @@
           priority, GetRequestHeaders("GET", "https", "/"), 0, nullptr));
 
   client_maker_.SetEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE);
-  quic_data.AddWrite(SYNCHRONOUS, client_maker_.MakeInitialSettingsPacket(2));
+  if (version_.transport_version != quic::QUIC_VERSION_99) {
+    quic_data.AddWrite(SYNCHRONOUS, client_maker_.MakeInitialSettingsPacket(2));
+  }
 
-  quic_data.AddWrite(SYNCHRONOUS,
-                     client_maker_.MakeRstPacket(
-                         3, true, GetNthClientInitiatedBidirectionalStreamId(0),
-                         quic::QUIC_STREAM_CANCELLED));
   if (quic::VersionUsesQpack(version_.transport_version)) {
+    quic_data.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeRstPacket(
+                         2, true, GetNthClientInitiatedBidirectionalStreamId(0),
+                         quic::QUIC_STREAM_CANCELLED));
     // Since the headers are sent on the data stream, when the stream is reset
     // the headers are no longer retransmitted.
+    client_maker_.RemoveSavedStreamFrames(
+        GetNthClientInitiatedBidirectionalStreamId(0));
     // TLP 1
     quic_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeRetransmissionPacket(2, 4, true));
+                       client_maker_.MakeRetransmissionPacket(1, 3, true));
     // TLP 2
     quic_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeRetransmissionPacket(3, 5, true));
+                       client_maker_.MakeRetransmissionPacket(2, 4, true));
     // RTO 1
     quic_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeRetransmissionPacket(2, 6, true));
+                       client_maker_.MakeRetransmissionPacket(1, 5, true));
     quic_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeRetransmissionPacket(3, 7, true));
+                       client_maker_.MakeRetransmissionPacket(2, 6, true));
     // RTO 2
     quic_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeRetransmissionPacket(2, 8, true));
+                       client_maker_.MakeRetransmissionPacket(1, 7, true));
     quic_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeRetransmissionPacket(3, 9, true));
+                       client_maker_.MakeRetransmissionPacket(2, 8, true));
     // RTO 3
     quic_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeRetransmissionPacket(2, 10, true));
+                       client_maker_.MakeRetransmissionPacket(1, 9, true));
     quic_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeRetransmissionPacket(3, 11, true));
+                       client_maker_.MakeRetransmissionPacket(2, 10, true));
     // RTO 4
     quic_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeRetransmissionPacket(2, 12, true));
+                       client_maker_.MakeRetransmissionPacket(1, 11, true));
     quic_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeRetransmissionPacket(3, 13, true));
+                       client_maker_.MakeRetransmissionPacket(2, 12, true));
+    // RTO 5
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeConnectionClosePacket(
+                           13, true, quic::QUIC_TOO_MANY_RTOS,
+                           "5 consecutive retransmission timeouts"));
   } else {
+    quic_data.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeRstPacket(
+                         3, true, GetNthClientInitiatedBidirectionalStreamId(0),
+                         quic::QUIC_STREAM_CANCELLED));
     // TLP 1
     quic_data.AddWrite(SYNCHRONOUS,
                        client_maker_.MakeRetransmissionPacket(1, 4, true));
@@ -2847,11 +2877,11 @@
                        client_maker_.MakeRetransmissionPacket(3, 12, true));
     quic_data.AddWrite(SYNCHRONOUS,
                        client_maker_.MakeRetransmissionPacket(1, 13, true));
-  }
   // RTO 5
   quic_data.AddWrite(SYNCHRONOUS, client_maker_.MakeConnectionClosePacket(
                                       14, true, quic::QUIC_TOO_MANY_RTOS,
                                       "5 consecutive retransmission timeouts"));
+  }
 
   quic_data.AddRead(ASYNC, OK);
   quic_data.AddSocketDataToFactory(&socket_factory_);
@@ -2900,11 +2930,6 @@
 // Verify that if a QUIC protocol error occurs after the handshake is confirmed
 // the request fails with QUIC_PROTOCOL_ERROR.
 TEST_P(QuicNetworkTransactionTest, ProtocolErrorAfterHandshakeConfirmed) {
-  if (version_.transport_version == quic::QUIC_VERSION_99) {
-    // TODO(rch): Re-enable.
-    return;
-  }
-
   session_params_.retry_without_alt_svc_on_quic_errors = false;
   // The request will initially go out over QUIC.
   MockQuicData quic_data(version_);
@@ -2919,6 +2944,7 @@
     quic_data.AddWrite(SYNCHRONOUS,
                        ConstructInitialSettingsPacket(packet_number++));
   }
+  quic_data.AddRead(ASYNC, ERR_IO_PENDING);  // Pause
   // Peer sending data from an non-existing stream causes this end to raise
   // error and close connection.
   quic_data.AddRead(
@@ -2957,6 +2983,7 @@
       quic::QuicSession::HANDSHAKE_CONFIRMED);
 
   ASSERT_FALSE(quic_data.AllReadDataConsumed());
+  quic_data.Resume();
 
   // Run the QUIC session to completion.
   base::RunLoop().RunUntilIdle();
@@ -3548,11 +3575,6 @@
 // QUIC will be marked as broken.
 TEST_P(QuicNetworkTransactionTest,
        TooManyRtosAfterHandshakeConfirmedAndStreamResetThenBroken) {
-  if (version_.transport_version == quic::QUIC_VERSION_99) {
-    // TODO(rch): Re-enable.
-    return;
-  }
-
   session_params_.mark_quic_broken_when_network_blackholes = true;
   session_params_.quic_connection_options.push_back(quic::k5RTO);
 
@@ -3570,42 +3592,53 @@
           priority, GetRequestHeaders("GET", "https", "/"), 0, nullptr));
 
   client_maker_.SetEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE);
-  quic_data.AddWrite(SYNCHRONOUS, client_maker_.MakeInitialSettingsPacket(2));
-  quic_data.AddWrite(SYNCHRONOUS,
-                     client_maker_.MakeRstPacket(
-                         3, true, GetNthClientInitiatedBidirectionalStreamId(0),
-                         quic::QUIC_STREAM_CANCELLED));
 
   if (quic::VersionUsesQpack(version_.transport_version)) {
+    quic_data.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeRstPacket(
+                         2, true, GetNthClientInitiatedBidirectionalStreamId(0),
+                         quic::QUIC_STREAM_CANCELLED));
     // Since the headers are sent on the data stream, when the stream is reset
     // the headers are no longer retransmitted.
+    client_maker_.RemoveSavedStreamFrames(
+        GetNthClientInitiatedBidirectionalStreamId(0));
     // TLP 1
     quic_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeRetransmissionPacket(2, 4, true));
+                       client_maker_.MakeRetransmissionPacket(1, 3, true));
     // TLP 2
     quic_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeRetransmissionPacket(3, 5, true));
+                       client_maker_.MakeRetransmissionPacket(2, 4, true));
     // RTO 1
     quic_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeRetransmissionPacket(2, 6, true));
+                       client_maker_.MakeRetransmissionPacket(1, 5, true));
     quic_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeRetransmissionPacket(3, 7, true));
+                       client_maker_.MakeRetransmissionPacket(2, 6, true));
     // RTO 2
     quic_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeRetransmissionPacket(2, 8, true));
+                       client_maker_.MakeRetransmissionPacket(1, 7, true));
     quic_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeRetransmissionPacket(3, 9, true));
+                       client_maker_.MakeRetransmissionPacket(2, 8, true));
     // RTO 3
     quic_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeRetransmissionPacket(2, 10, true));
+                       client_maker_.MakeRetransmissionPacket(1, 9, true));
     quic_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeRetransmissionPacket(3, 11, true));
+                       client_maker_.MakeRetransmissionPacket(2, 10, true));
     // RTO 4
     quic_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeRetransmissionPacket(2, 12, true));
+                       client_maker_.MakeRetransmissionPacket(1, 11, true));
     quic_data.AddWrite(SYNCHRONOUS,
-                       client_maker_.MakeRetransmissionPacket(3, 13, true));
+                       client_maker_.MakeRetransmissionPacket(2, 12, true));
+    // RTO 5
+    quic_data.AddWrite(SYNCHRONOUS,
+                       client_maker_.MakeConnectionClosePacket(
+                           13, true, quic::QUIC_TOO_MANY_RTOS,
+                           "5 consecutive retransmission timeouts"));
   } else {
+    quic_data.AddWrite(SYNCHRONOUS, client_maker_.MakeInitialSettingsPacket(2));
+    quic_data.AddWrite(
+        SYNCHRONOUS, client_maker_.MakeRstPacket(
+                         3, true, GetNthClientInitiatedBidirectionalStreamId(0),
+                         quic::QUIC_STREAM_CANCELLED));
     // TLP 1
     quic_data.AddWrite(SYNCHRONOUS,
                        client_maker_.MakeRetransmissionPacket(1, 4, true));
@@ -3632,12 +3665,11 @@
                        client_maker_.MakeRetransmissionPacket(3, 12, true));
     quic_data.AddWrite(SYNCHRONOUS,
                        client_maker_.MakeRetransmissionPacket(1, 13, true));
-  }
-
   // RTO 5
   quic_data.AddWrite(SYNCHRONOUS, client_maker_.MakeConnectionClosePacket(
                                       14, true, quic::QUIC_TOO_MANY_RTOS,
                                       "5 consecutive retransmission timeouts"));
+  }
 
   quic_data.AddRead(ASYNC, OK);
   quic_data.AddSocketDataToFactory(&socket_factory_);
@@ -3688,11 +3720,6 @@
 // retried over TCP and the QUIC will be marked as broken.
 TEST_P(QuicNetworkTransactionTest,
        ProtocolErrorAfterHandshakeConfirmedThenBroken) {
-  if (version_.transport_version == quic::QUIC_VERSION_99) {
-    // TODO(rch): Re-enable.
-    return;
-  }
-
   session_params_.quic_idle_connection_timeout_seconds = 5;
 
   // The request will initially go out over QUIC.
@@ -3703,7 +3730,13 @@
                          1, GetNthClientInitiatedBidirectionalStreamId(0), true,
                          true, GetRequestHeaders("GET", "https", "/")));
   client_maker_.SetEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE);
-  quic_data.AddWrite(SYNCHRONOUS, ConstructInitialSettingsPacket(2));
+  uint64_t packet_number = 2;
+  if (version_.transport_version != quic::QUIC_VERSION_99) {
+    quic_data.AddWrite(SYNCHRONOUS,
+                       ConstructInitialSettingsPacket(packet_number++));
+  }
+  quic_data.AddRead(ASYNC, ERR_IO_PENDING);  // Pause
+
   // Peer sending data from an non-existing stream causes this end to raise
   // error and close connection.
   quic_data.AddRead(
@@ -3711,10 +3744,10 @@
                  1, false, GetNthClientInitiatedBidirectionalStreamId(47),
                  quic::QUIC_STREAM_LAST_ERROR));
   std::string quic_error_details = "Data for nonexistent stream";
-  quic_data.AddWrite(SYNCHRONOUS,
-                     ConstructClientAckAndConnectionClosePacket(
-                         3, quic::QuicTime::Delta::Zero(), 1, 1, 1,
-                         quic::QUIC_INVALID_STREAM_ID, quic_error_details));
+  quic_data.AddWrite(
+      SYNCHRONOUS, ConstructClientAckAndConnectionClosePacket(
+                       packet_number++, quic::QuicTime::Delta::Zero(), 1, 1, 1,
+                       quic::QUIC_INVALID_STREAM_ID, quic_error_details));
   quic_data.AddSocketDataToFactory(&socket_factory_);
 
   // After that fails, it will be resent via TCP.
@@ -3754,6 +3787,7 @@
   // Explicitly confirm the handshake.
   crypto_client_stream_factory_.last_stream()->SendOnCryptoHandshakeEvent(
       quic::QuicSession::HANDSHAKE_CONFIRMED);
+  quic_data.Resume();
 
   // Run the QUIC session to completion.
   base::RunLoop().RunUntilIdle();
@@ -3779,11 +3813,6 @@
 // request is reset from, then QUIC will be marked as broken and the request
 // retried over TCP.
 TEST_P(QuicNetworkTransactionTest, ResetAfterHandshakeConfirmedThenBroken) {
-  if (version_.transport_version == quic::QUIC_VERSION_99) {
-    // TODO(rch): Re-enable.
-    return;
-  }
-
   // The request will initially go out over QUIC.
   MockQuicData quic_data(version_);
   spdy::SpdyPriority priority =
@@ -3797,7 +3826,10 @@
           priority, GetRequestHeaders("GET", "https", "/"), 0, nullptr));
 
   client_maker_.SetEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE);
-  quic_data.AddWrite(SYNCHRONOUS, client_maker_.MakeInitialSettingsPacket(2));
+  if (version_.transport_version != quic::QUIC_VERSION_99) {
+    quic_data.AddWrite(SYNCHRONOUS, client_maker_.MakeInitialSettingsPacket(2));
+  }
+  quic_data.AddRead(ASYNC, ERR_IO_PENDING);  // Pause
 
   quic_data.AddRead(ASYNC,
                     ConstructServerRstPacket(
@@ -3844,6 +3876,7 @@
   // Explicitly confirm the handshake.
   crypto_client_stream_factory_.last_stream()->SendOnCryptoHandshakeEvent(
       quic::QuicSession::HANDSHAKE_CONFIRMED);
+  quic_data.Resume();
 
   // Run the QUIC session to completion.
   ASSERT_TRUE(quic_data.AllWriteDataConsumed());
diff --git a/net/quic/quic_test_packet_maker.cc b/net/quic/quic_test_packet_maker.cc
index b09dbc0c..9c1f15f 100644
--- a/net/quic/quic_test_packet_maker.cc
+++ b/net/quic/quic_test_packet_maker.cc
@@ -1455,6 +1455,21 @@
       nullptr);
 }
 
+void QuicTestPacketMaker::RemoveSavedStreamFrames(
+    quic::QuicStreamId stream_id) {
+  for (auto& kv : saved_frames_) {
+    auto it = kv.second.begin();
+    while (it != kv.second.end()) {
+      if (it->type == quic::STREAM_FRAME &&
+          it->stream_frame.stream_id == stream_id) {
+        it = kv.second.erase(it);
+      } else {
+        ++it;
+      }
+    }
+  }
+}
+
 void QuicTestPacketMaker::SetEncryptionLevel(quic::EncryptionLevel level) {
   encryption_level_ = level;
     switch (level) {
diff --git a/net/quic/quic_test_packet_maker.h b/net/quic/quic_test_packet_maker.h
index 1518270..7b18e4e 100644
--- a/net/quic/quic_test_packet_maker.h
+++ b/net/quic/quic_test_packet_maker.h
@@ -309,6 +309,9 @@
       uint64_t new_packet_number,
       bool should_include_version);
 
+  // Removes all stream frames associated with |stream_id|.
+  void RemoveSavedStreamFrames(quic::QuicStreamId stream_id);
+
   void SetEncryptionLevel(quic::EncryptionLevel level);
 
   spdy::SpdyHeaderBlock GetRequestHeaders(const std::string& method,
diff --git a/remoting/protocol/webrtc_audio_stream.cc b/remoting/protocol/webrtc_audio_stream.cc
index 1f198be8..703ecda 100644
--- a/remoting/protocol/webrtc_audio_stream.cc
+++ b/remoting/protocol/webrtc_audio_stream.cc
@@ -22,12 +22,7 @@
 const char kAudioTrackLabel[] = "system_audio";
 
 WebrtcAudioStream::WebrtcAudioStream() = default;
-
-WebrtcAudioStream::~WebrtcAudioStream() {
-  if (audio_sender_) {
-    peer_connection_->RemoveTrack(audio_sender_.get());
-  }
-}
+WebrtcAudioStream::~WebrtcAudioStream() = default;
 
 void WebrtcAudioStream::Start(
     scoped_refptr<base::SingleThreadTaskRunner> audio_task_runner,
@@ -47,13 +42,15 @@
       peer_connection_factory->CreateAudioTrack(kAudioTrackLabel,
                                                 source_adapter_.get());
 
-  // value() DCHECKs if AddTrack() fails, which only happens if a track was
-  // already added with the stream label.
-  audio_sender_ =
-      peer_connection_->AddTrack(audio_track.get(), {kAudioStreamLabel})
-          .value();
+  webrtc::RtpTransceiverInit init;
+  init.stream_ids = {kAudioStreamLabel};
 
-  webrtc_transport->OnAudioSenderCreated(audio_sender_);
+  // value() DCHECKs if AddTransceiver() fails, which only happens if a track
+  // was already added with the stream label.
+  auto transceiver =
+      peer_connection_->AddTransceiver(audio_track, init).value();
+
+  webrtc_transport->OnAudioTransceiverCreated(transceiver);
 }
 
 void WebrtcAudioStream::Pause(bool pause) {
diff --git a/remoting/protocol/webrtc_audio_stream.h b/remoting/protocol/webrtc_audio_stream.h
index 13baf221..0865f01 100644
--- a/remoting/protocol/webrtc_audio_stream.h
+++ b/remoting/protocol/webrtc_audio_stream.h
@@ -18,7 +18,6 @@
 
 namespace webrtc {
 class PeerConnectionInterface;
-class RtpSenderInterface;
 }  // namespace webrtc
 
 namespace remoting {
@@ -44,7 +43,6 @@
   scoped_refptr<WebrtcAudioSourceAdapter> source_adapter_;
 
   scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
-  rtc::scoped_refptr<webrtc::RtpSenderInterface> audio_sender_;
 
   DISALLOW_COPY_AND_ASSIGN(WebrtcAudioStream);
 };
diff --git a/remoting/protocol/webrtc_transport.cc b/remoting/protocol/webrtc_transport.cc
index cbea8e8..1e4275a5 100644
--- a/remoting/protocol/webrtc_transport.cc
+++ b/remoting/protocol/webrtc_transport.cc
@@ -576,14 +576,12 @@
   }
 }
 
-void WebrtcTransport::OnAudioSenderCreated(
-    rtc::scoped_refptr<webrtc::RtpSenderInterface> sender) {}
+void WebrtcTransport::OnAudioTransceiverCreated(
+    rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver) {}
 
-void WebrtcTransport::OnVideoSenderCreated(
-    rtc::scoped_refptr<webrtc::RtpSenderInterface> sender) {
-  // TODO(lambroslambrou): Store the VideoSender here, instead of looping over
-  // all Senders in GetVideoSender().
-  DCHECK_EQ(GetVideoSender(), sender);
+void WebrtcTransport::OnVideoTransceiverCreated(
+    rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver) {
+  video_transceiver_ = transceiver;
   SetSenderBitrates(MaxBitrateForConnection());
 }
 
@@ -956,13 +954,7 @@
 
 rtc::scoped_refptr<webrtc::RtpSenderInterface>
 WebrtcTransport::GetVideoSender() {
-  auto senders = peer_connection()->GetSenders();
-  for (rtc::scoped_refptr<webrtc::RtpSenderInterface> sender : senders) {
-    if (sender->media_type() == cricket::MediaType::MEDIA_TYPE_VIDEO) {
-      return sender;
-    }
-  }
-  return nullptr;
+  return video_transceiver_ ? video_transceiver_->sender() : nullptr;
 }
 
 }  // namespace protocol
diff --git a/remoting/protocol/webrtc_transport.h b/remoting/protocol/webrtc_transport.h
index b58c6684..d3a98f1 100644
--- a/remoting/protocol/webrtc_transport.h
+++ b/remoting/protocol/webrtc_transport.h
@@ -87,15 +87,13 @@
 
   void ApplySessionOptions(const SessionOptions& options);
 
-  // Called when a new AudioSender has been created from
-  // PeerConnection::AddTrack().
-  void OnAudioSenderCreated(
-      rtc::scoped_refptr<webrtc::RtpSenderInterface> sender);
+  // Called when a new audio transceiver has been created by the PeerConnection.
+  void OnAudioTransceiverCreated(
+      rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver);
 
-  // Called when a new VideoSender has been created from
-  // PeerConnection::AddTrack().
-  void OnVideoSenderCreated(
-      rtc::scoped_refptr<webrtc::RtpSenderInterface> sender);
+  // Called when a new video transceiver has been created by the PeerConnection.
+  void OnVideoTransceiverCreated(
+      rtc::scoped_refptr<webrtc::RtpTransceiverInterface> transceiver);
 
  private:
   // PeerConnectionWrapper is responsible for PeerConnection creation,
@@ -184,6 +182,8 @@
 
   std::string preferred_video_codec_;
 
+  rtc::scoped_refptr<webrtc::RtpTransceiverInterface> video_transceiver_;
+
   base::WeakPtrFactory<WebrtcTransport> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(WebrtcTransport);
diff --git a/remoting/protocol/webrtc_video_stream.cc b/remoting/protocol/webrtc_video_stream.cc
index 6e4331d..b5503a9e 100644
--- a/remoting/protocol/webrtc_video_stream.cc
+++ b/remoting/protocol/webrtc_video_stream.cc
@@ -100,9 +100,6 @@
 
 WebrtcVideoStream::~WebrtcVideoStream() {
   DCHECK(thread_checker_.CalledOnValidThread());
-  if (video_sender_) {
-    peer_connection_->RemoveTrack(video_sender_.get());
-  }
 }
 
 void WebrtcVideoStream::Start(
@@ -135,12 +132,15 @@
   rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track =
       peer_connection_factory->CreateVideoTrack(kVideoLabel, src);
 
-  // value() DCHECKs if AddTrack() fails, which only happens if a track was
-  // already added with the stream label.
-  video_sender_ =
-      peer_connection_->AddTrack(video_track.get(), {kStreamLabel}).value();
+  webrtc::RtpTransceiverInit init;
+  init.stream_ids = {kStreamLabel};
 
-  webrtc_transport_->OnVideoSenderCreated(video_sender_);
+  // value() DCHECKs if AddTransceiver() fails, which only happens if a track
+  // was already added with the stream label.
+  auto transceiver =
+      peer_connection_->AddTransceiver(video_track, init).value();
+
+  webrtc_transport_->OnVideoTransceiverCreated(transceiver);
 
   scheduler_.reset(new WebrtcFrameSchedulerSimple(session_options_));
   scheduler_->Start(
diff --git a/remoting/protocol/webrtc_video_stream.h b/remoting/protocol/webrtc_video_stream.h
index 07a3cfc..40457e0 100644
--- a/remoting/protocol/webrtc_video_stream.h
+++ b/remoting/protocol/webrtc_video_stream.h
@@ -27,7 +27,6 @@
 
 namespace webrtc {
 class PeerConnectionInterface;
-class RtpSenderInterface;
 }  // namespace webrtc
 
 namespace remoting {
@@ -88,7 +87,6 @@
   scoped_refptr<InputEventTimestampsSource> event_timestamps_source_;
 
   scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
-  rtc::scoped_refptr<webrtc::RtpSenderInterface> video_sender_;
 
   HostVideoStatsDispatcher video_stats_dispatcher_;
 
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index 98dc151..6bb9205 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -6993,6 +6993,54 @@
         },
         "test": "content_browsertests"
       }
+    ],
+    "isolated_scripts": [
+      {
+        "args": [
+          "pixel",
+          "--show-stdout",
+          "--browser=android-chromium",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc \"--use-vulkan=native --disable-vulkan-fallback-to-gl-for-testing --enable-features=UseSkiaRenderer\"",
+          "--dont-restore-color-profile-after-test",
+          "--refimg-cloud-storage-bucket",
+          "chromium-gpu-archive/reference-images",
+          "--os-type",
+          "android",
+          "--build-revision",
+          "${got_revision}",
+          "--test-machine-name",
+          "${buildername}"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "pixel_test",
+        "non_precommit_args": [
+          "--upload-refimg-to-cloud-storage"
+        ],
+        "precommit_args": [
+          "--download-refimg-from-cloud-storage"
+        ],
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "device_os": "P",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android",
+              "pool": "Chrome-GPU"
+            }
+          ],
+          "idempotent": false
+        }
+      }
     ]
   },
   "Android FYI dEQP Release (Nexus 5X)": {
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index f292e3a7..d2ad2615 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -3600,6 +3600,30 @@
       },
     },
 
+    'gpu_skia_renderer_vulkan_telemetry_tests': {
+      'pixel': {
+        'name': 'pixel_test',
+        'args': [
+          '--dont-restore-color-profile-after-test',
+          '--refimg-cloud-storage-bucket',
+          'chromium-gpu-archive/reference-images',
+          '--os-type',
+          '${os_type}',
+          '--build-revision',
+          '${got_revision}',
+          '--test-machine-name',
+          '${buildername}',
+          '--extra-browser-args="--use-vulkan=native --disable-vulkan-fallback-to-gl-for-testing --enable-features=UseSkiaRenderer"',
+        ],
+        'non_precommit_args': [
+          '--upload-refimg-to-cloud-storage',
+        ],
+        'precommit_args': [
+          '--download-refimg-from-cloud-storage',
+        ],
+      },
+    },
+
     'gpu_swiftshader_gtests': {
       'swiftshader_unittests': {
       },
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index b1bc2cb..45f6389 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -2477,6 +2477,7 @@
         ],
         'test_suites': {
           'gtest_tests': 'gpu_skia_renderer_vulkan_gtests',
+          'gpu_telemetry_tests': 'gpu_skia_renderer_vulkan_telemetry_tests',
         },
       },
       'Android FYI dEQP Release (Nexus 5X)': {
diff --git a/third_party/blink/public/mojom/use_counter/OWNERS b/third_party/blink/public/mojom/use_counter/OWNERS
index 0dbbfa1..7f417e2 100644
--- a/third_party/blink/public/mojom/use_counter/OWNERS
+++ b/third_party/blink/public/mojom/use_counter/OWNERS
@@ -1,6 +1,12 @@
-loonybear@chromium.org
+dcheng@chromium.org
 
 # COMPONENT: Blink>UseCounter
 
 per-file *.mojom=set noparent
 per-file *.mojom=file://ipc/SECURITY_OWNERS
+
+# Changes in css_property_id.mojom are always just mechanical updates to
+# kMaximumCSSSampleId, hence we allow all committers to review this file.
+# See also UseCounterHelperTest.MaximumCSSSampleId, which verifies the
+# correctness of kMaximumCSSSampleId.
+per-file css_property_id.mojom=*
diff --git a/third_party/blink/renderer/bindings/scripts/generate_web_idl_collection.py b/third_party/blink/renderer/bindings/scripts/generate_web_idl_collection.py
index 323d0f9c..4b31740 100644
--- a/third_party/blink/renderer/bindings/scripts/generate_web_idl_collection.py
+++ b/third_party/blink/renderer/bindings/scripts/generate_web_idl_collection.py
@@ -11,6 +11,7 @@
 import optparse
 import utilities
 from web_idl.collector import Collector
+from web_idl.collection import Collection
 
 
 _VALID_COMPONENTS = ("core", "modules")
@@ -42,7 +43,7 @@
     parser = blink_idl_parser.BlinkIDLParser()
     collector = Collector(component=options.component, parser=parser)
     collector.collect_from_idl_files(idl_file_names)
-    utilities.write_pickle_file(options.output, collector.get_collection())
+    Collection.write_to_file(collector.get_collection(), options.output)
 
 
 if __name__ == '__main__':
diff --git a/third_party/blink/renderer/bindings/scripts/scripts.gni b/third_party/blink/renderer/bindings/scripts/scripts.gni
index de96ea8a..31e7e2f 100644
--- a/third_party/blink/renderer/bindings/scripts/scripts.gni
+++ b/third_party/blink/renderer/bindings/scripts/scripts.gni
@@ -61,6 +61,7 @@
                                   "web_idl/argument.py",
                                   "web_idl/attribute.py",
                                   "web_idl/callback_function.py",
+                                  "web_idl/collection.py",
                                   "web_idl/common.py",
                                   "web_idl/constant.py",
                                   "web_idl/dictionary.py",
diff --git a/third_party/blink/renderer/bindings/scripts/web_idl/collection.py b/third_party/blink/renderer/bindings/scripts/web_idl/collection.py
index 751e630..2fc705d 100644
--- a/third_party/blink/renderer/bindings/scripts/web_idl/collection.py
+++ b/third_party/blink/renderer/bindings/scripts/web_idl/collection.py
@@ -2,6 +2,10 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import pickle
+import idl_parser
+from .common import Component
+
 
 class Collection(object):
     """
@@ -10,14 +14,29 @@
     """
 
     def __init__(self, component=None):
+        assert component is None or isinstance(component, Component)
         self._asts = []
         self._component = component
 
     def add_ast(self, ast):
+        assert isinstance(ast, idl_parser.idl_node.IDLNode)
         assert ast.GetClass() == 'File', (
             'Root node of an AST must be a File node., but is %s.' % ast.GetClass())
         self._asts.append(ast)
 
+    @staticmethod
+    def load_from_file(filepath):
+        with open(filepath, 'r') as pickle_file:
+            collection = pickle.load(pickle_file)
+        assert isinstance(collection, Collection)
+        return collection
+
+    @staticmethod
+    def write_to_file(collection, filepath):
+        assert isinstance(collection, Collection)
+        with open(filepath, 'w') as pickle_file:
+            pickle.dump(collection, pickle_file)
+
     @property
     def asts(self):
         return self._asts
diff --git a/third_party/blink/renderer/bindings/scripts/web_idl/common.py b/third_party/blink/renderer/bindings/scripts/web_idl/common.py
index 97f6da7..d17d109 100644
--- a/third_party/blink/renderer/bindings/scripts/web_idl/common.py
+++ b/third_party/blink/renderer/bindings/scripts/web_idl/common.py
@@ -2,7 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import exceptions
+import copy
 
 from .extended_attribute import ExtendedAttributes
 from .exposure import Exposure
@@ -47,7 +47,9 @@
         return self._extended_attributes
 
 
-CodeGeneratorInfo = dict
+class CodeGeneratorInfo(dict):
+    def make_copy(self):
+        return copy.deepcopy(self)
 
 
 class WithCodeGeneratorInfo(object):
@@ -102,9 +104,22 @@
     fragments that are involved into this object.
     """
 
-    def __init__(self, component):
-        assert isinstance(component, Component)
-        self._components = [component]
+    def __init__(self, component=None, components=None):
+        """
+        Args:
+            component:
+            components: Either of |component| or |components| must be given.
+        """
+        assert component is None or isinstance(component, Component)
+        assert components is None or (isinstance(components, (list, tuple))
+                                      and all(
+                                          isinstance(component, Component)
+                                          for component in components))
+        assert (component or components) and not (component and components)
+        if components:
+            self._components = list(components)
+        else:
+            self._components = [component]
 
     @property
     def components(self):
@@ -114,6 +129,13 @@
         """
         return tuple(self._components)
 
+    def add_components(self, components):
+        assert isinstance(components, (list, tuple)) and all(
+            isinstance(component, Component) for component in components)
+        for component in components:
+            if component not in self.components:
+                self._components.append(component)
+
 
 class DebugInfo(object):
     """Provides information useful for debugging."""
@@ -136,6 +158,12 @@
                     text += ':{}'.format(self._column_number)
             return text
 
+        def make_copy(self):
+            return DebugInfo.Location(
+                filepath=self._filepath,
+                line_number=self._line_number,
+                column_number=self._column_number)
+
         @property
         def filepath(self):
             return self._filepath
@@ -148,13 +176,24 @@
         def column_number(self):
             return self._column_number
 
-    def __init__(self, location=None):
+    def __init__(self, location=None, locations=None):
         assert location is None or isinstance(location, DebugInfo.Location)
-        location = location or DebugInfo.Location()
+        assert locations is None or (isinstance(
+            locations, (list, tuple)) and all(
+                isinstance(location, DebugInfo.Location)
+                for location in locations))
+        assert not (location and locations)
         # The first entry is the primary location, e.g. location of non-partial
         # interface.  The rest is secondary locations, e.g. location of partial
         # interfaces and mixins.
-        self._locations = [location]
+        if locations:
+            self._locations = locations
+        else:
+            self._locations = [location or DebugInfo.Location()]
+
+    def make_copy(self):
+        return DebugInfo(
+            locations=map(DebugInfo.Location.make_copy, self._locations))
 
     @property
     def location(self):
@@ -173,6 +212,11 @@
         """
         return tuple(self._locations)
 
+    def add_locations(self, locations):
+        assert isinstance(locations, (list, tuple)) and all(
+            isinstance(location, DebugInfo.Location) for location in locations)
+        self._locations.extend(locations)
+
 
 class WithDebugInfo(object):
     """WithDebugInfo class is an interface that its inheritances can have
diff --git a/third_party/blink/renderer/bindings/scripts/web_idl/dictionary.py b/third_party/blink/renderer/bindings/scripts/web_idl/dictionary.py
index ad35a13..3e871e8 100644
--- a/third_party/blink/renderer/bindings/scripts/web_idl/dictionary.py
+++ b/third_party/blink/renderer/bindings/scripts/web_idl/dictionary.py
@@ -10,6 +10,7 @@
 from .common import WithIdentifier
 from .identifier_ir_map import IdentifierIRMap
 from .idl_member import IdlMember
+from .idl_reference_proxy import RefByIdFactory
 from .idl_types import IdlType
 from .user_defined_type import UserDefinedType
 from .values import DefaultValue
@@ -24,12 +25,15 @@
         def __init__(self,
                      identifier,
                      is_partial,
+                     inherited=None,
                      own_members=None,
                      extended_attributes=None,
                      code_generator_info=None,
                      component=None,
+                     components=None,
                      debug_info=None):
             assert isinstance(is_partial, bool)
+            assert inherited is None or RefByIdFactory.is_reference(inherited)
             assert isinstance(own_members, (list, tuple)) and all(
                 isinstance(member, DictionaryMember.IR)
                 for member in own_members)
@@ -39,11 +43,26 @@
             IdentifierIRMap.IR.__init__(self, identifier=identifier, kind=kind)
             WithExtendedAttributes.__init__(self, extended_attributes)
             WithCodeGeneratorInfo.__init__(self, code_generator_info)
-            WithComponent.__init__(self, component)
+            WithComponent.__init__(
+                self, component=component, components=components)
             WithDebugInfo.__init__(self, debug_info)
 
+            self.is_partial = is_partial
+            self.inherited = inherited
             self.own_members = own_members
 
+        def make_copy(self):
+            return Dictionary.IR(
+                identifier=self.identifier,
+                is_partial=self.is_partial,
+                inherited=self.inherited,
+                own_members=map(DictionaryMember.IR.make_copy,
+                                self.own_members),
+                extended_attributes=self.extended_attributes.make_copy(),
+                code_generator_info=self.code_generator_info.make_copy(),
+                components=self.components,
+                debug_info=self.debug_info.make_copy())
+
     @property
     def inherited_dictionary(self):
         """
@@ -86,6 +105,7 @@
                      extended_attributes=None,
                      code_generator_info=None,
                      component=None,
+                     components=None,
                      debug_info=None):
             assert isinstance(idl_type, IdlType)
             assert isinstance(is_required, bool)
@@ -95,13 +115,25 @@
             WithIdentifier.__init__(self, identifier)
             WithExtendedAttributes.__init__(self, extended_attributes)
             WithCodeGeneratorInfo.__init__(self, code_generator_info)
-            WithComponent.__init__(self, component)
+            WithComponent.__init__(
+                self, component=component, components=components)
             WithDebugInfo.__init__(self, debug_info)
 
             self.idl_type = idl_type
             self.is_required = is_required
             self.default_value = default_value
 
+        def make_copy(self):
+            return DictionaryMember.IR(
+                identifier=self.identifier,
+                idl_type=self.idl_type,
+                is_required=self.is_required,
+                default_value=self.default_value,
+                extended_attributes=self.extended_attributes.make_copy(),
+                code_generator_info=self.code_generator_info.make_copy(),
+                components=self.components,
+                debug_info=self.debug_info.make_copy())
+
     @property
     def idl_type(self):
         """
diff --git a/third_party/blink/renderer/bindings/scripts/web_idl/extended_attribute.py b/third_party/blink/renderer/bindings/scripts/web_idl/extended_attribute.py
index 3e2b834..f7c240d 100644
--- a/third_party/blink/renderer/bindings/scripts/web_idl/extended_attribute.py
+++ b/third_party/blink/renderer/bindings/scripts/web_idl/extended_attribute.py
@@ -73,6 +73,13 @@
         # Should not reach here.
         assert False, 'Unknown format: {}'.format(self._format)
 
+    def make_copy(self):
+        return ExtendedAttribute(
+            key=self._key,
+            values=self._values,
+            arguments=self._arguments,
+            name=self._name)
+
     @property
     def key(self):
         """
@@ -175,6 +182,9 @@
         attrs = [str(attr) for attr in self]
         return '[{}]'.format(', '.join(attrs))
 
+    def make_copy(self):
+        return ExtendedAttributes(map(ExtendedAttribute.make_copy, self))
+
     def get(self, key):
         """
         Returns an exnteded attribute whose key is |key|.
diff --git a/third_party/blink/renderer/bindings/scripts/web_idl/idl_compiler.py b/third_party/blink/renderer/bindings/scripts/web_idl/idl_compiler.py
index 898e5067..d7a5170 100644
--- a/third_party/blink/renderer/bindings/scripts/web_idl/idl_compiler.py
+++ b/third_party/blink/renderer/bindings/scripts/web_idl/idl_compiler.py
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+from .identifier_ir_map import IdentifierIRMap
+
 
 class IdlCompiler(object):
     """
@@ -45,9 +47,30 @@
         """
         Merges partial definitions with corresponding non-partial definitions.
         """
-        self._ir_map.move_to_new_phase()
+        self._merge_partial_dictionaries()
         # TODO(peria): Implement this. http:///crbug.com/839389
 
+    def _merge_partial_dictionaries(self):
+        old_dictionaries = self._ir_map.find_by_kind(
+            IdentifierIRMap.IR.Kind.DICTIONARY)
+        old_partial_dictionaries = self._ir_map.find_by_kind(
+            IdentifierIRMap.IR.Kind.PARTIAL_DICTIONARY)
+
+        self._ir_map.move_to_new_phase()
+
+        for identifier, old_dictionary in old_dictionaries.iteritems():
+            new_dictionary = old_dictionary.make_copy()
+            for partial_dictionary in old_partial_dictionaries.get(
+                    identifier, []):
+                new_dictionary.add_components(partial_dictionary.components)
+                new_dictionary.debug_info.add_locations(
+                    partial_dictionary.debug_info.all_locations)
+                new_dictionary.own_members.extend([
+                    member.make_copy()
+                    for member in partial_dictionary.own_members
+                ])
+            self._ir_map.add(new_dictionary)
+
     def _merge_mixins(self):
         """
         Merges mixins with interfaces that connected with includes statements.
diff --git a/third_party/blink/renderer/bindings/scripts/web_idl/ir_builder.py b/third_party/blink/renderer/bindings/scripts/web_idl/ir_builder.py
index 40beebc..c9e9bad 100644
--- a/third_party/blink/renderer/bindings/scripts/web_idl/ir_builder.py
+++ b/third_party/blink/renderer/bindings/scripts/web_idl/ir_builder.py
@@ -2,17 +2,9 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-try:
-    import _pickle as pickle
-except ImportError:
-    try:
-        import cPickle as pickle
-    except ImportError:
-        import pickle
-
-import idl_parser
 from .callback_function import CallbackFunction
 from .callback_interface import CallbackInterface
+from .collection import Collection
 from .common import DebugInfo
 from .dictionary import Dictionary
 from .dictionary import DictionaryMember
@@ -51,18 +43,15 @@
     assert callable(register_ir)
 
     for filepath in filepaths:
-        with open(filepath) as pickle_file:
-            asts_per_component = pickle.load(pickle_file)
-            component = asts_per_component.component
-            builder = _IRBuilder(component, create_ref_to_idl_type,
-                                 create_ref_to_idl_def)
+        asts_per_component = Collection.load_from_file(filepath)
+        component = asts_per_component.component
+        builder = _IRBuilder(component, create_ref_to_idl_type,
+                             create_ref_to_idl_def)
 
-            for file_node in asts_per_component.asts:
-                assert isinstance(file_node, idl_parser.idl_node.IDLNode)
-                assert file_node.GetClass() == 'File'
-
-                for top_level_node in file_node.GetChildren():
-                    register_ir(builder.build_top_level_def(top_level_node))
+        for file_node in asts_per_component.asts:
+            assert file_node.GetClass() == 'File'
+            for top_level_node in file_node.GetChildren():
+                register_ir(builder.build_top_level_def(top_level_node))
 
 
 class _IRBuilder(object):
@@ -79,9 +68,9 @@
         assert callable(create_ref_to_idl_type)
         assert callable(create_ref_to_idl_def)
 
-        self.component = component
-        self.create_ref_to_idl_type = create_ref_to_idl_type
-        self.create_ref_to_idl_def = create_ref_to_idl_def
+        self._component = component
+        self._create_ref_to_idl_type = create_ref_to_idl_type
+        self._create_ref_to_idl_def = create_ref_to_idl_def
 
     def build_top_level_def(self, node):
         build_functions = {
@@ -105,7 +94,7 @@
             identifier=node.GetName(),
             is_partial=bool(node.GetProperty('PARTIAL')),
             is_mixin=bool(node.GetProperty('MIXIN')),
-            component=self.component,
+            component=self._component,
             debug_info=self._build_debug_info(node))
         # TODO(peria): Build members and register them in |interface|
         return interface
@@ -114,7 +103,7 @@
         namespace = Namespace.IR(
             identifier=node.GetName(),
             is_partial=bool(node.GetProperty('PARTIAL')),
-            component=self.component,
+            component=self._component,
             debug_info=self._build_debug_info(node))
         # TODO(peria): Build members and register them in |namespace|
         return namespace
@@ -122,20 +111,17 @@
     def _build_dictionary(self, node):
         child_nodes = list(node.GetChildren())
         extended_attributes = self._take_extended_attributes(child_nodes)
-        # TODO(yukishiino): Implement dictionary inheritance.
-        _ = self._take_inheritance(child_nodes)
-        own_members = [
-            self._build_dictionary_member(child) for child in child_nodes
-        ]
+        inherited = self._take_inheritance(child_nodes)
+        own_members = map(self._build_dictionary_member, child_nodes)
 
-        dictionary = Dictionary.IR(
+        return Dictionary.IR(
             identifier=node.GetName(),
             is_partial=bool(node.GetProperty('PARTIAL')),
+            inherited=inherited,
             own_members=own_members,
             extended_attributes=extended_attributes,
-            component=self.component,
+            component=self._component,
             debug_info=self._build_debug_info(node))
-        return dictionary
 
     def _build_dictionary_member(self, node):
         assert node.GetClass() == 'Key'
@@ -152,13 +138,13 @@
             is_required=bool(node.GetProperty('REQUIRED')),
             default_value=default_value,
             extended_attributes=extended_attributes,
-            component=self.component,
+            component=self._component,
             debug_info=self._build_debug_info(node))
 
     def _build_callback_interface(self, node):
         callback_interface = CallbackInterface.IR(
             identifier=node.GetName(),
-            component=self.component,
+            component=self._component,
             debug_info=self._build_debug_info(node))
         # TODO(peria): Build members and register them in |callback_interface|
         return callback_interface
@@ -166,7 +152,7 @@
     def _build_callback_function(self, node):
         callback_function = CallbackFunction.IR(
             identifier=node.GetName(),
-            component=self.component,
+            component=self._component,
             debug_info=self._build_debug_info(node))
         # TODO(peria): Build members and register them in |callback_function|
         return callback_function
@@ -175,7 +161,7 @@
         enumeration = Enumeration.IR(
             identifier=node.GetName(),
             values=[child.GetName() for child in node.GetChildren()],
-            component=self.component,
+            component=self._component,
             debug_info=self._build_debug_info(node))
         return enumeration
 
@@ -187,7 +173,7 @@
         typedef = Typedef.IR(
             identifier=node.GetName(),
             idl_type=idl_type,
-            component=self.component,
+            component=self._component,
             debug_info=self._build_debug_info(node))
         return typedef
 
@@ -195,7 +181,7 @@
         includes = Includes.IR(
             interface_identifier=node.GetName(),
             mixin_identifier=node.GetProperty('REFERENCE'),
-            component=self.component,
+            component=self._component,
             debug_info=self._build_debug_info(node))
         return includes
 
@@ -218,7 +204,7 @@
 
     def _build_inheritance(self, node):
         assert node.GetClass() == 'Inherit'
-        return None
+        return self._create_ref_to_idl_def(node.GetName())
 
     def _build_type(self, node):
         def build_maybe_inner_type(node):
@@ -280,7 +266,7 @@
         def build_reference_type(node):
             identifier = node.GetName()
             ref_type = ReferenceType(
-                ref_to_idl_type=self.create_ref_to_idl_type(identifier),
+                ref_to_idl_type=self._create_ref_to_idl_type(identifier),
                 debug_info=self._build_debug_info(node))
             return ref_type
 
diff --git a/third_party/blink/renderer/bindings/templates/interface_base.cc.tmpl b/third_party/blink/renderer/bindings/templates/interface_base.cc.tmpl
index 8dc3c151..fd0aaf5f 100644
--- a/third_party/blink/renderer/bindings/templates/interface_base.cc.tmpl
+++ b/third_party/blink/renderer/bindings/templates/interface_base.cc.tmpl
@@ -883,14 +883,14 @@
   {% if attribute.is_data_type_property %}
   static constexpr V8DOMConfiguration::AttributeConfiguration
   k{{attribute.name}}Configurations[] = {
-      {{attribute_configuration(attribute) | trim | indent(4)}}
+      {{attribute_configuration(attribute) | trim | indent(6)}}
   };
   for (const auto& config : k{{attribute.name}}Configurations)
     V8DOMConfiguration::InstallAttribute(isolate, world, instance, prototype, config);
   {% else %}
   static constexpr V8DOMConfiguration::AccessorConfiguration
   k{{attribute.name}}Configurations[] = {
-      {{accessor_configuration(attribute) | trim | indent(4)}}
+      {{accessor_configuration(attribute) | trim | indent(6)}}
   };
   for (const auto& config : k{{attribute.name}}Configurations) {
     V8DOMConfiguration::InstallAccessor(isolate, world, instance, prototype,
@@ -913,7 +913,7 @@
   {% filter secure_context(method.secure_context_test) %}
   static constexpr V8DOMConfiguration::MethodConfiguration
   k{{method.camel_case_name}}Configurations[] = {
-      {{method_configuration(method) | trim | indent(4)}}
+      {{method_configuration(method) | trim | indent(6)}}
   };
   for (const auto& config : k{{method.camel_case_name}}Configurations) {
     V8DOMConfiguration::InstallMethod(
diff --git a/third_party/blink/renderer/bindings/tests/results/core/v8_test_object.cc b/third_party/blink/renderer/bindings/tests/results/core/v8_test_object.cc
index 7b72dc3..e38c6a6 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/v8_test_object.cc
+++ b/third_party/blink/renderer/bindings/tests/results/core/v8_test_object.cc
@@ -13615,7 +13615,7 @@
   static constexpr V8DOMConfiguration::MethodConfiguration
   kPerWorldBindingsOriginTrialEnabledVoidMethodConfigurations[] = {
       {"perWorldBindingsOriginTrialEnabledVoidMethod", V8TestObject::PerWorldBindingsOriginTrialEnabledVoidMethodMethodCallbackForMainWorld, 0, v8::None, V8DOMConfiguration::kOnPrototype, V8DOMConfiguration::kCheckHolder, V8DOMConfiguration::kDoNotCheckAccess, V8DOMConfiguration::kHasSideEffect, V8DOMConfiguration::kMainWorld},
-    {"perWorldBindingsOriginTrialEnabledVoidMethod", V8TestObject::PerWorldBindingsOriginTrialEnabledVoidMethodMethodCallback, 0, v8::None, V8DOMConfiguration::kOnPrototype, V8DOMConfiguration::kCheckHolder, V8DOMConfiguration::kDoNotCheckAccess, V8DOMConfiguration::kHasSideEffect, V8DOMConfiguration::kNonMainWorlds}
+      {"perWorldBindingsOriginTrialEnabledVoidMethod", V8TestObject::PerWorldBindingsOriginTrialEnabledVoidMethodMethodCallback, 0, v8::None, V8DOMConfiguration::kOnPrototype, V8DOMConfiguration::kCheckHolder, V8DOMConfiguration::kDoNotCheckAccess, V8DOMConfiguration::kHasSideEffect, V8DOMConfiguration::kNonMainWorlds}
   };
   for (const auto& config : kPerWorldBindingsOriginTrialEnabledVoidMethodConfigurations) {
     V8DOMConfiguration::InstallMethod(
diff --git a/third_party/blink/renderer/core/animation/keyframe_effect.cc b/third_party/blink/renderer/core/animation/keyframe_effect.cc
index a6b9953..81fc728 100644
--- a/third_party/blink/renderer/core/animation/keyframe_effect.cc
+++ b/third_party/blink/renderer/core/animation/keyframe_effect.cc
@@ -34,7 +34,6 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_object_builder.h"
 #include "third_party/blink/renderer/core/animation/effect_input.h"
 #include "third_party/blink/renderer/core/animation/element_animations.h"
-#include "third_party/blink/renderer/core/animation/keyframe_effect_options.h"
 #include "third_party/blink/renderer/core/animation/sampled_effect.h"
 #include "third_party/blink/renderer/core/animation/timing_input.h"
 #include "third_party/blink/renderer/core/dom/element.h"
diff --git a/third_party/blink/renderer/core/css/css_numeric_literal_value.cc b/third_party/blink/renderer/core/css/css_numeric_literal_value.cc
index 24a06a03..0adefe1 100644
--- a/third_party/blink/renderer/core/css/css_numeric_literal_value.cc
+++ b/third_party/blink/renderer/core/css/css_numeric_literal_value.cc
@@ -24,6 +24,7 @@
 CSSNumericLiteralValue::CSSNumericLiteralValue(double num, UnitType type)
     : CSSPrimitiveValue(kNumericLiteralClass), num_(num) {
   DCHECK(std::isfinite(num));
+  DCHECK_NE(UnitType::kUnknown, type);
   numeric_literal_unit_type_ = static_cast<unsigned>(type);
 }
 
diff --git a/third_party/blink/renderer/core/html/html_slot_element.cc b/third_party/blink/renderer/core/html/html_slot_element.cc
index 58d24cdc..0aacd6c6 100644
--- a/third_party/blink/renderer/core/html/html_slot_element.cc
+++ b/third_party/blink/renderer/core/html/html_slot_element.cc
@@ -30,8 +30,6 @@
 
 #include "third_party/blink/renderer/core/html/html_slot_element.h"
 
-#include <array>
-
 #include "third_party/blink/renderer/core/css/style_change_reason.h"
 #include "third_party/blink/renderer/core/css/style_engine.h"
 #include "third_party/blink/renderer/core/dom/events/event.h"
@@ -399,14 +397,15 @@
     const HeapVector<Member<Node>>& old_slotted,
     const HeapVector<Member<Node>>& new_slotted) {
   // Use dynamic programming to minimize the number of nodes being reattached.
-  using LCSTable = std::array<std::array<wtf_size_t, kLCSTableSizeLimit>,
-                              kLCSTableSizeLimit>;
+  using LCSTable =
+      Vector<LCSArray<wtf_size_t, kLCSTableSizeLimit>, kLCSTableSizeLimit>;
   using Backtrack = std::pair<wtf_size_t, wtf_size_t>;
   using BacktrackTable =
-      std::array<std::array<Backtrack, kLCSTableSizeLimit>, kLCSTableSizeLimit>;
+      Vector<LCSArray<Backtrack, kLCSTableSizeLimit>, kLCSTableSizeLimit>;
 
-  DEFINE_STATIC_LOCAL(LCSTable*, lcs_table, (new LCSTable));
-  DEFINE_STATIC_LOCAL(BacktrackTable*, backtrack_table, (new BacktrackTable));
+  DEFINE_STATIC_LOCAL(LCSTable*, lcs_table, (new LCSTable(kLCSTableSizeLimit)));
+  DEFINE_STATIC_LOCAL(BacktrackTable*, backtrack_table,
+                      (new BacktrackTable(kLCSTableSizeLimit)));
 
   FillLongestCommonSubsequenceDynamicProgrammingTable(
       old_slotted, new_slotted, *lcs_table, *backtrack_table);
diff --git a/third_party/blink/renderer/core/html/html_slot_element.h b/third_party/blink/renderer/core/html/html_slot_element.h
index 38390c91..0821db4 100644
--- a/third_party/blink/renderer/core/html/html_slot_element.h
+++ b/third_party/blink/renderer/core/html/html_slot_element.h
@@ -148,6 +148,14 @@
   // For imperative Shadow DOM distribution APIs
   HeapHashSet<Member<Node>> assigned_nodes_candidates_;
 
+  template <typename T, wtf_size_t S>
+  struct LCSArray {
+    LCSArray() : values(S) {}
+    T& operator[](wtf_size_t i) { return values[i]; }
+    wtf_size_t size() { return values.size(); }
+    Vector<T, S> values;
+  };
+
   // TODO(hayato): Move this to more appropriate directory (e.g. platform/wtf)
   // if there are more than one usages.
   template <typename Container, typename LCSTable, typename BacktrackTable>
diff --git a/third_party/blink/renderer/core/html/html_slot_element_test.cc b/third_party/blink/renderer/core/html/html_slot_element_test.cc
index a0012df..bb8898cb 100644
--- a/third_party/blink/renderer/core/html/html_slot_element_test.cc
+++ b/third_party/blink/renderer/core/html/html_slot_element_test.cc
@@ -4,8 +4,6 @@
 
 #include "third_party/blink/renderer/core/html/html_slot_element.h"
 
-#include <array>
-
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/core/css/style_engine.h"
 #include "third_party/blink/renderer/core/dom/node_computed_style.h"
@@ -22,10 +20,12 @@
 
 class HTMLSlotElementTest : public testing::Test {
  protected:
-  HTMLSlotElementTest() = default;
+  HTMLSlotElementTest()
+      : lcs_table_(kTableSize), backtrack_table_(kTableSize) {}
   Seq LongestCommonSubsequence(const Seq& seq1, const Seq& seq2);
-  std::array<std::array<size_t, kTableSize>, kTableSize> lcs_table_;
-  std::array<std::array<Backtrack, kTableSize>, kTableSize> backtrack_table_;
+  Vector<HTMLSlotElement::LCSArray<size_t, kTableSize>, kTableSize> lcs_table_;
+  Vector<HTMLSlotElement::LCSArray<Backtrack, kTableSize>, kTableSize>
+      backtrack_table_;
 };
 
 Vector<char> HTMLSlotElementTest::LongestCommonSubsequence(const Seq& seq1,
diff --git a/third_party/blink/renderer/core/html/track/html_track_element.idl b/third_party/blink/renderer/core/html/track/html_track_element.idl
index 79bd122..a6d3296 100644
--- a/third_party/blink/renderer/core/html/track/html_track_element.idl
+++ b/third_party/blink/renderer/core/html/track/html_track_element.idl
@@ -25,8 +25,10 @@
 
 // https://html.spec.whatwg.org/C/#the-track-element
 
-[HTMLConstructor]
-interface HTMLTrackElement : HTMLElement {
+[
+    Exposed=Window,
+    HTMLConstructor
+] interface HTMLTrackElement : HTMLElement {
     [CEReactions] attribute DOMString kind;
     [CEReactions, Reflect, URL, RaisesException=Setter] attribute URLString src;
     [CEReactions, Reflect] attribute DOMString srclang;
diff --git a/third_party/blink/renderer/core/layout/layout_shift_region.cc b/third_party/blink/renderer/core/layout/layout_shift_region.cc
index 41197bd..b43c354 100644
--- a/third_party/blink/renderer/core/layout/layout_shift_region.cc
+++ b/third_party/blink/renderer/core/layout/layout_shift_region.cc
@@ -4,7 +4,6 @@
 
 #include "third_party/blink/renderer/core/layout/layout_shift_region.h"
 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
-#include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
 
 namespace blink {
 
@@ -45,11 +44,12 @@
 
  private:
   Vector<int> endpoints_;
-  // Avoid WTF::HashMap as key may be 0 or -1.
-  HashMap<int,
+  // Use int64_t which is larger than real |int| since the empty value of the
+  // key is max and deleted value of the key is max - 1 in HashMap.
+  HashMap<int64_t,
           unsigned,
-          WTF::AlreadyHashed,
-          WTF::UnsignedWithZeroKeyHashTraits<int>>
+          WTF::IntHash<int64_t>,
+          WTF::UnsignedWithZeroKeyHashTraits<int64_t>>
       endpoint_to_index_;
 
 #if DCHECK_IS_ON()
diff --git a/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/index.mjs b/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/index.mjs
index a7e0541..086a565 100644
--- a/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/index.mjs
+++ b/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/index.mjs
@@ -51,6 +51,7 @@
   static observedAttributes = ['open'];
   #shadow = this.attachShadow({mode: 'closed'});
   #timeoutID;
+  #actionSlot;
 
   constructor(message) {
     super();
@@ -58,6 +59,10 @@
     this.#shadow.adoptedStyleSheets = [generateStylesheet()];
 
     this.#shadow.innerHTML = `<slot></slot>`;
+    this.#actionSlot = document.createElement('slot');
+    this.#actionSlot.setAttribute('name', 'action');
+    this.#shadow.appendChild(this.#actionSlot);
+
     if (message !== undefined) {
       this.textContent = message;
     }
diff --git a/third_party/blink/renderer/devtools/front_end/ui/SyntaxHighlighter.js b/third_party/blink/renderer/devtools/front_end/ui/SyntaxHighlighter.js
index f4b4aa2..fb2ebbd 100644
--- a/third_party/blink/renderer/devtools/front_end/ui/SyntaxHighlighter.js
+++ b/third_party/blink/renderer/devtools/front_end/ui/SyntaxHighlighter.js
@@ -48,7 +48,7 @@
    */
   createSpan(content, className) {
     const span = createElement('span');
-    span.className = 'cm-' + className;
+    span.className = className.replace(/\S+/g, 'cm-$&');
     if (this._stripExtraWhitespace && className !== 'whitespace')
       content = content.replace(/^[\n\r]*/, '').replace(/\s*$/, '');
     span.createTextChild(content);
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
index 4c596286..de0ca749 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
@@ -3035,45 +3035,6 @@
       .RectDrawing(FloatRect(0, 0, 100, 100), Color::kBlack);
   Update(artifact.Build());
 
-  if (RuntimeEnabledFeatures::FastBorderRadiusEnabled()) {
-    // Expectation in effect stack diagram:
-    //   l0         l1
-    // [ e1 ]
-    // [  mask_isolation_0   ]
-    // [         e0          ]
-    // One content layer, one clip mask.
-    ASSERT_EQ(1u, RootLayer()->children().size());
-    ASSERT_EQ(1u, ContentLayerCount());
-    // There is still a "synthesized layer" but it's null.
-    ASSERT_EQ(1u, SynthesizedClipLayerCount());
-    EXPECT_FALSE(SynthesizedClipLayerAt(0));
-
-    const cc::Layer* content0 = RootLayer()->children()[0].get();
-
-    constexpr int c0_id = 1;
-    constexpr int e0_id = 1;
-
-    EXPECT_EQ(ContentLayerAt(0), content0);
-    int c1_id = content0->clip_tree_index();
-    const cc::ClipNode& cc_c1 = *GetPropertyTrees().clip_tree.Node(c1_id);
-    EXPECT_EQ(gfx::RectF(50, 50, 300, 200), cc_c1.clip);
-    ASSERT_EQ(c0_id, cc_c1.parent_id);
-    int e1_id = content0->effect_tree_index();
-    const cc::EffectNode& cc_e1 = *GetPropertyTrees().effect_tree.Node(e1_id);
-    EXPECT_EQ(c1_id, cc_e1.clip_id);
-    int mask_isolation_0_id = cc_e1.parent_id;
-    const cc::EffectNode& mask_isolation_0 =
-        *GetPropertyTrees().effect_tree.Node(mask_isolation_0_id);
-    ASSERT_EQ(e0_id, mask_isolation_0.parent_id);
-    EXPECT_EQ(c1_id, mask_isolation_0.clip_id);
-    EXPECT_EQ(SkBlendMode::kSrcOver, mask_isolation_0.blend_mode);
-    EXPECT_TRUE(mask_isolation_0.is_fast_rounded_corner);
-    EXPECT_EQ(gfx::RRectF(50, 50, 300, 200, 0),
-              mask_isolation_0.rounded_corner_bounds);
-    EXPECT_FALSE(mask_isolation_0.HasRenderSurface());
-    return;
-  }
-
   // Expectation in effect stack diagram:
   //   l0         l1
   // [ e1 ][ mask_effect_0 ]
diff --git a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
index d34a180..9ae9343f 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
@@ -808,7 +808,7 @@
 }
 
 bool PropertyTreeManager::SupportsShaderBasedRoundedCorner(
-    const FloatRoundedRect& rect,
+    const ClipPaintPropertyNode& clip,
     PropertyTreeManager::CcEffectType type) {
   if (!RuntimeEnabledFeatures::FastBorderRadiusEnabled())
     return false;
@@ -816,11 +816,14 @@
   if (type & CcEffectType::kSyntheticFor2dAxisAlignment)
     return false;
 
+  if (clip.ClipPath())
+    return false;
+
   auto WidthAndHeightAreTheSame = [](const FloatSize& size) {
     return size.Width() == size.Height();
   };
 
-  const FloatRoundedRect::Radii& radii = rect.GetRadii();
+  const FloatRoundedRect::Radii& radii = clip.ClipRect().GetRadii();
   if (!WidthAndHeightAreTheSame(radii.TopLeft()) ||
       !WidthAndHeightAreTheSame(radii.TopRight()) ||
       !WidthAndHeightAreTheSame(radii.BottomRight()) ||
@@ -917,7 +920,7 @@
     synthetic_effect.transform_id = EnsureCompositorTransformNode(transform);
     synthetic_effect.double_sided = !transform.IsBackfaceHidden();
     if (pending_clip.type & CcEffectType::kSyntheticForNonTrivialClip) {
-      if (SupportsShaderBasedRoundedCorner(pending_clip.clip->ClipRect(),
+      if (SupportsShaderBasedRoundedCorner(*pending_clip.clip,
                                            pending_clip.type)) {
         synthetic_effect.rounded_corner_bounds =
             gfx::RRectF(pending_clip.clip->ClipRect());
diff --git a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h
index 23c4c34..0031fa2 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h
+++ b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h
@@ -28,7 +28,6 @@
 namespace blink {
 
 class ClipPaintPropertyNode;
-class FloatRoundedRect;
 class LayerListBuilder;
 class EffectPaintPropertyNode;
 class ScrollPaintPropertyNode;
@@ -152,7 +151,7 @@
     kSyntheticFor2dAxisAlignment = 1 << 1
   };
 
-  static bool SupportsShaderBasedRoundedCorner(const FloatRoundedRect& rect,
+  static bool SupportsShaderBasedRoundedCorner(const ClipPaintPropertyNode&,
                                                CcEffectType type);
 
   struct EffectState {
diff --git a/third_party/blink/renderer/platform/graphics/video_frame_submitter.h b/third_party/blink/renderer/platform/graphics/video_frame_submitter.h
index 8b8727c..3facb36 100644
--- a/third_party/blink/renderer/platform/graphics/video_frame_submitter.h
+++ b/third_party/blink/renderer/platform/graphics/video_frame_submitter.h
@@ -141,7 +141,11 @@
 
   // If the surface is not visible within in the current view port, we should
   // not submit. Not submitting when off-screen saves significant memory.
-  bool is_surface_visible_ = false;
+  //
+  // We start as visible to avoid a white flash for on-screen content. This does
+  // not seem to cause a memory regression, since when off-screen it the layer
+  // will quickly be marked as such.
+  bool is_surface_visible_ = true;
 
   // Likewise, if the entire page is not visible, we should not submit. Not
   // submitting in the background causes the VideoFrameProvider to enter a
diff --git a/third_party/blink/renderer/platform/heap/heap_allocator.cc b/third_party/blink/renderer/platform/heap/heap_allocator.cc
index e55ee53..74bdb07 100644
--- a/third_party/blink/renderer/platform/heap/heap_allocator.cc
+++ b/third_party/blink/renderer/platform/heap/heap_allocator.cc
@@ -17,14 +17,16 @@
 BackingModifier CanFreeOrShrinkBacking(ThreadState* const state,
                                        void* address) {
   // - |SweepForbidden| protects against modifying objects from destructors.
+  // - |IsSweepingInProgress| protects against modifying objects while
+  // concurrent sweeping is in progress.
   // - |in_atomic_pause| protects against modifying objects from within the GC.
   // This can
   //   e.g. happen when hash table buckets that have containers inlined are
   //   freed during weakness processing.
   // - |IsMarkingInProgress| protects against incremental marking which may have
   //   registered callbacks.
-  if (state->SweepForbidden() || state->in_atomic_pause() ||
-      state->IsMarkingInProgress())
+  if (state->SweepForbidden() || state->IsSweepingInProgress() ||
+      state->in_atomic_pause() || state->IsMarkingInProgress())
     return {false, nullptr, nullptr};
 
   // - Don't adjust large objects because their page is never reused.
@@ -76,7 +78,8 @@
     return false;
 
   ThreadState* state = ThreadState::Current();
-  if (state->SweepForbidden())
+  // Don't expand if concurrent sweeping is in progress.
+  if (state->SweepForbidden() || state->IsSweepingInProgress())
     return false;
   DCHECK(!state->in_atomic_pause());
   DCHECK(state->IsAllocationAllowed());
diff --git a/third_party/blink/renderer/platform/heap/heap_test.cc b/third_party/blink/renderer/platform/heap/heap_test.cc
index 899666b..4ec3955 100644
--- a/third_party/blink/renderer/platform/heap/heap_test.cc
+++ b/third_party/blink/renderer/platform/heap/heap_test.cc
@@ -6115,4 +6115,13 @@
   MakeGarbageCollected<P>();
 }
 
+TEST(HeapTest, PersistentAssignsDeletedValue) {
+  // Regression test: https://crbug.com/982313
+
+  Persistent<IntWrapper> deleted(WTF::kHashTableDeletedValue);
+  Persistent<IntWrapper> pre_initialized(MakeGarbageCollected<IntWrapper>(1));
+  pre_initialized = deleted;
+  PreciselyCollectGarbage();
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/heap/persistent.h b/third_party/blink/renderer/platform/heap/persistent.h
index 826e4d54..0f119b1 100644
--- a/third_party/blink/renderer/platform/heap/persistent.h
+++ b/third_party/blink/renderer/platform/heap/persistent.h
@@ -261,7 +261,7 @@
       raw_ = ptr;
     }
     CheckPointer();
-    if (raw_) {
+    if (raw_ && !IsHashTableDeletedValue()) {
       if (!persistent_node_.IsInitialized())
         Initialize();
       return;
@@ -269,11 +269,11 @@
     Uninitialize();
   }
 
-  template <typename VisitorDispatcher>
-  void TracePersistent(VisitorDispatcher visitor) {
+  void TracePersistent(Visitor* visitor) {
     static_assert(sizeof(T), "T must be fully defined");
     static_assert(IsGarbageCollectedType<T>::value,
                   "T needs to be a garbage collected object");
+    DCHECK(!IsHashTableDeletedValue());
     if (weaknessConfiguration == kWeakPersistentConfiguration) {
       visitor->RegisterWeakCallback(this, HandleWeakPersistent);
     } else {
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 cce019a..e2ea9f7a 100644
--- a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint
+++ b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint
@@ -467,8 +467,7 @@
 Bug(none) fast/multicol/composited-layer-will-change.html [ Crash ]
 
 # Clip-path and mask.
-crbug.com/979369 compositing/images/direct-image-clip-path.html [ Failure ]
-crbug.com/979369 compositing/images/direct-image-dynamic-clip-path.html [ Failure ]
+crbug.com/979369 compositing/geometry/child-layer-position-with-clip-path-overflow.html [ Failure ]
 crbug.com/979369 compositing/overflow/accelerated-scrolling-with-clip-path.html [ Failure ]
 crbug.com/979369 compositing/overflow/ancestor-with-clip-path.html [ Failure ]
 crbug.com/979369 compositing/overflow/descendant-with-clip-path.html [ Failure ]
@@ -483,9 +482,7 @@
 crbug.com/979369 external/wpt/css/css-masking/mask-svg-content/mask-type-002.svg [ Failure ]
 crbug.com/979369 external/wpt/css/css-masking/mask-svg-content/mask-type-003.svg [ Failure ]
 crbug.com/979369 paint/clipath/change-mask-clip-path-multicol-crash.html [ Crash ]
-crbug.com/979369 paint/clipath/clip-path-with-background-and-box-behind.html [ Failure ]
 crbug.com/979369 paint/invalidation/clip/clip-path-constant-repaint.html [ Failure ]
-crbug.com/979369 paint/invalidation/clip/clip-path-in-mask-layer.html [ Failure ]
 crbug.com/979369 svg/W3C-SVG-1.1/masking-intro-01-f.svg [ Failure ]
 crbug.com/979369 svg/clip-path/clip-in-mask.svg [ Failure ]
 crbug.com/979369 svg/clip-path/deep-nested-clip-in-mask-different-unitTypes.svg [ Failure ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index dd6c63a..e27a5236 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -6260,6 +6260,7 @@
 crbug.com/982289 virtual/gpu-rasterization/images/color-profile-image-canvas.html [ Pass Failure ]
 crbug.com/982289 virtual/gpu-rasterization/images/color-profile-mask-image-svg.html [ Pass Failure ]
 crbug.com/982289 virtual/gpu-rasterization/images/ycbcr-with-cmyk-color-profile.html [ Pass Failure ]
+crbug.com/982289 virtual/gpu-rasterization/images/color-profile-image.html [ Pass Failure ]
 
 # Sheriff 2019-07-09
 crbug.com/966249 [ Mac ] external/wpt/css/css-fonts/inheritance.html [ Pass Failure ]
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_attributes_hoverable_pointers-manual.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_attributes_hoverable_pointers.html
similarity index 86%
rename from third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_attributes_hoverable_pointers-manual.html
rename to third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_attributes_hoverable_pointers.html
index e7471451..97e0db1 100644
--- a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_attributes_hoverable_pointers-manual.html
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_attributes_hoverable_pointers.html
@@ -6,6 +6,9 @@
         <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
         <script src="/resources/testharness.js"></script>
         <script src="/resources/testharnessreport.js"></script>
+        <script src="/resources/testdriver.js"></script>
+        <script src="/resources/testdriver-actions.js"></script>
+        <script src="/resources/testdriver-vendor.js"></script>
         <!-- Additional helper script for common checks across event types -->
         <script type="text/javascript" src="pointerevent_support.js"></script>
         <script>
@@ -95,6 +98,7 @@
                 var innerFrame = document.getElementById('innerFrame');
                 var square2 = innerFrame.contentDocument.getElementById('square2');
                 var rectSquare2 = square2.getBoundingClientRect();
+                var actions_promise;
 
                 eventList.forEach(function(eventName) {
                     on_event(square1, eventName, function (event) {
@@ -112,10 +116,32 @@
                         checkPointerEventAttributes(event, rectSquare2, "Inner frame ");
                         if (Object.keys(detected_eventTypes).length == eventList.length) {
                             square2.style.visibility = 'hidden';
-                            test_pointerEvent.done();
+                            // Make sure the test finishes after all the input actions are completed.
+                            actions_promise.then( () => {
+                                test_pointerEvent.done();
+                            });
                         }
                     });
                 });
+
+                // Inject mouse and pen inputs.
+                actions_promise = clickInTarget("mouse", square1).then(function() {
+                    return moveToDocument("mouse");
+                }).then(function() {
+                    return clickInTarget("mouse", square2);
+                }).then(function() {
+                    return moveToDocument("mouse");
+                }).then(function() {
+                    test_pointerEvent.done();
+                }).then(function() {
+                    return clickInTarget("pen", square1);
+                }).then(function() {
+                    return moveToDocument("pen");
+                }).then(function() {
+                    return clickInTarget("pen", square2);
+                }).then(function() {
+                    return moveToDocument("pen");
+                });
             }
         </script>
     </head>
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_attributes_nohover_pointers-manual.html b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_attributes_nohover_pointers.html
similarity index 90%
rename from third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_attributes_nohover_pointers-manual.html
rename to third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_attributes_nohover_pointers.html
index 0fd7904..e0073de 100644
--- a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_attributes_nohover_pointers-manual.html
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_attributes_nohover_pointers.html
@@ -6,6 +6,9 @@
         <link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
         <script src="/resources/testharness.js"></script>
         <script src="/resources/testharnessreport.js"></script>
+        <script src="/resources/testdriver.js"></script>
+        <script src="/resources/testdriver-actions.js"></script>
+        <script src="/resources/testdriver-vendor.js"></script>
         <!-- Additional helper script for common checks across event types -->
         <script type="text/javascript" src="pointerevent_support.js"></script>
         <script>
@@ -76,6 +79,7 @@
                 var innerFrame = document.getElementById('innerFrame');
                 var square2 = innerFrame.contentDocument.getElementById('square2');
                 var rectSquare2 = square2.getBoundingClientRect();
+                var actions_promise;
 
                 eventList.forEach(function(eventName) {
                     on_event(square1, eventName, function (event) {
@@ -93,10 +97,18 @@
                         checkPointerEventAttributes(event, rectSquare2, "Inner frame ");
                         if (Object.keys(detected_eventTypes).length == eventList.length) {
                             square2.style.visibility = 'hidden';
-                            test_pointerEvent.done();
+                            // Make sure the test finishes after all the input actions are completed.
+                            actions_promise.then( () => {
+                                test_pointerEvent.done();
+                            });
                         }
                     });
                 });
+
+                // Inject touch inputs.
+                actions_promise = clickInTarget("touch", square1).then(function() {
+                    return clickInTarget("touch", square2);
+                });
             }
         </script>
     </head>
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_support.js b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_support.js
index ae9b55c..3b37f48 100644
--- a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_support.js
+++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_support.js
@@ -353,3 +353,11 @@
                    .pointerMove(3 * x_delta, 3 * y_delta, {origin: target})
                    .send();
 }
+
+function moveToDocument(pointerType) {
+    var pointerId = pointerType + "Pointer1";
+    return new test_driver.Actions()
+                   .addPointer(pointerId, pointerType)
+                   .pointerMove(0, 0)
+                   .send();
+}
diff --git a/third_party/blink/web_tests/external/wpt/shape-detection/detection-HTMLVideoElement-invalid-state.html b/third_party/blink/web_tests/external/wpt/shape-detection/detection-HTMLVideoElement-invalid-state.html
new file mode 100644
index 0000000..dcf9e3a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/shape-detection/detection-HTMLVideoElement-invalid-state.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+const videoElementTests =
+    [
+      {
+        createDetector: () => { return new FaceDetector(); },
+        name: "Face - detect(HTMLVideoElement)",
+      },
+      {
+        createDetector: () => { return new BarcodeDetector(); },
+        name: "Barcode - detect(HTMLVideoElement)",
+      }
+    ];
+
+for (let videoElementTest of videoElementTests) {
+
+  // Detector's detect() rejects on a HAVE_NOTHING HTMLVideoElement.
+  promise_test(async t => {
+    const video = document.createElement("video");
+    video.src = "";
+    const videoWatcher = new EventWatcher(t, video, ["play", "error"]);
+    video.load();
+    await videoWatcher.wait_for("error");
+    assert_equals(video.readyState, video.HAVE_NOTHING);
+
+    const detector = videoElementTest.createDetector();
+    await promise_rejects(t, 'InvalidStateError', detector.detect(video));
+  }, `${videoElementTest.name} - HAVE_NOTHING`);
+
+  // Detector's detect() rejects on a HAVE_METADATA HTMLVideoElement.
+  promise_test(async t => {
+    const url = '/media/test-av-384k-44100Hz-1ch-320x240-30fps-10kfr.webm';
+    const type = 'video/webm; codecs="vp8, vorbis"';
+    const video = document.createElement("video");
+    document.body.appendChild(video);
+    t.add_cleanup(() => { document.body.removeChild(video); });
+
+    // Attach MediaSource to the video element.
+    const mediaSource = new MediaSource();
+    const mediaSourceURL = URL.createObjectURL(mediaSource);
+    const mediaSourceWatcher =
+        new EventWatcher(t, mediaSource, ["sourceopen", "error"]);
+    video.src = mediaSourceURL;
+    await mediaSourceWatcher.wait_for("sourceopen");
+    const sourceBuffer = mediaSource.addSourceBuffer(type);
+
+    // Load media data from the video file to create an initialization segment.
+    const response = await fetch(url);
+    const mediaData = await response.arrayBuffer();
+    const initSegment = new Uint8Array(mediaData, 0, 4052);
+
+    // Append the initialization segment to trigger a transition
+    // to HAVE_METADATA.
+    const videoWatcher =
+        new EventWatcher(t, video, ["loadedmetadata", "error"]);
+    sourceBuffer.appendBuffer(initSegment);
+
+    await videoWatcher.wait_for("loadedmetadata");
+    assert_equals(video.readyState, video.HAVE_METADATA);
+
+    const detector = videoElementTest.createDetector();
+    await promise_rejects(t, 'InvalidStateError', detector.detect(video));
+  }, `${videoElementTest.name} - HAVE_METADATA`);
+
+}
+
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/shape-detection/shapedetection-cross-origin.sub.html b/third_party/blink/web_tests/external/wpt/shape-detection/shapedetection-cross-origin.sub.html
index c9d86430..f4536926 100644
--- a/third_party/blink/web_tests/external/wpt/shape-detection/shapedetection-cross-origin.sub.html
+++ b/third_party/blink/web_tests/external/wpt/shape-detection/shapedetection-cross-origin.sub.html
@@ -30,9 +30,9 @@
     img.src = IMAGE_URL;
     await imgWatcher.wait_for("load");
     const detector = crossOriginTest.createDetector();
-    promise_rejects(t, "SecurityError", detector.detect(img));
-  }, crossOriginTest.detectorType
-  + " should reject cross-origin HTMLImageElements with a SecurityError.");
+    await promise_rejects(t, "SecurityError", detector.detect(img));
+  }, `${crossOriginTest.detectorType} should reject cross-origin \
+HTMLImageElements with a SecurityError.`);
 
   // Verifies that Detector rejects a cross-origin ImageBitmap.
   promise_test(async t => {
@@ -42,9 +42,9 @@
     await imgWatcher.wait_for("load");
     const imgBitmap = await createImageBitmap(img);
     const detector = crossOriginTest.createDetector();
-    promise_rejects(t, "SecurityError", detector.detect(imgBitmap));
-  }, crossOriginTest.detectorType
-  + " should reject cross-origin ImageBitmaps with a SecurityError.");
+    await promise_rejects(t, "SecurityError", detector.detect(imgBitmap));
+  }, `${crossOriginTest.detectorType} should reject cross-origin \
+ImageBitmaps with a SecurityError.`);
 
   // Verifies that Detector rejects a cross-origin HTMLVideoElement.
   promise_test(async t => {
@@ -53,9 +53,22 @@
     video.src = VIDEO_URL;
     await videoWatcher.wait_for("loadeddata");
     const detector = crossOriginTest.createDetector();
-    promise_rejects(t, "SecurityError", detector.detect(video));
-  }, crossOriginTest.detectorType
-  + " should reject cross-origin HTMLVideoElements with a SecurityError.");
+    await promise_rejects(t, "SecurityError", detector.detect(video));
+  }, `${crossOriginTest.detectorType} should reject cross-origin \
+HTMLVideoElements with a SecurityError.`);
+
+  // Verifies that Detector rejects a cross-origin HTMLCanvasElement.
+  promise_test(async t => {
+    const img = new Image();
+    const imgWatcher = new EventWatcher(t, img, ["load", "error"]);
+    img.src = IMAGE_URL;
+    await imgWatcher.wait_for("load");
+    const canvas = document.createElement("canvas");
+    canvas.getContext("2d").drawImage(img, 0, 0);
+    const detector = crossOriginTest.createDetector();
+    await promise_rejects(t, "SecurityError", detector.detect(canvas));
+  }, `${crossOriginTest.detectorType} should reject cross-origin \
+HTMLCanvasElement with a SecurityError.`);
 
 }
 
diff --git a/third_party/blink/web_tests/external/wpt/std-toast/ref-tests/toast-slotting-expected.html b/third_party/blink/web_tests/external/wpt/std-toast/ref-tests/toast-slotting-expected.html
new file mode 100644
index 0000000..b4b365c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/std-toast/ref-tests/toast-slotting-expected.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Toast: slotting test reference</title>
+<script type="module">
+import 'std:elements/toast';
+</script>
+<p>Pass if the toast is displayed with the action button last.</p>
+<std-toast open>First. Second. <button>Last.</button></std-toast>
diff --git a/third_party/blink/web_tests/external/wpt/std-toast/ref-tests/toast-slotting.html b/third_party/blink/web_tests/external/wpt/std-toast/ref-tests/toast-slotting.html
new file mode 100644
index 0000000..4d6f5038
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/std-toast/ref-tests/toast-slotting.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Toast: slotting test</title>
+<link rel="help" href="https://github.com/jackbsteinberg/std-toast">
+<meta name="assert" content="Toast slots action button behind any text">
+<link rel="match" href="toast-slotting-expected.html">
+<script type="module">
+import 'std:elements/toast';
+</script>
+<p>Pass if the toast is displayed with the action button last.</p>
+<std-toast open>First. <button slot="action">Last.</button> Second. </std-toast>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/std-toast/resources/helpers.js b/third_party/blink/web_tests/external/wpt/std-toast/resources/helpers.js
index 41b7672..e1b55f4 100644
--- a/third_party/blink/web_tests/external/wpt/std-toast/resources/helpers.js
+++ b/third_party/blink/web_tests/external/wpt/std-toast/resources/helpers.js
@@ -2,10 +2,10 @@
 
 // helper functions to keep tests from bleeding into each other
 
-const runTest = (testFn, name, toast) => {
+const runTest = (testFn, name, toast, action) => {
     try {
         test(() => {
-            testFn(toast);
+            testFn(toast, action);
         }, name);
     } finally {
         toast.remove();
@@ -41,6 +41,17 @@
     runTest(testFn, name, toast);
 };
 
+export const testActionToast = (testFn, name) => {
+    const toast = new StdToastElement('Message', {});
+    const action = document.createElement('button');
+    action.setAttribute('slot', 'action');
+    action.textContent = 'action';
+    toast.appendChild(action);
+    document.querySelector('main').appendChild(toast);
+
+    runTest(testFn, name, toast, action);
+};
+
 export const assertToastShown = (toast) => {
     assert_not_equals(window.getComputedStyle(toast).display, 'none');
     assert_true(toast.hasAttribute('open'));
diff --git a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_attributes_hoverable_pointers-manual-automation.js b/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_attributes_hoverable_pointers-manual-automation.js
deleted file mode 100644
index f2f8295..0000000
--- a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_attributes_hoverable_pointers-manual-automation.js
+++ /dev/null
@@ -1,15 +0,0 @@
-importAutomationScript('/pointerevents/pointerevent_common_input.js');
-
-function inject_input() {
-  return mouseClickInTarget('#square1').then(function() {
-    return mouseClickInTarget('#square2', document.querySelector('#innerFrame'));
-  }).then(function() {
-    return mouseMoveToDocument();
-  }).then(function() {
-    return penClickInTarget('#square1');
-  }).then(function() {
-    return penClickInTarget('#square2', document.querySelector('#innerFrame'));
-  }).then(function() {
-    return penMoveToDocument();
-  });
-}
diff --git a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_attributes_nohover_pointers-manual-automation.js b/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_attributes_nohover_pointers-manual-automation.js
deleted file mode 100644
index d9ef3f3..0000000
--- a/third_party/blink/web_tests/external/wpt_automation/pointerevents/pointerevent_attributes_nohover_pointers-manual-automation.js
+++ /dev/null
@@ -1,7 +0,0 @@
-importAutomationScript('/pointerevents/pointerevent_common_input.js');
-
-function inject_input() {
-  return touchTapInTarget('#square1').then(function() {
-    return touchTapInTarget('#square2', document.querySelector('#innerFrame'));
-  });
-}
diff --git a/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/clip/clip-path-in-mask-layer-expected.txt b/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/clip/clip-path-in-mask-layer-expected.txt
new file mode 100644
index 0000000..61ba22d
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-blink-features=CompositeAfterPaint/paint/invalidation/clip/clip-path-in-mask-layer-expected.txt
@@ -0,0 +1,35 @@
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutNGBlockFlow DIV",
+      "bounds": [200, 200],
+      "backgroundColor": "#0000FF",
+      "paintInvalidations": [
+        {
+          "object": "LayoutNGBlockFlow DIV",
+          "rect": [0, 0, 200, 200],
+          "reason": "full"
+        }
+      ],
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [8, 8, 0, 1]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/http/tests/devtools/syntax-highlight-html-expected.txt b/third_party/blink/web_tests/http/tests/devtools/syntax-highlight-html-expected.txt
index b24992d..7948df21 100644
--- a/third_party/blink/web_tests/http/tests/devtools/syntax-highlight-html-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/syntax-highlight-html-expected.txt
@@ -1,11 +1,11 @@
 Tests that SourceHTMLTokenizer detects the tokens.
 
-<html>: cm-xml-tag xml-bracket, cm-xml-tag, cm-xml-tag xml-bracket
-<table cellspacing=0>: cm-xml-tag xml-bracket, cm-xml-tag, *, cm-xml-attribute, *, cm-xml-string, cm-xml-tag xml-bracket
-<input checked value="foo">: cm-xml-tag xml-bracket, cm-xml-tag, *, cm-xml-attribute, *, cm-xml-attribute, *, cm-xml-string, cm-xml-tag xml-bracket
-<table cellspacing="0" cellpadding='0'>: cm-xml-tag xml-bracket, cm-xml-tag, *, cm-xml-attribute, *, cm-xml-string, *, cm-xml-attribute, *, cm-xml-string, cm-xml-tag xml-bracket
+<html>: cm-xml-tag cm-xml-bracket, cm-xml-tag, cm-xml-tag cm-xml-bracket
+<table cellspacing=0>: cm-xml-tag cm-xml-bracket, cm-xml-tag, *, cm-xml-attribute, *, cm-xml-string, cm-xml-tag cm-xml-bracket
+<input checked value="foo">: cm-xml-tag cm-xml-bracket, cm-xml-tag, *, cm-xml-attribute, *, cm-xml-attribute, *, cm-xml-string, cm-xml-tag cm-xml-bracket
+<table cellspacing="0" cellpadding='0'>: cm-xml-tag cm-xml-bracket, cm-xml-tag, *, cm-xml-attribute, *, cm-xml-string, *, cm-xml-attribute, *, cm-xml-string, cm-xml-tag cm-xml-bracket
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">: cm-xml-meta
 <!--div><div foobar-->: cm-xml-comment
-<script></script><!--div-->: cm-xml-tag xml-bracket, cm-xml-tag, cm-xml-tag xml-bracket, cm-xml-tag xml-bracket, cm-xml-tag, cm-xml-tag xml-bracket, cm-xml-comment
-<script type="text/javascript">document.write('<script type="text/javascript"></' + 'script>');</script>: cm-xml-tag xml-bracket, cm-xml-tag, *, cm-xml-attribute, *, cm-xml-string, cm-xml-tag xml-bracket, cm-js-variable, *, cm-js-property, *, cm-js-string, *, cm-js-operator, *, cm-js-string, *, cm-xml-tag xml-bracket, cm-xml-tag, cm-xml-tag xml-bracket
+<script></script><!--div-->: cm-xml-tag cm-xml-bracket, cm-xml-tag, cm-xml-tag cm-xml-bracket, cm-xml-tag cm-xml-bracket, cm-xml-tag, cm-xml-tag cm-xml-bracket, cm-xml-comment
+<script type="text/javascript">document.write('<script type="text/javascript"></' + 'script>');</script>: cm-xml-tag cm-xml-bracket, cm-xml-tag, *, cm-xml-attribute, *, cm-xml-string, cm-xml-tag cm-xml-bracket, cm-js-variable, *, cm-js-property, *, cm-js-string, *, cm-js-operator, *, cm-js-string, *, cm-xml-tag cm-xml-bracket, cm-xml-tag, cm-xml-tag cm-xml-bracket
 
diff --git a/third_party/blink/web_tests/http/tests/shapedetection/detection-HTMLVideoElement-invalid-state.html b/third_party/blink/web_tests/http/tests/shapedetection/detection-HTMLVideoElement-invalid-state.html
new file mode 100644
index 0000000..e245ff5
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/shapedetection/detection-HTMLVideoElement-invalid-state.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<script src="../../../../resources/testharness.js"></script>
+<script src="../../../../resources/testharnessreport.js"></script>
+<script>
+
+// Detector's detect() rejects on a HAVE_NOTHING HTMLVideoElement.
+promise_test(async t => {
+  const video = document.createElement("video");
+  video.src = "";
+  const videoWatcher = new EventWatcher(t, video, ["play", "error"]);
+  video.load();
+  await videoWatcher.wait_for("error");
+  assert_equals(video.readyState, video.HAVE_NOTHING);
+  const detector = new TextDetector();
+  await promise_rejects(t, 'InvalidStateError', detector.detect(video));
+}, "Text - detect(HTMLVideoElement) - HAVE_NOTHING");
+
+// Detector's detect() rejects on a HAVE_METADATA HTMLVideoElement.
+promise_test(async t => {
+  const url = "/media/resources/media-source/webm/"
+      + "test-av-384k-44100Hz-1ch-320x240-30fps-10kfr.webm";
+  const type = 'video/webm; codecs="vp8, vorbis"';
+  const video = document.createElement("video");
+  document.body.appendChild(video);
+  t.add_cleanup(() => { document.body.removeChild(video); });
+
+  // Attach MediaSource to the video element.
+  const mediaSource = new MediaSource();
+  const mediaSourceURL = URL.createObjectURL(mediaSource);
+  const mediaSourceWatcher =
+      new EventWatcher(t, mediaSource, ["sourceopen", "error"]);
+  video.src = mediaSourceURL;
+  await mediaSourceWatcher.wait_for("sourceopen");
+  const sourceBuffer = mediaSource.addSourceBuffer(type);
+
+  // Load media data from the video file to create an initialization segment.
+  const response = await fetch(url);
+  const mediaData = await response.arrayBuffer();
+  const initSegment = new Uint8Array(mediaData, 0, 4052);
+
+  // Append the initialization segment to trigger a transition
+  // to HAVE_METADATA.
+  const videoWatcher =
+      new EventWatcher(t, video, ["loadedmetadata", "error"]);
+  sourceBuffer.appendBuffer(initSegment);
+
+  await videoWatcher.wait_for("loadedmetadata");
+  assert_equals(video.readyState, video.HAVE_METADATA);
+
+  const detector = new TextDetector();
+  await promise_rejects(t, 'InvalidStateError', detector.detect(video));
+}, "Text - detect(HTMLVideoElement) - HAVE_METADATA");
+
+</script>
diff --git a/third_party/blink/web_tests/http/tests/shapedetection/shapedetection-cross-origin.html b/third_party/blink/web_tests/http/tests/shapedetection/shapedetection-cross-origin.html
index 16956f79..593b06d1 100644
--- a/third_party/blink/web_tests/http/tests/shapedetection/shapedetection-cross-origin.html
+++ b/third_party/blink/web_tests/http/tests/shapedetection/shapedetection-cross-origin.html
@@ -7,74 +7,51 @@
 const IMAGE_URL = "http://localhost:8080/security/resources/abe.png";
 const VIDEO_URL = "http://localhost:8080/external/wpt/media/white.webm";
 
-// Returns a Promise that is resolve()d if detect() is rejected. Needs an input
-// |element| (e.g. an HTMLImageElement or HTMLVideoElement) and a |url| to load.
-function detectTextOnElementAndExpectError(element, url) {
-  return new Promise(function(resolve, reject) {
-    var tryTextDetection = function() {
-      var textDetector = new TextDetector();
-      textDetector.detect(element)
-          .then(textDetectionResult => {
-            reject("Promise should have been rejected.");
-          })
-          .catch(error => {
-            resolve(error);
-          });
-    };
-    element.onload = tryTextDetection;
-    element.onerror = tryTextDetection;
-    element.src = url;
-  });
-}
-
-function detectTextOnImageBitmapAndExpectError(imageUrl) {
-  return new Promise(function(resolve, reject) {
-    var image = new Image();
-    image.onload = function() {
-      createImageBitmap(image)
-          .then(imageBitmap => {
-            var textDetector = new TextDetector();
-            return textDetector.detect(imageBitmap);
-          })
-          .then(textDetectionResult => {
-            reject("Promise should have been rejected.");
-          })
-          .catch(error => {
-            resolve(error);
-          });
-    };
-    image.onerror = () => {};  // Explicitly ignore expected error events.
-    image.src = imageUrl;
-  });
-}
-
 // Verifies that TextDetector rejects a cross-origin HTMLImageElement.
-promise_test(function(t) {
-  var image = new Image();
-  return detectTextOnElementAndExpectError(image, IMAGE_URL)
-      .then(error => {
-        assert_equals(error.name, "SecurityError");
-      });
+promise_test(async t => {
+  const img = new Image();
+  const imgWatcher = new EventWatcher(t, img, ["load", "error"]);
+  img.src = IMAGE_URL;
+  await imgWatcher.wait_for("load");
+  const detector = new TextDetector();
+  await promise_rejects(t, "SecurityError", detector.detect(img));
 },
 "TextDetector should reject cross-origin HTMLImageElements with a SecurityError.");
 
 // Verifies that TextDetector rejects a cross-origin ImageBitmap.
-promise_test(function(t) {
-  return detectTextOnImageBitmapAndExpectError(IMAGE_URL)
-      .then(error => {
-        assert_equals(error.name, "SecurityError");
-      });
+promise_test(async t => {
+  const img = new Image();
+  const imgWatcher = new EventWatcher(t, img, ["load", "error"]);
+  img.src = IMAGE_URL;
+  await imgWatcher.wait_for("load");
+  const imgBitmap = await createImageBitmap(img);
+  const detector = new TextDetector();
+  await promise_rejects(t, "SecurityError", detector.detect(imgBitmap));
 },
 "TextDetector should reject cross-origin ImageBitmaps with a SecurityError.");
 
 // Verifies that TextDetector rejects a cross-origin HTMLVideoElement.
-promise_test(function(t) {
-  var video = document.createElement('video');
-  return detectTextOnElementAndExpectError(video, VIDEO_URL)
-      .then(error => {
-        assert_equals(error.name, "SecurityError");
-      });
+promise_test(async t => {
+  const video = document.createElement('video');
+  const videoWatcher = new EventWatcher(t, video, ["load", "error"]);
+  video.src = VIDEO_URL;
+  await videoWatcher.wait_for("error");
+  const detector = new TextDetector();
+  await promise_rejects(t, "SecurityError", detector.detect(video));
 },
 "TextDetector should reject cross-origin HTMLVideoElements with a SecurityError.");
 
+// Verifies that TextDetector rejects a cross-origin HTMLCanvasElement.
+promise_test(async t => {
+  const img = new Image();
+  const imgWatcher = new EventWatcher(t, img, ["load", "error"]);
+  img.src = IMAGE_URL;
+  await imgWatcher.wait_for("load");
+  const canvas = document.createElement("canvas");
+  canvas.getContext("2d").drawImage(img, 0, 0);
+  const detector = new TextDetector();
+  await promise_rejects(t, "SecurityError", detector.detect(canvas));
+},
+"TextDetector should reject cross-origin HTMLCanvasElements with a SecurityError.");
+
 </script>
diff --git a/third_party/blink/web_tests/resources/testdriver-vendor.js b/third_party/blink/web_tests/resources/testdriver-vendor.js
index 17b89e22..0250c168 100644
--- a/third_party/blink/web_tests/resources/testdriver-vendor.js
+++ b/third_party/blink/web_tests/resources/testdriver-vendor.js
@@ -17,31 +17,47 @@
     return [x, y];
   }
 
-  function getPointerInteractablePaintTree(element) {
-    if (!window.document.contains(element)) {
+  function getPointerInteractablePaintTree(element, frame) {
+    var frameDocument = frame == window ? window.document : frame.contentDocument;
+    if (!frameDocument.contains(element)) {
       return [];
     }
 
     var rectangles = element.getClientRects();
-
     if (rectangles.length === 0) {
       return [];
     }
 
     var centerPoint = getInViewCenterPoint(rectangles[0]);
     if ("elementsFromPoint" in document) {
-      return document.elementsFromPoint(centerPoint[0], centerPoint[1]);
+      return frameDocument.elementsFromPoint(centerPoint[0], centerPoint[1]);
     } else if ("msElementsFromPoint" in document) {
-      var rv = document.msElementsFromPoint(centerPoint[0], centerPoint[1]);
+      var rv = frameDocument.msElementsFromPoint(centerPoint[0], centerPoint[1]);
       return Array.prototype.slice.call(rv ? rv : []);
     } else {
       throw new Error("document.elementsFromPoint unsupported");
     }
   }
 
-  function inView(element) {
-    var pointerInteractablePaintTree = getPointerInteractablePaintTree(element);
-    return pointerInteractablePaintTree.indexOf(element) !== -1 || element.contains(pointerInteractablePaintTree[0]);
+  function inView(element, frame) {
+    var pointerInteractablePaintTree = getPointerInteractablePaintTree(element, frame);
+    return pointerInteractablePaintTree.indexOf(element) !== -1 || element.contains(pointerInteractablePaintTree[0], frame);
+  }
+
+  function findElementInFrame(element, frame) {
+    var foundFrame = frame;
+    var frameDocument = frame == window ? window.document : frame.contentDocument;
+    if (!frameDocument.contains(element)) {
+      foundFrame = null;
+      var frames = document.getElementsByTagName("iframe");
+      for (let i = 0; i < frames.length; i++) {
+        if (findElementInFrame(element, frames[i])) {
+          foundFrame = frames[i];
+          break;
+        }
+      }
+    }
+    return foundFrame;
   }
 
   window.test_driver_internal.click = function(element, coords) {
@@ -122,12 +138,13 @@
                return Promise.reject(new Error("pointer origin is not given correctly"));
              }
           } else {
-            let element = actions[i].actions[j].origin;
-            if (!window.document.contains(element)) {
-              return Promise.reject(new Error("element in different document or shadow tree"));
+            var element = actions[i].actions[j].origin;
+            var frame = findElementInFrame(element, window);
+            if (frame == null) {
+              return Promise.reject(new Error("element in different document or iframe"));
             }
 
-            if (!inView(element)) {
+            if (!inView(element, frame)) {
               if (didScrollIntoView)
                 return Promise.reject(new Error("already scrolled into view, the element is not found"));
 
@@ -137,7 +154,7 @@
               didScrollIntoView = true;
             }
 
-            var pointerInteractablePaintTree = getPointerInteractablePaintTree(element);
+            var pointerInteractablePaintTree = getPointerInteractablePaintTree(element, frame);
             if (pointerInteractablePaintTree.length === 0 ||
                 !element.contains(pointerInteractablePaintTree[0])) {
               return Promise.reject(new Error("element click intercepted error"));
@@ -147,6 +164,11 @@
             var centerPoint = getInViewCenterPoint(rect);
             last_x_position = actions[i].actions[j].x + centerPoint[0];
             last_y_position = actions[i].actions[j].y + centerPoint[1];
+            if (frame != window) {
+              var frameRect = frame.getClientRects();
+              last_x_position += frameRect[0].left;
+              last_y_position += frameRect[0].top;
+            }
           }
         }
 
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 6c660d4..9d85e7d 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -4810,6 +4810,7 @@
   <int value="215" label="RFHI_BEGIN_NAVIGATION_NON_WEBBY_TRANSITION"/>
   <int value="216" label="RFH_NO_MATCHING_NAVIGATION_REQUEST_ON_COMMIT"/>
   <int value="217" label="AUTH_INVALID_ICON_URL"/>
+  <int value="218" label="REGISTER_PROTOCOL_HANDLER_INVALID_URL"/>
 </enum>
 
 <enum name="BadMessageReasonExtensions">
@@ -33558,6 +33559,7 @@
   <int value="-2075807193" label="enable-webusb-on-any-origin"/>
   <int value="-2075725205" label="disable-new-zip-unpacker"/>
   <int value="-2074080173" label="NightLight:disabled"/>
+  <int value="-2073617042" label="CrostiniWebUIInstaller:enabled"/>
   <int value="-2067166422" label="enable-slimming-paint-v2"/>
   <int value="-2066541315" label="Mus:enabled"/>
   <int value="-2064164557" label="DownloadsForeground:disabled"/>
@@ -35818,6 +35820,7 @@
   <int value="1173244409" label="AutofillUseMobileLabelDisambiguation:enabled"/>
   <int value="1174088940" label="enable-wasm"/>
   <int value="1177120582" label="InstallableInkDrop:disabled"/>
+  <int value="1178215520" label="CrostiniWebUIInstaller:disabled"/>
   <int value="1179013979"
       label="OmniboxUIExperimentMaxAutocompleteMatches:enabled"/>
   <int value="1179936481" label="enable-android-pay-integration-v1"/>
@@ -56554,6 +56557,7 @@
   <int value="42" label="Send Tab"/>
   <int value="43" label="Security Events"/>
   <int value="44" label="Wi-Fi Configurations"/>
+  <int value="45" label="Web Apps"/>
 </enum>
 
 <enum name="SyncModelTypeStoreInitResult">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index d745689..974a0c9 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -46148,7 +46148,10 @@
 
 <histogram
     name="GPU.DirectComposition.DisableLargerThanScreenOverlaysWorkaround"
-    enum="BooleanActive" expires_after="2019-7-31">
+    enum="BooleanActive" expires_after="2019-07-09">
+  <obsolete>
+    No longer needed. Data has been collected.
+  </obsolete>
   <owner>magchen@chromium.org</owner>
   <owner>zmo@chromium.org</owner>
   <summary>
@@ -110474,6 +110477,18 @@
   </summary>
 </histogram>
 
+<histogram name="ResourceScheduler.PeakObservedQueueingDelay" units="ms"
+    expires_after="M80">
+  <owner>jfwang@google.com</owner>
+  <owner>tbansal@chromium.org</owner>
+  <summary>
+    Within a request's lifetime, the peak observed queueing delay tells whether
+    the request was affected by the network congestion. Records the maximum
+    network queueing delay when the given request was in-flight. This is emitted
+    when the request is completed.
+  </summary>
+</histogram>
+
 <histogram name="ResourceScheduler.RequestQueuingDuration" units="ms">
   <owner>tbansal@chromium.org</owner>
   <summary>
@@ -122756,7 +122771,7 @@
 </histogram>
 
 <histogram name="SignedExchange.ValidityPingDuration" units="ms"
-    expires_after="M77">
+    expires_after="M82">
   <owner>kinuko@chromium.org</owner>
   <owner>kouhei@chromium.org</owner>
   <owner>ksakamoto@chromium.org</owner>
@@ -122768,7 +122783,7 @@
 </histogram>
 
 <histogram name="SignedExchange.ValidityPingResult"
-    enum="SignedExchangeValidityPingResult" expires_after="M77">
+    enum="SignedExchangeValidityPingResult" expires_after="M82">
   <owner>kinuko@chromium.org</owner>
   <owner>kouhei@chromium.org</owner>
   <owner>ksakamoto@chromium.org</owner>
@@ -165009,6 +165024,7 @@
   <suffix name="USER_CONSENT" label="USER_CONSENT"/>
   <suffix name="USER_EVENT" label="USER_EVENT"/>
   <suffix name="WALLET_METADATA" label="WALLET_METADATA"/>
+  <suffix name="WEB_APP" label="WEB_APP"/>
   <suffix name="WIFI_CONFIGURATION" label="WIFI_CONFIGURATION"/>
   <suffix name="WIFI_CREDENTIAL" label="WIFI_CREDENTIAL"/>
   <affected-histogram name="FCMInvalidations.SubscriptionResponseCodeForTopic"/>
diff --git a/tools/perf/chrome_telemetry_build/BUILD.gn b/tools/perf/chrome_telemetry_build/BUILD.gn
index 55970a2..ff9df95 100644
--- a/tools/perf/chrome_telemetry_build/BUILD.gn
+++ b/tools/perf/chrome_telemetry_build/BUILD.gn
@@ -95,7 +95,16 @@
     "//third_party/catapult:telemetry_chrome_test_support",
   ]
   if (is_android) {
-    data += [ "//build/android/stacktrace/" ]
+    data += [
+      "//build/android/stacktrace/",
+      "//build/android/tombstones.py",
+
+      # TODO(httpss://crbug.com/833808): Remove this once bots always set
+      # CHROMIUM_OUTPUT_DIR correctly. Currently, this is necessary in order
+      # for //build/android/pylib/constants/__init__.py to detect the output
+      # directory, which tombstones.py depends on.
+      "$root_out_dir/build.ninja",
+    ]
     data_deps += [
       "//chrome/android/webapk/shell_apk:maps_go_webapk",
       "//build/android:devil_chromium_py",
diff --git a/tools/perf/core/minidump_unittest.py b/tools/perf/core/minidump_unittest.py
index 2ad6cf4..47870df1 100644
--- a/tools/perf/core/minidump_unittest.py
+++ b/tools/perf/core/minidump_unittest.py
@@ -13,14 +13,16 @@
 
 class BrowserMinidumpTest(tab_test_case.TabTestCase):
   @decorators.Isolated
-  # Disabled tests due to flakiness: http://crbug.com/641469
-  @decorators.Disabled('all')
+  # ChromeOS and Android are currently hard coded to return None for minidump
+  # paths, so disable on those platforms. Windows 7 doesn't find any minidump
+  # paths for some reason.
+  @decorators.Disabled('chromeos', 'android', 'win7')
   def testSymbolizeMinidump(self):
     # Wait for the browser to restart fully before crashing
     self._LoadPageThenWait('var sam = "car";', 'sam')
     self._browser.tabs.New().Navigate('chrome://gpucrash', timeout=5)
     crash_minidump_path = self._browser.GetMostRecentMinidumpPath()
-    #self.assertIsNotNone(crash_minidump_path)
+    self.assertIsNotNone(crash_minidump_path)
 
     if crash_minidump_path is not None:
       logging.info('testSymbolizeMinidump: most recent path = '
@@ -32,7 +34,7 @@
     if all_unsymbolized_paths is not None:
       logging.info('testSymbolizeMinidump: all unsymbolized paths '
           + ''.join(all_unsymbolized_paths))
-    #self.assertTrue(len(all_unsymbolized_paths) == 1)
+    self.assertTrue(len(all_unsymbolized_paths) == 1)
 
     # Now symbolize that minidump and make sure there are no longer any present
     self._browser.SymbolizeMinidump(crash_minidump_path)
@@ -43,19 +45,57 @@
       logging.info('testSymbolizeMinidump: after symbolize all '
           + 'unsymbolized paths: '
           + ''.join(all_unsymbolized_after_symbolize_paths))
-    #self.assertTrue(len(all_unsymbolized_after_symbolize_paths) == 0)
-
+    self.assertTrue(len(all_unsymbolized_after_symbolize_paths) == 0)
 
   @decorators.Isolated
-  # Disabled tests due to flakiness: http://crbug.com/641469
-  @decorators.Disabled('all')
+  # The way Android handles crashes is through a different set of methods, so
+  # have an Android-specific test similar to testSymbolizeMinidump.
+  @decorators.Enabled('android')
+  def testGetStackTrace(self):
+    self._LoadPageThenWait('var sam = "car";', 'sam')
+    self._browser.tabs.New().Navigate('chrome://gpucrash', timeout=5)
+    _, output = self._browser.GetStackTrace()
+
+    # The output is a single string with multiple sections:
+    # 1. UI Dump
+    # 2. Logcat
+    # 3. Stack from Logcat
+    # 4. Tombstones
+    # 5. Crashpad stackwalk
+    # Each section is finished with 80 asterisks, so split based on that.
+    sections = output.split('*' * 80 + '\n')
+
+    # We will always get the UI dump and logcat sections. The logcat stack,
+    # tombstones, and Crashpad stack are dependent on the necessary tools being
+    # present, which they always should be. The Crashpad section actually having
+    # data is dependent on a Crashpad dump being found, which may not always be
+    # the case. So, expect 5 actual sections (6 total due to the way .split()
+    # works), the 5th possibly not having any valid data.
+    self.assertTrue(len(sections) == 6)
+    self.assertTrue(sections[2].startswith('Stack from Logcat'))
+    self.assertTrue(sections[3].startswith('Tombstones'))
+
+    # Since the crash is a simulated one from gl::Crash(), we expect that to
+    # show up in the symbolized stacks, but not the unsymbolized one.
+    crash_function = 'gl::Crash()'
+    self.assertFalse(crash_function in sections[1])
+    self.assertTrue(crash_function in sections[2])
+    self.assertTrue(crash_function in sections[3])
+    # If we actually have a valid Crashpad stack, make sure it contains the
+    # crash function as well.
+    print sections[4][:80]
+    if '**EMPTY**' not in sections[4]:
+      self.assertTrue(crash_function in sections[4])
+
+  @decorators.Isolated
+  @decorators.Disabled('chromeos', 'android', 'win7')
   def testMultipleCrashMinidumps(self):
     # Wait for the browser to restart fully before crashing
     self._LoadPageThenWait('var cat = "dog";', 'cat')
     self._browser.tabs.New().Navigate('chrome://gpucrash', timeout=5)
     first_crash_path = self._browser.GetMostRecentMinidumpPath()
 
-    #self.assertIsNotNone(first_crash_path)
+    self.assertIsNotNone(first_crash_path)
     if first_crash_path is not None:
       logging.info('testMultipleCrashMinidumps: first crash most recent path'
           + first_crash_path)
@@ -63,10 +103,10 @@
     if all_paths is not None:
       logging.info('testMultipleCrashMinidumps: first crash all paths: '
           + ''.join(all_paths))
-    #self.assertEquals(len(all_paths), 1)
-    #self.assertEqual(all_paths[0], first_crash_path)
+    self.assertEquals(len(all_paths), 1)
+    self.assertEqual(all_paths[0], first_crash_path)
     all_unsymbolized_paths = self._browser.GetAllUnsymbolizedMinidumpPaths()
-    #self.assertTrue(len(all_unsymbolized_paths) == 1)
+    self.assertTrue(len(all_unsymbolized_paths) == 1)
     if all_unsymbolized_paths is not None:
       logging.info('testMultipleCrashMinidumps: first crash all unsymbolized '
           'paths: ' + ''.join(all_unsymbolized_paths))
@@ -79,7 +119,7 @@
 
     self._browser.tabs.New().Navigate('chrome://gpucrash', timeout=5)
     second_crash_path = self._browser.GetMostRecentMinidumpPath()
-    #self.assertIsNotNone(second_crash_path)
+    self.assertIsNotNone(second_crash_path)
     if second_crash_path is not None:
       logging.info('testMultipleCrashMinidumps: second crash most recent path'
           + second_crash_path)
@@ -93,11 +133,11 @@
     if second_crash_all_unsymbolized_paths is not None:
       logging.info('testMultipleCrashMinidumps: second crash all unsymbolized '
           'paths: ' + ''.join(second_crash_all_unsymbolized_paths))
-    #self.assertEquals(len(second_crash_all_paths), 2)
+    self.assertEquals(len(second_crash_all_paths), 2)
     # Check that both paths are now present and unsymbolized
-    #self.assertTrue(first_crash_path in second_crash_all_paths)
-    #self.assertTrue(second_crash_path in second_crash_all_paths)
-    #self.assertTrue(len(second_crash_all_unsymbolized_paths) == 2)
+    self.assertTrue(first_crash_path in second_crash_all_paths)
+    self.assertTrue(second_crash_path in second_crash_all_paths)
+    self.assertTrue(len(second_crash_all_unsymbolized_paths) == 2)
 
 
     # Now symbolize one of those paths and assert that there is still one
@@ -107,15 +147,15 @@
     if after_symbolize_all_paths is not None:
       logging.info('testMultipleCrashMinidumps: after symbolize all paths: '
           + ''.join(after_symbolize_all_paths))
-    #self.assertEquals(len(after_symbolize_all_paths), 2)
+    self.assertEquals(len(after_symbolize_all_paths), 2)
     after_symbolize_all_unsymbolized_paths = \
         self._browser.GetAllUnsymbolizedMinidumpPaths()
     if after_symbolize_all_unsymbolized_paths is not None:
       logging.info('testMultipleCrashMinidumps: after symbolize all '
           + 'unsymbolized paths: '
           + ''.join(after_symbolize_all_unsymbolized_paths))
-    #self.assertEquals(after_symbolize_all_unsymbolized_paths,
-     #   [first_crash_path])
+    self.assertEquals(after_symbolize_all_unsymbolized_paths,
+        [first_crash_path])
 
   def _LoadPageThenWait(self, script, value):
     # We are occasionally seeing these tests fail on the first load and
diff --git a/tools/perf/core/perf_benchmark_unittest.py b/tools/perf/core/perf_benchmark_unittest.py
index ee4014a..8c97a754 100644
--- a/tools/perf/core/perf_benchmark_unittest.py
+++ b/tools/perf/core/perf_benchmark_unittest.py
@@ -8,6 +8,7 @@
 import tempfile
 import unittest
 
+from telemetry.core import util
 from telemetry.internal.browser import browser_finder
 from telemetry.testing import options_for_unittests
 
@@ -126,6 +127,17 @@
       self.assertNotIn(arg, options.browser_options.extra_browser_args)
 
   def testNoAdTaggingRuleset(self):
+    # This tests (badly) assumes that util.GetBuildDirectories() will always
+    # return a list of multiple directories, with Debug ordered before Release.
+    # This is not the case if CHROMIUM_OUTPUT_DIR is set or a build.ninja file
+    # exists in the current working directory - in those cases, only a single
+    # directory is returned. So, abort early if we only get back one directory.
+    num_dirs = 0
+    for _ in util.GetBuildDirectories(self._chrome_root):
+      num_dirs += 1
+    if num_dirs < 2:
+      return
+
     benchmark = perf_benchmark.PerfBenchmark()
     options = options_for_unittests.GetCopy()
 
@@ -194,6 +206,17 @@
     # directories matching the browser_type.
     self._PopulateGenFiles(os.path.join(self._chrome_root, 'out', 'Debug'))
 
+    # This tests (badly) assumes that util.GetBuildDirectories() will always
+    # return a list of multiple directories, with Debug ordered before Release.
+    # This is not the case if CHROMIUM_OUTPUT_DIR is set or a build.ninja file
+    # exists in the current working directory - in those cases, only a single
+    # directory is returned. So, abort early if we only get back one directory.
+    num_dirs = 0
+    for _ in util.GetBuildDirectories(self._chrome_root):
+      num_dirs += 1
+    if num_dirs < 2:
+      return
+
     benchmark = perf_benchmark.PerfBenchmark()
     options = options_for_unittests.GetCopy()
     options.chrome_root = self._chrome_root
diff --git a/tools/traffic_annotation/scripts/traffic_annotation_auditor_tests.py b/tools/traffic_annotation/scripts/traffic_annotation_auditor_tests.py
index 4c1719ec..cf3db645 100755
--- a/tools/traffic_annotation/scripts/traffic_annotation_auditor_tests.py
+++ b/tools/traffic_annotation/scripts/traffic_annotation_auditor_tests.py
@@ -54,7 +54,16 @@
     configs = [
       ["--test-only", "--error-resilient"],  # Similar to trybot.
       ["--test-only"],                       # Failing on any runtime error.
-      ["--test-only", "--no-filtering"]      # Not using heuristic filtering.
+      ["--test-only", "--no-filtering"],     # Not using heuristic filtering.
+      [                                      # extractor.py.
+          "--test-only",
+          "--extractor-backend=python_script",
+      ],
+      [                                      # extractor.py, no filtering.
+          "--test-only",
+          "--no-filtering",
+          "--extractor-backend=python_script",
+      ],
     ]
 
     self.last_result = None
diff --git a/ui/accessibility/ax_language_detection.h b/ui/accessibility/ax_language_detection.h
index 8851290..41b50f83 100644
--- a/ui/accessibility/ax_language_detection.h
+++ b/ui/accessibility/ax_language_detection.h
@@ -21,61 +21,28 @@
 class AXTree;
 
 // This module implements language detection enabling Chrome to automatically
-// detect the language for spans of text within the page without relying on any
-// declared attributes.
+// detect the language for runs of text within the page.
 //
-// Language detection relies on four key data structures:
-//   AXLanguageInfo represents the local language detection data for all text
-//        within an AXNode.
-//   AXLanguageInfoStats records statistics about AXLanguageInfo for all AXNodes
-//        within a single AXTree, this is used to help give language detection
-//        some context to reduce false positive language assignment.
-//   AXLanguageSpan represents local language detection data for spans of text
-//        within an AXNode, this is used by sub-node level language detection.
-//   AXLanguageDetectionManager is in charge of managing all language detection
-//        context for a single AXTree.
+// Node-level language detection runs once per page after the load complete
+// event. This involves two passes:
+//   *Detect* walks the tree from the given root using cld3 to detect up to 3
+//            potential languages per node. A ranked list is created enumerating
+//            all potential languages on a page.
+//   *Label* re-walks the tree, assigning a language to each node considering
+//           the potential languages from the detect phase, page level
+//           statistics, and the assigned languages of ancestor nodes.
 //
-//
-// Language detection is currently separated into two related implementation
-// which are trying to address slightly different use cases, one operates at the
-// node level, while the other operates at the sub-node level.
-//
-//
-// Language detection at the node-level attempts to assign at most one language
-// for each AXNode in order to support mixed-language pages. Node-level language
-// detection is implemented as a two-pass process to reduce the assignment of
-// spurious languages.
-//
-// After the first pass no languages have been assigned to AXNode(s), this is
-// left to the second pass so that we can take use tree-level statistics to
-// better inform the local language assigned.
-//
-// The first pass 'Detect' (entry point DetectLanguageForSubtree) walks the
-// subtree from a given AXNode and attempts to detect the language of any text
-// found. It records results in an instance of AXLanguageInfo which it stores on
-// the AXNode, it also records statistics on the languages found in the
-// AXLanguageInfoStats instance associated with each AXTree.
-//
-// The second pass 'Label' (entry point LabelLanguageForSubtree) walks the
-// subtree from a given AXNode and attempts to find an appropriate language to
-// associate with each AXNode based on a combination of the local detection
-// results (AXLanguageInfo) and the global stats (AXLanguageInfoStats).
-//
-//
-// Language detection at the sub-node level differs from node-level in that it
-// operates at a much finer granularity of text, potentially down to individual
-// characters in order to support mixed language sentences.
-// We would like to detect languages that may only occur once throughout the
-// entire document. Sub-node-level language detection is performed by using a
-// language identifier constructed with a byte minimum of
-// kShortTextIdentifierMinByteLength. This way, it can potentially detect the
-// language of strings that are as short as one character in length.
-//
-// The entry point for sub-nod level language detection is
-// GetLanguageAnnotationForStringAttribute.
+// Optionally an embedder may run *sub-node* language detection which attempts
+// to assign languages for runs of text within a node, potentially down to the
+// individual character level. This is useful in cases where a single paragraph
+// involves switching between multiple languages, and where the speech engine
+// doesn't automatically switch voices to handle different character sets.
+// Due to the potentially small lengths of text runs involved this tends to be
+// lower in accuracy, and works best when a node is composed of multiple
+// languages with easily distinguishable scripts.
 
-// An instance of AXLanguageInfo is used to record the detected and assigned
-// languages for a single AXNode, this data is entirely local to the AXNode.
+// AXLanguageInfo represents the local language detection data for all text
+// within an AXNode. Stored on AXNode.
 struct AX_EXPORT AXLanguageInfo {
   AXLanguageInfo();
   ~AXLanguageInfo();
@@ -121,15 +88,21 @@
   float probability;
 };
 
-// A single AXLanguageInfoStats instance is stored for each AXTree and
-// represents the language detection statistics for every AXNode within that
-// AXTree.
+// A single AXLanguageInfoStats instance is stored on each AXTree and contains
+// statistics on detected languages for all the AXNodes in that tree.
 //
-// We rely on these tree-level statistics to avoid spurious language detection
-// assignments.
+// We rely on these tree-level statistics when labelling individual nodes, to
+// provide extra signals to increase our confidence in assigning a detected
+// language.
 //
 // The Label step will only assign a detected language to a node if that
-// language is one of the dominant languages on the page.
+// language is one of the most frequent languages on the page.
+//
+// For example, if a single node has detected_languages (in order of probability
+// assigned by cld_3): da-DK, en-AU, fr-FR, but the page statistics overall
+// indicate that the page is generally in en-AU and ja-JP, it is more likely to
+// be a mis-recognition of Danish than an accurate assignment, so we assign
+// en-AU instead of da-DK.
 class AX_EXPORT AXLanguageInfoStats {
  public:
   AXLanguageInfoStats();
@@ -163,8 +136,8 @@
   DISALLOW_COPY_AND_ASSIGN(AXLanguageInfoStats);
 };
 
-// An instance of AXLanguageDetectionManager manages all the context needed for
-// language detection within a single AXTree.
+// AXLanguageDetectionManager manages all of the context needed for language
+// detection within an AXTree.
 class AX_EXPORT AXLanguageDetectionManager {
  public:
   AXLanguageDetectionManager();
@@ -182,7 +155,7 @@
   // having already completed.
   void LabelLanguageForSubtree(AXNode* subtree_root);
 
-  // Detect and return languages for string attribute.
+  // Sub-node language detection for a given string attribute.
   // For example, if a node has name: "My name is Fred", then calling
   // GetLanguageAnnotationForStringAttribute(*node, ax::mojom::StringAttribute::
   // kName) would return language detection information about "My name is Fred".
diff --git a/ui/accessibility/platform/ax_platform_node_base.cc b/ui/accessibility/platform/ax_platform_node_base.cc
index e99c109..5a5394f 100644
--- a/ui/accessibility/platform/ax_platform_node_base.cc
+++ b/ui/accessibility/platform/ax_platform_node_base.cc
@@ -733,6 +733,7 @@
 bool AXPlatformNodeBase::IsInvisibleOrIgnored() const {
   const AXNodeData& data = GetData();
   return data.HasState(ax::mojom::State::kInvisible) ||
+         data.HasState(ax::mojom::State::kIgnored) ||
          data.role == ax::mojom::Role::kIgnored;
 }
 
diff --git a/ui/base/l10n/l10n_util_unittest.cc b/ui/base/l10n/l10n_util_unittest.cc
index ba9a3ea7..c3070971 100644
--- a/ui/base/l10n/l10n_util_unittest.cc
+++ b/ui/base/l10n/l10n_util_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/i18n/time_formatting.h"
 #include "base/path_service.h"
 #include "base/stl_util.h"
+#include "base/strings/pattern.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/icu_test_util.h"
@@ -430,12 +431,18 @@
 TEST_F(L10nUtilTest, GetDisplayNameForLocale) {
   // TODO(jungshik): Make this test more extensive.
   // Test zh-CN and zh-TW are treated as zh-Hans and zh-Hant.
+  // Displays as "Chinese, Simplified" on iOS 13+ and as "Chinese (Simplified)"
+  // on other platforms.
   base::string16 result =
       l10n_util::GetDisplayNameForLocale("zh-CN", "en", false);
-  EXPECT_EQ(ASCIIToUTF16("Chinese (Simplified)"), result);
+  EXPECT_TRUE(
+      base::MatchPattern(base::UTF16ToUTF8(result), "Chinese*Simplified*"));
 
+  // Displays as "Chinese, Traditional" on iOS 13+ and as
+  // "Chinese (Traditional)" on other platforms.
   result = l10n_util::GetDisplayNameForLocale("zh-TW", "en", false);
-  EXPECT_EQ(ASCIIToUTF16("Chinese (Traditional)"), result);
+  EXPECT_TRUE(
+      base::MatchPattern(base::UTF16ToUTF8(result), "Chinese*Traditional*"));
 
   // tl and fil are not identical to be strict, but we treat them as
   // synonyms.
diff --git a/ui/compositor/compositor.cc b/ui/compositor/compositor.cc
index e9be02b..04529b09 100644
--- a/ui/compositor/compositor.cc
+++ b/ui/compositor/compositor.cc
@@ -206,6 +206,7 @@
           features::kCompositorThreadedScrollbarScrolling)) {
     settings.compositor_threaded_scrollbar_scrolling = true;
   }
+  settings.layer_transforms_should_scale_layer_contents = true;
 
   animation_host_ = cc::AnimationHost::CreateMainInstance();
 
diff --git a/ui/events/ozone/evdev/touch_event_converter_evdev.cc b/ui/events/ozone/evdev/touch_event_converter_evdev.cc
index 77d364f6..b1d83fa 100644
--- a/ui/events/ozone/evdev/touch_event_converter_evdev.cc
+++ b/ui/events/ozone/evdev/touch_event_converter_evdev.cc
@@ -608,7 +608,7 @@
   ReportEvents(EventTimeForNow());
 }
 
-float TouchEventConverterEvdev::ScalePressure(int32_t value) {
+float TouchEventConverterEvdev::ScalePressure(int32_t value) const {
   float pressure = value - pressure_min_;
   if (pressure_max_ - pressure_min_)
     pressure /= pressure_max_ - pressure_min_;
diff --git a/ui/events/ozone/evdev/touch_event_converter_evdev.h b/ui/events/ozone/evdev/touch_event_converter_evdev.h
index 2bbdc40..991f88b 100644
--- a/ui/events/ozone/evdev/touch_event_converter_evdev.h
+++ b/ui/events/ozone/evdev/touch_event_converter_evdev.h
@@ -93,7 +93,7 @@
   void CancelAllTouches();
   bool IsPalm(const InProgressTouchEvdev& touch);
   // Normalize pressure value to [0, 1].
-  float ScalePressure(int32_t value);
+  float ScalePressure(int32_t value) const;
 
   int NextTrackingId();
 
@@ -165,7 +165,6 @@
 
   // Callback to enable/disable palm suppression.
   base::RepeatingCallback<void(bool)> enable_palm_suppression_callback_;
-
   DISALLOW_COPY_AND_ASSIGN(TouchEventConverterEvdev);
 };
 
diff --git a/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc b/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc
index b9404ce..e8acc7c 100644
--- a/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc
+++ b/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc
@@ -1543,6 +1543,40 @@
             up_event.pointer_details.pointer_type);
 }
 
+TEST_F(TouchEventConverterEvdevTest, ScalePressure) {
+  EventDeviceInfo devinfo;
+  EXPECT_TRUE(CapabilitiesToDeviceInfo(kEveTouchScreen, &devinfo));
+  device()->Initialize(devinfo);
+  timeval time;
+  time = {1507226211, 483601};
+  // Fake abroken input: note the pressure.
+  struct input_event mock_kernel_queue[] = {
+      {time, EV_ABS, ABS_MT_TRACKING_ID, 461},
+      {time, EV_ABS, ABS_MT_POSITION_X, 1795},
+      {time, EV_ABS, ABS_MT_POSITION_Y, 5559},
+      {time, EV_ABS, ABS_MT_PRESSURE,
+       devinfo.GetAbsMaximum(ABS_MT_PRESSURE) * 2},
+      {time, EV_ABS, ABS_MT_TOUCH_MAJOR, 14},
+      {time, EV_ABS, ABS_MT_TOUCH_MINOR, 14},
+      {time, EV_KEY, BTN_TOUCH, 1},
+      {time, EV_ABS, ABS_X, 1795},
+      {time, EV_ABS, ABS_Y, 5559},
+      {time, EV_ABS, ABS_PRESSURE, 217},
+      {time, EV_MSC, MSC_TIMESTAMP, 0},
+      {time, EV_SYN, SYN_REPORT, 0},
+  };
+  // Set test now time to ensure above timestamps are in the past.
+  SetTestNowTime(time);
+
+  // Finger pressed with major/minor reported.
+  device()->ConfigureReadMock(mock_kernel_queue, base::size(mock_kernel_queue),
+                              0);
+  device()->ReadNow();
+  EXPECT_EQ(1u, size());
+  ui::TouchEventParams event = dispatched_touch_event(0);
+  EXPECT_FLOAT_EQ(1.0, event.pointer_details.force);
+}
+
 // crbug.com/771374
 TEST_F(TouchEventConverterEvdevTest, FingerSizeWithResolution) {
   ui::MockTouchEventConverterEvdev* dev = device();
@@ -1581,9 +1615,10 @@
   EXPECT_EQ(1795, event.location.x());
   EXPECT_EQ(5559, event.location.y());
   EXPECT_EQ(0, event.slot);
+  EXPECT_FLOAT_EQ(217.0 / devinfo.GetAbsMaximum(ABS_MT_PRESSURE),
+                  event.pointer_details.force);
   EXPECT_EQ(EventPointerType::POINTER_TYPE_TOUCH,
             event.pointer_details.pointer_type);
   EXPECT_FLOAT_EQ(280.f, event.pointer_details.radius_x);
-  EXPECT_FLOAT_EQ(0.8509804f, event.pointer_details.force);
 }
 }  // namespace ui
diff --git a/ui/file_manager/file_manager/foreground/js/metadata_box_controller.js b/ui/file_manager/file_manager/foreground/js/metadata_box_controller.js
index ff51b42..4ed41605e 100644
--- a/ui/file_manager/file_manager/foreground/js/metadata_box_controller.js
+++ b/ui/file_manager/file_manager/foreground/js/metadata_box_controller.js
@@ -239,20 +239,20 @@
  * A loading animation is shown while fetching the directory size. However, it
  * won't show if there is no size value. Use a dummy value ' ' in that case.
  *
- * If previous getDirectorySize is still running, next getDirectorySize is not
- * called at the time. After the previous callback is finished, getDirectorySize
- * that corresponds to the last setDirectorySize_ is called.
+ * To avoid flooding the OS system with chrome.getDirectorySize requests, if a
+ * previous request is active, store the new request and return. Only the most
+ * recent new request is stored. When the active request returns, it calls the
+ * stored request instead of updating the size field.
  *
  * @param {!DirectoryEntry} entry
- * @param {boolean} isSameEntry if the entry is not changed from the last time.
+ * @param {boolean} isSameEntry True if the entry is not changed from the last
+ *    time. False enables the loading animation.
  *
  * @private
  */
 MetadataBoxController.prototype.setDirectorySize_ = function(
     entry, isSameEntry) {
-  if (!entry.isDirectory) {
-    return;
-  }
+  assert(entry.isDirectory);
 
   if (this.metadataBox_.size === '') {
     this.metadataBox_.size = ' ';  // Provide a dummy size value.
@@ -263,14 +263,13 @@
       this.metadataBox_.isSizeLoading = true;
     }
 
-    // Only retain the last setDirectorySize_ request.
+    // Store the new setDirectorySize_ request and return.
     this.onDirectorySizeLoaded_ = lastEntry => {
       this.setDirectorySize_(entry, util.isSameEntry(entry, lastEntry));
     };
     return;
   }
 
-  // false if the entry is same. true if the entry is changed.
   this.metadataBox_.isSizeLoading = !isSameEntry;
 
   this.isDirectorySizeLoading_ = true;
@@ -280,6 +279,7 @@
     if (this.onDirectorySizeLoaded_) {
       setTimeout(this.onDirectorySizeLoaded_.bind(null, entry));
       this.onDirectorySizeLoaded_ = null;
+      return;
     }
 
     if (this.quickViewModel_.getSelectedEntry() != entry) {
@@ -287,8 +287,8 @@
     }
 
     if (chrome.runtime.lastError) {
-      this.metadataBox_.isSizeLoading = false;
-      return;
+      console.error(chrome.runtime.lastError);
+      size = undefined;
     }
 
     this.metadataBox_.size = this.fileMetadataFormatter_.formatSize(size, true);
diff --git a/ui/gfx/linux/native_pixmap_dmabuf.cc b/ui/gfx/linux/native_pixmap_dmabuf.cc
index 97fa6de..e9f5bd3 100644
--- a/ui/gfx/linux/native_pixmap_dmabuf.cc
+++ b/ui/gfx/linux/native_pixmap_dmabuf.cc
@@ -76,7 +76,7 @@
 }
 
 gfx::NativePixmapHandle NativePixmapDmaBuf::ExportHandle() {
-  return gfx::NativePixmapHandle();
+  return gfx::CloneHandleForIPC(handle_);
 }
 
 }  // namespace gfx
diff --git a/ui/gl/swap_chain_presenter.cc b/ui/gl/swap_chain_presenter.cc
index 4967369..9783b72 100644
--- a/ui/gl/swap_chain_presenter.cc
+++ b/ui/gl/swap_chain_presenter.cc
@@ -62,8 +62,7 @@
   kMaxValue = kNotAvailable,
 };
 
-void RecordOverlayFullScreenTypes(bool workaround_applied,
-                                  const gfx::Rect& overlay_onscreen_rect) {
+void RecordOverlayFullScreenTypes(const gfx::Rect& overlay_onscreen_rect) {
   OverlayFullScreenTypes full_screen_type;
   const gfx::Size& screen_size =
       DirectCompositionSurfaceWin::GetOverlayMonitorSize();
@@ -91,12 +90,6 @@
 
   UMA_HISTOGRAM_ENUMERATION("GPU.DirectComposition.OverlayFullScreenTypes",
                             full_screen_type);
-
-  // TODO(magchen): To be deleted once we know if this workaround is still
-  // needed
-  UMA_HISTOGRAM_BOOLEAN(
-      "GPU.DirectComposition.DisableLargerThanScreenOverlaysWorkaround",
-      workaround_applied);
 }
 
 const char* ProtectedVideoTypeToString(gfx::ProtectedVideoType type) {
@@ -373,7 +366,6 @@
     swap_chain_size.SetToMin(params.content_rect.size());
   }
 
-  bool workaround_applied = false;
   gfx::Size overlay_monitor_size =
       DirectCompositionSurfaceWin::GetOverlayMonitorSize();
   if (layer_tree_->disable_larger_than_screen_overlays() &&
@@ -393,19 +385,15 @@
         (swap_chain_size.width() <=
          overlay_monitor_size.width() + kOversizeMargin)) {
       swap_chain_size.set_width(overlay_monitor_size.width());
-      workaround_applied = true;
     }
 
     if ((swap_chain_size.height() > overlay_monitor_size.height()) &&
         (swap_chain_size.height() <=
          overlay_monitor_size.height() + kOversizeMargin)) {
       swap_chain_size.set_height(overlay_monitor_size.height());
-      workaround_applied = true;
     }
   }
-  RecordOverlayFullScreenTypes(
-      workaround_applied,
-      /*overlay_onscreen_rect*/ gfx::ToEnclosingRect(bounds));
+  RecordOverlayFullScreenTypes(gfx::ToEnclosingRect(bounds));
 
   // 4:2:2 subsampled formats like YUY2 must have an even width, and 4:2:0
   // subsampled formats like NV12 must have an even width and height.
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_config.js b/ui/webui/resources/cr_components/chromeos/network/network_config.js
index 58fb3bd7..930b447 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_config.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_config.js
@@ -406,7 +406,9 @@
             this.getManagedPropertiesCallback_(managedProperties);
           });
     } else {
-      this.focusFirstInput_();
+      setTimeout(() => {
+        this.focusFirstInput_();
+      });
     }
 
     if (this.type == CrOnc.Type.VPN ||
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_nameservers.js b/ui/webui/resources/cr_components/chromeos/network/network_nameservers.js
index edcd2d8..995cad9 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_nameservers.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_nameservers.js
@@ -193,9 +193,9 @@
     return nameserversType == 'custom' &&
         !this.isNetworkPolicyEnforced(
             networkProperties.NameServersConfigType) &&
-        !!networkProperties.StaticIPConfig &&
-        !this.isNetworkPolicyEnforced(
-            networkProperties.StaticIPConfig.NameServers);
+        (!networkProperties.StaticIPConfig ||
+         !this.isNetworkPolicyEnforced(
+             networkProperties.StaticIPConfig.NameServers));
   },
 
   /**