diff --git a/BUILD.gn b/BUILD.gn
index c01e2de..1b83d3c 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -785,6 +785,10 @@
     "//third_party/pffft:pffft_benchmark",
     "//third_party/pffft:pffft_unittest",
   ]
+
+  if (is_linux) {
+    deps += [ "//third_party/openscreen/src:gn_all" ]
+  }
 }
 
 if ((is_linux || is_win) && enable_remoting && !use_ozone) {
diff --git a/DEPS b/DEPS
index 31be422..3fd23d7 100644
--- a/DEPS
+++ b/DEPS
@@ -133,11 +133,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '69258ffdb88a4338c2e59a0c0fa198570822b6a8',
+  'skia_revision': 'c8a84d2b7ff98b44c42a53d1ba750a2c02f9063a',
   # 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': '8642c0be41ae5f9f62e09f247e3cab302d27b755',
+  'v8_revision': '0829f5e5e466d8078da8365602f3ea5df735cfcd',
   # 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.
@@ -149,18 +149,18 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': '69c37491787a39b4219311c9f03d16c4eb85679d',
+  'swiftshader_revision': '3d7b7ea134d1245748a26898c40f7d078fea16d7',
   # 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': '726ece1b53406b9599f5c635feae37dc7fb310c5',
+  'pdfium_revision': '0db20e0a220b9ce2ba7d6bee980ec1bcd7a667e7',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
   #
   # Note this revision should be updated with
   # third_party/boringssl/roll_boringssl.py, not roll-dep.
-  'boringssl_revision': '777a239175c26fcaa4c6c2049fedc90e859bd9b6',
+  'boringssl_revision': 'f014d609c07cf83d5a9be730469f67bd199c017e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling google-toolbox-for-mac
   # and whatever else without interference from each other.
@@ -196,7 +196,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': '88ad352aa1c60e7239c3a3259fd4fdc0f7727090',
+  'catapult_revision': '578d0b9046cc2dbce6c0c38637b71627169cb5d2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -802,7 +802,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '40748dc575b75a353c9e3afea5236892674d6df5',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '5ea0721b4537610d30bc17d95ff2298b5e932e12',
       'condition': 'checkout_linux',
   },
 
@@ -827,7 +827,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '9f1377b76560ba96b1c23c27c9f7878de188aa6c',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '3d86f06bc794b457a0e1329dfd4a0b5bd0dc2a0e',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -1154,7 +1154,7 @@
     Var('chromium_git') + '/external/github.com/cisco/openh264' + '@' + '6f26bce0b1c4e8ce0e13332f7c0083788def5fdf',
 
   'src/third_party/openscreen/src':
-    Var('chromium_git') + '/openscreen' + '@' + 'b1d095ba4f8f3ec1efd90dbd5cb0a37e5b4b21f7',
+    Var('chromium_git') + '/openscreen' + '@' + '035ec87f86835b249e5d873d0b52add07b299406',
 
   'src/third_party/ow2_asm': {
       'packages': [
@@ -1177,7 +1177,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '031ad2bc420ab6c1a0e7b3dcfb218d9d1e543859',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  'a47facdd75f95c30d421beebe7421591e7935bca',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + 'ac0d98b5cee6c024b0cffeb4f8f45b6fc5ccdb78',
@@ -1389,7 +1389,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@ddf034cde9e9e4015dd5051331d486148325ccce',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@013ffd95d645dc185e569896d40e6af9a13778be',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/net/aw_url_request_context_getter_unittest.cc b/android_webview/browser/net/aw_url_request_context_getter_unittest.cc
index 31969a5..1bde77d5 100644
--- a/android_webview/browser/net/aw_url_request_context_getter_unittest.cc
+++ b/android_webview/browser/net/aw_url_request_context_getter_unittest.cc
@@ -123,7 +123,8 @@
   std::unique_ptr<net::CertVerifier::Request> request;
   int error = context->cert_verifier()->Verify(
       net::CertVerifier::RequestParams(cert, "www.ahrn.com", flags,
-                                       std::string()),
+                                       /*ocsp_response=*/std::string(),
+                                       /*sct_list=*/std::string()),
       &result, callback.callback(), &request, net::NetLogWithSource());
   EXPECT_THAT(error, net::test::IsError(net::ERR_IO_PENDING));
   EXPECT_TRUE(request);
@@ -160,7 +161,9 @@
   net::TestCompletionCallback callback;
   std::unique_ptr<net::CertVerifier::Request> request;
   int error = context->cert_verifier()->Verify(
-      net::CertVerifier::RequestParams(cert, "127.0.0.1", flags, std::string()),
+      net::CertVerifier::RequestParams(cert, "127.0.0.1", flags,
+                                       /*ocsp_response=*/std::string(),
+                                       /*sct_list=*/std::string()),
       &result, callback.callback(), &request, net::NetLogWithSource());
   EXPECT_THAT(error, net::test::IsError(net::ERR_IO_PENDING));
   EXPECT_TRUE(request);
diff --git a/ash/app_list/views/app_list_folder_view.cc b/ash/app_list/views/app_list_folder_view.cc
index 44d631f..e2b0dcc5 100644
--- a/ash/app_list/views/app_list_folder_view.cc
+++ b/ash/app_list/views/app_list_folder_view.cc
@@ -403,10 +403,11 @@
       folder_view_->SetVisible(false);
 
     // Set the view bounds to a small rect, so that it won't overlap the root
-    // level apps grid view during folder item reprenting transitional period.
+    // level apps grid view during folder item reparenting transitional period.
     if (hide_for_reparent_) {
       gfx::Rect rect(folder_view_->bounds());
       folder_view_->SetBoundsRect(gfx::Rect(rect.x(), rect.y(), 1, 1));
+      folder_view_->UpdateBackgroundMaskBounds();
     }
 
     // Reset the transform after animation so that the following folder's
@@ -657,6 +658,10 @@
   background_view_->layer()->SetMaskLayer(background_mask_->layer());
 }
 
+void AppListFolderView::UpdateBackgroundMaskBounds() {
+  background_mask_->layer()->SetBounds(background_view_->GetLocalBounds());
+}
+
 void AppListFolderView::OnTabletModeChanged(bool started) {
   folder_header_view()->set_tablet_mode(started);
   page_switcher_->set_is_tablet_mode(started);
diff --git a/ash/app_list/views/app_list_folder_view.h b/ash/app_list/views/app_list_folder_view.h
index cb47cff..91be81af 100644
--- a/ash/app_list/views/app_list_folder_view.h
+++ b/ash/app_list/views/app_list_folder_view.h
@@ -109,6 +109,9 @@
   // Sets the layer mask's corner radius and insets in background.
   void UpdateBackgroundMask(int corner_radius, const gfx::Insets& insets);
 
+  // Updates the |background_mask_| layer bounds.
+  void UpdateBackgroundMaskBounds();
+
   // Called when tablet mode starts and ends.
   void OnTabletModeChanged(bool started);
 
diff --git a/ash/app_list/views/assistant/assistant_main_stage.cc b/ash/app_list/views/assistant/assistant_main_stage.cc
index e84e401..93afcc4 100644
--- a/ash/app_list/views/assistant/assistant_main_stage.cc
+++ b/ash/app_list/views/assistant/assistant_main_stage.cc
@@ -132,7 +132,7 @@
   views::BoxLayout* layout =
       SetLayoutManager(std::make_unique<views::BoxLayout>(
           views::BoxLayout::Orientation::kVertical));
-  layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kCenter);
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
 
diff --git a/ash/app_list/views/suggestion_chip_container_view.cc b/ash/app_list/views/suggestion_chip_container_view.cc
index a52f19b..e7274da 100644
--- a/ash/app_list/views/suggestion_chip_container_view.cc
+++ b/ash/app_list/views/suggestion_chip_container_view.cc
@@ -44,7 +44,7 @@
           views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
           kChipSpacing));
   layout_manager->set_main_axis_alignment(
-      views::BoxLayout::MainAxisAlignment::MAIN_AXIS_ALIGNMENT_CENTER);
+      views::BoxLayout::MainAxisAlignment::kCenter);
 
   for (size_t i = 0; i < static_cast<size_t>(
                              AppListConfig::instance().num_start_page_tiles());
diff --git a/ash/assistant/ui/dialog_plate/mic_view.cc b/ash/assistant/ui/dialog_plate/mic_view.cc
index 6dfde08..a4304c5 100644
--- a/ash/assistant/ui/dialog_plate/mic_view.cc
+++ b/ash/assistant/ui/dialog_plate/mic_view.cc
@@ -69,7 +69,7 @@
       views::BoxLayout::CrossAxisAlignment::kCenter);
 
   layout_manager->set_main_axis_alignment(
-      views::BoxLayout::MainAxisAlignment::MAIN_AXIS_ALIGNMENT_CENTER);
+      views::BoxLayout::MainAxisAlignment::kCenter);
 
   // Logo view.
   logo_view_ = LogoView::Create();
diff --git a/ash/assistant/ui/main_stage/assistant_opt_in_view.cc b/ash/assistant/ui/main_stage/assistant_opt_in_view.cc
index 1e0a0fc..e38a2aa 100644
--- a/ash/assistant/ui/main_stage/assistant_opt_in_view.cc
+++ b/ash/assistant/ui/main_stage/assistant_opt_in_view.cc
@@ -129,7 +129,7 @@
           : views::BoxLayout::CrossAxisAlignment::kEnd);
 
   layout_manager->set_main_axis_alignment(
-      views::BoxLayout::MainAxisAlignment::MAIN_AXIS_ALIGNMENT_CENTER);
+      views::BoxLayout::MainAxisAlignment::kCenter);
 
   // Container.
   container_ = new AssistantOptInContainer(/*listener=*/this);
diff --git a/ash/assistant/ui/main_stage/assistant_query_view.cc b/ash/assistant/ui/main_stage/assistant_query_view.cc
index a3a2b30..bb9a6a3 100644
--- a/ash/assistant/ui/main_stage/assistant_query_view.cc
+++ b/ash/assistant/ui/main_stage/assistant_query_view.cc
@@ -70,7 +70,7 @@
           views::BoxLayout::Orientation::kVertical));
 
   layout_manager->set_main_axis_alignment(
-      views::BoxLayout::MainAxisAlignment::MAIN_AXIS_ALIGNMENT_CENTER);
+      views::BoxLayout::MainAxisAlignment::kCenter);
 
   layout_manager->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
diff --git a/ash/assistant/ui/main_stage/suggestion_container_view.cc b/ash/assistant/ui/main_stage/suggestion_container_view.cc
index 0c1a27d..ec90e7f 100644
--- a/ash/assistant/ui/main_stage/suggestion_container_view.cc
+++ b/ash/assistant/ui/main_stage/suggestion_container_view.cc
@@ -79,7 +79,7 @@
 
   // We center align when showing conversation starters.
   layout_manager_->set_main_axis_alignment(
-      views::BoxLayout::MainAxisAlignment::MAIN_AXIS_ALIGNMENT_CENTER);
+      views::BoxLayout::MainAxisAlignment::kCenter);
 }
 
 void SuggestionContainerView::OnConversationStartersChanged(
@@ -97,7 +97,7 @@
 
   // When no longer showing conversation starters, we start align our content.
   layout_manager_->set_main_axis_alignment(
-      views::BoxLayout::MainAxisAlignment::MAIN_AXIS_ALIGNMENT_START);
+      views::BoxLayout::MainAxisAlignment::kStart);
 
   OnSuggestionsChanged(response->GetSuggestions());
 }
@@ -208,7 +208,7 @@
   // When we start a new session we will be showing conversation starters so
   // we need to center align our content.
   layout_manager_->set_main_axis_alignment(
-      views::BoxLayout::MainAxisAlignment::MAIN_AXIS_ALIGNMENT_CENTER);
+      views::BoxLayout::MainAxisAlignment::kCenter);
 }
 
 }  // namespace ash
diff --git a/ash/components/shortcut_viewer/views/bubble_view.cc b/ash/components/shortcut_viewer/views/bubble_view.cc
index df7c5c0..d325ae5 100644
--- a/ash/components/shortcut_viewer/views/bubble_view.cc
+++ b/ash/components/shortcut_viewer/views/bubble_view.cc
@@ -47,7 +47,7 @@
   views::BoxLayout* layout =
       SetLayoutManager(std::make_unique<views::BoxLayout>(
           views::BoxLayout::kHorizontal, gfx::Insets(), kIconTextSpacing));
-  layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kCenter);
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
 }
diff --git a/ash/components/shortcut_viewer/views/keyboard_shortcut_item_list_view.cc b/ash/components/shortcut_viewer/views/keyboard_shortcut_item_list_view.cc
index 85aa4c5..2a71479 100644
--- a/ash/components/shortcut_viewer/views/keyboard_shortcut_item_list_view.cc
+++ b/ash/components/shortcut_viewer/views/keyboard_shortcut_item_list_view.cc
@@ -54,7 +54,7 @@
   constexpr int kLeftPadding = 16;
   constexpr int kRightPadding = 32;
   auto layout = std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical);
-  layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kCenter);
   SetLayoutManager(std::move(layout));
   SetBorder(
       views::CreateEmptyBorder(gfx::Insets(0, kLeftPadding, 0, kRightPadding)));
diff --git a/ash/components/shortcut_viewer/views/keyboard_shortcut_view.cc b/ash/components/shortcut_viewer/views/keyboard_shortcut_view.cc
index cef4806..db0276c 100644
--- a/ash/components/shortcut_viewer/views/keyboard_shortcut_view.cc
+++ b/ash/components/shortcut_viewer/views/keyboard_shortcut_view.cc
@@ -71,7 +71,7 @@
   views::BoxLayout* layout =
       illustration_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
           views::BoxLayout::kVertical, gfx::Insets(kTopPadding, 0, 0, 0)));
-  layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
   views::ImageView* image_view = new views::ImageView();
   image_view->SetImage(
       gfx::CreateVectorIcon(icon, kSearchIllustrationIconColor));
diff --git a/ash/login/ui/lock_contents_view.cc b/ash/login/ui/lock_contents_view.cc
index c5daae0..c697cd4 100644
--- a/ash/login/ui/lock_contents_view.cc
+++ b/ash/login/ui/lock_contents_view.cc
@@ -405,7 +405,7 @@
   auto top_header_layout =
       std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal);
   top_header_layout->set_main_axis_alignment(
-      views::BoxLayout::MAIN_AXIS_ALIGNMENT_END);
+      views::BoxLayout::MainAxisAlignment::kEnd);
   top_header_->SetLayoutManager(std::move(top_header_layout));
   AddChildView(top_header_);
 
@@ -640,7 +640,7 @@
   auto* main_layout = main_view_->SetLayoutManager(
       std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal));
   main_layout->set_main_axis_alignment(
-      views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+      views::BoxLayout::MainAxisAlignment::kCenter);
   main_layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
   primary_big_view_ = AllocateLoginBigUserView(users[0], true /*is_primary*/);
diff --git a/ash/login/ui/login_auth_user_view.cc b/ash/login/ui/login_auth_user_view.cc
index d7a3369..96c690b3 100644
--- a/ash/login/ui/login_auth_user_view.cc
+++ b/ash/login/ui/login_auth_user_view.cc
@@ -331,7 +331,7 @@
         views::BoxLayout::kVertical, gfx::Insets(),
         kSpacingBetweenFingerprintIconAndLabelDp));
     layout->set_main_axis_alignment(
-        views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+        views::BoxLayout::MainAxisAlignment::kCenter);
 
     icon_ = new AnimatedRoundedImageView(
         gfx::Size(kFingerprintIconSizeDp, kFingerprintIconSizeDp),
diff --git a/ash/login/ui/login_expanded_public_account_view.cc b/ash/login/ui/login_expanded_public_account_view.cc
index c5eccd6..f54c158 100644
--- a/ash/login/ui/login_expanded_public_account_view.cc
+++ b/ash/login/ui/login_expanded_public_account_view.cc
@@ -147,7 +147,7 @@
     views::BoxLayout* label_layout = label_container->SetLayoutManager(
         std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal));
     label_layout->set_main_axis_alignment(
-        views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+        views::BoxLayout::MainAxisAlignment::kStart);
     AddChildView(label_container);
 
     label_ = CreateLabel(text, SK_ColorWHITE);
@@ -158,7 +158,7 @@
     views::BoxLayout* icon_layout = icon_container->SetLayoutManager(
         std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal));
     icon_layout->set_main_axis_alignment(
-        views::BoxLayout::MAIN_AXIS_ALIGNMENT_END);
+        views::BoxLayout::MainAxisAlignment::kEnd);
     AddChildView(icon_container);
 
     icon_ = new views::ImageView;
diff --git a/ash/login/ui/login_menu_view.cc b/ash/login/ui/login_menu_view.cc
index 4a27882..864b04f 100644
--- a/ash/login/ui/login_menu_view.cc
+++ b/ash/login/ui/login_menu_view.cc
@@ -149,8 +149,7 @@
       std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical));
   layout->SetDefaultFlex(1);
   layout->set_minimum_cross_axis_size(kMenuItemWidthDp);
-  layout->set_main_axis_alignment(
-      views::BoxLayout::MainAxisAlignment::MAIN_AXIS_ALIGNMENT_CENTER);
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kCenter);
 
   for (size_t i = 0; i < items.size(); i++) {
     const Item& item = items[i];
diff --git a/ash/login/ui/login_password_view.cc b/ash/login/ui/login_password_view.cc
index 42c0f01..6369734 100644
--- a/ash/login/ui/login_password_view.cc
+++ b/ash/login/ui/login_password_view.cc
@@ -334,14 +334,14 @@
   auto* root_layout = SetLayoutManager(
       std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical));
   root_layout->set_main_axis_alignment(
-      views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+      views::BoxLayout::MainAxisAlignment::kCenter);
 
   password_row_ = new NonAccessibleView();
 
   auto layout = std::make_unique<views::BoxLayout>(
       views::BoxLayout::kHorizontal,
       gfx::Insets(kMarginAboveBelowPasswordIconsDp, 0));
-  layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kCenter);
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
   auto* layout_ptr = password_row_->SetLayoutManager(std::move(layout));
diff --git a/ash/login/ui/login_pin_view.cc b/ash/login/ui/login_pin_view.cc
index 0e991b4..f06b8a0 100644
--- a/ash/login/ui/login_pin_view.cc
+++ b/ash/login/ui/login_pin_view.cc
@@ -96,7 +96,7 @@
     auto layout =
         std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical);
     layout->set_main_axis_alignment(
-        views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+        views::BoxLayout::MainAxisAlignment::kCenter);
     SetLayoutManager(std::move(layout));
 
     // Layer rendering is needed for animation. Enable it here for
diff --git a/ash/login/ui/login_user_view.cc b/ash/login/ui/login_user_view.cc
index e34cb5d..ef28bae 100644
--- a/ash/login/ui/login_user_view.cc
+++ b/ash/login/ui/login_user_view.cc
@@ -256,7 +256,7 @@
     auto layout =
         std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal);
     layout->set_main_axis_alignment(
-        views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+        views::BoxLayout::MainAxisAlignment::kCenter);
     SetLayoutManager(std::move(layout));
 
     views::ImageView* image = new views::ImageView();
diff --git a/ash/login/ui/parent_access_view.cc b/ash/login/ui/parent_access_view.cc
index 6d534fa..6052946 100644
--- a/ash/login/ui/parent_access_view.cc
+++ b/ash/login/ui/parent_access_view.cc
@@ -404,7 +404,7 @@
       gfx::Insets(kParentAccessViewVerticalInsetDp,
                   kParentAccessViewHorizontalInsetDp),
       0);
-  layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
   views::BoxLayout* main_layout = SetLayoutManager(std::move(layout));
@@ -420,7 +420,7 @@
   auto header_layout = std::make_unique<views::BoxLayout>(
       views::BoxLayout::kHorizontal, gfx::Insets(), 0);
   header_layout->set_main_axis_alignment(
-      views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+      views::BoxLayout::MainAxisAlignment::kStart);
   auto* header = new NonAccessibleView();
   header->SetPreferredSize(gfx::Size(child_view_width, 0));
   header->SetLayoutManager(std::move(header_layout));
diff --git a/ash/login/ui/scrollable_users_list_view.cc b/ash/login/ui/scrollable_users_list_view.cc
index d1c048b..7d1aabf 100644
--- a/ash/login/ui/scrollable_users_list_view.cc
+++ b/ash/login/ui/scrollable_users_list_view.cc
@@ -301,7 +301,7 @@
   user_view_host_layout_->set_minimum_cross_axis_size(
       LoginUserView::WidthForLayoutStyle(display_style));
   user_view_host_layout_->set_main_axis_alignment(
-      views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+      views::BoxLayout::MainAxisAlignment::kCenter);
   user_view_host_layout_->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
 
@@ -329,7 +329,7 @@
   ensure_min_height
       ->SetLayoutManager(
           std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical))
-      ->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+      ->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kCenter);
   ensure_min_height->AddChildView(user_view_host_);
   SetContents(std::move(ensure_min_height));
   SetBackgroundColor(SK_ColorTRANSPARENT);
diff --git a/ash/media/media_notification_view.cc b/ash/media/media_notification_view.cc
index 5641ba2..0187e8e 100644
--- a/ash/media/media_notification_view.cc
+++ b/ash/media/media_notification_view.cc
@@ -143,7 +143,7 @@
       title_artist_row->SetLayoutManager(std::make_unique<views::BoxLayout>(
           views::BoxLayout::kVertical, kMediaTitleArtistInsets, 0));
   title_artist_row_layout_->set_main_axis_alignment(
-      views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+      views::BoxLayout::MainAxisAlignment::kCenter);
   title_artist_row_layout_->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kStart);
   title_artist_row_ = main_row_->AddChildView(std::move(title_artist_row));
@@ -413,7 +413,7 @@
   // information. If it is collapsed then the buttons will be to the right.
   if (expanded) {
     static_cast<views::BoxLayout*>(button_row_->GetLayoutManager())
-        ->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+        ->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
 
     main_row_
         ->SetLayoutManager(std::make_unique<views::BoxLayout>(
@@ -425,7 +425,7 @@
         ->SetDefaultFlex(1);
   } else {
     static_cast<views::BoxLayout*>(button_row_->GetLayoutManager())
-        ->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+        ->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kCenter);
 
     main_row_
         ->SetLayoutManager(std::make_unique<views::BoxLayout>(
diff --git a/ash/public/cpp/caption_buttons/frame_caption_button_container_view.cc b/ash/public/cpp/caption_buttons/frame_caption_button_container_view.cc
index 4b5a9bd..ef51251 100644
--- a/ash/public/cpp/caption_buttons/frame_caption_button_container_view.cc
+++ b/ash/public/cpp/caption_buttons/frame_caption_button_container_view.cc
@@ -158,7 +158,7 @@
       std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal);
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
-  layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_END);
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kEnd);
   SetLayoutManager(std::move(layout));
   tablet_mode_animation_.reset(new gfx::SlideAnimation(this));
   tablet_mode_animation_->SetTweenType(gfx::Tween::LINEAR);
diff --git a/ash/system/bluetooth/bluetooth_detailed_view.cc b/ash/system/bluetooth/bluetooth_detailed_view.cc
index 7059e6d..4271f36 100644
--- a/ash/system/bluetooth/bluetooth_detailed_view.cc
+++ b/ash/system/bluetooth/bluetooth_detailed_view.cc
@@ -75,7 +75,7 @@
   auto box_layout =
       std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical);
   box_layout->set_main_axis_alignment(
-      views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+      views::BoxLayout::MainAxisAlignment::kCenter);
   container->SetLayoutManager(std::move(box_layout));
 
   TrayPopupItemStyle style(TrayPopupItemStyle::FontStyle::DETAILED_VIEW_LABEL);
diff --git a/ash/system/message_center/notification_swipe_control_view.cc b/ash/system/message_center/notification_swipe_control_view.cc
index 5f7a2033..575ee9a 100644
--- a/ash/system/message_center/notification_swipe_control_view.cc
+++ b/ash/system/message_center/notification_swipe_control_view.cc
@@ -32,7 +32,7 @@
       message_center_style::kSwipeControlButtonHorizontalMargin));
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kStart);
-  layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_END);
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kEnd);
 
   // Draw on its own layer to round corners
   SetPaintToLayer();
@@ -46,10 +46,10 @@
                                                bool show_snooze) {
   views::BoxLayout* layout = static_cast<views::BoxLayout*>(GetLayoutManager());
   if ((button_position == ButtonPosition::RIGHT) != base::i18n::IsRTL()) {
-    layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_END);
+    layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kEnd);
   } else {
     layout->set_main_axis_alignment(
-        views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+        views::BoxLayout::MainAxisAlignment::kStart);
   }
   ShowSettingsButton(show_settings);
   ShowSnoozeButton(show_snooze);
diff --git a/ash/system/message_center/notifier_settings_view.cc b/ash/system/message_center/notifier_settings_view.cc
index e44d53c..98f730e 100644
--- a/ash/system/message_center/notifier_settings_view.cc
+++ b/ash/system/message_center/notifier_settings_view.cc
@@ -253,7 +253,7 @@
     auto layout = std::make_unique<views::BoxLayout>(
         views::BoxLayout::kVertical, gfx::Insets(), 0);
     layout->set_main_axis_alignment(
-        views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+        views::BoxLayout::MainAxisAlignment::kCenter);
     layout->set_cross_axis_alignment(
         views::BoxLayout::CrossAxisAlignment::kCenter);
     SetLayoutManager(std::move(layout));
diff --git a/ash/system/message_center/unified_message_center_view.cc b/ash/system/message_center/unified_message_center_view.cc
index 0fa27e8..28b5eb25 100644
--- a/ash/system/message_center/unified_message_center_view.cc
+++ b/ash/system/message_center/unified_message_center_view.cc
@@ -69,7 +69,7 @@
             views::BoxLayout::kHorizontal,
             gfx::Insets(kUnifiedNotificationCenterSpacing), 0));
     button_layout->set_main_axis_alignment(
-        views::BoxLayout::MAIN_AXIS_ALIGNMENT_END);
+        views::BoxLayout::MainAxisAlignment::kEnd);
 
     auto* clear_all_button = new RoundedLabelButton(
         listener, l10n_util::GetStringUTF16(
diff --git a/ash/system/network/active_network_icon_unittest.cc b/ash/system/network/active_network_icon_unittest.cc
index ebf5f2b..52cb2e5 100644
--- a/ash/system/network/active_network_icon_unittest.cc
+++ b/ash/system/network/active_network_icon_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/message_loop/message_loop.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
+#include "chromeos/network/network_state_handler.h"
 #include "chromeos/network/network_state_test_helper.h"
 #include "chromeos/services/network_config/public/cpp/cros_network_config_test_helper.h"
 #include "services/service_manager/public/cpp/connector.h"
@@ -20,6 +21,9 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/image/image_unittest_util.h"
 
+using chromeos::network_config::mojom::ConnectionStateType;
+using chromeos::network_config::mojom::NetworkType;
+
 namespace ash {
 
 namespace {
@@ -101,9 +105,17 @@
     base::RunLoop().RunUntilIdle();
   }
 
-  gfx::ImageSkia ImageForNetwork(const chromeos::NetworkState* network) {
+  gfx::ImageSkia ImageForNetwork(
+      chromeos::network_config::mojom::NetworkType type,
+      chromeos::network_config::mojom::ConnectionStateType connection_state,
+      int signal_strength = 100) {
+    std::string id = base::StringPrintf("reference_%d", reference_count_++);
+    chromeos::network_config::mojom::NetworkStatePropertiesPtr
+        reference_properties =
+            network_state_helper().CreateStandaloneNetworkProperties(
+                id, type, connection_state, signal_strength);
     return network_icon::GetImageForNonVirtualNetwork(
-        network_icon::NetworkIconState(network), icon_type_,
+        network_icon::NetworkIconState(reference_properties.get()), icon_type_,
         false /* show_vpn_badge */);
   }
 
@@ -122,18 +134,6 @@
     network_state_helper().SetServiceProperty(service_path, key, value);
   }
 
-  std::unique_ptr<chromeos::NetworkState> CreateStandaloneNetworkState(
-      const std::string& type,
-      const std::string& connection_state,
-      int signal_strength = 100) {
-    std::string id = base::StringPrintf("reference_%d", reference_count_++);
-    VLOG(1) << "CreateStandaloneNetworkState: " << id << " type: " << type
-            << " State: " << connection_state
-            << " Strength: " << signal_strength;
-    return network_state_helper().CreateStandaloneNetworkState(
-        id, type, connection_state, signal_strength);
-  }
-
   chromeos::NetworkStateTestHelper& network_state_helper() {
     return network_config_helper_.network_state_helper();
   }
@@ -190,17 +190,17 @@
   bool animating;
   gfx::ImageSkia image =
       active_network_icon()->GetSingleImage(icon_type(), &animating);
-  std::unique_ptr<chromeos::NetworkState> reference_network =
-      CreateStandaloneNetworkState(shill::kTypeCellular, shill::kStateOnline);
-  EXPECT_TRUE(AreImagesEqual(image, ImageForNetwork(reference_network.get())));
+  EXPECT_TRUE(AreImagesEqual(
+      image,
+      ImageForNetwork(NetworkType::kCellular, ConnectionStateType::kOnline)));
   EXPECT_FALSE(animating);
 
   // Cellular + WiFi connected = WiFi connected icon
   SetupWiFi(shill::kStateOnline);
   image = active_network_icon()->GetSingleImage(icon_type(), &animating);
-  reference_network =
-      CreateStandaloneNetworkState(shill::kTypeWifi, shill::kStateOnline);
-  EXPECT_TRUE(AreImagesEqual(image, ImageForNetwork(reference_network.get())));
+  EXPECT_TRUE(AreImagesEqual(
+      image,
+      ImageForNetwork(NetworkType::kWiFi, ConnectionStateType::kOnline)));
   EXPECT_FALSE(animating);
 
   // Cellular + WiFi connecting = WiFi connecting icon
@@ -210,15 +210,17 @@
                      base::Value(50));
   base::RunLoop().RunUntilIdle();
   image = active_network_icon()->GetSingleImage(icon_type(), &animating);
-  reference_network = CreateStandaloneNetworkState(
-      shill::kTypeWifi, shill::kStateAssociation, 50);
-  EXPECT_TRUE(AreImagesEqual(image, ImageForNetwork(reference_network.get())));
+  EXPECT_TRUE(AreImagesEqual(
+      image, ImageForNetwork(NetworkType::kWiFi,
+                             ConnectionStateType::kConnecting, 50)));
   EXPECT_TRUE(animating);
 
   // Cellular + WiFi connecting + Ethernet = WiFi connecting icon
   SetupEthernet();
   image = active_network_icon()->GetSingleImage(icon_type(), &animating);
-  EXPECT_TRUE(AreImagesEqual(image, ImageForNetwork(reference_network.get())));
+  EXPECT_TRUE(AreImagesEqual(
+      image, ImageForNetwork(NetworkType::kWiFi,
+                             ConnectionStateType::kConnecting, 50)));
   EXPECT_TRUE(animating);
 
   // Cellular + WiFi connected + Ethernet = No icon
@@ -240,10 +242,9 @@
   bool animating;
   gfx::ImageSkia image =
       active_network_icon()->GetSingleImage(icon_type(), &animating);
-  std::unique_ptr<chromeos::NetworkState> reference_network =
-      CreateStandaloneNetworkState(shill::kTypeCellular,
-                                   shill::kStateAssociation);
-  EXPECT_TRUE(AreImagesEqual(image, ImageForNetwork(reference_network.get())));
+  EXPECT_TRUE(
+      AreImagesEqual(image, ImageForNetwork(NetworkType::kCellular,
+                                            ConnectionStateType::kConnecting)));
   EXPECT_TRUE(animating);
 }
 
@@ -260,10 +261,9 @@
   bool animating;
   gfx::ImageSkia image =
       active_network_icon()->GetSingleImage(icon_type(), &animating);
-  std::unique_ptr<chromeos::NetworkState> reference_network =
-      CreateStandaloneNetworkState(shill::kTypeCellular,
-                                   shill::kStateAssociation);
-  EXPECT_TRUE(AreImagesEqual(image, ImageForNetwork(reference_network.get())));
+  EXPECT_TRUE(
+      AreImagesEqual(image, ImageForNetwork(NetworkType::kCellular,
+                                            ConnectionStateType::kConnecting)));
   EXPECT_TRUE(animating);
 }
 
diff --git a/ash/system/network/network_feature_pod_button.cc b/ash/system/network/network_feature_pod_button.cc
index 32a9cf0..b1619f5 100644
--- a/ash/system/network/network_feature_pod_button.cc
+++ b/ash/system/network/network_feature_pod_button.cc
@@ -90,6 +90,11 @@
 base::string16 GetSubLabelForConnectedNetwork(const NetworkState* network) {
   DCHECK(network && network->IsConnectedState());
 
+  if (!network->Matches(NetworkTypePattern::Wireless())) {
+    return l10n_util::GetStringUTF16(
+        IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTED);
+  }
+
   if (NetworkTypePattern::Cellular().MatchesType(network->type())) {
     if (network->network_technology() == shill::kNetworkTechnology1Xrtt) {
       return l10n_util::GetStringUTF16(
@@ -137,7 +142,10 @@
         IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTED);
   }
 
-  switch (network_icon::GetSignalStrengthForNetwork(network)) {
+  switch (network_icon::GetSignalStrength(network->signal_strength())) {
+    case network_icon::SignalStrength::NONE:
+      return l10n_util::GetStringUTF16(
+          IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTED);
     case network_icon::SignalStrength::WEAK:
       return l10n_util::GetStringUTF16(
           IDS_ASH_STATUS_TRAY_NETWORK_SIGNAL_WEAK_SUBLABEL);
@@ -147,10 +155,10 @@
     case network_icon::SignalStrength::STRONG:
       return l10n_util::GetStringUTF16(
           IDS_ASH_STATUS_TRAY_NETWORK_SIGNAL_STRONG_SUBLABEL);
-    default:
-      return l10n_util::GetStringUTF16(
-          IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTED);
   }
+  NOTREACHED();
+  return l10n_util::GetStringUTF16(
+      IDS_ASH_STATUS_TRAY_NETWORK_STATUS_CONNECTED);
 }
 
 }  // namespace
diff --git a/ash/system/network/network_icon.cc b/ash/system/network/network_icon.cc
index df5ffbb..4d4ac1c 100644
--- a/ash/system/network/network_icon.cc
+++ b/ash/system/network/network_icon.cc
@@ -16,10 +16,6 @@
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/strings/utf_string_conversions.h"
-#include "chromeos/network/network_state.h"
-#include "chromeos/network/network_type_pattern.h"
-#include "chromeos/network/onc/onc_translation_tables.h"
-#include "chromeos/network/tether_constants.h"
 #include "chromeos/services/network_config/public/cpp/cros_network_config_util.h"
 #include "components/onc/onc_constants.h"
 #include "components/vector_icons/vector_icons.h"
@@ -30,7 +26,6 @@
 #include "ui/gfx/skia_util.h"
 #include "ui/gfx/vector_icon_types.h"
 
-using chromeos::NetworkTypePattern;
 using chromeos::network_config::mojom::ActivationStateType;
 using chromeos::network_config::mojom::ConnectionStateType;
 using chromeos::network_config::mojom::NetworkType;
@@ -296,6 +291,8 @@
     return gfx::CreateVectorIcon(kNetworkVpnIcon,
                                  GetDefaultColorForIconType(ICON_TYPE_LIST));
   }
+  DCHECK_GE(strength_index, 0) << "Strength not set for type: " << network.type;
+  DCHECK_LT(strength_index, kNumNetworkImages);
   return GetImageForIndex(ImageTypeForNetworkType(network.type), icon_type,
                           strength_index);
 }
@@ -308,56 +305,13 @@
 
 }  // namespace
 
-NetworkIconState::NetworkIconState(const chromeos::NetworkState* network) {
-  guid = network->guid();
-  name = network->name();
-
-  const std::string& network_type = network->type();
-  if (NetworkTypePattern::Cellular().MatchesType(network_type) ||
-      NetworkTypePattern::Wimax().MatchesType(network_type)) {
-    type = NetworkType::kCellular;
-  } else if (NetworkTypePattern::Ethernet().MatchesType(network_type)) {
-    type = NetworkType::kEthernet;
-  } else if (NetworkTypePattern::Tether().MatchesType(network_type)) {
-    type = NetworkType::kTether;
-  } else if (NetworkTypePattern::VPN().MatchesType(network_type)) {
-    type = NetworkType::kVPN;
-  } else {
-    type = NetworkType::kWiFi;
-  }
-
-  if (network->IsCaptivePortal()) {
-    connection_state = ConnectionStateType::kPortal;
-  } else if (network->IsConnectedState()) {
-    connection_state = ConnectionStateType::kConnected;
-  } else if (network->IsConnectingState()) {
-    connection_state = ConnectionStateType::kConnecting;
-  } else {
-    connection_state = ConnectionStateType::kNotConnected;
-  }
-
-  security = network->GetMojoSecurity();
-
-  if (type == NetworkType::kCellular) {
-    if (!network->network_technology().empty()) {
-      chromeos::onc::TranslateStringToONC(
-          chromeos::onc::kNetworkTechnologyTable, network->network_technology(),
-          &network_technology);
-    }
-  }
-
-  activation_state = network->GetMojoActivationState();
-  signal_strength = network->signal_strength();
-  is_roaming = network->IndicateRoaming();
-}
-
 NetworkIconState::NetworkIconState(
     const chromeos::network_config::mojom::NetworkStateProperties* network) {
   guid = network->guid;
   name = network->name;
   type = network->type;
   connection_state = network->connection_state;
-  if (type == NetworkType::kCellular && network->cellular) {
+  if (type == NetworkType::kCellular) {
     activation_state = network->cellular->activation_state;
     network_technology = network->cellular->network_technology;
     is_roaming = network->cellular->roaming;
@@ -365,16 +319,16 @@
   } else {
     activation_state = ActivationStateType::kUnknown;
   }
-  if (type == NetworkType::kTether && network->tether) {
+  if (type == NetworkType::kTether) {
     signal_strength = network->tether->signal_strength;
   }
-  if (type == NetworkType::kWiFi && network->wifi) {
+  if (type == NetworkType::kWiFi) {
     security = network->wifi->security;
     signal_strength = network->wifi->signal_strength;
   } else {
     security = chromeos::network_config::mojom::SecurityType::kNone;
   }
-  if (type == NetworkType::kWiMAX && network->wimax) {
+  if (type == NetworkType::kWiMAX) {
     signal_strength = network->wimax->signal_strength;
   }
 }
@@ -409,8 +363,8 @@
   }
 
   NetworkType type = network.type;
-  if (type == NetworkType::kCellular || type == NetworkType::kTether ||
-      type == NetworkType::kWiFi) {
+  if (chromeos::network_config::NetworkTypeMatchesType(
+          type, NetworkType::kWireless)) {
     dirty |= UpdateWirelessStrengthIndex(network);
   }
 
@@ -679,16 +633,12 @@
   PurgeIconMap(ICON_TYPE_MENU_LIST, network_guids);
 }
 
-SignalStrength GetSignalStrengthForNetwork(
-    const chromeos::NetworkState* network) {
-  if (!network->Matches(NetworkTypePattern::Wireless()))
-    return SignalStrength::NOT_WIRELESS;
-
+SignalStrength GetSignalStrength(int strength) {
   // Decide whether the signal is considered weak, medium or strong based on the
   // strength index. Each signal strength corresponds to a bucket which
   // attempted to be split evenly from |kNumNetworkImages| - 1. Remainders go
   // first to the lowest bucket and then the second lowest bucket.
-  const int index = StrengthIndex(network->signal_strength());
+  const int index = StrengthIndex(strength);
   if (index == 0)
     return SignalStrength::NONE;
   const int seperations = kNumNetworkImages - 1;
diff --git a/ash/system/network/network_icon.h b/ash/system/network/network_icon.h
index 317b92a..4ff01b3 100644
--- a/ash/system/network/network_icon.h
+++ b/ash/system/network/network_icon.h
@@ -15,17 +15,11 @@
 #include "ui/gfx/image/canvas_image_source.h"
 #include "ui/gfx/image/image_skia.h"
 
-namespace chromeos {
-class NetworkState;
-}
-
 namespace ash {
 namespace network_icon {
 
 // TODO(stevenjb): Replace with network_config::mojom::NetworkStateProperties.
 struct ASH_EXPORT NetworkIconState {
-  // Constructs a NetworkIconState from a NetworkState.
-  explicit NetworkIconState(const chromeos::NetworkState* network);
   // Constructs a NetworkIconState from mojom::NetworkStateProperties.
   explicit NetworkIconState(
       const chromeos::network_config::mojom::NetworkStateProperties* network);
@@ -54,7 +48,7 @@
 };
 
 // Strength of a wireless signal.
-enum class SignalStrength { NONE, WEAK, MEDIUM, STRONG, NOT_WIRELESS };
+enum class SignalStrength { NONE, WEAK, MEDIUM, STRONG };
 
 // Returns true if |icon_state| is connected or portal.
 bool IsConnected(const NetworkIconState& icon_state);
@@ -124,10 +118,8 @@
     const std::set<std::string>& network_guids);
 
 // Called by ChromeVox to give a verbal indication of the network icon. Returns
-// the signal strength of |network|, if it is a network type with a signal
-// strength.
-ASH_EXPORT SignalStrength
-GetSignalStrengthForNetwork(const chromeos::NetworkState* network);
+// a signal strength enum for |strength| value 0-100.
+ASH_EXPORT SignalStrength GetSignalStrength(int strength);
 
 }  // namespace network_icon
 }  // namespace ash
diff --git a/ash/system/network/network_icon_unittest.cc b/ash/system/network/network_icon_unittest.cc
index 9b3e891..01f69d6c 100644
--- a/ash/system/network/network_icon_unittest.cc
+++ b/ash/system/network/network_icon_unittest.cc
@@ -13,7 +13,6 @@
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
-#include "chromeos/network/network_state.h"
 #include "chromeos/network/network_state_handler.h"
 #include "chromeos/network/network_state_test_helper.h"
 #include "chromeos/network/tether_constants.h"
@@ -26,6 +25,11 @@
 // This tests both the helper functions in network_icon, and ActiveNetworkIcon
 // which is a primary consumer of the helper functions.
 
+using chromeos::network_config::mojom::ConnectionStateType;
+using chromeos::network_config::mojom::NetworkStateProperties;
+using chromeos::network_config::mojom::NetworkStatePropertiesPtr;
+using chromeos::network_config::mojom::NetworkType;
+
 namespace ash {
 
 namespace network_icon {
@@ -76,34 +80,22 @@
     ASSERT_FALSE(cellular_path_.empty());
   }
 
-  std::unique_ptr<chromeos::NetworkState> CreateStandaloneNetworkState(
+  NetworkStatePropertiesPtr CreateStandaloneNetworkProperties(
       const std::string& id,
-      const std::string& type,
-      const std::string& connection_state,
+      NetworkType type,
+      ConnectionStateType connection_state,
       int signal_strength) {
-    return helper().CreateStandaloneNetworkState(id, type, connection_state,
-                                                 signal_strength);
+    return helper().CreateStandaloneNetworkProperties(
+        id, type, connection_state, signal_strength);
   }
 
-  std::unique_ptr<chromeos::NetworkState>
-  CreateStandaloneWifiTetherNetworkState(const std::string& id,
-                                         const std::string& tether_guid,
-                                         const std::string& connection_state,
-                                         int signal_strength) {
-    std::unique_ptr<chromeos::NetworkState> network =
-        CreateStandaloneNetworkState(id, shill::kTypeWifi, connection_state,
-                                     signal_strength);
-    network->set_tether_guid(tether_guid);
-    return network;
-  }
-
-  gfx::Image GetImageForNonVirtualNetwork(const chromeos::NetworkState* network,
+  gfx::Image GetImageForNonVirtualNetwork(const NetworkStateProperties* network,
                                           bool badge_vpn) {
     return gfx::Image(network_icon::GetImageForNonVirtualNetwork(
         network_icon::NetworkIconState(network), icon_type_, badge_vpn));
   }
 
-  gfx::Image ImageForNetwork(const chromeos::NetworkState* network) {
+  gfx::Image ImageForNetwork(const NetworkStateProperties* network) {
     return GetImageForNonVirtualNetwork(network, false /* show_vpn_badge */);
   }
 
@@ -121,16 +113,16 @@
   // for a Wi-Fi network. The icon for a Tether network should be the same as
   // one for a Wi-Fi network with an associated Tether guid.
   void GetAndCompareImagesByNetworkType(
-      const chromeos::NetworkState* wifi_network,
-      const chromeos::NetworkState* cellular_network,
-      const chromeos::NetworkState* tether_network) {
-    ASSERT_EQ(wifi_network->type(), shill::kTypeWifi);
+      const NetworkStateProperties* wifi_network,
+      const NetworkStateProperties* cellular_network,
+      const NetworkStateProperties* tether_network) {
+    ASSERT_EQ(wifi_network->type, NetworkType::kWiFi);
     gfx::Image wifi_image = ImageForNetwork(wifi_network);
 
-    ASSERT_EQ(cellular_network->type(), shill::kTypeCellular);
+    ASSERT_EQ(cellular_network->type, NetworkType::kCellular);
     gfx::Image cellular_image = ImageForNetwork(cellular_network);
 
-    ASSERT_EQ(tether_network->type(), chromeos::kTypeTether);
+    ASSERT_EQ(tether_network->type, NetworkType::kTether);
     gfx::Image tether_image = ImageForNetwork(tether_network);
 
     EXPECT_FALSE(gfx::test::AreImagesEqual(tether_image, wifi_image));
@@ -180,98 +172,68 @@
 // verifies that the Tether network and Wi-Fi network with associated Tether
 // guid are treated the same for purposes of icon display
 TEST_F(NetworkIconTest, CompareImagesByNetworkType_NotVisible) {
-  std::unique_ptr<chromeos::NetworkState> wifi_network =
-      CreateStandaloneNetworkState("wifi", shill::kTypeWifi, shill::kStateIdle,
-                                   50);
+  NetworkStatePropertiesPtr wifi_network = CreateStandaloneNetworkProperties(
+      "wifi", NetworkType::kWiFi, ConnectionStateType::kNotConnected, 50);
 
-  std::unique_ptr<chromeos::NetworkState> cellular_network =
-      CreateStandaloneNetworkState("cellular", shill::kTypeCellular,
-                                   shill::kStateIdle, 50);
+  NetworkStatePropertiesPtr cellular_network =
+      CreateStandaloneNetworkProperties("cellular", NetworkType::kCellular,
+                                        ConnectionStateType::kNotConnected, 50);
 
-  std::unique_ptr<chromeos::NetworkState> wimax_network =
-      CreateStandaloneNetworkState("wimax", shill::kTypeWimax,
-                                   shill::kStateIdle, 50);
+  NetworkStatePropertiesPtr wimax_network = CreateStandaloneNetworkProperties(
+      "wimax", NetworkType::kWiMAX, ConnectionStateType::kNotConnected, 50);
 
-  EXPECT_TRUE(gfx::test::AreImagesEqual(ImageForNetwork(cellular_network.get()),
+  EXPECT_TRUE(gfx::test::AreImagesEqual(ImageForNetwork(wifi_network.get()),
                                         ImageForNetwork(wimax_network.get())));
 
-  std::unique_ptr<chromeos::NetworkState> tether_network =
-      CreateStandaloneNetworkState("tether", chromeos::kTypeTether,
-                                   shill::kStateIdle, 50);
+  NetworkStatePropertiesPtr tether_network = CreateStandaloneNetworkProperties(
+      "tether", NetworkType::kTether, ConnectionStateType::kNotConnected, 50);
 
   GetAndCompareImagesByNetworkType(wifi_network.get(), cellular_network.get(),
                                    tether_network.get());
 }
 
 TEST_F(NetworkIconTest, CompareImagesByNetworkType_Connecting) {
-  std::unique_ptr<chromeos::NetworkState> wifi_network =
-      CreateStandaloneNetworkState("wifi", shill::kTypeWifi,
-                                   shill::kStateAssociation, 50);
+  NetworkStatePropertiesPtr wifi_network = CreateStandaloneNetworkProperties(
+      "wifi", NetworkType::kWiFi, ConnectionStateType::kConnecting, 50);
 
-  std::unique_ptr<chromeos::NetworkState> cellular_network =
-      CreateStandaloneNetworkState("cellular", shill::kTypeCellular,
-                                   shill::kStateAssociation, 50);
+  NetworkStatePropertiesPtr cellular_network =
+      CreateStandaloneNetworkProperties("cellular", NetworkType::kCellular,
+                                        ConnectionStateType::kConnecting, 50);
 
-  std::unique_ptr<chromeos::NetworkState> tether_network =
-      CreateStandaloneNetworkState("tether", chromeos::kTypeTether,
-                                   shill::kStateAssociation, 50);
+  NetworkStatePropertiesPtr tether_network = CreateStandaloneNetworkProperties(
+      "tether", NetworkType::kTether, ConnectionStateType::kConnecting, 50);
 
   GetAndCompareImagesByNetworkType(wifi_network.get(), cellular_network.get(),
                                    tether_network.get());
 }
 
 TEST_F(NetworkIconTest, CompareImagesByNetworkType_Connected) {
-  std::unique_ptr<chromeos::NetworkState> wifi_network =
-      CreateStandaloneNetworkState("wifi", shill::kTypeWifi,
-                                   shill::kStateOnline, 50);
+  NetworkStatePropertiesPtr wifi_network = CreateStandaloneNetworkProperties(
+      "wifi", NetworkType::kWiFi, ConnectionStateType::kOnline, 50);
 
-  std::unique_ptr<chromeos::NetworkState> cellular_network =
-      CreateStandaloneNetworkState("cellular", shill::kTypeCellular,
-                                   shill::kStateOnline, 50);
+  NetworkStatePropertiesPtr cellular_network =
+      CreateStandaloneNetworkProperties("cellular", NetworkType::kCellular,
+                                        ConnectionStateType::kOnline, 50);
 
-  std::unique_ptr<chromeos::NetworkState> tether_network =
-      CreateStandaloneNetworkState("tether", chromeos::kTypeTether,
-                                   shill::kStateOnline, 50);
+  NetworkStatePropertiesPtr tether_network = CreateStandaloneNetworkProperties(
+      "tether", NetworkType::kTether, ConnectionStateType::kOnline, 50);
 
   GetAndCompareImagesByNetworkType(wifi_network.get(), cellular_network.get(),
                                    tether_network.get());
 }
 
 TEST_F(NetworkIconTest, NetworkSignalStrength) {
-  using ss = SignalStrength;
-
-  std::unique_ptr<chromeos::NetworkState> ethernet_network =
-      CreateStandaloneNetworkState("eth", shill::kTypeEthernet,
-                                   shill::kStateOnline, 50);
-
-  std::unique_ptr<chromeos::NetworkState> wifi_network =
-      CreateStandaloneNetworkState("wifi", shill::kTypeWifi,
-                                   shill::kStateOnline, 50);
-
-  // Verify non-wireless network types return SignalStrength::NOT_WIRELESS, and
-  // wireless network types return something other than
-  // SignalStrength::NOT_WIRELESS.
-  EXPECT_EQ(ss::NOT_WIRELESS,
-            GetSignalStrengthForNetwork(ethernet_network.get()));
-  EXPECT_NE(ss::NOT_WIRELESS, GetSignalStrengthForNetwork(wifi_network.get()));
-
   // Signal strength is divided into four categories: none, weak, medium and
   // strong. They are meant to match the number of sections in the wifi icon.
   // The wifi icon currently has four levels; signals [0, 100] are mapped to [1,
   // 4]. There are only three signal strengths so icons that were mapped to 2
   // are also considered weak.
-  wifi_network->set_signal_strength(0);
-  EXPECT_EQ(ss::NONE, GetSignalStrengthForNetwork(wifi_network.get()));
-  wifi_network->set_signal_strength(50);
-  EXPECT_EQ(ss::WEAK, GetSignalStrengthForNetwork(wifi_network.get()));
-  wifi_network->set_signal_strength(51);
-  EXPECT_EQ(ss::MEDIUM, GetSignalStrengthForNetwork(wifi_network.get()));
-  wifi_network->set_signal_strength(75);
-  EXPECT_EQ(ss::MEDIUM, GetSignalStrengthForNetwork(wifi_network.get()));
-  wifi_network->set_signal_strength(76);
-  EXPECT_EQ(ss::STRONG, GetSignalStrengthForNetwork(wifi_network.get()));
-  wifi_network->set_signal_strength(100);
-  EXPECT_EQ(ss::STRONG, GetSignalStrengthForNetwork(wifi_network.get()));
+  EXPECT_EQ(SignalStrength::NONE, GetSignalStrength(0));
+  EXPECT_EQ(SignalStrength::WEAK, GetSignalStrength(50));
+  EXPECT_EQ(SignalStrength::MEDIUM, GetSignalStrength(51));
+  EXPECT_EQ(SignalStrength::MEDIUM, GetSignalStrength(75));
+  EXPECT_EQ(SignalStrength::STRONG, GetSignalStrength(76));
+  EXPECT_EQ(SignalStrength::STRONG, GetSignalStrength(100));
 }
 
 TEST_F(NetworkIconTest, DefaultImageAndLabelWifiConnected) {
@@ -289,9 +251,9 @@
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
 
-  std::unique_ptr<chromeos::NetworkState> reference_network =
-      CreateStandaloneNetworkState("reference", shill::kTypeWifi,
-                                   shill::kStateOnline, 45);
+  NetworkStatePropertiesPtr reference_network =
+      CreateStandaloneNetworkProperties("reference", NetworkType::kWiFi,
+                                        ConnectionStateType::kOnline, 45);
   EXPECT_TRUE(gfx::test::AreImagesEqual(
       gfx::Image(default_image), ImageForNetwork(reference_network.get())));
 }
@@ -311,9 +273,9 @@
   ASSERT_FALSE(default_image.isNull());
   EXPECT_TRUE(animating);
 
-  std::unique_ptr<chromeos::NetworkState> reference_network =
-      CreateStandaloneNetworkState("reference", shill::kTypeWifi,
-                                   shill::kStateAssociation, 45);
+  NetworkStatePropertiesPtr reference_network =
+      CreateStandaloneNetworkProperties("reference", NetworkType::kWiFi,
+                                        ConnectionStateType::kConnecting, 45);
   EXPECT_TRUE(gfx::test::AreImagesEqual(
       gfx::Image(default_image), ImageForNetwork(reference_network.get())));
 }
@@ -346,9 +308,9 @@
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
 
-  std::unique_ptr<chromeos::NetworkState> reference_network =
-      CreateStandaloneNetworkState("reference", shill::kTypeCellular,
-                                   shill::kStateOnline, 65);
+  NetworkStatePropertiesPtr reference_network =
+      CreateStandaloneNetworkProperties("reference", NetworkType::kCellular,
+                                        ConnectionStateType::kOnline, 65);
 
   EXPECT_TRUE(gfx::test::AreImagesEqual(
       gfx::Image(default_image), ImageForNetwork(reference_network.get())));
@@ -383,9 +345,9 @@
   ASSERT_FALSE(default_image.isNull());
   EXPECT_TRUE(animating);
 
-  std::unique_ptr<chromeos::NetworkState> reference_network_1 =
-      CreateStandaloneNetworkState("reference1", shill::kTypeWifi,
-                                   shill::kStateAssociation, 45);
+  NetworkStatePropertiesPtr reference_network_1 =
+      CreateStandaloneNetworkProperties("reference1", NetworkType::kWiFi,
+                                        ConnectionStateType::kConnecting, 45);
   EXPECT_TRUE(gfx::test::AreImagesEqual(
       gfx::Image(default_image), ImageForNetwork(reference_network_1.get())));
 
@@ -399,9 +361,9 @@
   //     icon could flash between wifi connecting and connected icons - this
   //     should be fixed, for example by not showing connecting icon when
   //     reconnecting a network if another network is connected.
-  std::unique_ptr<chromeos::NetworkState> reference_network_2 =
-      CreateStandaloneNetworkState("reference2", shill::kTypeCellular,
-                                   shill::kStateOnline, 65);
+  NetworkStatePropertiesPtr reference_network_2 =
+      CreateStandaloneNetworkProperties("reference2", NetworkType::kCellular,
+                                        ConnectionStateType::kOnline, 65);
   GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
                                  &animating);
   ASSERT_FALSE(default_image.isNull());
@@ -415,9 +377,9 @@
 
   // The wifi network is online, and thus default - the default network icon
   // should display the wifi network's associated image again.
-  std::unique_ptr<chromeos::NetworkState> reference_network_3 =
-      CreateStandaloneNetworkState("reference3", shill::kTypeWifi,
-                                   shill::kStateOnline, 45);
+  NetworkStatePropertiesPtr reference_network_3 =
+      CreateStandaloneNetworkProperties("reference3", NetworkType::kWiFi,
+                                        ConnectionStateType::kOnline, 45);
   GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
                                  &animating);
   ASSERT_FALSE(default_image.isNull());
@@ -451,9 +413,9 @@
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
 
-  std::unique_ptr<chromeos::NetworkState> reference_network =
-      CreateStandaloneNetworkState("reference", shill::kTypeCellular,
-                                   shill::kStateOnline, 65);
+  NetworkStatePropertiesPtr reference_network =
+      CreateStandaloneNetworkProperties("reference", NetworkType::kCellular,
+                                        ConnectionStateType::kOnline, 65);
   EXPECT_TRUE(gfx::test::AreImagesEqual(
       gfx::Image(default_image), ImageForNetwork(reference_network.get())));
 }
@@ -488,9 +450,9 @@
   ASSERT_FALSE(default_image.isNull());
   EXPECT_TRUE(animating);
 
-  std::unique_ptr<chromeos::NetworkState> reference_network_1 =
-      CreateStandaloneNetworkState("reference1", shill::kTypeCellular,
-                                   shill::kStateAssociation, 65);
+  NetworkStatePropertiesPtr reference_network_1 =
+      CreateStandaloneNetworkProperties("reference1", NetworkType::kCellular,
+                                        ConnectionStateType::kConnecting, 65);
   EXPECT_TRUE(gfx::test::AreImagesEqual(
       gfx::Image(default_image), ImageForNetwork(reference_network_1.get())));
 
@@ -503,9 +465,9 @@
                                  &animating);
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
-  std::unique_ptr<chromeos::NetworkState> reference_network_2 =
-      CreateStandaloneNetworkState("reference2", shill::kTypeWifi,
-                                   shill::kStateOnline, 45);
+  NetworkStatePropertiesPtr reference_network_2 =
+      CreateStandaloneNetworkProperties("reference2", NetworkType::kWiFi,
+                                        ConnectionStateType::kOnline, 45);
   EXPECT_TRUE(gfx::test::AreImagesEqual(
       gfx::Image(default_image), ImageForNetwork(reference_network_2.get())));
 
@@ -544,9 +506,9 @@
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
 
-  std::unique_ptr<chromeos::NetworkState> reference_network =
-      CreateStandaloneNetworkState("reference", shill::kTypeCellular,
-                                   shill::kStateOnline, 65);
+  NetworkStatePropertiesPtr reference_network =
+      CreateStandaloneNetworkProperties("reference", NetworkType::kCellular,
+                                        ConnectionStateType::kOnline, 65);
   EXPECT_TRUE(gfx::test::AreImagesEqual(
       gfx::Image(default_image), ImageForNetwork(reference_network.get())));
 }
@@ -567,9 +529,9 @@
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
 
-  std::unique_ptr<chromeos::NetworkState> reference_network =
-      CreateStandaloneNetworkState("reference", shill::kTypeCellular,
-                                   shill::kStateIdle, 65);
+  NetworkStatePropertiesPtr reference_network =
+      CreateStandaloneNetworkProperties("reference", NetworkType::kCellular,
+                                        ConnectionStateType::kNotConnected, 65);
   EXPECT_TRUE(gfx::test::AreImagesEqual(
       gfx::Image(default_image), ImageForNetwork(reference_network.get())));
 }
@@ -596,9 +558,9 @@
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
 
-  std::unique_ptr<chromeos::NetworkState> reference_network =
-      CreateStandaloneNetworkState("reference", shill::kTypeWifi,
-                                   shill::kStateIdle, 45);
+  NetworkStatePropertiesPtr reference_network =
+      CreateStandaloneNetworkProperties("reference", NetworkType::kWiFi,
+                                        ConnectionStateType::kNotConnected, 45);
   EXPECT_TRUE(gfx::test::AreImagesEqual(
       gfx::Image(default_image), ImageForNetwork(reference_network.get())));
 }
@@ -636,9 +598,8 @@
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
 
-  std::unique_ptr<chromeos::NetworkState> reference_eth =
-      CreateStandaloneNetworkState("reference_eth", shill::kTypeEthernet,
-                                   shill::kStateOnline, 0);
+  NetworkStatePropertiesPtr reference_eth = CreateStandaloneNetworkProperties(
+      "reference_eth", NetworkType::kEthernet, ConnectionStateType::kOnline, 0);
   gfx::Image reference_eth_unbadged = GetImageForNonVirtualNetwork(
       reference_eth.get(), false /* show_vpn_badge */);
   gfx::Image reference_eth_badged = GetImageForNonVirtualNetwork(
@@ -657,9 +618,8 @@
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
 
-  std::unique_ptr<chromeos::NetworkState> reference_wifi =
-      CreateStandaloneNetworkState("reference_wifi", shill::kTypeWifi,
-                                   shill::kStateOnline, 45);
+  NetworkStatePropertiesPtr reference_wifi = CreateStandaloneNetworkProperties(
+      "reference_wifi", NetworkType::kWiFi, ConnectionStateType::kOnline, 45);
   gfx::Image reference_wifi_badged = GetImageForNonVirtualNetwork(
       reference_wifi.get(), true /* show_vpn_badge */);
   EXPECT_TRUE(gfx::test::AreImagesEqual(gfx::Image(default_image),
@@ -693,9 +653,9 @@
   ASSERT_FALSE(default_image.isNull());
   EXPECT_TRUE(animating);
 
-  std::unique_ptr<chromeos::NetworkState> reference_network =
-      CreateStandaloneNetworkState("reference", shill::kTypeWifi,
-                                   shill::kStateAssociation, 65);
+  NetworkStatePropertiesPtr reference_network =
+      CreateStandaloneNetworkProperties("reference", NetworkType::kWiFi,
+                                        ConnectionStateType::kConnecting, 65);
   EXPECT_TRUE(gfx::test::AreImagesEqual(
       gfx::Image(default_image), ImageForNetwork(reference_network.get())));
 }
@@ -720,9 +680,9 @@
   ASSERT_FALSE(default_image.isNull());
   EXPECT_TRUE(animating);
 
-  std::unique_ptr<chromeos::NetworkState> reference_network =
-      CreateStandaloneNetworkState("reference", shill::kTypeCellular,
-                                   shill::kStateAssociation, 65);
+  NetworkStatePropertiesPtr reference_network =
+      CreateStandaloneNetworkProperties("reference", NetworkType::kCellular,
+                                        ConnectionStateType::kConnecting, 65);
   EXPECT_TRUE(gfx::test::AreImagesEqual(
       gfx::Image(default_image), ImageForNetwork(reference_network.get())));
 }
diff --git a/ash/system/network/network_tray_icon_strategy.cc b/ash/system/network/network_tray_icon_strategy.cc
index 6666627..9213b52 100644
--- a/ash/system/network/network_tray_icon_strategy.cc
+++ b/ash/system/network/network_tray_icon_strategy.cc
@@ -10,17 +10,8 @@
 #include "ash/system/network/active_network_icon.h"
 #include "ash/system/network/network_icon.h"
 #include "base/logging.h"
-#include "chromeos/network/network_state.h"
-#include "chromeos/network/network_state_handler.h"
-#include "third_party/cros_system_api/dbus/service_constants.h"
 #include "ui/gfx/image/image_skia.h"
 
-using chromeos::NetworkConnectionHandler;
-using chromeos::NetworkHandler;
-using chromeos::NetworkState;
-using chromeos::NetworkStateHandler;
-using chromeos::NetworkTypePattern;
-
 namespace ash {
 namespace tray {
 
diff --git a/ash/system/network/network_tray_view.cc b/ash/system/network/network_tray_view.cc
index 1e24b22..e30d665 100644
--- a/ash/system/network/network_tray_view.cc
+++ b/ash/system/network/network_tray_view.cc
@@ -126,31 +126,34 @@
         IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED,
         base::UTF8ToUTF16(connected_network->name()));
 
-    // Retrieve the string describing the signal strength, if it is applicable
-    // to |connected_network|.
-    base::string16 signal_strength_string;
-    switch (network_icon::GetSignalStrengthForNetwork(connected_network)) {
-      case SignalStrength::NONE:
-      case SignalStrength::NOT_WIRELESS:
-        break;
-      case SignalStrength::WEAK:
-        signal_strength_string =
-            l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NETWORK_SIGNAL_WEAK);
-        break;
-      case SignalStrength::MEDIUM:
-        signal_strength_string = l10n_util::GetStringUTF16(
-            IDS_ASH_STATUS_TRAY_NETWORK_SIGNAL_MEDIUM);
-        break;
-      case SignalStrength::STRONG:
-        signal_strength_string = l10n_util::GetStringUTF16(
-            IDS_ASH_STATUS_TRAY_NETWORK_SIGNAL_STRONG);
-        break;
-    }
+    if (connected_network->Matches(NetworkTypePattern::Wireless())) {
+      // Retrieve the string describing the signal strength, if it is applicable
+      // to |connected_network|.
+      base::string16 signal_strength_string;
+      switch (network_icon::GetSignalStrength(
+          connected_network->signal_strength())) {
+        case SignalStrength::NONE:
+          break;
+        case SignalStrength::WEAK:
+          signal_strength_string = l10n_util::GetStringUTF16(
+              IDS_ASH_STATUS_TRAY_NETWORK_SIGNAL_WEAK);
+          break;
+        case SignalStrength::MEDIUM:
+          signal_strength_string = l10n_util::GetStringUTF16(
+              IDS_ASH_STATUS_TRAY_NETWORK_SIGNAL_MEDIUM);
+          break;
+        case SignalStrength::STRONG:
+          signal_strength_string = l10n_util::GetStringUTF16(
+              IDS_ASH_STATUS_TRAY_NETWORK_SIGNAL_STRONG);
+          break;
+      }
 
-    if (!signal_strength_string.empty()) {
-      new_connection_status_string = l10n_util::GetStringFUTF16(
-          IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED_ACCESSIBLE,
-          base::UTF8ToUTF16(connected_network->name()), signal_strength_string);
+      if (!signal_strength_string.empty()) {
+        new_connection_status_string = l10n_util::GetStringFUTF16(
+            IDS_ASH_STATUS_TRAY_NETWORK_CONNECTED_ACCESSIBLE,
+            base::UTF8ToUTF16(connected_network->name()),
+            signal_strength_string);
+      }
     }
     connection_status_tooltip_ = new_connection_status_string;
   } else {
diff --git a/ash/system/tray/detailed_view_delegate.cc b/ash/system/tray/detailed_view_delegate.cc
index f2ea075bd..2a4b0b9 100644
--- a/ash/system/tray/detailed_view_delegate.cc
+++ b/ash/system/tray/detailed_view_delegate.cc
@@ -38,7 +38,7 @@
                                                   gfx::Insets(),
                                                   kUnifiedTopShortcutSpacing);
       layout->set_main_axis_alignment(
-          views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+          views::BoxLayout::MainAxisAlignment::kCenter);
       layout->set_cross_axis_alignment(
           views::BoxLayout::CrossAxisAlignment::kCenter);
       break;
@@ -47,7 +47,7 @@
 
       layout = std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical);
       layout->set_main_axis_alignment(
-          views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+          views::BoxLayout::MainAxisAlignment::kCenter);
       layout->set_cross_axis_alignment(
           views::BoxLayout::CrossAxisAlignment::kStretch);
       break;
diff --git a/ash/system/tray/tray_constants.h b/ash/system/tray/tray_constants.h
index 523ef01..9b9230e 100644
--- a/ash/system/tray/tray_constants.h
+++ b/ash/system/tray/tray_constants.h
@@ -230,14 +230,16 @@
 constexpr int kUnifiedFeaturePodHoverRadius = 4;
 constexpr int kUnifiedFeaturePodVerticalPadding = 28;
 constexpr int kUnifiedFeaturePodTopPadding = 24;
-constexpr int kUnifiedFeaturePodBottomPadding = 20;
+constexpr int kUnifiedFeaturePodBottomPadding = 5;
 constexpr int kUnifiedFeaturePodHorizontalSidePadding = 12;
 constexpr int kUnifiedFeaturePodHorizontalMiddlePadding = 0;
-constexpr int kUnifiedFeaturePodCollapsedVerticalPadding = 16;
+constexpr int kUnifiedFeaturePodCollapsedVerticalPadding = 12;
 constexpr int kUnifiedFeaturePodCollapsedHorizontalPadding = 24;
 constexpr int kUnifiedFeaturePodArrowSpacing = 4;
 constexpr int kUnifiedFeaturePodItemsInRow = 3;
+constexpr int kUnifiedFeaturePodItemsRows = 3;
 constexpr int kUnifiedFeaturePodMaxItemsInCollapsed = 5;
+constexpr int kUnifiedFeaturePodsPageSpacing = 48;
 constexpr int kUnifiedNotificationSeparatorThickness = 1;
 
 // Constants used in PageIndicatorView of UnifiedSystemTray.
diff --git a/ash/system/tray/tray_popup_utils.cc b/ash/system/tray/tray_popup_utils.cc
index 30a4737..ad148b6 100644
--- a/ash/system/tray/tray_popup_utils.cc
+++ b/ash/system/tray/tray_popup_utils.cc
@@ -48,7 +48,7 @@
       views::BoxLayout::kVertical,
       gfx::Insets(8, kTrayPopupLabelHorizontalPadding));
   box_layout->set_main_axis_alignment(
-      views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+      views::BoxLayout::MainAxisAlignment::kCenter);
   box_layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kStretch);
   return std::move(box_layout);
@@ -60,7 +60,7 @@
   auto box_layout =
       std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal);
   box_layout->set_main_axis_alignment(
-      views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+      views::BoxLayout::MainAxisAlignment::kCenter);
   box_layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
   return std::move(box_layout);
diff --git a/ash/system/unified/feature_pods_container_view.cc b/ash/system/unified/feature_pods_container_view.cc
index 21127a8..b6004d33 100644
--- a/ash/system/unified/feature_pods_container_view.cc
+++ b/ash/system/unified/feature_pods_container_view.cc
@@ -4,15 +4,26 @@
 
 #include "ash/system/unified/feature_pods_container_view.h"
 
+#include "ash/public/cpp/ash_features.h"
+#include "ash/public/cpp/pagination/pagination_controller.h"
+#include "ash/public/cpp/pagination/pagination_model.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/unified/feature_pod_button.h"
 
 namespace ash {
 
-FeaturePodsContainerView::FeaturePodsContainerView(bool initially_expanded)
-    : expanded_amount_(initially_expanded ? 1.0 : 0.0) {}
+FeaturePodsContainerView::FeaturePodsContainerView(
+    PaginationModel* pagination_model,
+    bool initially_expanded)
+    : pagination_model_(pagination_model),
+      expanded_amount_(initially_expanded ? 1.0 : 0.0) {
+  pagination_model_->AddObserver(this);
+}
 
-FeaturePodsContainerView::~FeaturePodsContainerView() = default;
+FeaturePodsContainerView::~FeaturePodsContainerView() {
+  DCHECK(pagination_model_);
+  pagination_model_->RemoveObserver(this);
+}
 
 void FeaturePodsContainerView::SetExpandedAmount(double expanded_amount) {
   DCHECK(0.0 <= expanded_amount && expanded_amount <= 1.0);
@@ -33,6 +44,10 @@
   // floor(visible_count / kUnifiedFeaturePodItemsInRow)
   int number_of_lines = (visible_count + kUnifiedFeaturePodItemsInRow - 1) /
                         kUnifiedFeaturePodItemsInRow;
+
+  if (features::IsSystemTrayFeaturePodsPaginationEnabled())
+    number_of_lines = std::min(number_of_lines, kUnifiedFeaturePodItemsRows);
+
   return kUnifiedFeaturePodBottomPadding +
          (kUnifiedFeaturePodVerticalPadding + kUnifiedFeaturePodSize.height()) *
              number_of_lines;
@@ -83,27 +98,10 @@
 
 void FeaturePodsContainerView::Layout() {
   UpdateCollapsedSidePadding();
-
-  int visible_count = 0;
-  for (auto* child : children()) {
-    if (!child->visible())
-      continue;
-
-    gfx::Size child_size;
-    if (expanded_amount_ > 0.0) {
-      child_size = kUnifiedFeaturePodSize;
-
-      // Flexibly give more height if the child view doesn't fit into the
-      // default height, so that label texts won't be broken up in the middle.
-      child_size.set_height(std::max(
-          child_size.height(), child->GetHeightForWidth(child_size.height())));
-    } else {
-      child_size = kUnifiedFeaturePodCollapsedSize;
-    }
-
-    child->SetBoundsRect(
-        gfx::Rect(GetButtonPosition(visible_count++), child_size));
-    child->Layout();
+  CalculateIdealBoundsForFeaturePods();
+  for (int i = 0; i < visible_buttons_.view_size(); ++i) {
+    auto* button = visible_buttons_.view_at(i);
+    button->SetBoundsRect(visible_buttons_.ideal_bounds(i));
   }
 }
 
@@ -114,17 +112,28 @@
   int visible_count = 0;
   for (auto* view : children()) {
     auto* child = static_cast<FeaturePodButton*>(view);
-    bool visible = child->visible_preferred() &&
-                   (expanded_amount_ > 0.0 ||
-                    visible_count < kUnifiedFeaturePodMaxItemsInCollapsed);
+    bool visible = IsButtonVisible(child, visible_count);
     child->SetVisibleByContainer(visible);
-    if (visible)
+    if (visible) {
+      if (visible_buttons_.GetIndexOfView(child) < 0)
+        visible_buttons_.Add(child, visible_count);
       ++visible_count;
+    } else {
+      if (visible_buttons_.GetIndexOfView(child))
+        visible_buttons_.Remove(visible_buttons_.GetIndexOfView(child));
+    }
   }
-
+  UpdateTotalPages();
   changing_visibility_ = false;
 }
 
+bool FeaturePodsContainerView::IsButtonVisible(FeaturePodButton* button,
+                                               int index) {
+  return button->visible_preferred() &&
+         (expanded_amount_ > 0.0 ||
+          index < kUnifiedFeaturePodMaxItemsInCollapsed);
+}
+
 int FeaturePodsContainerView::GetVisibleCount() const {
   return std::count_if(
       children().cbegin(), children().cend(), [](const auto* v) {
@@ -182,4 +191,106 @@
   DCHECK(collapsed_side_padding_ > 0);
 }
 
+void FeaturePodsContainerView::AddFeaturePodButton(FeaturePodButton* button) {
+  int view_size = visible_buttons_.view_size();
+  if (IsButtonVisible(button, view_size)) {
+    visible_buttons_.Add(button, view_size);
+  }
+  AddChildView(button);
+
+  UpdateTotalPages();
+}
+
+const gfx::Vector2d FeaturePodsContainerView::CalculateTransitionOffset(
+    int page_of_view) const {
+  gfx::Size grid_size = CalculatePreferredSize();
+
+  // If there is a transition, calculates offset for current and target page.
+  const int current_page = pagination_model_->selected_page();
+  const PaginationModel::Transition& transition =
+      pagination_model_->transition();
+  const bool is_valid =
+      pagination_model_->is_valid_page(transition.target_page);
+
+  // Transition to previous page means negative offset.
+  const int direction = transition.target_page > current_page ? -1 : 1;
+
+  int x_offset = 0;
+  int y_offset = 0;
+
+  // Page size including padding pixels. A tile.x + page_width means the same
+  // tile slot in the next page.
+  const int page_width = grid_size.width() + kUnifiedFeaturePodsPageSpacing;
+  if (page_of_view < current_page)
+    x_offset = -page_width;
+  else if (page_of_view > current_page)
+    x_offset = page_width;
+
+  if (is_valid) {
+    if (page_of_view == current_page ||
+        page_of_view == transition.target_page) {
+      x_offset += transition.progress * page_width * direction;
+    }
+  }
+
+  return gfx::Vector2d(x_offset, y_offset);
+}
+
+void FeaturePodsContainerView::CalculateIdealBoundsForFeaturePods() {
+  for (int i = 0; i < visible_buttons_.view_size(); ++i) {
+    gfx::Rect tile_bounds;
+    gfx::Size child_size;
+    if (expanded_amount_ > 0.0) {
+      child_size = kUnifiedFeaturePodSize;
+
+      // Flexibly give more height if the child view doesn't fit into the
+      // default height, so that label texts won't be broken up in the middle.
+      child_size.set_height(std::max(
+          child_size.height(),
+          visible_buttons_.view_at(i)->GetHeightForWidth(child_size.height())));
+
+      tile_bounds =
+          gfx::Rect(GetButtonPosition(i % GetTilesPerPage()), child_size);
+      // TODO(amehfooz): refactor this logic so that the ideal_bounds are set
+      // once when the transition starts and the actual feature pod bounds are
+      // interpolated using the ideal_bounds as the transition progresses.
+      tile_bounds.Offset(CalculateTransitionOffset(i / GetTilesPerPage()));
+    } else {
+      child_size = kUnifiedFeaturePodCollapsedSize;
+      tile_bounds = gfx::Rect(GetButtonPosition(i), child_size);
+    }
+
+    visible_buttons_.set_ideal_bounds(i, tile_bounds);
+  }
+}
+
+int FeaturePodsContainerView::GetTilesPerPage() const {
+  if (features::IsSystemTrayFeaturePodsPaginationEnabled())
+    return kUnifiedFeaturePodItemsInRow * kUnifiedFeaturePodItemsRows;
+  else
+    return children().size();
+}
+
+void FeaturePodsContainerView::UpdateTotalPages() {
+  int total_pages = 0;
+
+  int total_visible = visible_buttons_.view_size();
+  int tiles_per_page = GetTilesPerPage();
+
+  if (!visible_buttons_.view_size() || !tiles_per_page) {
+    total_pages = 0;
+  } else {
+    total_pages = (total_visible / tiles_per_page) +
+                  (total_visible % tiles_per_page ? 1 : 0);
+  }
+  pagination_model_->SetTotalPages(total_pages);
+}
+
+void FeaturePodsContainerView::TransitionChanged() {
+  const PaginationModel::Transition& transition =
+      pagination_model_->transition();
+  if (pagination_model_->is_valid_page(transition.target_page))
+    Layout();
+}
+
 }  // namespace ash
diff --git a/ash/system/unified/feature_pods_container_view.h b/ash/system/unified/feature_pods_container_view.h
index c93737e..a8ade81 100644
--- a/ash/system/unified/feature_pods_container_view.h
+++ b/ash/system/unified/feature_pods_container_view.h
@@ -6,20 +6,31 @@
 #define ASH_SYSTEM_UNIFIED_FEATURE_PODS_CONTAINER_VIEW_H_
 
 #include "ash/ash_export.h"
+#include "ash/public/cpp/pagination/pagination_model_observer.h"
 #include "ui/views/view.h"
+#include "ui/views/view_model.h"
 
 namespace ash {
 
+class FeaturePodButton;
+class PaginationModel;
+
 // Container of feature pods buttons in the middle of UnifiedSystemTrayView.
 // The container has number of buttons placed in 3x3 plane at regular distances.
 // FeaturePodButtons implements these individual buttons.
 // The container also implements collapsed state where the top 5 buttons are
 // horizontally placed and others are hidden.
-class ASH_EXPORT FeaturePodsContainerView : public views::View {
+class ASH_EXPORT FeaturePodsContainerView : public views::View,
+                                            public PaginationModelObserver {
  public:
-  explicit FeaturePodsContainerView(bool initially_expanded);
+  FeaturePodsContainerView(PaginationModel* pagination_model,
+                           bool initially_expanded);
   ~FeaturePodsContainerView() override;
 
+  // Add a FeaturePodButton as a child view and if it's visible add it to the
+  // view structure and update the pagination model.
+  void AddFeaturePodButton(FeaturePodButton* button);
+
   // Change the expanded state. 0.0 if collapsed, and 1.0 if expanded.
   // Otherwise, it shows intermediate state. If collapsed, all the buttons are
   // horizontally placed.
@@ -47,15 +58,39 @@
   void Layout() override;
 
  private:
-  void UpdateChildVisibility();
+  friend class FeaturePodsContainerViewTest;
 
   // Calculate the current position of the button from |visible_index| and
   // |expanded_amount_|.
   gfx::Point GetButtonPosition(int visible_index) const;
 
+  void UpdateChildVisibility();
+
   // Update |collapsed_state_padding_|.
   void UpdateCollapsedSidePadding();
 
+  // Calculates the ideal bounds for all feature pods.
+  void CalculateIdealBoundsForFeaturePods();
+
+  // Calculates the offset for |page_of_view| based on current page and
+  // transition target page.
+  const gfx::Vector2d CalculateTransitionOffset(int page_of_view) const;
+
+  // Returns true if button at provided index is visible.
+  bool IsButtonVisible(FeaturePodButton* button, int index);
+
+  // Returns the number of tiles per page.
+  int GetTilesPerPage() const;
+
+  // Updates page splits for feature pod buttons.
+  void UpdateTotalPages();
+
+  // PaginationModelObserver:
+  void TransitionChanged() override;
+
+  // Owned by UnifiedSystemTrayModel.
+  PaginationModel* pagination_model_;
+
   // The last |expanded_amount| passed to SetExpandedAmount().
   double expanded_amount_;
 
@@ -69,6 +104,10 @@
   // A button that had focus at the point SaveButtonFocus is called.
   views::View* focused_button_ = nullptr;
 
+  // A view model that contains all visible feature pod buttons.
+  // Used to calculate required number of pages.
+  views::ViewModelT<FeaturePodButton> visible_buttons_;
+
   DISALLOW_COPY_AND_ASSIGN(FeaturePodsContainerView);
 };
 
diff --git a/ash/system/unified/feature_pods_container_view_unittest.cc b/ash/system/unified/feature_pods_container_view_unittest.cc
index 8a3ec63..747add5 100644
--- a/ash/system/unified/feature_pods_container_view_unittest.cc
+++ b/ash/system/unified/feature_pods_container_view_unittest.cc
@@ -4,10 +4,13 @@
 
 #include "ash/system/unified/feature_pods_container_view.h"
 
+#include "ash/public/cpp/ash_features.h"
+#include "ash/public/cpp/pagination/pagination_model.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/unified/feature_pod_button.h"
 #include "ash/system/unified/feature_pod_controller_base.h"
 #include "ash/test/ash_test_base.h"
+#include "base/test/scoped_feature_list.h"
 #include "ui/views/view_observer.h"
 
 namespace ash {
@@ -22,10 +25,13 @@
   // AshTestBase:
   void SetUp() override {
     AshTestBase::SetUp();
+    pagination_model_ = std::make_unique<PaginationModel>();
     container_ = std::make_unique<FeaturePodsContainerView>(
-        true /* initially_expanded */);
+        pagination_model_.get(), true /* initially_expanded */);
     container_->AddObserver(this);
     preferred_size_changed_count_ = 0;
+
+    scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
   }
 
   // FeaturePodControllerBase:
@@ -41,6 +47,11 @@
   }
 
  protected:
+  void EnablePagination() {
+    scoped_feature_list_->InitAndEnableFeature(
+        features::kSystemTrayFeaturePodsPagination);
+  }
+
   void AddButtons(int count) {
     for (int i = 0; i < count; ++i) {
       buttons_.push_back(new FeaturePodButton(this));
@@ -52,6 +63,8 @@
 
   FeaturePodsContainerView* container() { return container_.get(); }
 
+  PaginationModel* pagination_model() { return pagination_model_.get(); }
+
   int preferred_size_changed_count() const {
     return preferred_size_changed_count_;
   }
@@ -59,7 +72,9 @@
   std::vector<FeaturePodButton*> buttons_;
 
  private:
+  std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
   std::unique_ptr<FeaturePodsContainerView> container_;
+  std::unique_ptr<PaginationModel> pagination_model_;
   int preferred_size_changed_count_ = 0;
 
   DISALLOW_COPY_AND_ASSIGN(FeaturePodsContainerViewTest);
@@ -164,4 +179,59 @@
   EXPECT_EQ(2, preferred_size_changed_count());
 }
 
+TEST_F(FeaturePodsContainerViewTest, NumberOfPagesChanged) {
+  const int kNumberOfPages = 8;
+
+  EnablePagination();
+  AddButtons(kUnifiedFeaturePodItemsInRow * kUnifiedFeaturePodItemsRows *
+             kNumberOfPages);
+
+  // Adding buttons to fill kNumberOfPages should cause the the same number of
+  // pages to be created.
+  EXPECT_EQ(kNumberOfPages, pagination_model()->total_pages());
+
+  // Adding an additional button causes a new page to be added.
+  AddButtons(1);
+  EXPECT_EQ(pagination_model()->total_pages(), kNumberOfPages + 1);
+}
+
+TEST_F(FeaturePodsContainerViewTest, PaginationTransition) {
+  const int kNumberOfPages = 8;
+
+  EnablePagination();
+  AddButtons(kUnifiedFeaturePodItemsInRow * kUnifiedFeaturePodItemsRows *
+             kNumberOfPages);
+
+  // Position of a button should slide to the left during a page
+  // transition to the next page.
+  gfx::Rect current_bounds;
+  gfx::Rect initial_bounds = buttons_[0]->bounds();
+  gfx::Rect previous_bounds = initial_bounds;
+
+  PaginationModel::Transition transition(
+      pagination_model()->selected_page() + 1, 0);
+
+  for (double i = 0.1; i <= 1.0; i += 0.1) {
+    transition.progress = i;
+    pagination_model()->SetTransition(transition);
+
+    current_bounds = buttons_[0]->bounds();
+
+    EXPECT_LT(current_bounds.x(), previous_bounds.x());
+    EXPECT_EQ(current_bounds.y(), previous_bounds.y());
+
+    previous_bounds = current_bounds;
+  }
+
+  // Button Position after page switch should move to the left by a page offset.
+  int page_offset = container()->CalculatePreferredSize().width() +
+                    kUnifiedFeaturePodsPageSpacing;
+  gfx::Rect final_bounds =
+      gfx::Rect(initial_bounds.x() - page_offset, initial_bounds.y(),
+                initial_bounds.width(), initial_bounds.height());
+  pagination_model()->SelectPage(1, false);
+  container()->Layout();
+  EXPECT_EQ(final_bounds, buttons_[0]->bounds());
+}
+
 }  // namespace ash
diff --git a/ash/system/unified/unified_system_tray_view.cc b/ash/system/unified/unified_system_tray_view.cc
index a5ed7ea..e376ec2 100644
--- a/ash/system/unified/unified_system_tray_view.cc
+++ b/ash/system/unified/unified_system_tray_view.cc
@@ -16,6 +16,7 @@
 #include "ash/system/unified/feature_pod_button.h"
 #include "ash/system/unified/feature_pods_container_view.h"
 #include "ash/system/unified/notification_hidden_view.h"
+#include "ash/system/unified/page_indicator_view.h"
 #include "ash/system/unified/top_shortcuts_view.h"
 #include "ash/system/unified/unified_system_info_view.h"
 #include "ash/system/unified/unified_system_tray_controller.h"
@@ -220,7 +221,11 @@
       controller_(controller),
       notification_hidden_view_(new NotificationHiddenView()),
       top_shortcuts_view_(new TopShortcutsView(controller_)),
-      feature_pods_container_(new FeaturePodsContainerView(initially_expanded)),
+      feature_pods_container_(
+          new FeaturePodsContainerView(controller_->model()->pagination_model(),
+                                       initially_expanded)),
+      page_indicator_view_(
+          new PageIndicatorView(controller_, initially_expanded)),
       sliders_container_(new UnifiedSlidersContainerView(initially_expanded)),
       system_info_view_(new UnifiedSystemInfoView(controller_)),
       system_tray_container_(new SystemTrayContainer()),
@@ -255,6 +260,7 @@
 
   system_tray_container_->AddChildView(top_shortcuts_view_);
   system_tray_container_->AddChildView(feature_pods_container_);
+  system_tray_container_->AddChildView(page_indicator_view_);
   system_tray_container_->AddChildView(sliders_container_);
   system_tray_container_->AddChildView(system_info_view_);
 
@@ -288,7 +294,7 @@
 }
 
 void UnifiedSystemTrayView::AddFeaturePodButton(FeaturePodButton* button) {
-  feature_pods_container_->AddChildView(button);
+  feature_pods_container_->AddFeaturePodButton(button);
 }
 
 void UnifiedSystemTrayView::AddSliderView(views::View* slider_view) {
@@ -335,6 +341,7 @@
 
   top_shortcuts_view_->SetExpandedAmount(expanded_amount);
   feature_pods_container_->SetExpandedAmount(expanded_amount);
+  page_indicator_view_->SetExpandedAmount(expanded_amount);
   sliders_container_->SetExpandedAmount(expanded_amount);
 
   if (!IsTransformEnabled()) {
@@ -358,6 +365,7 @@
               : 0) +
          top_shortcuts_view_->GetPreferredSize().height() +
          feature_pods_container_->GetExpandedHeight() +
+         page_indicator_view_->GetPreferredSize().height() +
          sliders_container_->GetExpandedHeight() +
          system_info_view_->GetPreferredSize().height();
 }
diff --git a/ash/system/unified/unified_system_tray_view.h b/ash/system/unified/unified_system_tray_view.h
index fb1e556..423a5ff 100644
--- a/ash/system/unified/unified_system_tray_view.h
+++ b/ash/system/unified/unified_system_tray_view.h
@@ -15,6 +15,7 @@
 class FeaturePodsContainerView;
 class TopShortcutsView;
 class NotificationHiddenView;
+class PageIndicatorView;
 class UnifiedMessageCenterView;
 class UnifiedSystemInfoView;
 class UnifiedSystemTrayController;
@@ -147,6 +148,7 @@
   NotificationHiddenView* const notification_hidden_view_;
   TopShortcutsView* const top_shortcuts_view_;
   FeaturePodsContainerView* const feature_pods_container_;
+  PageIndicatorView* const page_indicator_view_;
   UnifiedSlidersContainerView* const sliders_container_;
   UnifiedSystemInfoView* const system_info_view_;
   views::View* const system_tray_container_;
diff --git a/ash/test/ash_test_base.cc b/ash/test/ash_test_base.cc
index 83bbea9..c778b7e 100644
--- a/ash/test/ash_test_base.cc
+++ b/ash/test/ash_test_base.cc
@@ -223,10 +223,6 @@
       .UpdateNaturalOrientation();
 }
 
-void AshTestBase::SetRunningOutsideAsh() {
-  ash_test_helper_->SetRunningOutsideAsh();
-}
-
 aura::Window* AshTestBase::CurrentContext() {
   return ash_test_helper_->CurrentContext();
 }
diff --git a/ash/test/ash_test_base.h b/ash/test/ash_test_base.h
index 3301f58..da1a195 100644
--- a/ash/test/ash_test_base.h
+++ b/ash/test/ash_test_base.h
@@ -92,12 +92,6 @@
   // some environments. Use this to destroy it.
   void DestroyScopedTaskEnvironment();
 
-  // Call this only if this code is being run outside of ash, for example, in
-  // browser tests that use AshTestBase. This disables CHECKs that are
-  // applicable only when used inside ash.
-  // TODO: remove this and ban usage of AshTestBase outside of ash.
-  void SetRunningOutsideAsh();
-
   // Update the display configuration as given in |display_specs|.
   // See ash::DisplayManagerTestApi::UpdateDisplay for more details.
   void UpdateDisplay(const std::string& display_specs);
diff --git a/ash/test/ash_test_helper.cc b/ash/test/ash_test_helper.cc
index df435ad..04e5906 100644
--- a/ash/test/ash_test_helper.cc
+++ b/ash/test/ash_test_helper.cc
@@ -75,13 +75,6 @@
 }
 
 void AshTestHelper::SetUp(bool start_session, bool provide_local_state) {
-#if !defined(NDEBUG)
-  aura::Window::SetEnvArgRequired(
-      "Within ash you must supply a non-null aura::Env when creating a Window, "
-      "use window_factory, or supply the Env obtained from "
-      "Shell::Get()->aura_env()");
-#endif
-
   // 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.
@@ -238,9 +231,6 @@
   display::Display::ResetForceDeviceScaleFactorForTesting();
 
   CHECK(!::wm::CaptureController::Get());
-#if !defined(NDEBUG)
-  aura::Window::SetEnvArgRequired(nullptr);
-#endif
 
   ui::test::EventGeneratorDelegate::SetFactoryFunction(
       ui::test::EventGeneratorDelegate::FactoryFunction());
@@ -248,13 +238,6 @@
   statistics_provider_.reset();
 }
 
-void AshTestHelper::SetRunningOutsideAsh() {
-  test_views_delegate_->set_running_outside_ash();
-#if DCHECK_IS_ON()
-  aura::Window::SetEnvArgRequired(nullptr);
-#endif
-}
-
 PrefService* AshTestHelper::GetLocalStatePrefService() {
   return Shell::Get()->local_state_;
 }
diff --git a/ash/test/ash_test_helper.h b/ash/test/ash_test_helper.h
index d3cc44c..094b6d8 100644
--- a/ash/test/ash_test_helper.h
+++ b/ash/test/ash_test_helper.h
@@ -63,12 +63,6 @@
   // Destroys the ash::Shell and performs associated cleanup.
   void TearDown();
 
-  // Call this only if this code is being run outside of ash, for example, in
-  // browser tests that use AshTestBase. This disables CHECKs that are
-  // applicable only when used inside ash.
-  // TODO: remove this and ban usage of AshTestHelper outside of ash.
-  void SetRunningOutsideAsh();
-
   // Returns a root Window. Usually this is the active root Window, but that
   // method can return NULL sometimes, and in those cases, we fall back on the
   // primary root Window.
diff --git a/ash/test/ash_test_views_delegate.cc b/ash/test/ash_test_views_delegate.cc
index 4c8be685..14811b3 100644
--- a/ash/test/ash_test_views_delegate.cc
+++ b/ash/test/ash_test_views_delegate.cc
@@ -5,7 +5,6 @@
 #include "ash/test/ash_test_views_delegate.h"
 
 #include "ash/shell.h"
-#include "ui/base/ui_base_features.h"
 
 namespace ash {
 
@@ -16,21 +15,8 @@
 void AshTestViewsDelegate::OnBeforeWidgetInit(
     views::Widget::InitParams* params,
     views::internal::NativeWidgetDelegate* delegate) {
-  if (running_outside_ash_) {
-    // With Mash, MusClient::CreateNativeWidget uses a DesktopNativeWidgetAura,
-    // which doesn't need a context.
-    if (!features::IsUsingWindowService()) {
-      DCHECK(ash::Shell::HasInstance());
-      if (!params->parent && !params->context)
-        params->context = Shell::GetRootWindowForNewWindows();
-    }
-  } else {
-    CHECK(params->native_widget || params->context || params->parent)
-        << "Widgets must be created with a context or parent. In tests use "
-        << "CurrentContext(). In non-test code you likely want to use the "
-        << "parent the Widget will be added to, or possibly "
-        << "Shell::GetRootWindowForNewWindows().";
-  }
+  if (!params->parent && !params->context)
+    params->context = Shell::GetRootWindowForNewWindows();
 
   TestViewsDelegate::OnBeforeWidgetInit(params, delegate);
 }
diff --git a/ash/test/ash_test_views_delegate.h b/ash/test/ash_test_views_delegate.h
index 25bbaea..ee1cfe1 100644
--- a/ash/test/ash_test_views_delegate.h
+++ b/ash/test/ash_test_views_delegate.h
@@ -18,12 +18,6 @@
   AshTestViewsDelegate();
   ~AshTestViewsDelegate() override;
 
-  // Call this only if this code is being run outside of ash, for example, in
-  // browser tests that use AshTestBase. This disables CHECKs that are
-  // applicable only when used inside ash.
-  // TODO: remove this and ban usage of AshTestHelper outside of ash.
-  void set_running_outside_ash() { running_outside_ash_ = true; }
-
   // Overriden from TestViewsDelegate.
   void OnBeforeWidgetInit(
       views::Widget::InitParams* params,
@@ -41,8 +35,6 @@
   // matches with this.
   ui::Accelerator close_menu_accelerator_;
 
-  bool running_outside_ash_ = false;
-
   DISALLOW_COPY_AND_ASSIGN(AshTestViewsDelegate);
 };
 
diff --git a/base/callback_list.h b/base/callback_list.h
index f455c65..781adbf 100644
--- a/base/callback_list.h
+++ b/base/callback_list.h
@@ -8,10 +8,12 @@
 #include <list>
 #include <memory>
 
+#include "base/bind.h"
 #include "base/callback.h"
 #include "base/compiler_specific.h"
 #include "base/logging.h"
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 
 // OVERVIEW:
 //
@@ -74,36 +76,31 @@
  public:
   class Subscription {
    public:
-    Subscription(CallbackListBase<CallbackType>* list,
-                 typename std::list<CallbackType>::iterator iter)
-        : list_(list),
-          iter_(iter) {
-    }
+    explicit Subscription(base::OnceClosure subscription_destroyed)
+        : subscription_destroyed_(std::move(subscription_destroyed)) {}
 
-    ~Subscription() {
-      if (list_->active_iterator_count_) {
-        iter_->Reset();
-      } else {
-        list_->callbacks_.erase(iter_);
-        if (!list_->removal_callback_.is_null())
-          list_->removal_callback_.Run();
-      }
-    }
+    ~Subscription() { std::move(subscription_destroyed_).Run(); }
+
+    // Returns true if the CallbackList associated with this subscription has
+    // been deleted, which means that the associated callback will no longer be
+    // invoked.
+    bool IsCancelled() const { return subscription_destroyed_.IsCancelled(); }
 
    private:
-    CallbackListBase<CallbackType>* list_;
-    typename std::list<CallbackType>::iterator iter_;
+    base::OnceClosure subscription_destroyed_;
 
     DISALLOW_COPY_AND_ASSIGN(Subscription);
   };
 
   // Add a callback to the list. The callback will remain registered until the
-  // returned Subscription is destroyed, which must occur before the
-  // CallbackList is destroyed.
+  // returned Subscription is destroyed. When the CallbackList is destroyed, any
+  // outstanding subscriptions are safely invalidated.
   std::unique_ptr<Subscription> Add(const CallbackType& cb) WARN_UNUSED_RESULT {
     DCHECK(!cb.is_null());
     return std::make_unique<Subscription>(
-        this, callbacks_.insert(callbacks_.end(), cb));
+        base::BindOnce(&CallbackListBase::OnSubscriptionDestroyed,
+                       weak_ptr_factory_.GetWeakPtr(),
+                       callbacks_.insert(callbacks_.end(), cb)));
   }
 
   // Sets a callback which will be run when a subscription list is changed.
@@ -114,7 +111,7 @@
   // Returns true if there are no subscriptions. This is only valid to call when
   // not looping through the list.
   bool empty() {
-    DCHECK_EQ(0, active_iterator_count_);
+    DCHECK_EQ(0u, active_iterator_count_);
     return callbacks_.empty();
   }
 
@@ -157,12 +154,9 @@
     typename std::list<CallbackType>::iterator list_iter_;
   };
 
-  CallbackListBase() : active_iterator_count_(0) {}
+  CallbackListBase() = default;
 
-  ~CallbackListBase() {
-    DCHECK_EQ(0, active_iterator_count_);
-    DCHECK_EQ(0U, callbacks_.size());
-  }
+  ~CallbackListBase() { DCHECK_EQ(0u, active_iterator_count_); }
 
   // Returns an instance of a CallbackListBase::Iterator which can be used
   // to run callbacks.
@@ -189,9 +183,22 @@
   }
 
  private:
+  void OnSubscriptionDestroyed(
+      const typename std::list<CallbackType>::iterator& iter) {
+    if (active_iterator_count_) {
+      iter->Reset();
+    } else {
+      callbacks_.erase(iter);
+      if (removal_callback_)
+        removal_callback_.Run();
+    }
+    // Note that |removal_callback_| may destroy |this|.
+  }
+
   std::list<CallbackType> callbacks_;
-  int active_iterator_count_;
+  size_t active_iterator_count_ = 0;
   RepeatingClosure removal_callback_;
+  WeakPtrFactory<CallbackListBase> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(CallbackListBase);
 };
diff --git a/base/callback_list_unittest.cc b/base/callback_list_unittest.cc
index a628287..e7a97af 100644
--- a/base/callback_list_unittest.cc
+++ b/base/callback_list_unittest.cc
@@ -342,5 +342,21 @@
   EXPECT_TRUE(cb_reg.empty());
 }
 
+TEST(CallbackList, AbandonSubscriptions) {
+  Listener listener;
+  std::unique_ptr<CallbackList<void(void)>::Subscription> subscription;
+  {
+    CallbackList<void(void)> cb_reg;
+    subscription = cb_reg.Add(
+        BindRepeating(&Listener::IncrementTotal, Unretained(&listener)));
+    // Make sure the callback is signaled while cb_reg is in scope.
+    cb_reg.Notify();
+    // Exiting this scope and running the cb_reg destructor shouldn't fail.
+  }
+  EXPECT_EQ(1, listener.total());
+  // The subscription from the destroyed callback list should be cancelled now.
+  EXPECT_TRUE(subscription->IsCancelled());
+}
+
 }  // namespace
 }  // namespace base
diff --git a/cc/base/rtree.h b/cc/base/rtree.h
index 2490981..83504eac6 100644
--- a/cc/base/rtree.h
+++ b/cc/base/rtree.h
@@ -230,60 +230,54 @@
       remainder = kMinChildren - remainder;
   }
 
-  int num_strips = static_cast<int>(std::ceil(std::sqrt(num_branches)));
-  int num_tiles = static_cast<int>(
-      std::ceil(num_branches / static_cast<float>(num_strips)));
   size_t current_branch = 0;
 
   size_t new_branch_index = 0;
-  for (int i = 0; i < num_strips; ++i) {
-    // Might be worth sorting by X here too.
-    for (int j = 0; j < num_tiles && current_branch < branches->size(); ++j) {
-      int increment_by = kMaxChildren;
-      if (remainder != 0) {
-        // if need be, omit some nodes to make up for remainder
-        if (remainder <= kMaxChildren - kMinChildren) {
-          increment_by -= remainder;
-          remainder = 0;
-        } else {
-          increment_by = kMinChildren;
-          remainder -= kMaxChildren - kMinChildren;
-        }
+  while (current_branch < branches->size()) {
+    int increment_by = kMaxChildren;
+    if (remainder != 0) {
+      // if need be, omit some nodes to make up for remainder
+      if (remainder <= kMaxChildren - kMinChildren) {
+        increment_by -= remainder;
+        remainder = 0;
+      } else {
+        increment_by = kMinChildren;
+        remainder -= kMaxChildren - kMinChildren;
       }
-      Node<T>* node = AllocateNodeAtLevel(level);
-      node->num_children = 1;
-      node->children[0] = (*branches)[current_branch];
-
-      Branch<T> branch;
-      branch.bounds = (*branches)[current_branch].bounds;
-      branch.subtree = node;
-      ++current_branch;
-      int x = branch.bounds.x();
-      int y = branch.bounds.y();
-      int right = branch.bounds.right();
-      int bottom = branch.bounds.bottom();
-      for (int k = 1; k < increment_by && current_branch < branches->size();
-           ++k) {
-        // We use a custom union instead of gfx::Rect::Union here, since this
-        // bypasses some empty checks and extra setters, which improves
-        // performance.
-        auto& bounds = (*branches)[current_branch].bounds;
-        x = std::min(x, bounds.x());
-        y = std::min(y, bounds.y());
-        right = std::max(right, bounds.right());
-        bottom = std::max(bottom, bounds.bottom());
-
-        node->children[k] = (*branches)[current_branch];
-        ++node->num_children;
-        ++current_branch;
-      }
-      branch.bounds.SetRect(x, y, base::ClampSub(right, x),
-                            base::ClampSub(bottom, y));
-
-      DCHECK_LT(new_branch_index, current_branch);
-      (*branches)[new_branch_index] = std::move(branch);
-      ++new_branch_index;
     }
+    Node<T>* node = AllocateNodeAtLevel(level);
+    node->num_children = 1;
+    node->children[0] = (*branches)[current_branch];
+
+    Branch<T> branch;
+    branch.bounds = (*branches)[current_branch].bounds;
+    branch.subtree = node;
+    ++current_branch;
+    int x = branch.bounds.x();
+    int y = branch.bounds.y();
+    int right = branch.bounds.right();
+    int bottom = branch.bounds.bottom();
+    for (int k = 1; k < increment_by && current_branch < branches->size();
+         ++k) {
+      // We use a custom union instead of gfx::Rect::Union here, since this
+      // bypasses some empty checks and extra setters, which improves
+      // performance.
+      auto& bounds = (*branches)[current_branch].bounds;
+      x = std::min(x, bounds.x());
+      y = std::min(y, bounds.y());
+      right = std::max(right, bounds.right());
+      bottom = std::max(bottom, bounds.bottom());
+
+      node->children[k] = (*branches)[current_branch];
+      ++node->num_children;
+      ++current_branch;
+    }
+    branch.bounds.SetRect(x, y, base::ClampSub(right, x),
+                          base::ClampSub(bottom, y));
+
+    DCHECK_LT(new_branch_index, current_branch);
+    (*branches)[new_branch_index] = std::move(branch);
+    ++new_branch_index;
   }
   branches->resize(new_branch_index);
   return BuildRecursive(branches, level + 1);
diff --git a/cc/debug/layer_tree_debug_state.cc b/cc/debug/layer_tree_debug_state.cc
index 6b51ff0..8c154cc 100644
--- a/cc/debug/layer_tree_debug_state.cc
+++ b/cc/debug/layer_tree_debug_state.cc
@@ -12,6 +12,7 @@
 LayerTreeDebugState::LayerTreeDebugState()
     : show_fps_counter(false),
       show_debug_borders(false),
+      show_layout_shift_regions(false),
       show_paint_rects(false),
       show_property_changed_rects(false),
       show_surface_damage_rects(false),
@@ -49,7 +50,7 @@
          show_surface_damage_rects || show_screen_space_rects ||
          show_touch_event_handler_rects || show_wheel_event_handler_rects ||
          show_scroll_event_handler_rects || show_non_fast_scrollable_rects ||
-         show_layer_animation_bounds_rects;
+         show_layer_animation_bounds_rects || show_layout_shift_regions;
 }
 
 bool LayerTreeDebugState::ShowMemoryStats() const {
@@ -61,6 +62,7 @@
   return (
       a.show_fps_counter == b.show_fps_counter &&
       a.show_debug_borders == b.show_debug_borders &&
+      a.show_layout_shift_regions == b.show_layout_shift_regions &&
       a.show_paint_rects == b.show_paint_rects &&
       a.show_property_changed_rects == b.show_property_changed_rects &&
       a.show_surface_damage_rects == b.show_surface_damage_rects &&
@@ -85,6 +87,7 @@
   r.show_fps_counter |= b.show_fps_counter;
   r.show_debug_borders |= b.show_debug_borders;
 
+  r.show_layout_shift_regions |= b.show_layout_shift_regions;
   r.show_paint_rects |= b.show_paint_rects;
   r.show_property_changed_rects |= b.show_property_changed_rects;
   r.show_surface_damage_rects |= b.show_surface_damage_rects;
diff --git a/cc/debug/layer_tree_debug_state.h b/cc/debug/layer_tree_debug_state.h
index c7438b6..50dd230 100644
--- a/cc/debug/layer_tree_debug_state.h
+++ b/cc/debug/layer_tree_debug_state.h
@@ -32,6 +32,7 @@
   bool show_fps_counter;
   DebugBorderTypes show_debug_borders;
 
+  bool show_layout_shift_regions;
   bool show_paint_rects;
   bool show_property_changed_rects;
   bool show_surface_damage_rects;
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 3e705be..6816e8f 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -1166,7 +1166,7 @@
   "java/src/org/chromium/chrome/browser/payments/ui/SectionUiUtils.java",
   "java/src/org/chromium/chrome/browser/payments/ui/ShoppingCart.java",
   "java/src/org/chromium/chrome/browser/permissions/AndroidPermissionRequester.java",
-  "java/src/org/chromium/chrome/browser/permissions/PermissionAppModalDialogView.java",
+  "java/src/org/chromium/chrome/browser/permissions/PermissionDialogModel.java",
   "java/src/org/chromium/chrome/browser/permissions/PermissionDialogController.java",
   "java/src/org/chromium/chrome/browser/permissions/PermissionDialogDelegate.java",
   "java/src/org/chromium/chrome/browser/permissions/PermissionUmaUtil.java",
diff --git a/chrome/android/java/res/drawable/checkerboard_background.xml b/chrome/android/java/res/drawable/checkerboard_background.xml
index d16fa3a..f7a1eb4 100644
--- a/chrome/android/java/res/drawable/checkerboard_background.xml
+++ b/chrome/android/java/res/drawable/checkerboard_background.xml
@@ -7,25 +7,25 @@
     <item>
         <shape android:shape="rectangle">
             <solid android:color="@android:color/white" />
-            <size android:height="10dp" android:width="10dp"/>
+            <size android:height="4dp" android:width="4dp"/>
         </shape>
     </item>
-    <item android:top="0dp" android:left="10dp">
+    <item android:top="0dp" android:left="4dp">
         <shape android:shape="rectangle">
             <solid android:color="@color/modern_grey_200"/>
-            <size android:height="10dp" android:width="10dp"/>
+            <size android:height="4dp" android:width="4dp"/>
         </shape>
     </item>
-    <item android:top="10dp" android:left="0dp">
+    <item android:top="4dp" android:left="0dp">
         <shape android:shape="rectangle">
             <solid android:color="@color/modern_grey_200"/>
-            <size android:height="10dp" android:width="10dp"/>
+            <size android:height="4dp" android:width="4dp"/>
         </shape>
     </item>
-    <item android:top="10dp" android:left="10dp">
+    <item android:top="4dp" android:left="4dp">
         <shape android:shape="rectangle">
             <solid android:color="@android:color/white" />
-            <size android:height="10dp" android:width="10dp"/>
+            <size android:height="4dp" android:width="4dp"/>
         </shape>
     </item>
 </layer-list>
diff --git a/chrome/android/java/res/layout/context_menu_divider.xml b/chrome/android/java/res/layout/context_menu_divider.xml
new file mode 100644
index 0000000..a1dd372
--- /dev/null
+++ b/chrome/android/java/res/layout/context_menu_divider.xml
@@ -0,0 +1,13 @@
+<?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. -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingBottom="@dimen/revamped_context_menu_divider_padding">
+
+
+    <!-- TODO(sinansahin): divider_preference can be renamed to horizontal_divider -->
+    <include layout="@layout/divider_preference" />
+</FrameLayout>
\ No newline at end of file
diff --git a/chrome/android/java/res/layout/revamped_context_menu.xml b/chrome/android/java/res/layout/revamped_context_menu.xml
index e306c1e..340bf2e 100644
--- a/chrome/android/java/res/layout/revamped_context_menu.xml
+++ b/chrome/android/java/res/layout/revamped_context_menu.xml
@@ -13,7 +13,10 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
-        android:layout_margin="24dp"
+        android:layout_marginStart="@dimen/revamped_context_menu_lateral_margin"
+        android:layout_marginEnd="@dimen/revamped_context_menu_lateral_margin"
+        android:layout_marginTop="@dimen/revamped_context_menu_vertical_margin"
+        android:layout_marginBottom="@dimen/revamped_context_menu_vertical_margin"
         android:background="@drawable/popup_bg_tinted"
         android:divider="@null" />
 </FrameLayout>
\ No newline at end of file
diff --git a/chrome/android/java/res/layout/revamped_context_menu_header.xml b/chrome/android/java/res/layout/revamped_context_menu_header.xml
index 7036556..8633ca8 100644
--- a/chrome/android/java/res/layout/revamped_context_menu_header.xml
+++ b/chrome/android/java/res/layout/revamped_context_menu_header.xml
@@ -5,46 +5,71 @@
 
 <!-- We used nested LinearLayouts here because it was harder to align the text vertically with
      the center of the image using a single RelativeLayout. A ConstraintLayout could be a better
-     choice here, but it isn't available to us, yet -->
+     choice here, but it isn't available to us, yet. -->
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:orientation="horizontal"
-    android:paddingTop="12dp"
-    android:paddingBottom="12dp"
-    android:paddingStart="16dp"
-    android:paddingEnd="16dp" >
+    android:paddingStart="@dimen/revamped_context_menu_list_lateral_padding"
+    android:paddingEnd="@dimen/revamped_context_menu_list_lateral_padding"
+    android:paddingTop="@dimen/revamped_context_menu_header_vertical_padding"
+    android:paddingBottom="@dimen/revamped_context_menu_header_vertical_padding">
 
-    <ImageView
-        android:id="@+id/menu_header_image"
-        android:layout_width="@dimen/revamped_context_menu_header_image_max_size"
-        android:layout_height="@dimen/revamped_context_menu_header_image_max_size"
-        android:layout_gravity="top|start"
-        android:src="@drawable/checkerboard_background"
-        android:scaleType="fitCenter"
-        android:importantForAccessibility="no" />
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="16dp">
+
+        <!-- Circle background for when we have a favicon or monogram -->
+        <View
+            android:id="@+id/circle_background"
+            android:background="@drawable/tile_view_icon_background_modern"
+            android:layout_width="@dimen/revamped_context_menu_header_circle_bg_diameter"
+            android:layout_height="@dimen/revamped_context_menu_header_circle_bg_diameter"
+            android:layout_margin="@dimen/revamped_context_menu_header_circle_bg_margin"
+            android:visibility="invisible" />
+
+        <org.chromium.ui.widget.RoundedCornerImageView
+            android:id="@+id/menu_header_image"
+            android:layout_width="@dimen/revamped_context_menu_header_image_max_size"
+            android:layout_height="@dimen/revamped_context_menu_header_image_max_size"
+            android:scaleType="centerInside"
+            android:importantForAccessibility="no"
+            app:cornerRadiusTopStart="@dimen/default_rounded_corner_radius"
+            app:cornerRadiusTopEnd="@dimen/default_rounded_corner_radius"
+            app:cornerRadiusBottomStart="@dimen/default_rounded_corner_radius"
+            app:cornerRadiusBottomEnd="@dimen/default_rounded_corner_radius"
+            app:roundedfillColor="@color/thumbnail_placeholder_on_primary_bg" />
+    </FrameLayout>
 
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="vertical"
-        android:paddingStart="16dp"
-        android:paddingEnd="16dp"
         android:layout_gravity="center_vertical">
-        <TextView
+
+        <!-- TODO(sinansahin): Sync with UX to decide on what to do with cases like RTL text on LTR
+             device and LTR text on RTL device -->
+        <org.chromium.ui.widget.TextViewWithLeading
             android:id="@+id/menu_header_title"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:textAlignment="viewStart"
             android:ellipsize="end"
             android:maxLines="1"
-            android:textAppearance="@style/TextAppearance.BlackCaptionDefault"/>
-        <TextView
+            android:textAppearance="@style/TextAppearance.BlackCaptionDefault"
+            app:leading="@dimen/text_size_small_leading" />
+
+        <org.chromium.ui.widget.TextViewWithLeading
             android:id="@+id/menu_header_url"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:textAlignment="viewStart"
             android:ellipsize="end"
             android:maxLines="1"
-            android:textAppearance="@style/TextAppearance.BlackCaption"/>
+            android:textAppearance="@style/TextAppearance.BlackCaption"
+            app:leading="@dimen/text_size_small_leading" />
     </LinearLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/chrome/android/java/res/layout/revamped_context_menu_row.xml b/chrome/android/java/res/layout/revamped_context_menu_row.xml
index a5c38b3..afe4792 100644
--- a/chrome/android/java/res/layout/revamped_context_menu_row.xml
+++ b/chrome/android/java/res/layout/revamped_context_menu_row.xml
@@ -10,6 +10,6 @@
     android:textAppearance="@style/TextAppearance.BlackTitle1"
     android:minHeight="48dp"
     android:gravity="center_vertical"
-    android:paddingStart="16dp"
-    android:paddingEnd="16dp"
+    android:paddingStart="@dimen/revamped_context_menu_list_lateral_padding"
+    android:paddingEnd="@dimen/revamped_context_menu_list_lateral_padding"
     android:background="?attr/selectableItemBackground"/>
\ No newline at end of file
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index 1aedd80..15fd4b6 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -542,8 +542,16 @@
     <dimen name="context_menu_selectable_items_min_size">120dp</dimen>
 
     <!-- Revamped Context Menu Dimensions -->
-    <dimen name="revamped_context_menu_header_image_max_size">64dp</dimen>
-
+    <dimen name="revamped_context_menu_lateral_margin">40dp</dimen>
+    <dimen name="revamped_context_menu_vertical_margin">20dp</dimen>
+    <dimen name="revamped_context_menu_list_lateral_padding">16dp</dimen>
+    <dimen name="revamped_context_menu_header_vertical_padding">16dp</dimen>
+    <dimen name="revamped_context_menu_divider_padding">8dp</dimen>
+    <dimen name="revamped_context_menu_header_image_max_size">60dp</dimen>
+    <dimen name="revamped_context_menu_header_circle_bg_diameter">48dp</dimen>
+    <dimen name="revamped_context_menu_header_circle_bg_margin">6dp</dimen>
+    <dimen name="revamped_context_menu_header_monogram_text_size">16dp</dimen>
+    <dimen name="revamped_context_menu_header_monogram_size">26dp</dimen>
     <!-- Reader Mode dimensions -->
     <!-- Padding surrounding the message. -->
     <dimen name="reader_mode_infobar_text_padding">8dp</dimen>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
index 02cdbf9..f73e04254 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
@@ -161,6 +161,7 @@
     public static final String ADJUST_WEBAPK_INSTALLATION_SPACE = "AdjustWebApkInstallationSpace";
     public static final String ANDROID_NIGHT_MODE = "AndroidNightMode";
     public static final String ANDROID_NIGHT_MODE_CCT = "AndroidNightModeCCT";
+    public static final String ANDROID_NIGHT_MODE_FOR_Q = "AndroidNightModeForQ";
     public static final String ANDROID_PAY_INTEGRATION_V1 = "AndroidPayIntegrationV1";
     public static final String ANDROID_PAY_INTEGRATION_V2 = "AndroidPayIntegrationV2";
     public static final String ANDROID_PAYMENT_APPS = "AndroidPaymentApps";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManager.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManager.java
index 4ad1ccd..8ac40a5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/content/TabContentManager.java
@@ -22,6 +22,7 @@
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.native_page.NativePage;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.display.DisplayAndroid;
 
@@ -64,7 +65,18 @@
      */
     private static int getIntegerResourceWithOverride(Context context, int resourceId,
             String commandLineSwitch) {
-        int val = context.getResources().getInteger(resourceId);
+        int val = -1;
+        // TODO(crbug/959054): Convert this to Finch config.
+        if (FeatureUtilities.isGridTabSwitcherEnabled()
+                || FeatureUtilities.isTabGroupsAndroidEnabled()) {
+            // With Grid Tab Switcher, we can greatly reduce the capacity of thumbnail cache.
+            // See crbug.com/959054 for more details.
+            if (resourceId == R.integer.default_thumbnail_cache_size) val = 2;
+            if (resourceId == R.integer.default_approximation_thumbnail_cache_size) val = 8;
+            assert val != -1;
+        } else {
+            val = context.getResources().getInteger(resourceId);
+        }
         String switchCount = CommandLine.getInstance().getSwitchValue(commandLineSwitch);
         if (switchCount != null) {
             int count = Integer.parseInt(switchCount);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java
index 7e2fecae..cbe7518 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java
@@ -224,21 +224,35 @@
      */
     public static String createHeaderText(ContextMenuParams params) {
         if (!isEmptyUrl(params.getLinkUrl())) {
-            // The context menu can be created without native library
-            // being loaded. Only use native URL formatting methods
-            // if the native libraries have been loaded.
-            if (BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
-                            .isStartupSuccessfullyCompleted()) {
-                return UrlFormatter.formatUrlForDisplayOmitHTTPScheme(params.getLinkUrl());
-            } else {
-                return params.getLinkUrl();
-            }
+            return getUrlText(params);
         } else if (!TextUtils.isEmpty(params.getTitleText())) {
             return params.getTitleText();
         }
         return "";
     }
 
+    /**
+     * Gets the link of the item or empty text if the Url is empty.
+     * @return A string with the link or an empty string.
+     */
+    public static String createUrlText(ContextMenuParams params) {
+        if (!isEmptyUrl(params.getLinkUrl())) {
+            return getUrlText(params);
+        }
+        return "";
+    }
+
+    private static String getUrlText(ContextMenuParams params) {
+        // The context menu can be created without native library
+        // being loaded. Only use native URL formatting methods
+        // if the native libraries have been loaded.
+        if (BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
+                        .isStartupSuccessfullyCompleted()) {
+            return UrlFormatter.formatUrlForDisplayOmitHTTPScheme(params.getLinkUrl());
+        }
+        return params.getLinkUrl();
+    }
+
     @Override
     public List<Pair<Integer, List<ContextMenuItem>>> buildContextMenu(
             ContextMenu menu, Context context, ContextMenuParams params) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java
index a49a925..862a82e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java
@@ -23,6 +23,8 @@
 import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.browser.favicon.LargeIconBridge;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.share.ShareHelper;
 import org.chromium.chrome.browser.share.ShareParams;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
@@ -138,12 +140,13 @@
                     mOnMenuShown, mOnMenuClosed);
             // TODO(sinansahin): This could be pushed in to the menuController.
             if (mCurrentContextMenuParams.isImage()) {
-                getThumbnail(menuController, new Callback<Bitmap>() {
-                    @Override
-                    public void onResult(Bitmap result) {
-                        menuController.onImageThumbnailRetrieved(result);
-                    }
-                });
+                getThumbnail(menuController, menuController::onImageThumbnailRetrieved);
+            } else {
+                LargeIconBridge iconBridge = new LargeIconBridge(Profile.getLastUsedProfile());
+                iconBridge.getLargeIconForUrl(mCurrentContextMenuParams.getUrl(),
+                        getActivity().getResources().getDimensionPixelSize(
+                                R.dimen.default_favicon_size),
+                        menuController::onFaviconAvailable);
             }
             return;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuController.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuController.java
index 5a6cba6..184a311 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuController.java
@@ -7,16 +7,33 @@
 import android.app.Activity;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Shader;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
 import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
 import android.support.v7.app.AlertDialog;
+import android.text.SpannableString;
+import android.text.TextUtils;
 import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.AdapterView;
 
+import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.Callback;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeBaseAppCompatActivity;
+import org.chromium.chrome.browser.favicon.IconType;
+import org.chromium.chrome.browser.night_mode.GlobalNightModeStateProviderHolder;
+import org.chromium.chrome.browser.omnibox.OmniboxUrlEmphasizer;
+import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.widget.ContextMenuDialog;
+import org.chromium.components.security_state.ConnectionSecurityLevel;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -44,13 +61,14 @@
     private RevampedContextMenuListAdapter mListAdapter;
     private Callback<Integer> mItemClickCallback;
     private float mTopContentOffsetPx;
+    private String mPlainUrl;
 
     /**
      * Constructor that also sets the content offset.
      *
      * @param topContentOffsetPx content offset from the top.
      */
-    public RevampedContextMenuController(float topContentOffsetPx) {
+    RevampedContextMenuController(float topContentOffsetPx) {
         mTopContentOffsetPx = topContentOffsetPx;
     }
 
@@ -59,15 +77,16 @@
             List<Pair<Integer, List<ContextMenuItem>>> items, Callback<Integer> onItemClicked,
             final Runnable onMenuShown, final Runnable onMenuClosed) {
         mItemClickCallback = onItemClicked;
-        float density = Resources.getSystem().getDisplayMetrics().density;
+        final Resources resources = activity.getResources();
+        final float density = resources.getDisplayMetrics().density;
         final float touchPointXPx = params.getTriggeringTouchXDp() * density;
         final float touchPointYPx = params.getTriggeringTouchYDp() * density;
 
         final View view =
                 LayoutInflater.from(activity).inflate(R.layout.revamped_context_menu, null);
         mContextMenuDialog = createContextMenuDialog(activity, view, touchPointXPx, touchPointYPx);
-        mContextMenuDialog.setOnShowListener(dialogInterface -> { onMenuShown.run(); });
-        mContextMenuDialog.setOnDismissListener(dialogInterface -> { onMenuClosed.run(); });
+        mContextMenuDialog.setOnShowListener(dialogInterface -> onMenuShown.run());
+        mContextMenuDialog.setOnDismissListener(dialogInterface -> onMenuClosed.run());
 
         // Integer here indicates if the item is the header, a divider, or a row (selectable option)
         List<Pair<Integer, ContextMenuItem>> itemList = new ArrayList<>();
@@ -82,9 +101,32 @@
             }
         }
 
-        mListAdapter = new RevampedContextMenuListAdapter(itemList);
-        mListAdapter.setHeaderTitle(params.getTitleText());
-        mListAdapter.setHeaderUrl(params.getLinkUrl());
+        String headerTitle = params.getTitleText();
+        if (TextUtils.isEmpty(headerTitle)) {
+            headerTitle = params.getLinkText();
+        }
+
+        mPlainUrl = params.getUrl();
+        CharSequence url = params.getUrl();
+        if (!TextUtils.isEmpty(url)) {
+            boolean useDarkColors =
+                    !GlobalNightModeStateProviderHolder.getInstance().isInNightMode();
+            if (activity instanceof ChromeBaseAppCompatActivity) {
+                useDarkColors = !((ChromeBaseAppCompatActivity) activity)
+                                         .getNightModeStateProvider()
+                                         .isInNightMode();
+            }
+
+            SpannableString spannableUrl =
+                    new SpannableString(ChromeContextMenuPopulator.createUrlText(params));
+            OmniboxUrlEmphasizer.emphasizeUrl(spannableUrl, resources, Profile.getLastUsedProfile(),
+                    ConnectionSecurityLevel.NONE, false, useDarkColors, false);
+            url = spannableUrl;
+        }
+
+        mListAdapter = new RevampedContextMenuListAdapter(activity, itemList);
+        mListAdapter.setHeaderTitle(headerTitle);
+        mListAdapter.setHeaderUrl(url);
         RevampedContextMenuListView listView = view.findViewById(R.id.context_menu_list_view);
         listView.setAdapter(mListAdapter);
         listView.setOnItemClickListener(this);
@@ -114,16 +156,55 @@
     }
 
     /**
+     * This adds a checkerboard style background to the image.
+     * It is useful for the transparent PNGs.
+     * @return The given image with the checkerboard pattern in the background.
+     */
+    static Bitmap getImageWithCheckerBackground(Resources res, Bitmap image) {
+        // 1. Create a bitmap for the checkerboard pattern.
+        Drawable drawable = ApiCompatibilityUtils.getDrawable(
+                res, org.chromium.chrome.R.drawable.checkerboard_background);
+        Bitmap tileBitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+                drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+        Canvas tileCanvas = new Canvas(tileBitmap);
+        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+        drawable.draw(tileCanvas);
+
+        // 2. Create a BitmapDrawable using the checkerboard pattern bitmap.
+        BitmapDrawable bitmapDrawable = new BitmapDrawable(res, tileBitmap);
+        bitmapDrawable.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
+        bitmapDrawable.setBounds(0, 0, image.getWidth(), image.getHeight());
+
+        // 3. Create a bitmap-backed canvas for the final image.
+        Bitmap bitmap =
+                Bitmap.createBitmap(image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+
+        // 4. Paint the checkerboard background into the final canvas
+        bitmapDrawable.draw(canvas);
+
+        // 5. Draw the image on top.
+        Paint paint = new Paint();
+        paint.setAntiAlias(true);
+        canvas.drawBitmap(image, new Matrix(), paint);
+
+        return bitmap;
+    }
+
+    /**
      * This is called when the thumbnail is fetched and ready to display.
      * @param thumbnail The bitmap received that will be displayed as the header image.
      */
-    public void onImageThumbnailRetrieved(Bitmap thumbnail) {
-        if (thumbnail != null) {
-            mListAdapter.getHeaderImage().setImageBitmap(thumbnail);
-        } else {
-            // TODO(sinansahin): Handle the failed image loads differently.
-            mListAdapter.getHeaderImage().setImageResource(R.drawable.sad_tab);
-        }
+    void onImageThumbnailRetrieved(Bitmap thumbnail) {
+        mListAdapter.onImageThumbnailRetrieved(thumbnail);
+    }
+
+    /**
+     * See {@link org.chromium.chrome.browser.favicon.LargeIconBridge#getLargeIconForUrl}
+     */
+    void onFaviconAvailable(@Nullable Bitmap icon, @ColorInt int fallbackColor,
+            boolean isColorDefault, @IconType int iconType) {
+        mListAdapter.onFaviconAvailable(icon, fallbackColor, mPlainUrl);
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuListAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuListAdapter.java
index a7d826e..0f5a5a8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuListAdapter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuListAdapter.java
@@ -4,16 +4,22 @@
 
 package org.chromium.chrome.browser.contextmenu;
 
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.support.annotation.ColorInt;
+import android.support.annotation.Nullable;
 import android.text.TextUtils;
 import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
-import android.widget.ImageView;
 import android.widget.TextView;
 
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.widget.RoundedIconGenerator;
+import org.chromium.ui.widget.RoundedCornerImageView;
 
 import java.util.List;
 
@@ -23,10 +29,17 @@
     private final List<Pair<Integer, ContextMenuItem>> mMenuItems;
 
     private String mHeaderTitle;
-    private String mHeaderUrl;
-    private ImageView mHeaderImage;
+    private CharSequence mHeaderUrl;
+    private RoundedCornerImageView mHeaderImage;
+    private Bitmap mHeaderBitmap;
 
-    public RevampedContextMenuListAdapter(List<Pair<Integer, ContextMenuItem>> menuItems) {
+    private boolean mIsHeaderImageThumbnail;
+
+    private Context mContext;
+
+    public RevampedContextMenuListAdapter(
+            Context context, List<Pair<Integer, ContextMenuItem>> menuItems) {
+        mContext = context;
         mMenuItems = menuItems;
     }
 
@@ -70,6 +83,12 @@
 
                 mHeaderImage = convertView.findViewById(R.id.menu_header_image);
 
+                // We should've cached the bitmap if the header wasn't ready when we received a
+                // thumbnail, favicon, or monogram. We can set the image to the cached bitmap here.
+                if (mHeaderBitmap != null) {
+                    setHeaderImage(mHeaderBitmap);
+                }
+
                 TextView titleText = convertView.findViewById(R.id.menu_header_title);
                 titleText.setText(mHeaderTitle);
 
@@ -98,8 +117,7 @@
                 }
             } else if (getItem(position).first
                     == RevampedContextMenuController.ListItemType.DIVIDER) {
-                // TODO(sinansahin): divider_preference can be renamed to horizontal_divider
-                layout = R.layout.divider_preference;
+                layout = R.layout.context_menu_divider;
                 convertView = LayoutInflater.from(parent.getContext()).inflate(layout, null);
             } else {
                 layout = R.layout.revamped_context_menu_row;
@@ -117,12 +135,72 @@
         mHeaderTitle = headerTitle;
     }
 
-    public void setHeaderUrl(String headerUrl) {
+    public void setHeaderUrl(CharSequence headerUrl) {
         mHeaderUrl = headerUrl;
     }
 
-    public ImageView getHeaderImage() {
-        return mHeaderImage;
+    /**
+     * This is called when the thumbnail is fetched and ready to display.
+     * @param thumbnail The bitmap received that will be displayed as the header image.
+     */
+    void onImageThumbnailRetrieved(Bitmap thumbnail) {
+        mIsHeaderImageThumbnail = true;
+        if (thumbnail != null) {
+            setHeaderImage(RevampedContextMenuController.getImageWithCheckerBackground(
+                    mContext.getResources(), thumbnail));
+        }
+        // TODO(sinansahin): Handle the case where the retrieval of the thumbnail fails.
+    }
+
+    void onFaviconAvailable(@Nullable Bitmap icon, @ColorInt int fallbackColor, String iconUrl) {
+        // If we didn't get a favicon, generate a monogram instead
+        if (icon == null) {
+            RoundedIconGenerator iconGenerator = createRoundedIconGenerator(fallbackColor);
+            icon = iconGenerator.generateIconForUrl(iconUrl);
+        }
+
+        mIsHeaderImageThumbnail = false;
+        final int size = mContext.getResources().getDimensionPixelSize(
+                R.dimen.revamped_context_menu_header_monogram_size);
+
+        if (icon.getWidth() > size) {
+            icon = Bitmap.createScaledBitmap(icon, size, size, true);
+        }
+
+        setHeaderImage(icon);
+    }
+
+    private void setHeaderImage(Bitmap bitmap) {
+        // If the HeaderImage hasn't been inflated by the time we receive the bitmap,
+        // cache the bitmap received to be set in #getView later.
+        if (mHeaderImage == null) {
+            mHeaderBitmap = bitmap;
+            return;
+        }
+
+        // Clear the loading background color now that we have the real image.
+        mHeaderImage.setRoundedFillColor(android.R.color.transparent);
+
+        if (mIsHeaderImageThumbnail) {
+            mHeaderImage.setImageBitmap(bitmap);
+            return;
+        }
+
+        ((View) mHeaderImage.getParent())
+                .findViewById(R.id.circle_background)
+                .setVisibility(View.VISIBLE);
+        mHeaderImage.setImageBitmap(bitmap);
+    }
+
+    private RoundedIconGenerator createRoundedIconGenerator(@ColorInt int iconColor) {
+        final Resources resources = mContext.getResources();
+        final int iconSize =
+                resources.getDimensionPixelSize(R.dimen.revamped_context_menu_header_monogram_size);
+        final int cornerRadius = iconSize / 2;
+        final int textSize = resources.getDimensionPixelSize(
+                R.dimen.revamped_context_menu_header_monogram_text_size);
+
+        return new RoundedIconGenerator(iconSize, iconSize, cornerRadius, iconColor, textSize);
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuListView.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuListView.java
index 21ffc9c..1480788 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuListView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/RevampedContextMenuListView.java
@@ -34,7 +34,8 @@
     private int calculateWidth() {
         int windowWidthPx = getResources().getDisplayMetrics().widthPixels;
         int maxWidth = getResources().getDimensionPixelSize(R.dimen.context_menu_max_width);
-        int lateralMargin = getResources().getDimensionPixelSize(R.dimen.context_menu_min_padding);
+        int lateralMargin =
+                getResources().getDimensionPixelSize(R.dimen.revamped_context_menu_lateral_margin);
 
         return Math.min(maxWidth, windowWidthPx - 2 * lateralMargin);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionAppModalDialogView.java b/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionAppModalDialogView.java
deleted file mode 100644
index 80e104d..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionAppModalDialogView.java
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.permissions;
-
-import android.content.Context;
-import android.support.v4.widget.TextViewCompat;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.TextView;
-
-import org.chromium.chrome.R;
-import org.chromium.ui.modaldialog.ModalDialogProperties;
-import org.chromium.ui.modelutil.PropertyModel;
-
-/**
- * The Permission dialog that is app modal.
- */
-class PermissionAppModalDialogView {
-    private final PropertyModel mDialogModel;
-
-    PermissionAppModalDialogView(
-            ModalDialogProperties.Controller controller, PermissionDialogDelegate delegate) {
-        Context context = delegate.getTab().getActivity();
-        LayoutInflater inflater = LayoutInflater.from(context);
-        View customView = inflater.inflate(R.layout.permission_dialog, null);
-
-        String messageText = delegate.getMessageText();
-        assert !TextUtils.isEmpty(messageText);
-
-        TextView messageTextView = customView.findViewById(R.id.text);
-        messageTextView.setText(messageText);
-        TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(
-                messageTextView, delegate.getDrawableId(), 0, 0, 0);
-
-        mDialogModel =
-                new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
-                        .with(ModalDialogProperties.CONTROLLER, controller)
-                        .with(ModalDialogProperties.CUSTOM_VIEW, customView)
-                        .with(ModalDialogProperties.POSITIVE_BUTTON_TEXT,
-                                delegate.getPrimaryButtonText())
-                        .with(ModalDialogProperties.NEGATIVE_BUTTON_TEXT,
-                                delegate.getSecondaryButtonText())
-                        .with(ModalDialogProperties.CONTENT_DESCRIPTION, delegate.getMessageText())
-                        .with(ModalDialogProperties.FILTER_TOUCH_FOR_SECURITY, true)
-                        .build();
-    }
-
-    /**
-     * @return The dialog model for the permission dialog.
-     */
-    PropertyModel getDialogModel() {
-        return mDialogModel;
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogController.java b/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogController.java
index 2174044..bd5715a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogController.java
@@ -45,7 +45,7 @@
         int REQUEST_ANDROID_PERMISSIONS = 5;
     }
 
-    private PermissionAppModalDialogView mAppModalDialogView;
+    private PropertyModel mDialogModel;
     private PermissionDialogDelegate mDialogDelegate;
     private ModalDialogManager mModalDialogManager;
 
@@ -189,9 +189,8 @@
         }
 
         mModalDialogManager = mDialogDelegate.getTab().getActivity().getModalDialogManager();
-        mAppModalDialogView = new PermissionAppModalDialogView(this, mDialogDelegate);
-        mModalDialogManager.showDialog(
-                mAppModalDialogView.getDialogModel(), ModalDialogManager.ModalDialogType.TAB);
+        mDialogModel = PermissionDialogModel.getModel(this, mDialogDelegate);
+        mModalDialogManager.showDialog(mDialogModel, ModalDialogManager.ModalDialogType.TAB);
         mState = State.PROMPT_OPEN;
     }
 
@@ -202,8 +201,8 @@
             // may be called after onClick and before onDismiss, or before both of those listeners.
             mDialogDelegate = null;
             if (mState == State.PROMPT_OPEN) {
-                mModalDialogManager.dismissDialog(mAppModalDialogView.getDialogModel(),
-                        DialogDismissalCause.DISMISSED_BY_NATIVE);
+                mModalDialogManager.dismissDialog(
+                        mDialogModel, DialogDismissalCause.DISMISSED_BY_NATIVE);
             } else {
                 assert mState == State.PROMPT_PENDING || mState == State.REQUEST_ANDROID_PERMISSIONS
                         || mState == State.PROMPT_DENIED || mState == State.PROMPT_ACCEPTED;
@@ -221,7 +220,7 @@
         // call this handler after the primary/secondary handler.
         // When the dialog is dismissed, the delegate's native pointers are
         // freed, and the next queued dialog (if any) is displayed.
-        mAppModalDialogView = null;
+        mDialogModel = null;
         if (mDialogDelegate == null) {
             // We get into here if a tab navigates or is closed underneath the
             // prompt.
@@ -286,6 +285,6 @@
 
     @VisibleForTesting
     public void clickButtonForTest(@ModalDialogProperties.ButtonType int buttonType) {
-        onClick(mAppModalDialogView.getDialogModel(), buttonType);
+        onClick(mDialogModel, buttonType);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogDelegate.java
index 5b37eb2..e3016ca 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogDelegate.java
@@ -29,6 +29,9 @@
     /** The icon to display in the dialog. */
     private int mDrawableId;
 
+    /** Title text that can be shown in the dialog. */
+    private String mTitleText;
+
     /** Text shown in the dialog. */
     private String mMessageText;
 
@@ -53,6 +56,10 @@
         return mDrawableId;
     }
 
+    public String getTitleText() {
+        return mTitleText;
+    }
+
     public String getMessageText() {
         return mMessageText;
     }
@@ -105,28 +112,30 @@
      * @param tab                   The tab to create the dialog for.
      * @param contentSettingsTypes  The content settings types requested by this dialog.
      * @param iconResourceId        The id of the icon to display in the dialog.
+     * @param title                 The title to display in the dialog. This is the permission type.
      * @param message               The message to display in the dialog.
      * @param primaryTextButton     The text to display on the primary button.
      * @param secondaryTextButton   The text to display on the primary button.
      */
     @CalledByNative
     private static PermissionDialogDelegate create(long nativeDelegatePtr, Tab tab,
-            int[] contentSettingsTypes, int enumeratedIconId, String message,
+            int[] contentSettingsTypes, int enumeratedIconId, String title, String message,
             String primaryButtonText, String secondaryButtonText) {
         return new PermissionDialogDelegate(nativeDelegatePtr, tab, contentSettingsTypes,
-                enumeratedIconId, message, primaryButtonText, secondaryButtonText);
+                enumeratedIconId, title, message, primaryButtonText, secondaryButtonText);
     }
 
     /**
      * Upon construction, this class takes ownership of the passed in native delegate.
      */
     private PermissionDialogDelegate(long nativeDelegatePtr, Tab tab, int[] contentSettingsTypes,
-            int enumeratedIconId, String message, String primaryButtonText,
+            int enumeratedIconId, String title, String message, String primaryButtonText,
             String secondaryButtonText) {
         mNativeDelegatePtr = nativeDelegatePtr;
         mTab = tab;
         mContentSettingsTypes = contentSettingsTypes;
         mDrawableId = ResourceId.mapToDrawableId(enumeratedIconId);
+        mTitleText = title;
         mMessageText = message;
         mPrimaryButtonText = primaryButtonText;
         mSecondaryButtonText = secondaryButtonText;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogModel.java b/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogModel.java
new file mode 100644
index 0000000..d82f58d
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionDialogModel.java
@@ -0,0 +1,53 @@
+// 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.
+
+package org.chromium.chrome.browser.permissions;
+
+import android.content.Context;
+import android.support.v4.widget.TextViewCompat;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.touchless.TouchlessDelegate;
+import org.chromium.chrome.browser.util.FeatureUtilities;
+import org.chromium.ui.modaldialog.ModalDialogProperties;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/**
+ * This class creates the model for permission dialog.
+ */
+class PermissionDialogModel {
+    public static PropertyModel getModel(
+            ModalDialogProperties.Controller controller, PermissionDialogDelegate delegate) {
+        if (FeatureUtilities.isNoTouchModeEnabled()) {
+            return TouchlessDelegate.getTouchlessPermissionDialogModel(controller, delegate);
+        }
+
+        Context context = delegate.getTab().getActivity();
+        LayoutInflater inflater = LayoutInflater.from(context);
+        View customView = inflater.inflate(R.layout.permission_dialog, null);
+
+        String messageText = delegate.getMessageText();
+        assert !TextUtils.isEmpty(messageText);
+
+        TextView messageTextView = customView.findViewById(R.id.text);
+        messageTextView.setText(messageText);
+        TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(
+                messageTextView, delegate.getDrawableId(), 0, 0, 0);
+
+        return new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
+                .with(ModalDialogProperties.CONTROLLER, controller)
+                .with(ModalDialogProperties.CUSTOM_VIEW, customView)
+                .with(ModalDialogProperties.POSITIVE_BUTTON_TEXT,
+                        delegate.getPrimaryButtonText())
+                .with(ModalDialogProperties.NEGATIVE_BUTTON_TEXT,
+                        delegate.getSecondaryButtonText())
+                .with(ModalDialogProperties.CONTENT_DESCRIPTION, delegate.getMessageText())
+                .with(ModalDialogProperties.FILTER_TOUCH_FOR_SECURITY, true)
+                .build();
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/AppThemeColorProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/AppThemeColorProvider.java
index adb270b..c4f730a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/AppThemeColorProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/AppThemeColorProvider.java
@@ -84,7 +84,8 @@
         final boolean isAccessibilityEnabled = DeviceClassManager.enableAccessibilityLayout();
         final boolean isHorizontalTabSwitcherEnabled =
                 ChromeFeatureList.isEnabled(ChromeFeatureList.HORIZONTAL_TAB_SWITCHER_ANDROID);
-        final boolean isTabGridEnabled = FeatureUtilities.isGridTabSwitcherEnabled();
+        final boolean isTabGridEnabled = FeatureUtilities.isGridTabSwitcherEnabled()
+                || FeatureUtilities.isTabGroupsAndroidEnabled();
         final boolean shouldUseIncognitoBackground = mIsIncognito
                 && (isAccessibilityEnabled || isHorizontalTabSwitcherEnabled || isTabGridEnabled
                         || !mIsOverviewVisible);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java b/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java
index 48a08dd3..2288815 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java
@@ -16,6 +16,7 @@
 import android.speech.RecognizerIntent;
 import android.support.annotation.Nullable;
 
+import org.chromium.base.BuildInfo;
 import org.chromium.base.CommandLine;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.SysUtils;
@@ -517,9 +518,11 @@
      * next startup, the value can be made available immediately.
      */
     public static void cacheNightModeAvailable() {
+        boolean available = ChromeFeatureList.isEnabled(ChromeFeatureList.ANDROID_NIGHT_MODE)
+                || (BuildInfo.isAtLeastQ()
+                        && ChromeFeatureList.isEnabled(ChromeFeatureList.ANDROID_NIGHT_MODE_FOR_Q));
         ChromePreferenceManager.getInstance().writeBoolean(
-                ChromePreferenceManager.NIGHT_MODE_AVAILABLE_KEY,
-                ChromeFeatureList.isEnabled(ChromeFeatureList.ANDROID_NIGHT_MODE));
+                ChromePreferenceManager.NIGHT_MODE_AVAILABLE_KEY, available);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/SplashController.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/SplashController.java
index 2c4985d..b18e1af 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/SplashController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/SplashController.java
@@ -177,6 +177,8 @@
         }
         mParentView = (ViewGroup) mActivity.findViewById(android.R.id.content);
         mParentView.addView(mSplashView);
+
+        recordTraceEventsShowedSplash();
     }
 
     private boolean canHideSplashScreen() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrInputTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrInputTest.java
index ef6f50d..31ad417 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrInputTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrInputTest.java
@@ -565,9 +565,9 @@
     }
 
     /**
-     * Verifies that a Gamepad API gamepad is not returned when using WebXR and a Daydream headset
-     * if WebXRGamepadSupport is not explicitly enabled. Correctness testing for
-     * https://crbug.com/830935.
+     * Verifies that a Gamepad API gamepad is returned on the XRSession's input
+     * source instead of the navigator array when using WebXR and a Daydream
+     * headset.
      */
     @Test
     @MediumTest
@@ -576,16 +576,14 @@
             .Remove({"enable-webvr"})
             @CommandLineFlags.Add({"enable-features=WebXR"})
             @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
-            public void testWebXrGamepadNotReturnedWithoutGamepadSupport()
-            throws InterruptedException {
-        webxrGamepadSupportImpl(
-                0 /* numExpectedGamepads */, true /* webxrPresent */, true /* daydream */);
+            public void testWebXrInputSourceHasGamepad() throws InterruptedException {
+        webxrGamepadSupportImpl(true /* daydream */);
     }
 
     /**
-     * Verifies that a Gamepad API gamepad is not returned when using WebXR and Cardboard if
-     * WebXRGamepadSupport is not explicitly enabled. Correctness testing for
-     * https://crbug.com/830935.
+     * Verifies that the XRSession has an input source when using WebXR and
+     * Cardboard. There should be no gamepads on the input source or navigator
+     * array.
      */
     @Test
     @MediumTest
@@ -594,95 +592,15 @@
             .Remove({"enable-webvr"})
             @CommandLineFlags.Add({"enable-features=WebXR"})
             @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
-            public void testWebXrGamepadNotReturnedWithoutGamepadSupport_Cardboard()
-            throws InterruptedException {
-        webxrGamepadSupportImpl(
-                0 /* numExpectedGamepads */, true /* webxrPresent */, false /* daydream */);
+            public void testWebXrInputSourceWithoutGamepad_Cardboard() throws InterruptedException {
+        webxrGamepadSupportImpl(false /* daydream */);
     }
 
-    /**
-     * Verifies that a Gamepad API gamepad is returned when using WebXR and a Daydream headset if
-     * WebXRGamepadSupport is explicitly enabled. Correctness testing for https://crbug.com/830935.
-     */
-    @Test
-    @MediumTest
-    @Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
-    @CommandLineFlags
-            .Remove({"enable-webvr"})
-            @CommandLineFlags.Add({"enable-features=WebXR,WebXRGamepadSupport"})
-            @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
-            public void testWebXrGamepadReturnedWithGamepadSupport() throws InterruptedException {
-        webxrGamepadSupportImpl(
-                1 /* numExpectedGamepads */, true /* webxrPresent */, true /* daydream */);
-    }
-
-    /**
-     * Verifies that a Gamepad API gamepad is returned when using WebXR and Cardboard if
-     * WebXRGamepadSupport is explicitly enabled. Correctness testing for https://crbug.com/830935.
-     */
-    @Test
-    @MediumTest
-    @Restriction(RESTRICTION_TYPE_VIEWER_NON_DAYDREAM)
-    @CommandLineFlags
-            .Remove({"enable-webvr"})
-            @CommandLineFlags.Add({"enable-features=WebXR,WebXRGamepadSupport"})
-            @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
-            public void testWebXrGamepadReturnedWithGamepadSupport_Cardboard()
-            throws InterruptedException {
-        webxrGamepadSupportImpl(
-                1 /* numExpectedGamepads */, true /* webxrPresent */, false /* daydream */);
-    }
-
-    /**
-     * Verifies that a Gamepad API gamepad is not returned when not using WebXR, WebVR, or the
-     * WebXRGamepadSupport feature with a Daydream headset. Correctness testing for
-     * https://crbug.com/830935.
-     */
-    @Test
-    @MediumTest
-    @Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
-    @CommandLineFlags.Remove({"enable-webvr"})
-    @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
-    public void testWebXrGamepadNotReturnedWithoutAnyFeatures() throws InterruptedException {
-        webxrGamepadSupportImpl(
-                0 /* numExpectedGamepads */, false /* webxrPresent */, true /* daydream */);
-    }
-
-    /**
-     * Verifies that a Gamepad API gamepad is not returned when not using WebXR, WebVR, or the
-     * WebXRGamepadSupport feature with Cardboard. Correctness testing for https://crbug.com/830935.
-     */
-    @Test
-    @MediumTest
-    @Restriction(RESTRICTION_TYPE_VIEWER_NON_DAYDREAM)
-    @CommandLineFlags.Remove({"enable-webvr"})
-    @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
-    public void testWebXrGamepadNotReturnedWithoutAnyFeatures_Cardboard()
-            throws InterruptedException {
-        webxrGamepadSupportImpl(
-                0 /* numExpectedGamepads */, false /* webxrPresent */, false /* daydream */);
-    }
-
-    private void webxrGamepadSupportImpl(int numExpectedGamepads, boolean webxrPresent,
-            boolean daydream) throws InterruptedException {
-        if (webxrPresent) {
-            // TODO(https://crbug.com/947581): Update the test and remove this when Gamepads are
-            // supported by WebXR again. getGamepads() should always return 0, and the test should
-            // check that too. For now, change the expected value rather than disabling the tests
-            // since we actually expect 0 gamepads in all cases after removing WebXRGamepadSupport
-            // (https://crbug.com/920025).
-            numExpectedGamepads = 0;
-
-            mWebXrVrTestFramework.loadUrlAndAwaitInitialization(
-                    WebXrVrTestFramework.getFileUrlForHtmlTestFile("test_webxr_gamepad_support"),
-                    PAGE_LOAD_TIMEOUT_S);
-            mWebXrVrTestFramework.enterSessionWithUserGestureOrFail();
-        } else {
-            mWebXrVrTestFramework.loadUrlAndAwaitInitialization(
-                    WebXrVrTestFramework.getFileUrlForHtmlTestFile(
-                            "test_webxr_gamepad_support_nowebxr"),
-                    PAGE_LOAD_TIMEOUT_S);
-        }
+    private void webxrGamepadSupportImpl(boolean daydream) throws InterruptedException {
+        mWebXrVrTestFramework.loadUrlAndAwaitInitialization(
+                WebXrVrTestFramework.getFileUrlForHtmlTestFile("test_webxr_gamepad_support"),
+                PAGE_LOAD_TIMEOUT_S);
+        mWebXrVrTestFramework.enterSessionWithUserGestureOrFail();
 
         // Spam input to make sure the Gamepad API registers the gamepad if it should.
         int numIterations = 10;
@@ -691,24 +609,21 @@
             for (int i = 0; i < numIterations; i++) {
                 controller.performControllerClick();
             }
+
+            // Verify that there is a gamepad on the XRInputSource and that it
+            // has the expected mapping of '' (the Daydream controller does not
+            // meet the 'xr-standard' mapping requirements).
+            mWebXrVrTestFramework.executeStepAndWait("validateMapping('')");
         } else {
             int x = mWebXrVrTestFramework.getCurrentContentView().getWidth() / 2;
             int y = mWebXrVrTestFramework.getCurrentContentView().getHeight() / 2;
-
-            View presentationView;
-            if (webxrPresent) {
-                presentationView =
-                        TestVrShellDelegate.getVrShellForTesting().getPresentationViewForTesting();
-            } else {
-                presentationView = mTestRule.getActivity().getWindow().getDecorView();
-            }
-
+            View presentationView =
+                    TestVrShellDelegate.getVrShellForTesting().getPresentationViewForTesting();
             spamScreenTaps(presentationView, x, y, numIterations);
+
+            mWebXrVrTestFramework.executeStepAndWait("validateInputSourceHasNoGamepad()");
         }
 
-        mWebXrVrTestFramework.runJavaScriptOrFail("assertNumNavigatorGamepadsMatchesExpectation("
-                        + String.valueOf(numExpectedGamepads) + ")",
-                POLL_TIMEOUT_SHORT_MS);
         mWebVrTestFramework.runJavaScriptOrFail("done()", POLL_TIMEOUT_SHORT_MS);
         mWebXrVrTestFramework.endTest();
     }
diff --git a/chrome/android/touchless/fallback/java/src/org/chromium/chrome/browser/touchless/TouchlessDelegate.java b/chrome/android/touchless/fallback/java/src/org/chromium/chrome/browser/touchless/TouchlessDelegate.java
index 318ccf8..5a551ff 100644
--- a/chrome/android/touchless/fallback/java/src/org/chromium/chrome/browser/touchless/TouchlessDelegate.java
+++ b/chrome/android/touchless/fallback/java/src/org/chromium/chrome/browser/touchless/TouchlessDelegate.java
@@ -7,6 +7,9 @@
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.native_page.NativePage;
 import org.chromium.chrome.browser.native_page.NativePageHost;
+import org.chromium.chrome.browser.permissions.PermissionDialogDelegate;
+import org.chromium.ui.modaldialog.ModalDialogProperties;
+import org.chromium.ui.modelutil.PropertyModel;
 
 /**
  * The fallback version of TouchlessDelegate, when touchless mode isn't enabled.
@@ -35,4 +38,9 @@
     public static Class<?> getTouchlessPreferencesClass() {
         return null;
     }
+
+    public static PropertyModel getTouchlessPermissionDialogModel(
+            ModalDialogProperties.Controller controller, PermissionDialogDelegate delegate) {
+        return null;
+    }
 }
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessDelegate.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessDelegate.java
index 7603a8e..15031a4 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessDelegate.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessDelegate.java
@@ -7,9 +7,13 @@
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.native_page.NativePage;
 import org.chromium.chrome.browser.native_page.NativePageHost;
+import org.chromium.chrome.browser.permissions.PermissionDialogDelegate;
+import org.chromium.chrome.browser.touchless.permissions.TouchlessPermissionDialogModel;
+import org.chromium.ui.modaldialog.ModalDialogProperties;
+import org.chromium.ui.modelutil.PropertyModel;
 
 /**
- *  Provides an entry point into touchless code from the rest of Chrome.
+ * Provides an entry point into touchless code from the rest of Chrome.
  */
 public class TouchlessDelegate {
     public static final boolean TOUCHLESS_MODE_ENABLED = true;
@@ -35,4 +39,9 @@
     public static Class<?> getTouchlessPreferencesClass() {
         return TouchlessPreferences.class;
     }
+
+    public static PropertyModel getTouchlessPermissionDialogModel(
+            ModalDialogProperties.Controller controller, PermissionDialogDelegate delegate) {
+        return TouchlessPermissionDialogModel.getModel(controller, delegate);
+    }
 }
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/permissions/TouchlessPermissionDialogModel.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/permissions/TouchlessPermissionDialogModel.java
new file mode 100644
index 0000000..466e7d1
--- /dev/null
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/permissions/TouchlessPermissionDialogModel.java
@@ -0,0 +1,84 @@
+// 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.touchless.permissions;
+
+import android.content.res.Resources;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+
+import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.permissions.PermissionDialogDelegate;
+import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties;
+import org.chromium.ui.modaldialog.DialogDismissalCause;
+import org.chromium.ui.modaldialog.ModalDialogProperties;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/**
+ * This class creates the model for permission dialog in touchless mode.
+ */
+public class TouchlessPermissionDialogModel {
+    public static PropertyModel getModel(
+            ModalDialogProperties.Controller controller, PermissionDialogDelegate delegate) {
+        Resources resources = delegate.getTab().getActivity().getResources();
+        TouchlessDialogProperties.ActionNames names = new TouchlessDialogProperties.ActionNames();
+        names.cancel = R.string.cancel;
+        names.select = R.string.select;
+        names.alt = 0;
+        Drawable icon =
+                ApiCompatibilityUtils.getDrawable(resources, delegate.getDrawableId()).mutate();
+        icon.setColorFilter(new PorterDuffColorFilter(
+                resources.getColor(R.color.modern_grey_700), PorterDuff.Mode.SRC_ATOP));
+        String messageText = delegate.getMessageText();
+        assert !TextUtils.isEmpty(messageText);
+
+        PropertyModel model =
+                new PropertyModel.Builder(TouchlessDialogProperties.ALL_DIALOG_KEYS)
+                        .with(TouchlessDialogProperties.IS_FULLSCREEN, false)
+                        .with(TouchlessDialogProperties.PRIORITY,
+                                TouchlessDialogProperties.Priority.HIGH)
+                        .with(TouchlessDialogProperties.ACTION_NAMES, names)
+                        .with(TouchlessDialogProperties.ALT_ACTION, null)
+                        .with(ModalDialogProperties.MESSAGE, messageText)
+                        .with(ModalDialogProperties.TITLE, delegate.getTitleText())
+                        .with(ModalDialogProperties.TITLE_ICON, icon)
+                        .with(ModalDialogProperties.CONTROLLER, controller)
+                        .with(ModalDialogProperties.FILTER_TOUCH_FOR_SECURITY, true)
+                        .with(ModalDialogProperties.CONTENT_DESCRIPTION, delegate.getMessageText())
+                        .build();
+
+        PropertyModel[] items = new PropertyModel[]{
+                new PropertyModel
+                        .Builder(TouchlessDialogProperties.DialogListItemProperties.ALL_KEYS)
+                        .with(TouchlessDialogProperties.DialogListItemProperties.TEXT,
+                                delegate.getPrimaryButtonText())
+                        .with(TouchlessDialogProperties.DialogListItemProperties.CLICK_LISTENER,
+                                (v) -> controller.onClick(model,
+                                        ModalDialogProperties.ButtonType.POSITIVE))
+                        .with(TouchlessDialogProperties.DialogListItemProperties.ICON,
+                                ApiCompatibilityUtils.getDrawable(
+                                        resources, R.drawable.ic_check_circle))
+                        .build(),
+                new PropertyModel
+                        .Builder(TouchlessDialogProperties.DialogListItemProperties.ALL_KEYS)
+                        .with(TouchlessDialogProperties.DialogListItemProperties.TEXT,
+                                delegate.getSecondaryButtonText())
+                        .with(TouchlessDialogProperties.DialogListItemProperties.CLICK_LISTENER,
+                                (v) -> controller.onClick(model,
+                                        ModalDialogProperties.ButtonType.NEGATIVE))
+                        .with(TouchlessDialogProperties.DialogListItemProperties.ICON,
+                                ApiCompatibilityUtils.getDrawable(
+                                        resources, R.drawable.ic_cancel_circle))
+                        .build()};
+
+        model.set(TouchlessDialogProperties.LIST_MODELS, items);
+        model.set(TouchlessDialogProperties.CANCEL_ACTION,
+                (v) -> delegate.getTab().getActivity().getModalDialogManager().dismissDialog(model,
+                        DialogDismissalCause.NAVIGATE_BACK_OR_TOUCH_OUTSIDE));
+        return model;
+    }
+}
diff --git a/chrome/android/touchless/touchless_java_sources.gni b/chrome/android/touchless/touchless_java_sources.gni
index b377af8..efa2219 100644
--- a/chrome/android/touchless/touchless_java_sources.gni
+++ b/chrome/android/touchless/touchless_java_sources.gni
@@ -49,6 +49,7 @@
   "touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessZoomHelper.java",
   "touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogPresenter.java",
   "touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogProperties.java",
+  "touchless/java/src/org/chromium/chrome/browser/touchless/permissions/TouchlessPermissionDialogModel.java",
   "touchless/java/src/org/chromium/chrome/browser/touchless/snackbar/BlackHoleSnackbarManager.java",
   "touchless/java/src/org/chromium/chrome/browser/touchless/ui/iph/KeyFunctionsIPHCoordinator.java",
   "touchless/java/src/org/chromium/chrome/browser/touchless/ui/iph/KeyFunctionsIPHMediator.java",
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index c74f016..ca1edc9 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -1727,12 +1727,21 @@
   <message name="IDS_REMOVABLE_DEVICE_NAVIGATION_MESSAGE" desc="Text of notification message to navigate users to the Files app. If user clicks a button in the notification, the Files app opens.">
     Explore the device's content in the Files app.
   </message>
+  <message name="IDS_REMOVABLE_DEVICE_ALLOW_PLAY_STORE_ACCESS_MESSAGE" desc="Text of notification message to navigate users to the ARC settings page. If user clicks a button in the notification, the page opens.">
+    Allow Play Store applications to access this device via Settings.
+  </message>
+  <message name="IDS_REMOVABLE_DEVICE_PLAY_STORE_APPS_HAVE_ACCESS_MESSAGE" desc="Text of notification message to navigate users to the ARC settings page. If user clicks a button in the notification, the page opens.">
+    Play Store applications have access to this device.
+  </message>
   <message name="IDS_REMOVABLE_DEVICE_NAVIGATION_MESSAGE_READONLY_POLICY" desc="Text of notification message to navigate users to the Files app when attaching a removable media, but the administrator's poilcy forbids writing data to them. If user clicks a button in the notification, the Files app opens.">
     Explore the device's content in the Files app. The content is restricted by an admin and can’t be modified.
   </message>
   <message name="IDS_REMOVABLE_DEVICE_NAVIGATION_BUTTON_LABEL" desc="Text of a button label in a notification to navigate users to the Files app. If user clicks the button, the Files app opens.">
     Open Files app
   </message>
+  <message name="IDS_REMOVABLE_DEVICE_OPEN_SETTTINGS_BUTTON_LABEL" desc="Text of a button label in a notification to navigate users to the ARC settings page. If user clicks the button, the page opens.">
+    Open settings
+  </message>
   <message name="IDS_REMOVABLE_DEVICE_IMPORT_MESSAGE" desc="Text of notification message to start the media import flow. If user clicks a button in the notification, the import flow begins.">
     Back up media from the device using the Files app.
   </message>
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 6f6a593..b9ce254 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -4918,9 +4918,15 @@
       <message name="IDS_OMNIBOX_WHY_THIS_SUGGESTION" desc="The title of a menu entry and bubble that explains why this suggestion appears in the omnibox popup.">
         Why this suggestion?
       </message>
-      <message name="IDS_OMNIBOX_REMOVE_SUGGESTION" desc="The title of a menu entry and bubble used to delete omnibox suggestions.">
+      <message name="IDS_OMNIBOX_REMOVE_SUGGESTION" desc="The text of a context menu entry to delete an omnibox suggestion.">
         Remove suggestion
       </message>
+      <message name="IDS_OMNIBOX_REMOVE_SUGGESTION_BUBBLE_TITLE" desc="The title of a bubble to delete an omnibox suggestion.">
+        Remove suggestion?
+      </message>
+      <message name="IDS_OMNIBOX_REMOVE_SUGGESTION_BUBBLE_DESCRIPTION" desc="The description of a bubble to delete an omnibox suggestion.">
+        This page will also be removed from your history and <ph name="SEARCH_ENGINE">$1<ex>Google</ex></ph> activity.
+      </message>
 
       <!-- NTP -->
       <message name="IDS_GOOGLE_SEARCH_BOX_EMPTY_HINT" desc="The text displayed in the fakebox (on the New Tab page) when it is empty, and Google is the default search engine.">
@@ -5236,6 +5242,9 @@
         <message name="IDS_ACCESSIBILITY_EVENTS_INFOBAR_TEXT" desc="Text requesting permission for a site to listen to accessibility events, for example if the user has a screen reader or braille device enabled">
           <ph name="URL">$1<ex>maps.google.com</ex></ph> wants to respond to accessibility events
         </message>
+        <message name="IDS_ACCESSIBILITY_EVENTS_PERMISSION_TITLE" desc="Title text for dialog requesting permission for a site to listen to accessibility events, for example if the user has a screen reader or braille device enabled">
+          Accessibility Events
+        </message>
       </if>
 
       <!-- Clipboard permission -->
@@ -5246,6 +5255,9 @@
         <message name="IDS_CLIPBOARD_INFOBAR_TEXT" desc="Text requesting permission for a site to read data from the system clipboard">
           <ph name="URL">$1<ex>maps.google.com</ex></ph> wants to see text and images copied to the clipboard
         </message>
+        <message name="IDS_CLIPBOARD_PERMISSION_TITLE" desc="Title text for dialog requesting permission for a site to read data from the system clipboard">
+          Clipboard
+        </message>
       </if>
       <message name="IDS_ALLOWED_CLIPBOARD_TITLE" desc="Title of the info bubble shown when a site has been allowed access to read the contents of the system clipboard.">
         Clipboard read access allowed
@@ -7113,6 +7125,11 @@
       <message name="IDS_MULTI_DOWNLOAD_WARNING" desc="Warning invoked if multiple downloads are attempted without user interaction.">
         <ph name="ORIGIN">$1<ex>https://backgroundfetch.com</ex></ph> wants to download multiple files
       </message>
+      <if expr="is_android">
+        <message name="IDS_MULTI_DOWNLOAD_WARNING_TITLE" desc="Title text for warning dialog invoked if multiple downloads are attempted without user interaction.">
+          Download
+        </message>
+      </if>
       <message name="IDS_MULTI_DOWNLOAD_PERMISSION_FRAGMENT" desc="Permission request shown if multiple downloads are attempted without user interaction and there are multiple permissions requested. Follows a prompt: 'This site would like to:'">
         Download multiple files
       </message>
@@ -7557,6 +7574,11 @@
       <message name="IDS_NOTIFICATIONS_INFOBAR_TEXT" desc="Text requesting permission for Web Notifications.">
         <ph name="site">$1<ex>mail.google.com</ex></ph> wants to send you notifications
       </message>
+      <if expr="is_android">
+        <message name="IDS_NOTIFICATIONS_PERMISSION_TITLE" desc="Title text for dialog requesting permission for Web Notifications.">
+          Notifications
+        </message>
+      </if>
       <message name="IDS_NOTIFICATION_PERMISSIONS_FRAGMENT" desc="Permission sentence fragment to show following the prompt 'This site wants to:' in a permissions request">
         Show notifications
       </message>
@@ -7986,6 +8008,9 @@
         <message name="IDS_GEOLOCATION_INFOBAR_TEXT" desc="Mobile: Text requesting permission for a site to access the user's physical location">
           <ph name="URL">$1<ex>maps.google.com</ex></ph> wants to use your device's location
         </message>
+        <message name="IDS_GEOLOCATION_PERMISSION_TITLE" desc="Mobile: Title text for dialog requesting permission for a site to access the user's physical location">
+          Location
+        </message>
       </if>
       <message name="IDS_GEOLOCATION_INFOBAR_PERMISSION_FRAGMENT" desc="Permission fragment used in the permission bubble, after 'This site wants to:' asking for permission to access the user's physical location.">
         Know your location
@@ -8016,6 +8041,11 @@
       <message name="IDS_MIDI_SYSEX_INFOBAR_TEXT" desc="Text requesting permission for a site to access MIDI devices with system exclusive messages.">
         <ph name="URL">$1<ex>www.google.com</ex></ph> wants to get full control of your MIDI devices
       </message>
+      <if expr="is_android">
+        <message name="IDS_MIDI_SYSEX_PERMISSION_TITLE" desc="Title text for dialog requesting permission for a site to access MIDI devices with system exclusive messages.">
+          MIDI Devices
+        </message>
+      </if>
       <message name="IDS_MIDI_SYSEX_PERMISSION_FRAGMENT" desc="Permission asked in the permission bubble when a URL wants to access MIDI devices with system exclusive messages, along with other permissions requests. Preceded by the prompt 'This site would like to:'">
         Use your MIDI devices
       </message>
@@ -8133,6 +8163,9 @@
         <message name="IDS_PROTECTED_MEDIA_IDENTIFIER_PER_DEVICE_PROVISIONING_INFOBAR_TEXT" desc="Text requesting permission for a site to access protected media identifier. It shows the origin of the URL.">
           <ph name="URL">$1<ex>https://www.youtube.com</ex></ph> wants to play protected content. Your device's identity will be verified by Google and may be accessed by this site.
         </message>
+        <message name="IDS_PROTECTED_MEDIA_IDENTIFIER_PERMISSION_TITLE" desc="Title text for dialog requesting permission for a site to access protected media identifier.">
+          Protected Media Identifier
+        </message>
       </if>
       <message name="IDS_MANAGE_PASSWORDS_CONFIRM_GENERATED_TEXT" desc="A message that the browser shows after saving a password it has autogenerated for the user. This message appears in a bubble and contains a link to all the user's saved autogenerated passwords. The link text is a separate string in the translation console and appears here as placeholder text.">
         View and manage saved passwords in your <ph name="SAVED_PASSWORDS_STORE">$1<ex>Google Account</ex></ph>
@@ -8363,6 +8396,17 @@
       <message name="IDS_MEDIA_CAPTURE_VIDEO_ONLY_PERMISSION_FRAGMENT" desc="Permission fragment shown in the permissions bubble when a web page requests access to the computer's camera.">
         Use your camera
       </message>
+      <if expr="is_android">
+        <message name="IDS_MEDIA_CAPTURE_AUDIO_ONLY_PERMISSION_TITLE" desc="Title text for dialog requesting permission for a site to access the device's microphone.">
+          Microphone
+        </message>
+        <message name="IDS_MEDIA_CAPTURE_VIDEO_ONLY_PERMISSION_TITLE" desc="Title text for dialog requesting permission for a site to access the device's camera.">
+          Camera
+        </message>
+        <message name="IDS_MEDIA_CAPTURE_AUDIO_AND_VIDEO_PERMISSION_TITLE" desc="Title text for dialog requesting permission for a site to access the device's microphone and camera.">
+          Media
+        </message>
+      </if>
 
       <!-- Sensor messages -->
       <message name="IDS_SENSORS_ALLOWED_TOOLTIP" desc="Location bar icon tooltip text when a page is allowed to use device's sensors.">
@@ -8416,6 +8460,9 @@
         <message name="IDS_REQUEST_LARGE_QUOTA_INFOBAR_TEXT" desc="Mobile: For Android device. Text requesting permission for a site to use a new (larger) quota to persistently store large data on the user's device (e.g. for persistent-type filesystem).">
           <ph name="URL">$1<ex>html5rocks.com</ex></ph> wants to permanently store large data on your device
         </message>
+        <message name="IDS_REQUEST_QUOTA_PERMISSION_TITLE" desc="Mobile: For Android device. Title text for dialog requesting permission for a site to use a new (larger) quota to persistently store large data on the user's device (e.g. for persistent-type filesystem).">
+          Storage
+        </message>
       </if>
       <if expr="not is_android">
         <message name="IDS_REQUEST_QUOTA_INFOBAR_TEXT" desc="Text requesting permission for a site to use a new (larger) quota to persistently store data on the user's local computer (e.g. for persistent-type filesystem).">
diff --git a/chrome/app/generated_resources_grd/IDS_OMNIBOX_REMOVE_SUGGESTION_BUBBLE_DESCRIPTION.png.sha1 b/chrome/app/generated_resources_grd/IDS_OMNIBOX_REMOVE_SUGGESTION_BUBBLE_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..88c435d
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_OMNIBOX_REMOVE_SUGGESTION_BUBBLE_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+4f7bd70a3a0304338ef3af278686c8cd75162ee9
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_OMNIBOX_REMOVE_SUGGESTION_BUBBLE_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_OMNIBOX_REMOVE_SUGGESTION_BUBBLE_TITLE.png.sha1
new file mode 100644
index 0000000..88c435d
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_OMNIBOX_REMOVE_SUGGESTION_BUBBLE_TITLE.png.sha1
@@ -0,0 +1 @@
+4f7bd70a3a0304338ef3af278686c8cd75162ee9
\ No newline at end of file
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index a99778cd..95fe0ae 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -757,12 +757,6 @@
      base::size(kOmniboxUIVerticalMargin10px), nullptr},
 };
 
-const FeatureEntry::Choice kAsyncImageDecodingChoices[] = {
-    {flags_ui::kGenericExperimentChoiceDefault, "", ""},
-    {flags_ui::kGenericExperimentChoiceDisabled,
-     cc::switches::kDisableCheckerImaging, ""},
-};
-
 const FeatureEntry::FeatureParam kMarkHttpAsDangerous[] = {
     {security_state::features::kMarkHttpAsFeatureParameterName,
      security_state::features::kMarkHttpAsParameterDangerous}};
@@ -2628,10 +2622,6 @@
      FEATURE_VALUE_TYPE(chrome::android::kOmniboxSpareRenderer)},
 #endif
 
-    {"enable-async-image-decoding", flag_descriptions::kAsyncImageDecodingName,
-     flag_descriptions::kAsyncImageDecodingDescription, kOsAll,
-     MULTI_VALUE_TYPE(kAsyncImageDecodingChoices)},
-
 #if defined(OS_CHROMEOS)
     {"double-tap-to-zoom-in-tablet-mode",
      flag_descriptions::kDoubleTapToZoomInTabletModeName,
@@ -3827,6 +3817,12 @@
      flag_descriptions::kFormControlsRefreshDescription, kOsAll,
      FEATURE_VALUE_TYPE(features::kFormControlsRefresh)},
 
+#if defined(OS_CHROMEOS)
+    {"auto-screen-brightness", flag_descriptions::kAutoScreenBrightnessName,
+     flag_descriptions::kAutoScreenBrightnessDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(chromeos::features::kAutoScreenBrightness)},
+#endif  // defined(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/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index be12c24..f724861 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -87,6 +87,7 @@
     &kAllowRemoteContextForNotifications,
     &kAndroidNightMode,
     &kAndroidNightModeCCT,
+    &kAndroidNightModeForQ,
     &kAndroidPayIntegrationV1,
     &kAndroidPayIntegrationV2,
     &kAndroidPaymentApps,
@@ -234,6 +235,9 @@
 const base::Feature kAndroidNightModeCCT{"AndroidNightModeCCT",
                                          base::FEATURE_ENABLED_BY_DEFAULT};
 
+const base::Feature kAndroidNightModeForQ{"AndroidNightModeForQ",
+                                          base::FEATURE_ENABLED_BY_DEFAULT};
+
 // TODO(rouslan): Remove this.
 const base::Feature kAndroidPayIntegrationV1{"AndroidPayIntegrationV1",
                                              base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/chrome/browser/android/chrome_feature_list.h b/chrome/browser/android/chrome_feature_list.h
index c249a17..edd9ffe 100644
--- a/chrome/browser/android/chrome_feature_list.h
+++ b/chrome/browser/android/chrome_feature_list.h
@@ -17,6 +17,7 @@
 extern const base::Feature kAllowRemoteContextForNotifications;
 extern const base::Feature kAndroidNightMode;
 extern const base::Feature kAndroidNightModeCCT;
+extern const base::Feature kAndroidNightModeForQ;
 extern const base::Feature kAndroidPayIntegrationV1;
 extern const base::Feature kAndroidPayIntegrationV2;
 extern const base::Feature kAndroidPaymentApps;
diff --git a/chrome/browser/android/compositor/layer/content_layer.cc b/chrome/browser/android/compositor/layer/content_layer.cc
index 78cc208..798f132 100644
--- a/chrome/browser/android/compositor/layer/content_layer.cc
+++ b/chrome/browser/android/compositor/layer/content_layer.cc
@@ -68,9 +68,6 @@
     live_layer->SetHideLayerAndSubtree(!can_use_live_layer);
   bool live_layer_draws = GetDrawsContentLeaf(live_layer);
 
-  scoped_refptr<ThumbnailLayer> static_layer =
-      tab_content_manager_->GetOrCreateStaticLayer(id, !live_layer_draws);
-
   float content_opacity =
       should_override_content_alpha ? content_alpha_override : 1.0f;
   float static_opacity =
@@ -89,22 +86,27 @@
 
     layer_->AddChild(live_layer);
   }
-  if (static_layer.get()) {
-    static_layer->layer()->SetIsDrawable(true);
-    if (should_clip)
-      static_layer->Clip(clip);
-    else
-      static_layer->ClearClip();
-    SetOpacityOnLeaf(static_layer->layer(), static_opacity);
 
-    cc::FilterOperations static_filter_operations;
-    if (saturation < 1.0f) {
-      static_filter_operations.Append(
-          cc::FilterOperation::CreateSaturateFilter(saturation));
+  if (static_opacity > 0) {
+    scoped_refptr<ThumbnailLayer> static_layer =
+        tab_content_manager_->GetOrCreateStaticLayer(id, !live_layer_draws);
+    if (static_layer.get()) {
+      static_layer->layer()->SetIsDrawable(true);
+      if (should_clip)
+        static_layer->Clip(clip);
+      else
+        static_layer->ClearClip();
+      SetOpacityOnLeaf(static_layer->layer(), static_opacity);
+
+      cc::FilterOperations static_filter_operations;
+      if (saturation < 1.0f) {
+        static_filter_operations.Append(
+            cc::FilterOperation::CreateSaturateFilter(saturation));
+      }
+      static_layer->layer()->SetFilters(static_filter_operations);
+
+      layer_->AddChild(static_layer->layer());
     }
-    static_layer->layer()->SetFilters(static_filter_operations);
-
-    layer_->AddChild(static_layer->layer());
   }
 }
 
diff --git a/chrome/browser/android/download/download_manager_service_unittest.cc b/chrome/browser/android/download/download_manager_service_unittest.cc
index a7315bc..8d2f26b 100644
--- a/chrome/browser/android/download/download_manager_service_unittest.cc
+++ b/chrome/browser/android/download/download_manager_service_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
 #include "base/bind.h"
+#include "base/bind_helpers.h"
 #include "base/macros.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
@@ -55,6 +56,7 @@
  public:
   DownloadManagerServiceTest()
       : service_(new MockDownloadManagerService()),
+        coordinator_(base::NullCallback()),
         finished_(false),
         success_(false) {}
 
diff --git a/chrome/browser/chrome_quota_permission_context.cc b/chrome/browser/chrome_quota_permission_context.cc
index 79f9fcea..58a99fe 100644
--- a/chrome/browser/chrome_quota_permission_context.cc
+++ b/chrome/browser/chrome_quota_permission_context.cc
@@ -56,6 +56,7 @@
   // PermissionRequest:
   IconId GetIconId() const override;
 #if defined(OS_ANDROID)
+  base::string16 GetTitleText() const override;
   base::string16 GetMessageText() const override;
 #endif
   base::string16 GetMessageTextFragment() const override;
@@ -98,6 +99,10 @@
 }
 
 #if defined(OS_ANDROID)
+base::string16 QuotaPermissionRequest::GetTitleText() const {
+  return l10n_util::GetStringUTF16(IDS_REQUEST_QUOTA_PERMISSION_TITLE);
+}
+
 base::string16 QuotaPermissionRequest::GetMessageText() const {
   // If the site requested larger quota than this threshold, show a different
   // message to the user.
diff --git a/chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service_unittest.cc b/chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service_unittest.cc
index b27b295..8656598 100644
--- a/chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service_unittest.cc
+++ b/chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service_unittest.cc
@@ -189,7 +189,6 @@
 
   void SetUp() override {
     ChromeAshTestBase::SetUp();
-    SetRunningOutsideAsh();
     ui::IMEBridge::Initialize();
     input_method_manager_ = new TestInputMethodManager();
     chromeos::input_method::InputMethodManager::Initialize(
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
index dea49d8..08204ad 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
@@ -38,6 +38,7 @@
 #include "chrome/browser/chromeos/login/lock/screen_locker.h"
 #include "chrome/browser/chromeos/printing/cups_printers_manager.h"
 #include "chrome/browser/chromeos/system/input_device_settings.h"
+#include "chrome/browser/extensions/extension_action.h"
 #include "chrome/browser/extensions/extension_action_manager.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
@@ -53,6 +54,7 @@
 #include "chrome/browser/ui/views/crostini/crostini_uninstaller_view.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/extensions/api/autotest_private.h"
+#include "chrome/common/extensions/api/extension_action/action_info.h"
 #include "chrome/common/pref_names.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
 #include "chromeos/dbus/session_manager/session_manager_client.h"
@@ -379,9 +381,11 @@
     extension_value->SetBoolean("isEnabled", service->IsExtensionEnabled(id));
     extension_value->SetBoolean(
         "allowedInIncognito", util::IsIncognitoEnabled(id, browser_context()));
+    const ExtensionAction* action =
+        extension_action_manager->GetExtensionAction(*extension);
     extension_value->SetBoolean(
         "hasPageAction",
-        extension_action_manager->GetPageAction(*extension) != NULL);
+        action && action->action_type() == ActionInfo::TYPE_PAGE);
 
     extensions_values->Append(std::move(extension_value));
   }
diff --git a/chrome/browser/chromeos/extensions/file_manager/event_router.cc b/chrome/browser/chromeos/extensions/file_manager/event_router.cc
index 1aeeeb10..372d2e1 100644
--- a/chrome/browser/chromeos/extensions/file_manager/event_router.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/event_router.cc
@@ -40,6 +40,7 @@
 #include "chromeos/dbus/power/power_manager_client.h"
 #include "chromeos/disks/disk.h"
 #include "chromeos/login/login_state/login_state.h"
+#include "components/arc/arc_prefs.h"
 #include "components/arc/intent_helper/arc_intent_helper_bridge.h"
 #include "components/drive/chromeos/file_system_interface.h"
 #include "components/drive/drive_pref_names.h"
@@ -576,6 +577,9 @@
       crostini::prefs::kCrostiniEnabled,
       base::BindRepeating(&EventRouter::OnCrostiniEnabledChanged,
                           weak_factory_.GetWeakPtr()));
+  pref_change_registrar_->Add(arc::prefs::kArcEnabled, callback);
+  pref_change_registrar_->Add(arc::prefs::kArcHasAccessToRemovableMedia,
+                              callback);
 
   chromeos::system::TimezoneSettings::GetInstance()->AddObserver(this);
 
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
index 4b41dab..084f92d 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_misc.cc
@@ -57,6 +57,7 @@
 #include "chromeos/constants/chromeos_features.h"
 #include "chromeos/settings/timezone_settings.h"
 #include "components/account_id/account_id.h"
+#include "components/arc/arc_prefs.h"
 #include "components/drive/drive_pref_names.h"
 #include "components/drive/event_logger.h"
 #include "components/prefs/pref_service.h"
@@ -232,6 +233,9 @@
   result.timezone =
       base::UTF16ToUTF8(chromeos::system::TimezoneSettings::GetInstance()
                             ->GetCurrentTimezoneID());
+  result.arc_enabled = service->GetBoolean(arc::prefs::kArcEnabled);
+  result.arc_removable_media_access_enabled =
+      service->GetBoolean(arc::prefs::kArcHasAccessToRemovableMedia);
 
   return RespondNow(OneArgument(result.ToValue()));
 }
diff --git a/chrome/browser/chromeos/file_manager/file_manager_string_util.cc b/chrome/browser/chromeos/file_manager/file_manager_string_util.cc
index 87fb383..e52b52e 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_string_util.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_string_util.cc
@@ -699,12 +699,18 @@
   SET_STRING("REFRESH_BUTTON_LABEL", IDS_FILE_BROWSER_REFRESH_BUTTON_LABEL);
   SET_STRING("REMOVABLE_DEVICE_DETECTION_TITLE",
              IDS_REMOVABLE_DEVICE_DETECTION_TITLE);
+  SET_STRING("REMOVABLE_DEVICE_ALLOW_PLAY_STORE_ACCESS_MESSAGE",
+             IDS_REMOVABLE_DEVICE_ALLOW_PLAY_STORE_ACCESS_MESSAGE);
+  SET_STRING("REMOVABLE_DEVICE_PLAY_STORE_APPS_HAVE_ACCESS_MESSAGE",
+             IDS_REMOVABLE_DEVICE_PLAY_STORE_APPS_HAVE_ACCESS_MESSAGE);
   SET_STRING("REMOVABLE_DEVICE_IMPORT_BUTTON_LABEL",
              IDS_REMOVABLE_DEVICE_IMPORT_BUTTON_LABEL);
   SET_STRING("REMOVABLE_DEVICE_IMPORT_MESSAGE",
              IDS_REMOVABLE_DEVICE_IMPORT_MESSAGE);
   SET_STRING("REMOVABLE_DEVICE_NAVIGATION_BUTTON_LABEL",
              IDS_REMOVABLE_DEVICE_NAVIGATION_BUTTON_LABEL);
+  SET_STRING("REMOVABLE_DEVICE_OPEN_SETTTINGS_BUTTON_LABEL",
+             IDS_REMOVABLE_DEVICE_OPEN_SETTTINGS_BUTTON_LABEL);
   SET_STRING("REMOVABLE_DEVICE_NAVIGATION_MESSAGE",
              IDS_REMOVABLE_DEVICE_NAVIGATION_MESSAGE);
   SET_STRING("REMOVABLE_DEVICE_NAVIGATION_MESSAGE_READONLY_POLICY",
diff --git a/chrome/browser/chromeos/login/error_screen_browsertest.cc b/chrome/browser/chromeos/login/error_screen_browsertest.cc
index 39d6ea1..7971eab 100644
--- a/chrome/browser/chromeos/login/error_screen_browsertest.cc
+++ b/chrome/browser/chromeos/login/error_screen_browsertest.cc
@@ -93,7 +93,7 @@
         "/device/stub_wifi_device", shill::kTypeWifi, "stub_wifi_device");
     network_helper_->service_test()->AddService(
         kWifiServiceName, "wifi_guid", kWifiNetworkName, shill::kTypeWifi,
-        shill::kStateDisconnect, true);
+        shill::kStateIdle, true);
     network_helper_->service_test()->SetServiceProperty(
         kWifiServiceName, shill::kConnectableProperty, base::Value(true));
     network_helper_->profile_test()->AddService(
@@ -203,11 +203,12 @@
 
   OobeScreenWaiter(ErrorScreenView::kScreenId).Wait();
   test::OobeJS().ExpectVisiblePath({"error-guest-signin-link"});
-  test::OobeJS().ClickOnPath({"error-guest-signin-link"});
 
   base::RunLoop restart_job_waiter;
   FakeSessionManagerClient::Get()->set_restart_job_callback(
       restart_job_waiter.QuitClosure());
+
+  test::OobeJS().ClickOnPath({"error-guest-signin-link"});
   restart_job_waiter.Run();
 }
 
diff --git a/chrome/browser/chromeos/login/users/multi_profile_user_controller_unittest.cc b/chrome/browser/chromeos/login/users/multi_profile_user_controller_unittest.cc
index 95f07879..372c4382 100644
--- a/chrome/browser/chromeos/login/users/multi_profile_user_controller_unittest.cc
+++ b/chrome/browser/chromeos/login/users/multi_profile_user_controller_unittest.cc
@@ -134,6 +134,7 @@
   int VerifyInternal(net::X509Certificate* cert,
                      const std::string& hostname,
                      const std::string& ocsp_response,
+                     const std::string& sct_list,
                      int flags,
                      net::CRLSet* crl_set,
                      const net::CertificateList& additional_trust_anchors,
diff --git a/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc b/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc
index 60dc1b2..c690245 100644
--- a/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc
+++ b/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc
@@ -1963,6 +1963,7 @@
   int expected_state;
   const char* address;
   const char* gateway;
+  bool visible;
 };
 
 // List of fake networks - primarily used to make sure that signal strength
@@ -1971,32 +1972,31 @@
 // network, so we use 1 below.
 static const FakeNetworkState kFakeNetworks[] = {
     {"offline", "/device/wifi", shill::kTypeWifi, 35, -85, shill::kStateOffline,
-     em::NetworkState::OFFLINE, "", ""},
+     em::NetworkState::OFFLINE, "", "", true},
     {"ethernet", "/device/ethernet", shill::kTypeEthernet, 0, 0,
-     shill::kStateOnline, em::NetworkState::ONLINE, "192.168.0.1", "8.8.8.8"},
+     shill::kStateOnline, em::NetworkState::ONLINE, "192.168.0.1", "8.8.8.8",
+     true},
     {"wifi", "/device/wifi", shill::kTypeWifi, 23, -97,
-     shill::kStateNoConnectivity, em::NetworkState::PORTAL, "", ""},
+     shill::kStateNoConnectivity, em::NetworkState::PORTAL, "", "", true},
     {"idle", "/device/cellular1", shill::kTypeCellular, 0, 0, shill::kStateIdle,
-     em::NetworkState::IDLE, "", ""},
-    {"carrier", "/device/cellular1", shill::kTypeCellular, 0, 0,
-     shill::kStateCarrier, em::NetworkState::CARRIER, "", ""},
+     em::NetworkState::IDLE, "", "", true},
+    {"not_visible", "/device/wifi", shill::kTypeWifi, 0, 0, shill::kStateIdle,
+     em::NetworkState::IDLE, "", "", false},
     {"association", "/device/cellular1", shill::kTypeCellular, 0, 0,
-     shill::kStateAssociation, em::NetworkState::ASSOCIATION, "", ""},
+     shill::kStateAssociation, em::NetworkState::ASSOCIATION, "", "", true},
     {"config", "/device/cellular1", shill::kTypeCellular, 0, 0,
-     shill::kStateConfiguration, em::NetworkState::CONFIGURATION, "", ""},
+     shill::kStateConfiguration, em::NetworkState::CONFIGURATION, "", "", true},
     // Set signal strength for this network to -20, but expected strength to 0
     // to test that we only report signal_strength for wifi connections.
     {"ready", "/device/cellular1", shill::kTypeCellular, -20, 0,
-     shill::kStateReady, em::NetworkState::READY, "", ""},
-    {"disconnect", "/device/wifi", shill::kTypeWifi, 1, -119,
-     shill::kStateDisconnect, em::NetworkState::DISCONNECT, "", ""},
+     shill::kStateReady, em::NetworkState::READY, "", "", true},
     {"failure", "/device/wifi", shill::kTypeWifi, 1, -119, shill::kStateFailure,
-     em::NetworkState::FAILURE, "", ""},
+     em::NetworkState::FAILURE, "", "", true},
     {"activation-failure", "/device/cellular1", shill::kTypeCellular, 0, 0,
      shill::kStateActivationFailure, em::NetworkState::ACTIVATION_FAILURE, "",
-     ""},
-    {"unknown", "", shill::kTypeWifi, 1, -119, "unknown",
-     em::NetworkState::UNKNOWN, "", ""},
+     "", true},
+    {"unknown", "", shill::kTypeWifi, 1, -119, shill::kStateIdle,
+     em::NetworkState::IDLE, "", "", true},
 };
 
 static const FakeNetworkState kUnconfiguredNetwork = {
@@ -2279,8 +2279,7 @@
     // Now add services for every fake network.
     for (const FakeNetworkState& fake_network : kFakeNetworks) {
       // Shill forces non-visible networks to report a disconnected state.
-      bool is_visible =
-          fake_network.connection_status != shill::kStateDisconnect;
+      bool is_visible = fake_network.connection_status != shill::kStateIdle;
       service_client->AddService(fake_network.name /* service_path */,
                                  fake_network.name /* guid */,
                                  fake_network.name, fake_network.type,
diff --git a/chrome/browser/chromeos/policy/user_network_configuration_updater_factory_browsertest.cc b/chrome/browser/chromeos/policy/user_network_configuration_updater_factory_browsertest.cc
index 709d632..1827b13 100644
--- a/chrome/browser/chromeos/policy/user_network_configuration_updater_factory_browsertest.cc
+++ b/chrome/browser/chromeos/policy/user_network_configuration_updater_factory_browsertest.cc
@@ -262,8 +262,9 @@
   int result = net::OK;
   content::BrowserContext::GetDefaultStoragePartition(profile)
       ->GetNetworkContext()
-      ->VerifyCertificateForTesting(certificate, "127.0.0.1", std::string(),
-                                    &result);
+      ->VerifyCertificateForTesting(certificate, "127.0.0.1",
+                                    /*ocsp_response=*/std::string(),
+                                    /*sct_list=*/std::string(), &result);
   return result;
 }
 
diff --git a/chrome/browser/data_reduction_proxy/data_reduction_proxy_browsertest.cc b/chrome/browser/data_reduction_proxy/data_reduction_proxy_browsertest.cc
index cf425e4..e9eba24 100644
--- a/chrome/browser/data_reduction_proxy/data_reduction_proxy_browsertest.cc
+++ b/chrome/browser/data_reduction_proxy/data_reduction_proxy_browsertest.cc
@@ -307,6 +307,8 @@
 
   size_t count_proxy_server_requests_received_ = 0u;
 
+  base::test::ScopedFeatureList scoped_feature_list_;
+
  private:
   std::unique_ptr<net::test_server::HttpResponse> GetConfigResponse(
       const net::test_server::HttpRequest& request) {
@@ -320,7 +322,6 @@
 
   ClientConfig config_;
   std::unique_ptr<base::RunLoop> config_run_loop_;
-  base::test::ScopedFeatureList scoped_feature_list_;
   base::test::ScopedFeatureList param_feature_list_;
   net::EmbeddedTestServer secure_proxy_check_server_;
   net::EmbeddedTestServer config_server_;
@@ -695,6 +696,49 @@
   EXPECT_LE(1u, count_proxy_server_requests_received_);
 }
 
+class DataReductionProxyExpFeatureBrowsertest
+    : public DataReductionProxyBrowsertest {
+ public:
+  void SetUp() override {
+    std::map<std::string, std::string> field_trial_params;
+    field_trial_params[data_reduction_proxy::params::
+                           GetDataSaverServerExperimentsOptionName()] =
+        experiment_name;
+
+    scoped_feature_list_.InitWithFeaturesAndParameters(
+        {{data_reduction_proxy::features::kDataReductionProxyServerExperiments,
+          {field_trial_params}},
+         {data_reduction_proxy::features::
+              kDataReductionProxyEnabledWithNetworkService,
+          {}}},
+        {});
+
+    InProcessBrowserTest::SetUp();
+  }
+
+  const std::string experiment_name = "foo_feature_experiment";
+};
+
+IN_PROC_BROWSER_TEST_F(DataReductionProxyExpFeatureBrowsertest,
+                       ChromeProxyExpHeaderSet) {
+  expect_exp_value_in_request_header_ = experiment_name;
+
+  net::EmbeddedTestServer proxy_server;
+  proxy_server.RegisterRequestMonitor(base::BindRepeating(
+      &DataReductionProxyBrowsertest::MonitorAndVerifyRequestsToProxyServer,
+      base::Unretained(this)));
+  proxy_server.RegisterRequestHandler(
+      base::BindRepeating(&BasicResponse, kPrimaryResponse));
+  ASSERT_TRUE(proxy_server.Start());
+  SetConfig(CreateConfigForServer(proxy_server));
+  // A network change forces the config to be fetched.
+  SimulateNetworkChange(network::mojom::ConnectionType::CONNECTION_3G);
+  WaitForConfig();
+
+  ui_test_utils::NavigateToURL(browser(), GURL("http://does.not.resolve/foo"));
+  EXPECT_LE(1u, count_proxy_server_requests_received_);
+}
+
 class DataReductionProxyBrowsertestWithNetworkService
     : public DataReductionProxyBrowsertest {
  public:
diff --git a/chrome/browser/download/download_permission_request.cc b/chrome/browser/download/download_permission_request.cc
index dda8f22..502baf6 100644
--- a/chrome/browser/download/download_permission_request.cc
+++ b/chrome/browser/download/download_permission_request.cc
@@ -35,6 +35,10 @@
 }
 
 #if defined(OS_ANDROID)
+base::string16 DownloadPermissionRequest::GetTitleText() const {
+  return l10n_util::GetStringUTF16(IDS_MULTI_DOWNLOAD_WARNING_TITLE);
+}
+
 base::string16 DownloadPermissionRequest::GetMessageText() const {
   return l10n_util::GetStringFUTF16(
       IDS_MULTI_DOWNLOAD_WARNING,
diff --git a/chrome/browser/download/download_permission_request.h b/chrome/browser/download/download_permission_request.h
index 805e9c2..f44e2d1 100644
--- a/chrome/browser/download/download_permission_request.h
+++ b/chrome/browser/download/download_permission_request.h
@@ -24,6 +24,7 @@
   // PermissionRequest:
   IconId GetIconId() const override;
 #if defined(OS_ANDROID)
+  base::string16 GetTitleText() const override;
   base::string16 GetMessageText() const override;
 #endif
   base::string16 GetMessageTextFragment() const override;
diff --git a/chrome/browser/download/simple_download_manager_coordinator_factory.cc b/chrome/browser/download/simple_download_manager_coordinator_factory.cc
index 3e8baf2..edd2f27 100644
--- a/chrome/browser/download/simple_download_manager_coordinator_factory.cc
+++ b/chrome/browser/download/simple_download_manager_coordinator_factory.cc
@@ -6,9 +6,30 @@
 
 #include "base/memory/singleton.h"
 #include "base/no_destructor.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/transition_manager/full_browser_transition_manager.h"
 #include "components/download/public/common/simple_download_manager_coordinator.h"
 #include "components/keyed_service/core/simple_dependency_manager.h"
 #include "components/keyed_service/core/simple_factory_key.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/download_manager.h"
+
+namespace {
+void DownloadUrl(std::unique_ptr<download::DownloadUrlParameters> parameters,
+                 Profile* profile) {
+  content::DownloadManager* manager =
+      content::BrowserContext::GetDownloadManager(profile);
+  DCHECK(manager);
+  manager->DownloadUrl(std::move(parameters));
+}
+
+void DownloadUrlWithDownloadManager(
+    SimpleFactoryKey* key,
+    std::unique_ptr<download::DownloadUrlParameters> parameters) {
+  FullBrowserTransitionManager::Get()->RegisterCallbackOnProfileCreation(
+      key, base::BindOnce(&DownloadUrl, std::move(parameters)));
+}
+}  // namespace
 
 // static
 SimpleDownloadManagerCoordinatorFactory*
@@ -35,7 +56,10 @@
 std::unique_ptr<KeyedService>
 SimpleDownloadManagerCoordinatorFactory::BuildServiceInstanceFor(
     SimpleFactoryKey* key) const {
-  return std::make_unique<download::SimpleDownloadManagerCoordinator>();
+  // Use unretained is safe as the key is associated with the callback.
+  return std::make_unique<download::SimpleDownloadManagerCoordinator>(
+      base::BindRepeating(&DownloadUrlWithDownloadManager,
+                          base::Unretained(key)));
 }
 
 SimpleFactoryKey* SimpleDownloadManagerCoordinatorFactory::GetKeyToUse(
diff --git a/chrome/browser/extensions/api/declarative_content/content_action_unittest.cc b/chrome/browser/extensions/api/declarative_content/content_action_unittest.cc
index 42e4936..02062bb 100644
--- a/chrome/browser/extensions/api/declarative_content/content_action_unittest.cc
+++ b/chrome/browser/extensions/api/declarative_content/content_action_unittest.cc
@@ -155,18 +155,15 @@
   EXPECT_TRUE(error.empty()) << error;
   ASSERT_TRUE(result.get());
 
-  ExtensionAction* action = nullptr;
   auto* action_manager = ExtensionActionManager::Get(env.profile());
-  const bool is_browser_action =
-      GetParam() == ExtensionBuilder::ActionType::BROWSER_ACTION;
-  if (is_browser_action) {
-    action = action_manager->GetBrowserAction(*extension);
-    ASSERT_TRUE(action);
+  ExtensionAction* action = action_manager->GetExtensionAction(*extension);
+  ASSERT_TRUE(action);
+  if (GetParam() == ExtensionBuilder::ActionType::BROWSER_ACTION) {
+    EXPECT_EQ(ActionInfo::TYPE_BROWSER, action->action_type());
     // Switch the default so we properly see the action toggling.
     action->SetIsVisible(ExtensionAction::kDefaultTabId, false);
   } else {
-    action = action_manager->GetPageAction(*extension);
-    ASSERT_TRUE(action);
+    EXPECT_EQ(ActionInfo::TYPE_PAGE, action->action_type());
   }
 
   std::unique_ptr<content::WebContents> contents = env.MakeTab();
@@ -242,21 +239,21 @@
                   "Extensions.DeclarativeSetIconWasVisibleRendered"),
               testing::ElementsAre(base::Bucket(1, 1)));
 
-  ExtensionAction* page_action =
-      ExtensionActionManager::Get(env.profile())->GetPageAction(*extension);
+  ExtensionAction* action = ExtensionActionManager::Get(env.profile())
+                                ->GetExtensionAction(*extension);
   std::unique_ptr<content::WebContents> contents = env.MakeTab();
   const int tab_id = ExtensionTabUtil::GetTabId(contents.get());
-  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
+  EXPECT_FALSE(action->GetIsVisible(tab_id));
   ContentAction::ApplyInfo apply_info = {
     extension, env.profile(), contents.get(), 100
   };
 
   // The declarative icon shouldn't exist unless the content action is applied.
-  EXPECT_TRUE(page_action->GetDeclarativeIcon(tab_id).IsEmpty());
+  EXPECT_TRUE(action->GetDeclarativeIcon(tab_id).IsEmpty());
   result->Apply(apply_info);
-  EXPECT_FALSE(page_action->GetDeclarativeIcon(tab_id).IsEmpty());
+  EXPECT_FALSE(action->GetDeclarativeIcon(tab_id).IsEmpty());
   result->Revert(apply_info);
-  EXPECT_TRUE(page_action->GetDeclarativeIcon(tab_id).IsEmpty());
+  EXPECT_TRUE(action->GetDeclarativeIcon(tab_id).IsEmpty());
 }
 
 TEST(DeclarativeContentActionTest, SetInvisibleIcon) {
diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc b/chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc
index d4483d6d..101ad56b 100644
--- a/chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc
+++ b/chrome/browser/extensions/api/declarative_content/declarative_content_apitest.cc
@@ -154,10 +154,10 @@
   ASSERT_TRUE(extension);
 
   Browser* incognito_browser = CreateIncognitoBrowser();
-  const ExtensionAction* incognito_page_action =
-      ExtensionActionManager::Get(incognito_browser->profile())->
-      GetPageAction(*extension);
-  ASSERT_TRUE(incognito_page_action);
+  const ExtensionAction* incognito_action =
+      ExtensionActionManager::Get(incognito_browser->profile())
+          ->GetExtensionAction(*extension);
+  ASSERT_TRUE(incognito_action);
 
   ASSERT_TRUE(ready.WaitUntilSatisfied());
   if (is_enabled_in_incognito && mode == SPLIT)
@@ -167,39 +167,39 @@
       incognito_browser->tab_strip_model()->GetWebContentsAt(0);
   const int incognito_tab_id = ExtensionTabUtil::GetTabId(incognito_tab);
 
-  EXPECT_FALSE(incognito_page_action->GetIsVisible(incognito_tab_id));
+  EXPECT_FALSE(incognito_action->GetIsVisible(incognito_tab_id));
 
   NavigateInRenderer(incognito_tab, GURL("http://test_split/"));
   if (mode == SPLIT) {
     EXPECT_EQ(is_enabled_in_incognito,
-              incognito_page_action->GetIsVisible(incognito_tab_id));
+              incognito_action->GetIsVisible(incognito_tab_id));
   } else {
-    EXPECT_FALSE(incognito_page_action->GetIsVisible(incognito_tab_id));
+    EXPECT_FALSE(incognito_action->GetIsVisible(incognito_tab_id));
   }
 
   NavigateInRenderer(incognito_tab, GURL("http://test_normal/"));
   if (mode != SPLIT) {
     EXPECT_EQ(is_enabled_in_incognito,
-              incognito_page_action->GetIsVisible(incognito_tab_id));
+              incognito_action->GetIsVisible(incognito_tab_id));
   } else {
-    EXPECT_FALSE(incognito_page_action->GetIsVisible(incognito_tab_id));
+    EXPECT_FALSE(incognito_action->GetIsVisible(incognito_tab_id));
   }
 
   // Verify that everything works as expected in the non-incognito browser.
-  const ExtensionAction* page_action =
-      ExtensionActionManager::Get(browser()->profile())->
-      GetPageAction(*extension);
+  const ExtensionAction* action =
+      ExtensionActionManager::Get(browser()->profile())
+          ->GetExtensionAction(*extension);
   content::WebContents* const tab =
       browser()->tab_strip_model()->GetWebContentsAt(0);
   const int tab_id = ExtensionTabUtil::GetTabId(tab);
 
-  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
+  EXPECT_FALSE(action->GetIsVisible(tab_id));
 
   NavigateInRenderer(tab, GURL("http://test_normal/"));
-  EXPECT_TRUE(page_action->GetIsVisible(tab_id));
+  EXPECT_TRUE(action->GetIsVisible(tab_id));
 
   NavigateInRenderer(tab, GURL("http://test_split/"));
-  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
+  EXPECT_FALSE(action->GetIsVisible(tab_id));
 }
 
 void DeclarativeContentApiTest::CheckBookmarkEvents(bool match_is_bookmarked) {
@@ -212,12 +212,13 @@
 
   const Extension* extension = LoadExtension(ext_dir_.UnpackedPath());
   ASSERT_TRUE(extension);
-  const ExtensionAction* page_action = ExtensionActionManager::Get(
-      browser()->profile())->GetPageAction(*extension);
-  ASSERT_TRUE(page_action);
+  const ExtensionAction* action =
+      ExtensionActionManager::Get(browser()->profile())
+          ->GetExtensionAction(*extension);
+  ASSERT_TRUE(action);
 
   NavigateInRenderer(tab, GURL("http://test1/"));
-  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
+  EXPECT_FALSE(action->GetIsVisible(tab_id));
 
   static const char kSetIsBookmarkedRule[] =
       "setRules([{\n"
@@ -229,7 +230,7 @@
       extension->id(),
       base::StringPrintf(kSetIsBookmarkedRule,
                          match_is_bookmarked ? "true" : "false")));
-  EXPECT_EQ(!match_is_bookmarked, page_action->GetIsVisible(tab_id));
+  EXPECT_EQ(!match_is_bookmarked, action->GetIsVisible(tab_id));
 
   // Check rule evaluation on add/remove bookmark.
   bookmarks::BookmarkModel* bookmark_model =
@@ -238,10 +239,10 @@
       bookmark_model->AddURL(bookmark_model->other_node(), 0,
                              base::ASCIIToUTF16("title"),
                              GURL("http://test1/"));
-  EXPECT_EQ(match_is_bookmarked, page_action->GetIsVisible(tab_id));
+  EXPECT_EQ(match_is_bookmarked, action->GetIsVisible(tab_id));
 
   bookmark_model->Remove(node);
-  EXPECT_EQ(!match_is_bookmarked, page_action->GetIsVisible(tab_id));
+  EXPECT_EQ(!match_is_bookmarked, action->GetIsVisible(tab_id));
 
   // Check rule evaluation on navigate to bookmarked and non-bookmarked URL.
   bookmark_model->AddURL(bookmark_model->other_node(), 0,
@@ -249,10 +250,10 @@
                          GURL("http://test2/"));
 
   NavigateInRenderer(tab, GURL("http://test2/"));
-  EXPECT_EQ(match_is_bookmarked, page_action->GetIsVisible(tab_id));
+  EXPECT_EQ(match_is_bookmarked, action->GetIsVisible(tab_id));
 
   NavigateInRenderer(tab, GURL("http://test3/"));
-  EXPECT_EQ(!match_is_bookmarked, page_action->GetIsVisible(tab_id));
+  EXPECT_EQ(!match_is_bookmarked, action->GetIsVisible(tab_id));
 }
 
 // Disabled due to flake. https://crbug.com/606574.
@@ -283,10 +284,10 @@
   ExtensionTestMessageListener ready("ready", false);
   const Extension* extension = LoadExtension(ext_dir_.UnpackedPath());
   ASSERT_TRUE(extension);
-  const ExtensionAction* page_action =
-      ExtensionActionManager::Get(browser()->profile())->
-      GetPageAction(*extension);
-  ASSERT_TRUE(page_action);
+  const ExtensionAction* action =
+      ExtensionActionManager::Get(browser()->profile())
+          ->GetExtensionAction(*extension);
+  ASSERT_TRUE(action);
 
   ASSERT_TRUE(ready.WaitUntilSatisfied());
   content::WebContents* const tab =
@@ -297,11 +298,11 @@
 
   // The declarative API should show the page action instantly, rather
   // than waiting for the extension to run.
-  EXPECT_TRUE(page_action->GetIsVisible(tab_id));
+  EXPECT_TRUE(action->GetIsVisible(tab_id));
 
   // Make sure leaving a matching page unshows the page action.
   NavigateInRenderer(tab, GURL("http://not_checked/"));
-  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
+  EXPECT_FALSE(action->GetIsVisible(tab_id));
 
   // Insert a password field to make sure that's noticed.
   // Notice that we touch offsetTop to force a synchronous layout.
@@ -313,7 +314,7 @@
   // update.
   ASSERT_TRUE(content::ExecuteScript(tab, std::string()));
 
-  EXPECT_TRUE(page_action->GetIsVisible(tab_id))
+  EXPECT_TRUE(action->GetIsVisible(tab_id))
       << "Adding a matching element should show the page action.";
 
   // Remove it again to make sure that reverts the action.
@@ -326,7 +327,7 @@
   // update.
   ASSERT_TRUE(content::ExecuteScript(tab, std::string()));
 
-  EXPECT_FALSE(page_action->GetIsVisible(tab_id))
+  EXPECT_FALSE(action->GetIsVisible(tab_id))
       << "Removing the matching element should hide the page action again.";
 }
 
@@ -366,10 +367,10 @@
   // Wait for declarative rules to be set up.
   content::BrowserContext::GetDefaultStoragePartition(profile())
       ->FlushNetworkInterfaceForTesting();
-  const ExtensionAction* page_action =
+  const ExtensionAction* action =
       ExtensionActionManager::Get(browser()->profile())
-          ->GetPageAction(*extension);
-  ASSERT_TRUE(page_action);
+          ->GetExtensionAction(*extension);
+  ASSERT_TRUE(action);
 
   ASSERT_TRUE(ready.WaitUntilSatisfied());
   content::WebContents* const tab =
@@ -379,7 +380,7 @@
   // This navigation matches both rules.
   NavigateInRenderer(tab, GURL("http://test1/"));
 
-  EXPECT_TRUE(page_action->GetIsVisible(tab_id));
+  EXPECT_TRUE(action->GetIsVisible(tab_id));
 }
 
 // Tests that the rules are evaluated at the time they are added or removed.
@@ -388,10 +389,10 @@
   ext_dir_.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundHelpers);
   const Extension* extension = LoadExtension(ext_dir_.UnpackedPath());
   ASSERT_TRUE(extension);
-  const ExtensionAction* page_action =
-      ExtensionActionManager::Get(browser()->profile())->
-      GetPageAction(*extension);
-  ASSERT_TRUE(page_action);
+  const ExtensionAction* action =
+      ExtensionActionManager::Get(browser()->profile())
+          ->GetExtensionAction(*extension);
+  ASSERT_TRUE(action);
 
   content::WebContents* const tab =
       browser()->tab_strip_model()->GetWebContentsAt(0);
@@ -414,23 +415,23 @@
   EXPECT_EQ("add_rules",
             ExecuteScriptInBackgroundPage(extension->id(), kAddTestRules));
 
-  EXPECT_TRUE(page_action->GetIsVisible(tab_id));
+  EXPECT_TRUE(action->GetIsVisible(tab_id));
 
   const std::string kRemoveTestRule1 = "removeRule('1', 'remove_rule1');\n";
   EXPECT_EQ("remove_rule1",
             ExecuteScriptInBackgroundPage(extension->id(), kRemoveTestRule1));
 
-  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
+  EXPECT_FALSE(action->GetIsVisible(tab_id));
 
   NavigateInRenderer(tab, GURL("http://test2/"));
 
-  EXPECT_TRUE(page_action->GetIsVisible(tab_id));
+  EXPECT_TRUE(action->GetIsVisible(tab_id));
 
   const std::string kRemoveTestRule2 = "removeRule('2', 'remove_rule2');\n";
   EXPECT_EQ("remove_rule2",
             ExecuteScriptInBackgroundPage(extension->id(), kRemoveTestRule2));
 
-  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
+  EXPECT_FALSE(action->GetIsVisible(tab_id));
 }
 
 class ParameterizedShowActionDeclarativeContentApiTest
@@ -517,10 +518,7 @@
   // action. Only browser action extensions are expected to not have a page
   // action.
   bool expect_page_action = !has_browser_action;
-  ExtensionAction* page_action =
-      ExtensionActionManager::Get(browser()->profile())
-          ->GetPageAction(*extension);
-  EXPECT_EQ(expect_page_action, page_action != nullptr);
+  EXPECT_EQ(expect_page_action, action->action_type() == ActionInfo::TYPE_PAGE);
   EXPECT_EQ(expect_page_action ? 1u : 0u,
             extension_action_test_util::GetVisiblePageActionCount(tab));
 }
@@ -573,21 +571,21 @@
   ext_dir_.WriteManifest(base::StringPrintf(manifest, GetParam()));
   const Extension* extension = LoadExtension(ext_dir_.UnpackedPath());
   ASSERT_TRUE(extension);
-  const ExtensionAction* page_action =
+  const ExtensionAction* action =
       ExtensionActionManager::Get(browser()->profile())
-          ->GetPageAction(*extension);
-  ASSERT_TRUE(page_action);
+          ->GetExtensionAction(*extension);
+  ASSERT_TRUE(action);
 
   content::WebContents* const tab =
       browser()->tab_strip_model()->GetWebContentsAt(0);
   const int tab_id = ExtensionTabUtil::GetTabId(tab);
 
   NavigateInRenderer(tab, GURL("http://blank/"));
-  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
+  EXPECT_FALSE(action->GetIsVisible(tab_id));
   NavigateInRenderer(tab, GURL("http://test1/"));
-  EXPECT_TRUE(page_action->GetIsVisible(tab_id));
+  EXPECT_TRUE(action->GetIsVisible(tab_id));
   NavigateInRenderer(tab, GURL("http://test2/"));
-  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
+  EXPECT_FALSE(action->GetIsVisible(tab_id));
 }
 
 INSTANTIATE_TEST_SUITE_P(LegacyShowActionKey,
@@ -641,13 +639,13 @@
   ASSERT_TRUE(extension);
   ASSERT_TRUE(ready.WaitUntilSatisfied());
 
-  const ExtensionAction* incognito_page_action =
-      ExtensionActionManager::Get(incognito_browser->profile())->
-      GetPageAction(*extension);
-  ASSERT_TRUE(incognito_page_action);
+  const ExtensionAction* incognito_action =
+      ExtensionActionManager::Get(incognito_browser->profile())
+          ->GetExtensionAction(*extension);
+  ASSERT_TRUE(incognito_action);
 
   // The page action should be shown.
-  EXPECT_TRUE(incognito_page_action->GetIsVisible(incognito_tab_id));
+  EXPECT_TRUE(incognito_action->GetIsVisible(incognito_tab_id));
 }
 
 // Sets up rules matching http://test1/ in a normal and incognito browser.
@@ -687,17 +685,17 @@
       browser()->tab_strip_model()->GetWebContentsAt(0);
   const int tab_id = ExtensionTabUtil::GetTabId(tab);
 
-  const ExtensionAction* page_action =
-      ExtensionActionManager::Get(browser()->profile())->
-      GetPageAction(*extension);
-  ASSERT_TRUE(page_action);
-  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
+  const ExtensionAction* action =
+      ExtensionActionManager::Get(browser()->profile())
+          ->GetExtensionAction(*extension);
+  ASSERT_TRUE(action);
+  EXPECT_FALSE(action->GetIsVisible(tab_id));
 
   NavigateInRenderer(tab, GURL("http://test_normal/"));
-  EXPECT_TRUE(page_action->GetIsVisible(tab_id));
+  EXPECT_TRUE(action->GetIsVisible(tab_id));
 
   NavigateInRenderer(tab, GURL("http://test_split/"));
-  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
+  EXPECT_FALSE(action->GetIsVisible(tab_id));
 
   // Check incognito browser.
   Browser* incognito_browser = CreateIncognitoBrowser();
@@ -706,16 +704,16 @@
       incognito_browser->tab_strip_model()->GetWebContentsAt(0);
   const int incognito_tab_id = ExtensionTabUtil::GetTabId(incognito_tab);
 
-  const ExtensionAction* incognito_page_action =
-      ExtensionActionManager::Get(incognito_browser->profile())->
-      GetPageAction(*extension);
-  ASSERT_TRUE(incognito_page_action);
+  const ExtensionAction* incognito_action =
+      ExtensionActionManager::Get(incognito_browser->profile())
+          ->GetExtensionAction(*extension);
+  ASSERT_TRUE(incognito_action);
 
   NavigateInRenderer(incognito_tab, GURL("http://test_split/"));
-  EXPECT_TRUE(incognito_page_action->GetIsVisible(incognito_tab_id));
+  EXPECT_TRUE(incognito_action->GetIsVisible(incognito_tab_id));
 
   NavigateInRenderer(incognito_tab, GURL("http://test_normal/"));
-  EXPECT_FALSE(incognito_page_action->GetIsVisible(incognito_tab_id));
+  EXPECT_FALSE(incognito_action->GetIsVisible(incognito_tab_id));
 }
 
 // http://crbug.com/304373
@@ -736,9 +734,10 @@
   content::BrowserContext::GetDefaultStoragePartition(profile())
       ->FlushNetworkInterfaceForTesting();
   const std::string extension_id = extension->id();
-  const ExtensionAction* page_action = ExtensionActionManager::Get(
-      browser()->profile())->GetPageAction(*extension);
-  ASSERT_TRUE(page_action);
+  const ExtensionAction* action =
+      ExtensionActionManager::Get(browser()->profile())
+          ->GetExtensionAction(*extension);
+  ASSERT_TRUE(action);
 
   const std::string kTestRule =
       "setRules([{\n"
@@ -755,12 +754,12 @@
 
   NavigateInRenderer(tab, GURL("http://test/"));
 
-  EXPECT_TRUE(page_action->GetIsVisible(tab_id));
+  EXPECT_TRUE(action->GetIsVisible(tab_id));
   EXPECT_TRUE(WaitForPageActionVisibilityChangeTo(1));
   EXPECT_EQ(1u, extension_action_test_util::GetVisiblePageActionCount(tab));
   EXPECT_EQ(1u, extension_action_test_util::GetTotalPageActionCount(tab));
 
-  ReloadExtension(extension_id);  // Invalidates page_action and extension.
+  ReloadExtension(extension_id);  // Invalidates action and extension.
   // Wait for declarative rules to be removed.
   content::BrowserContext::GetDefaultStoragePartition(profile())
       ->FlushNetworkInterfaceForTesting();
@@ -800,10 +799,11 @@
 
   const Extension* extension = LoadExtension(ext_dir_.UnpackedPath());
   ASSERT_TRUE(extension);
-  const ExtensionAction* page_action = ExtensionActionManager::Get(
-      browser()->profile())->GetPageAction(*extension);
-  ASSERT_TRUE(page_action);
-  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
+  const ExtensionAction* action =
+      ExtensionActionManager::Get(browser()->profile())
+          ->GetExtensionAction(*extension);
+  ASSERT_TRUE(action);
+  EXPECT_FALSE(action->GetIsVisible(tab_id));
 
   EXPECT_EQ("rule0", ExecuteScriptInBackgroundPage(
                          extension->id(),
@@ -820,7 +820,7 @@
   ASSERT_TRUE(content::ExecuteScript(tab, std::string()));
 
   EXPECT_FALSE(tab->IsCrashed());
-  EXPECT_TRUE(page_action->GetIsVisible(tab_id))
+  EXPECT_TRUE(action->GetIsVisible(tab_id))
       << "Loading an extension when an open page matches its rules "
       << "should show the page action.";
 
@@ -830,7 +830,7 @@
                 "onPageChanged.removeRules(undefined, function() {\n"
                 "  window.domAutomationController.send('removed');\n"
                 "});\n"));
-  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
+  EXPECT_FALSE(action->GetIsVisible(tab_id));
 }
 
 IN_PROC_BROWSER_TEST_F(DeclarativeContentApiTest,
@@ -929,9 +929,10 @@
   ext_dir_.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundHelpers);
   const Extension* extension = LoadExtension(ext_dir_.UnpackedPath());
   ASSERT_TRUE(extension);
-  const ExtensionAction* page_action = ExtensionActionManager::Get(
-      browser()->profile())->GetPageAction(*extension);
-  ASSERT_TRUE(page_action);
+  const ExtensionAction* action =
+      ExtensionActionManager::Get(browser()->profile())
+          ->GetExtensionAction(*extension);
+  ASSERT_TRUE(action);
 
   // Create two tabs.
   content::WebContents* const tab1 =
@@ -956,7 +957,7 @@
       "}], 'add_rules');\n";
   EXPECT_EQ("add_rules",
             ExecuteScriptInBackgroundPage(extension->id(), kAddTestRules));
-  EXPECT_TRUE(page_action->GetIsVisible(ExtensionTabUtil::GetTabId(tab2)));
+  EXPECT_TRUE(action->GetIsVisible(ExtensionTabUtil::GetTabId(tab2)));
 
   // Remove the rule.
   const std::string kRemoveTestRule1 = "removeRule('2', 'remove_rule1');\n";
@@ -968,7 +969,7 @@
   browser()->tab_strip_model()->DetachWebContentsAt(
       browser()->tab_strip_model()->GetIndexOfWebContents(tab2));
   NavigateInRenderer(tab1, GURL("http://test1/"));
-  EXPECT_TRUE(page_action->GetIsVisible(ExtensionTabUtil::GetTabId(tab1)));
+  EXPECT_TRUE(action->GetIsVisible(ExtensionTabUtil::GetTabId(tab1)));
 }
 
 // https://crbug.com/517492
diff --git a/chrome/browser/extensions/api/declarative_content/set_icon_apitest.cc b/chrome/browser/extensions/api/declarative_content/set_icon_apitest.cc
index b06f930..d017f72 100644
--- a/chrome/browser/extensions/api/declarative_content/set_icon_apitest.cc
+++ b/chrome/browser/extensions/api/declarative_content/set_icon_apitest.cc
@@ -78,10 +78,10 @@
   // Wait for declarative rules to be set up.
   content::BrowserContext::GetDefaultStoragePartition(profile())
       ->FlushNetworkInterfaceForTesting();
-  const ExtensionAction* page_action =
-      ExtensionActionManager::Get(browser()->profile())->
-      GetPageAction(*extension);
-  ASSERT_TRUE(page_action);
+  const ExtensionAction* action =
+      ExtensionActionManager::Get(browser()->profile())
+          ->GetExtensionAction(*extension);
+  ASSERT_TRUE(action);
 
   ASSERT_TRUE(ready.WaitUntilSatisfied());
   content::WebContents* const tab =
@@ -89,13 +89,13 @@
   const int tab_id = ExtensionTabUtil::GetTabId(tab);
 
   // There should be no declarative icon until we navigate to a matched page.
-  EXPECT_TRUE(page_action->GetDeclarativeIcon(tab_id).IsEmpty());
+  EXPECT_TRUE(action->GetDeclarativeIcon(tab_id).IsEmpty());
   NavigateInRenderer(tab, GURL("http://test1/"));
-  EXPECT_FALSE(page_action->GetDeclarativeIcon(tab_id).IsEmpty());
+  EXPECT_FALSE(action->GetDeclarativeIcon(tab_id).IsEmpty());
 
   // Navigating to an unmatched page should reset the icon.
   NavigateInRenderer(tab, GURL("http://test2/"));
-  EXPECT_TRUE(page_action->GetDeclarativeIcon(tab_id).IsEmpty());
+  EXPECT_TRUE(action->GetDeclarativeIcon(tab_id).IsEmpty());
 }
 }  // namespace
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc b/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc
index 482a95a6..c9aa19e 100644
--- a/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc
+++ b/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc
@@ -145,8 +145,12 @@
   }
 
   ExtensionAction* GetBrowserAction(const Extension& extension) {
-    return ExtensionActionManager::Get(browser()->profile())->
-        GetBrowserAction(extension);
+    ExtensionAction* extension_action =
+        ExtensionActionManager::Get(browser()->profile())
+            ->GetExtensionAction(extension);
+    return extension_action->action_type() == ActionInfo::TYPE_BROWSER
+               ? extension_action
+               : nullptr;
   }
 
  private:
diff --git a/chrome/browser/extensions/api/extension_action/browser_action_browsertest.cc b/chrome/browser/extensions/api/extension_action/browser_action_browsertest.cc
index be83e7d..2c4893d 100644
--- a/chrome/browser/extensions/api/extension_action/browser_action_browsertest.cc
+++ b/chrome/browser/extensions/api/extension_action/browser_action_browsertest.cc
@@ -64,7 +64,7 @@
   ASSERT_TRUE(listener.WaitUntilSatisfied());
 
   ExtensionAction* extension_action =
-      ExtensionActionManager::Get(profile())->GetBrowserAction(*extension);
+      ExtensionActionManager::Get(profile())->GetExtensionAction(*extension);
   ASSERT_TRUE(extension_action);
   EXPECT_EQ(SK_ColorBLUE, extension_action->GetBadgeBackgroundColor(0));
 }
@@ -89,7 +89,7 @@
   ASSERT_TRUE(extension) << "Could not find extension in registry.";
 
   ExtensionAction* extension_action =
-      ExtensionActionManager::Get(profile())->GetBrowserAction(*extension);
+      ExtensionActionManager::Get(profile())->GetExtensionAction(*extension);
   ASSERT_TRUE(extension_action);
 
   // If the extension hasn't already set the badge text, then we should wait for
diff --git a/chrome/browser/extensions/api/extension_action/page_action_apitest.cc b/chrome/browser/extensions/api/extension_action/page_action_apitest.cc
index 1ba7c0a..117882e 100644
--- a/chrome/browser/extensions/api/extension_action/page_action_apitest.cc
+++ b/chrome/browser/extensions/api/extension_action/page_action_apitest.cc
@@ -33,8 +33,12 @@
 class PageActionApiTest : public ExtensionApiTest {
  protected:
   ExtensionAction* GetPageAction(const Extension& extension) {
-    return ExtensionActionManager::Get(browser()->profile())->
-        GetPageAction(extension);
+    ExtensionAction* extension_action =
+        ExtensionActionManager::Get(browser()->profile())
+            ->GetExtensionAction(extension);
+    return extension_action->action_type() == ActionInfo::TYPE_PAGE
+               ? extension_action
+               : nullptr;
   }
 };
 
diff --git a/chrome/browser/extensions/api/platform_keys/verify_trust_api.cc b/chrome/browser/extensions/api/platform_keys/verify_trust_api.cc
index b1d8957d..09b9b97 100644
--- a/chrome/browser/extensions/api/platform_keys/verify_trust_api.cc
+++ b/chrome/browser/extensions/api/platform_keys/verify_trust_api.cc
@@ -192,6 +192,7 @@
   const int flags = 0;
 
   std::string ocsp_response;
+  std::string sct_list;
   net::CertVerifyResult* const verify_result_ptr = verify_result.get();
 
   RequestState* request_state = new RequestState();
@@ -201,7 +202,7 @@
 
   const int return_value = verifier->Verify(
       net::CertVerifier::RequestParams(std::move(cert_chain), details.hostname,
-                                       flags, ocsp_response),
+                                       flags, ocsp_response, sct_list),
       verify_result_ptr, bound_callback, &request_state->request, *net_log);
 
   if (return_value != net::ERR_IO_PENDING) {
diff --git a/chrome/browser/extensions/extension_action_icon_factory_unittest.cc b/chrome/browser/extensions/extension_action_icon_factory_unittest.cc
index 69cac08..8fce7ada 100644
--- a/chrome/browser/extensions/extension_action_icon_factory_unittest.cc
+++ b/chrome/browser/extensions/extension_action_icon_factory_unittest.cc
@@ -152,8 +152,9 @@
         IDR_EXTENSIONS_FAVICON);
   }
 
-  ExtensionAction* GetBrowserAction(const Extension& extension) {
-    return ExtensionActionManager::Get(profile())->GetBrowserAction(extension);
+  ExtensionAction* GetExtensionAction(const Extension& extension) {
+    return ExtensionActionManager::Get(profile())->GetExtensionAction(
+        extension);
   }
 
   TestingProfile* profile() { return profile_.get(); }
@@ -180,19 +181,18 @@
   scoped_refptr<Extension> extension(
       CreateExtension("browser_action/no_icon", Manifest::UNPACKED));
   ASSERT_TRUE(extension.get() != nullptr);
-  ExtensionAction* browser_action = GetBrowserAction(*extension);
-  ASSERT_TRUE(browser_action);
-  ASSERT_FALSE(browser_action->default_icon());
-  ASSERT_TRUE(browser_action->GetExplicitlySetIcon(0 /*tab id*/).IsEmpty());
+  ExtensionAction* action = GetExtensionAction(*extension);
+  ASSERT_TRUE(action);
+  ASSERT_FALSE(action->default_icon());
+  ASSERT_TRUE(action->GetExplicitlySetIcon(0 /*tab id*/).IsEmpty());
 
-  ExtensionActionIconFactory icon_factory(
-      profile(), extension.get(), browser_action, this);
+  ExtensionActionIconFactory icon_factory(profile(), extension.get(), action,
+                                          this);
 
   gfx::Image icon = icon_factory.GetIcon(0);
 
   EXPECT_TRUE(ImageRepsAreEqual(
-      browser_action->GetDefaultIconImage().ToImageSkia()->GetRepresentation(
-          1.0f),
+      action->GetDefaultIconImage().ToImageSkia()->GetRepresentation(1.0f),
       icon.ToImageSkia()->GetRepresentation(1.0f)));
 }
 
@@ -206,10 +206,10 @@
 
   // Check that the default icon is not sufficiently visible.
   ASSERT_TRUE(extension);
-  ExtensionAction* browser_action = GetBrowserAction(*extension);
-  ASSERT_TRUE(browser_action);
-  EXPECT_TRUE(browser_action->default_icon());
-  gfx::Image default_icon = browser_action->GetDefaultIconImage();
+  ExtensionAction* action = GetExtensionAction(*extension);
+  ASSERT_TRUE(action);
+  EXPECT_TRUE(action->default_icon());
+  gfx::Image default_icon = action->GetDefaultIconImage();
   EXPECT_FALSE(default_icon.IsEmpty());
   const SkBitmap* const bitmap = default_icon.ToSkBitmap();
   ASSERT_TRUE(bitmap);
@@ -218,17 +218,16 @@
   // Set the flag for testing.
   ExtensionActionIconFactory::SetAllowInvisibleIconsForTest(false);
 
-  ExtensionActionIconFactory icon_factory(profile(), extension.get(),
-                                          browser_action, this);
+  ExtensionActionIconFactory icon_factory(profile(), extension.get(), action,
+                                          this);
 
   base::HistogramTester histogram_tester;
   gfx::Image icon = icon_factory.GetIcon(0);
   // The default icon should not be returned, since it's invisible.
   // The placeholder icon should be returned instead.
-  EXPECT_TRUE(ImageRepsAreEqual(browser_action->GetPlaceholderIconImage()
-                                    .ToImageSkia()
-                                    ->GetRepresentation(1.0f),
-                                icon.ToImageSkia()->GetRepresentation(1.0f)));
+  EXPECT_TRUE(ImageRepsAreEqual(
+      action->GetPlaceholderIconImage().ToImageSkia()->GetRepresentation(1.0f),
+      icon.ToImageSkia()->GetRepresentation(1.0f)));
   EXPECT_THAT(histogram_tester.GetAllSamples(
                   "Extensions.ManifestIconSetIconWasVisibleForPacked"),
               testing::ElementsAre(base::Bucket(0, 1)));
@@ -249,20 +248,20 @@
   scoped_refptr<Extension> extension(
       CreateExtension("browser_action/no_icon", Manifest::UNPACKED));
   ASSERT_TRUE(extension.get() != nullptr);
-  ExtensionAction* browser_action = GetBrowserAction(*extension);
-  ASSERT_TRUE(browser_action);
-  ASSERT_FALSE(browser_action->default_icon());
-  ASSERT_TRUE(browser_action->GetExplicitlySetIcon(0 /*tab id*/).IsEmpty());
+  ExtensionAction* action = GetExtensionAction(*extension);
+  ASSERT_TRUE(action);
+  ASSERT_FALSE(action->default_icon());
+  ASSERT_TRUE(action->GetExplicitlySetIcon(0 /*tab id*/).IsEmpty());
 
   gfx::Image set_icon = LoadIcon("browser_action/no_icon/icon.png");
   ASSERT_FALSE(set_icon.IsEmpty());
 
-  browser_action->SetIcon(0, set_icon);
+  action->SetIcon(0, set_icon);
 
-  ASSERT_FALSE(browser_action->GetExplicitlySetIcon(0 /*tab id*/).IsEmpty());
+  ASSERT_FALSE(action->GetExplicitlySetIcon(0 /*tab id*/).IsEmpty());
 
-  ExtensionActionIconFactory icon_factory(
-      profile(), extension.get(), browser_action, this);
+  ExtensionActionIconFactory icon_factory(profile(), extension.get(), action,
+                                          this);
 
   gfx::Image icon = icon_factory.GetIcon(0);
 
@@ -274,8 +273,7 @@
   icon = icon_factory.GetIcon(1);
 
   EXPECT_TRUE(ImageRepsAreEqual(
-      browser_action->GetDefaultIconImage().ToImageSkia()->GetRepresentation(
-          1.0f),
+      action->GetDefaultIconImage().ToImageSkia()->GetRepresentation(1.0f),
       icon.ToImageSkia()->GetRepresentation(1.0f)));
 }
 
@@ -288,10 +286,10 @@
   scoped_refptr<Extension> extension(
       CreateExtension("browser_action/no_icon", Manifest::UNPACKED));
   ASSERT_TRUE(extension.get() != nullptr);
-  ExtensionAction* browser_action = GetBrowserAction(*extension);
-  ASSERT_TRUE(browser_action);
-  ASSERT_FALSE(browser_action->default_icon());
-  ASSERT_TRUE(browser_action->GetExplicitlySetIcon(0 /*tab id*/).IsEmpty());
+  ExtensionAction* action = GetExtensionAction(*extension);
+  ASSERT_TRUE(action);
+  ASSERT_FALSE(action->default_icon());
+  ASSERT_TRUE(action->GetExplicitlySetIcon(0 /*tab id*/).IsEmpty());
 
   scoped_refptr<const Extension> extension_with_icon =
       CreateExtension("browser_action_with_icon", Manifest::UNPACKED);
@@ -302,11 +300,11 @@
       EnsureImageSize(LoadIcon("browser_action_with_icon/icon.png"), icon_size);
   ASSERT_FALSE(default_icon.IsEmpty());
 
-  browser_action = GetBrowserAction(*extension_with_icon);
-  ASSERT_TRUE(browser_action->default_icon());
+  action = GetExtensionAction(*extension_with_icon);
+  ASSERT_TRUE(action->default_icon());
 
-  ExtensionActionIconFactory icon_factory(
-      profile(), extension_with_icon.get(), browser_action, this);
+  ExtensionActionIconFactory icon_factory(profile(), extension_with_icon.get(),
+                                          action, this);
 
   gfx::Image icon = icon_factory.GetIcon(0);
 
diff --git a/chrome/browser/extensions/extension_action_manager.cc b/chrome/browser/extensions/extension_action_manager.cc
index 7f4d737..a73ac32 100644
--- a/chrome/browser/extensions/extension_action_manager.cc
+++ b/chrome/browser/extensions/extension_action_manager.cc
@@ -80,31 +80,29 @@
     content::BrowserContext* browser_context,
     const Extension* extension,
     UnloadedExtensionReason reason) {
-  page_actions_.erase(extension->id());
-  browser_actions_.erase(extension->id());
+  actions_.erase(extension->id());
 }
 
-namespace {
+ExtensionAction* ExtensionActionManager::GetExtensionAction(
+    const Extension& extension) const {
+  auto iter = actions_.find(extension.id());
+  if (iter != actions_.end())
+    return iter->second.get();
 
-// Returns map[extension_id] if that entry exists. Otherwise, if
-// action_info!=nullptr, creates an ExtensionAction from it, fills in the map,
-// and returns that.  Otherwise (action_info==nullptr), returns nullptr.
-ExtensionAction* GetOrCreateOrNull(
-    std::map<std::string, std::unique_ptr<ExtensionAction>>* map,
-    const Extension& extension,
-    const ActionInfo* action_info,
-    Profile* profile) {
-  auto it = map->find(extension.id());
-  if (it != map->end())
-    return it->second.get();
+  // TODO(https://crbug.com/893373): Update this to use
+  // ActionInfo::GetAnyActionInfo() once all callers can handle a generic action
+  // type.
+  const ActionInfo* action_info = ActionInfo::GetBrowserActionInfo(&extension);
+  if (!action_info)
+    action_info = ActionInfo::GetPageActionInfo(&extension);
   if (!action_info)
     return nullptr;
 
   // Only create action info for enabled extensions.
   // This avoids bugs where actions are recreated just after being removed
   // in response to OnExtensionUnloaded().
-  if (!ExtensionRegistry::Get(profile)
-      ->enabled_extensions().Contains(extension.id())) {
+  if (!ExtensionRegistry::Get(profile_)->enabled_extensions().Contains(
+          extension.id())) {
     return nullptr;
   }
 
@@ -112,36 +110,14 @@
 
   if (action->default_icon()) {
     action->SetDefaultIconImage(std::make_unique<IconImage>(
-        profile, &extension, *action->default_icon(),
+        profile_, &extension, *action->default_icon(),
         ExtensionAction::ActionIconSize(),
         ExtensionAction::FallbackIcon().AsImageSkia(), nullptr));
   }
 
   ExtensionAction* raw_action = action.get();
-  (*map)[extension.id()] = std::move(action);
+  actions_[extension.id()] = std::move(action);
   return raw_action;
 }
 
-}  // namespace
-
-ExtensionAction* ExtensionActionManager::GetPageAction(
-    const Extension& extension) const {
-  return GetOrCreateOrNull(&page_actions_, extension,
-                           ActionInfo::GetPageActionInfo(&extension),
-                           profile_);
-}
-
-ExtensionAction* ExtensionActionManager::GetBrowserAction(
-    const Extension& extension) const {
-  return GetOrCreateOrNull(&browser_actions_, extension,
-                           ActionInfo::GetBrowserActionInfo(&extension),
-                           profile_);
-}
-
-ExtensionAction* ExtensionActionManager::GetExtensionAction(
-    const Extension& extension) const {
-  ExtensionAction* action = GetBrowserAction(extension);
-  return action ? action : GetPageAction(extension);
-}
-
 }  // namespace extensions
diff --git a/chrome/browser/extensions/extension_action_manager.h b/chrome/browser/extensions/extension_action_manager.h
index ddc0105..22cb934 100644
--- a/chrome/browser/extensions/extension_action_manager.h
+++ b/chrome/browser/extensions/extension_action_manager.h
@@ -34,14 +34,7 @@
   // shared between a profile and its incognito version.
   static ExtensionActionManager* Get(content::BrowserContext* browser_context);
 
-  // Retrieves the page action, browser action, or system indicator for
-  // |extension|.
-  // If the result is not NULL, it remains valid until the extension is
-  // unloaded.
-  ExtensionAction* GetPageAction(const Extension& extension) const;
-  ExtensionAction* GetBrowserAction(const Extension& extension) const;
-
-  // Returns either the PageAction or BrowserAction for |extension|, or NULL if
+  // Returns either the PageAction or BrowserAction for |extension|, or null if
   // none exists. Since an extension can only declare one of Browser|PageAction,
   // this is okay to use anywhere you need a generic "ExtensionAction".
   ExtensionAction* GetExtensionAction(const Extension& extension) const;
@@ -60,12 +53,10 @@
 
   // Keyed by Extension ID.  These maps are populated lazily when their
   // ExtensionAction is first requested, and the entries are removed when the
-  // extension is unloaded.  Not every extension has a page action or browser
-  // action.
+  // extension is unloaded.  Not every extension has an action.
   using ExtIdToActionMap =
       std::map<std::string, std::unique_ptr<ExtensionAction>>;
-  mutable ExtIdToActionMap page_actions_;
-  mutable ExtIdToActionMap browser_actions_;
+  mutable ExtIdToActionMap actions_;
 };
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/extension_action_manager_unittest.cc b/chrome/browser/extensions/extension_action_manager_unittest.cc
index 437172a..83277d0 100644
--- a/chrome/browser/extensions/extension_action_manager_unittest.cc
+++ b/chrome/browser/extensions/extension_action_manager_unittest.cc
@@ -4,195 +4,142 @@
 
 #include "chrome/browser/extensions/extension_action_manager.h"
 
-#include "base/strings/string_number_conversions.h"
 #include "chrome/browser/extensions/extension_action.h"
 #include "chrome/test/base/testing_profile.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/extension_builder.h"
+#include "extensions/common/manifest_constants.h"
 #include "extensions/common/manifest_handlers/icons_handler.h"
 #include "extensions/common/value_builder.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace extensions {
 
-namespace {
-
-const char kBrowserAction[] = "browser_action";
-const char kPageAction[] = "page_action";
-
-}  // namespace
-
-class ExtensionActionManagerTest : public testing::Test {
+// TODO(devlin): This really seems like more of an ExtensionAction test than
+// an ExtensionActionManager test.
+class ExtensionActionManagerTest
+    : public testing::TestWithParam<ActionInfo::Type> {
  public:
   ExtensionActionManagerTest();
 
  protected:
-  // Build an extension, populating |action_type| key with |action|, and
-  // "icons" key with |extension_icons|.
-  scoped_refptr<const Extension> BuildExtension(
-      std::unique_ptr<base::DictionaryValue> extension_icons,
-      std::unique_ptr<base::DictionaryValue> action,
-      const char* action_type);
+  const char* GetManifestKey() {
+    const char* key = nullptr;
+    switch (GetParam()) {
+      case ActionInfo::TYPE_ACTION:
+        // TODO(https://crbug.com/893373): Add support for this key.
+        NOTREACHED();
+        break;
+      case ActionInfo::TYPE_PAGE:
+        key = manifest_keys::kPageAction;
+        break;
+      case ActionInfo::TYPE_BROWSER:
+        key = manifest_keys::kBrowserAction;
+        break;
+    }
 
-  // Returns true if |action|'s title matches |extension|'s name.
-  bool TitlesMatch(const Extension& extension, const ExtensionAction& action);
-
-  // Returns true if |action|'s icon for size |action_key| matches
-  // |extension|'s icon for size |extension_key|;
-  bool IconsMatch(const Extension& extension,
-                  int extension_key,
-                  const ExtensionAction& action,
-                  int action_key);
-
-  // Returns the appropriate action for |extension| according to |action_type|.
-  ExtensionAction* GetAction(const char* action_type,
-                             const Extension& extension);
-
-  // Tests that values that are missing from the |action_type| key are properly
-  // populated with values from the other keys in the manifest (e.g.
-  // "default_icon" key of |action_type| is populated with "icons" key).
-  void TestPopulateMissingValues(const char* action_type);
+    return key;
+  }
 
   ExtensionActionManager* manager() { return manager_; }
+  ExtensionRegistry* registry() { return registry_; }
 
  private:
   content::TestBrowserThreadBundle thread_bundle_;
   ExtensionRegistry* registry_;
-  int curr_id_;
   ExtensionActionManager* manager_;
   std::unique_ptr<TestingProfile> profile_;
 };
 
 ExtensionActionManagerTest::ExtensionActionManagerTest()
-    : curr_id_(0),
-      profile_(new TestingProfile) {
+    : profile_(std::make_unique<TestingProfile>()) {
   registry_ = ExtensionRegistry::Get(profile_.get());
   manager_ = ExtensionActionManager::Get(profile_.get());
 }
 
-scoped_refptr<const Extension> ExtensionActionManagerTest::BuildExtension(
-    std::unique_ptr<base::DictionaryValue> extension_icons,
-    std::unique_ptr<base::DictionaryValue> action,
-    const char* action_type) {
-  std::string id = base::NumberToString(curr_id_++);
-  scoped_refptr<const Extension> extension =
-      ExtensionBuilder()
-          .SetManifest(
-              DictionaryBuilder()
-                  .Set("version", "1")
-                  .Set("manifest_version", 2)
-                  .Set("icons", std::move(extension_icons))
-                  .Set(action_type, std::move(action))
-                  .Set("name", std::string("Test Extension").append(id))
-                  .Build())
-          .SetID(id)
-          .Build();
-  registry_->AddEnabled(extension);
-  return extension;
-}
-
-bool ExtensionActionManagerTest::TitlesMatch(const Extension& extension,
-                                             const ExtensionAction& action) {
-  return action.GetTitle(ExtensionAction::kDefaultTabId) == extension.name();
-}
-
-bool ExtensionActionManagerTest::IconsMatch(const Extension& extension,
-                                            int extension_key,
-                                            const ExtensionAction& action,
-                                            int action_key) {
-  return action.default_icon()->Get(action_key,
-                                    ExtensionIconSet::MATCH_BIGGER) ==
-         IconsInfo::GetIcons(&extension)
-             .Get(extension_key, ExtensionIconSet::MATCH_EXACTLY);
-}
-
-ExtensionAction* ExtensionActionManagerTest::GetAction(
-    const char* action_type,
-    const Extension& extension) {
-  return (action_type == kBrowserAction) ?
-      manager_->GetBrowserAction(extension) :
-      manager_->GetPageAction(extension);
-}
-
-void ExtensionActionManagerTest::TestPopulateMissingValues(
-    const char* action_type) {
+// Tests that if no icons are specified in the extension's action, values from
+// the "icons" key are used instead.
+TEST_P(ExtensionActionManagerTest, TestPopulateMissingValues_Icons) {
   // Test that the largest icon from the extension's "icons" key is chosen as a
-  // replacement for missing action default_icons keys. "19" should not be
-  // replaced because "38" can always be used in its place.
+  // replacement for missing action default_icons keys. "48" should not be
+  // replaced because "128" can always be used in its place.
   scoped_refptr<const Extension> extension =
-      BuildExtension(DictionaryBuilder()
-                         .Set("48", "icon48.png")
-                         .Set("128", "icon128.png")
-                         .Build(),
-                     DictionaryBuilder().Build(), action_type);
+      ExtensionBuilder("Test Extension")
+          .SetManifestKey("icons", DictionaryBuilder()
+                                       .Set("48", "icon48.png")
+                                       .Set("128", "icon128.png")
+                                       .Build())
+          .SetManifestKey(GetManifestKey(),
+                          std::make_unique<base::DictionaryValue>())
+          .Build();
 
-  ASSERT_TRUE(extension.get());
-  const ExtensionAction* action = GetAction(action_type, *extension);
+  ASSERT_TRUE(extension);
+  registry()->AddEnabled(extension);
+  const ExtensionAction* action = manager()->GetExtensionAction(*extension);
   ASSERT_TRUE(action);
+  ASSERT_EQ(GetParam(), action->action_type());
 
-  ASSERT_TRUE(TitlesMatch(*extension, *action));
-  ASSERT_TRUE(IconsMatch(*extension, 48, *action, 38));
-
-  // Test that the action's missing default_icons are not replaced with smaller
-  // icons.
-  extension =
-      BuildExtension(DictionaryBuilder().Set("24", "icon24.png").Build(),
-                     DictionaryBuilder().Build(), action_type);
-
-  ASSERT_TRUE(extension.get());
-  action = GetAction(action_type, *extension);
-  ASSERT_TRUE(action);
-
-  ASSERT_TRUE(IconsMatch(*extension, 24, *action, 19));
-  ASSERT_FALSE(IconsMatch(*extension, 24, *action, 38));
-
-  // Test that an action's 19px icon is not replaced if a 38px action icon
-  // exists.
-  extension = BuildExtension(
-      DictionaryBuilder().Set("128", "icon128.png").Build(),
-      DictionaryBuilder()
-          .Set("default_icon",
-               DictionaryBuilder().Set("38", "action38.png").Build())
-          .Build(),
-      action_type);
-
-  ASSERT_TRUE(extension.get());
-  action = GetAction(action_type, *extension);
-  ASSERT_TRUE(action);
-
-  ASSERT_FALSE(IconsMatch(*extension, 128, *action, 19));
-
-  // Test that existing default_icons and default_title are not replaced.
-  extension =
-      BuildExtension(DictionaryBuilder().Set("128", "icon128.png").Build(),
-                     DictionaryBuilder()
-                         .Set("default_title", "Action!")
-                         .Set("default_icon", DictionaryBuilder()
-                                                  .Set("19", "action19.png")
-                                                  .Set("38", "action38.png")
-                                                  .Build())
-                         .Build(),
-                     action_type);
-
-  ASSERT_TRUE(extension.get());
-  action = GetAction(action_type, *extension);
-  ASSERT_TRUE(action);
-
-  ASSERT_FALSE(TitlesMatch(*extension, *action));
-  ASSERT_FALSE(IconsMatch(*extension, 128, *action, 19));
-  ASSERT_FALSE(IconsMatch(*extension, 128, *action, 38));
+  ASSERT_TRUE(action->default_icon());
+  // Since no icons are specified in the extension action, the product icons
+  // (from the "icons" key) are used instead.
+  EXPECT_EQ(action->default_icon()->map(),
+            IconsInfo::GetIcons(extension.get()).map());
 }
 
-namespace {
+TEST_P(ExtensionActionManagerTest, TestPopulateMissingValues_Title) {
+  scoped_refptr<const Extension> extension =
+      ExtensionBuilder("Test Extension")
+          .SetManifestKey(GetManifestKey(),
+                          std::make_unique<base::DictionaryValue>())
+          .Build();
 
-TEST_F(ExtensionActionManagerTest, PopulateBrowserAction) {
-  TestPopulateMissingValues(kBrowserAction);
+  ASSERT_TRUE(extension);
+  registry()->AddEnabled(extension);
+  const ExtensionAction* action = manager()->GetExtensionAction(*extension);
+  ASSERT_TRUE(action);
+  ASSERT_EQ(GetParam(), action->action_type());
+
+  EXPECT_EQ(extension->name(),
+            action->GetTitle(ExtensionAction::kDefaultTabId));
 }
 
-TEST_F(ExtensionActionManagerTest, PopulatePageAction) {
-  TestPopulateMissingValues(kPageAction);
+// Tests that if defaults are provided in the extension action specification,
+// those should be used.
+TEST_P(ExtensionActionManagerTest, TestDontOverrideIfDefaultsProvided) {
+  scoped_refptr<const Extension> extension =
+      ExtensionBuilder("Test Extension")
+          .SetManifestKey("icons",
+                          DictionaryBuilder().Set("24", "icon24.png").Build())
+          .SetManifestKey(
+              GetManifestKey(),
+              DictionaryBuilder()
+                  .Set("default_icon",
+                       DictionaryBuilder().Set("19", "icon19.png").Build())
+                  .Set("default_title", "Action!")
+                  .Build())
+          .Build();
+
+  ASSERT_TRUE(extension);
+  registry()->AddEnabled(extension);
+  const ExtensionAction* action = manager()->GetExtensionAction(*extension);
+  ASSERT_TRUE(action);
+  ASSERT_EQ(GetParam(), action->action_type());
+
+  ASSERT_TRUE(action->default_icon());
+  // Since there was at least one icon specified in the extension action, the
+  // action icon should use that.
+  EXPECT_THAT(action->default_icon()->map(),
+              testing::UnorderedElementsAre(std::make_pair(19, "icon19.png")));
+
+  // Since the default_title was specified, it should be used.
+  EXPECT_EQ("Action!", action->GetTitle(ExtensionAction::kDefaultTabId));
 }
 
-}  // namespace
+INSTANTIATE_TEST_SUITE_P(,
+                         ExtensionActionManagerTest,
+                         testing::Values(ActionInfo::TYPE_BROWSER,
+                                         ActionInfo::TYPE_PAGE));
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/extension_action_storage_manager.cc b/chrome/browser/extensions/extension_action_storage_manager.cc
index bc5a9ac..0a0889f4 100644
--- a/chrome/browser/extensions/extension_action_storage_manager.cc
+++ b/chrome/browser/extensions/extension_action_storage_manager.cc
@@ -214,8 +214,9 @@
 void ExtensionActionStorageManager::OnExtensionLoaded(
     content::BrowserContext* browser_context,
     const Extension* extension) {
-  if (!ExtensionActionManager::Get(browser_context_)->GetBrowserAction(
-          *extension))
+  ExtensionAction* action = ExtensionActionManager::Get(browser_context_)
+                                ->GetExtensionAction(*extension);
+  if (!action || action->action_type() != ActionInfo::TYPE_BROWSER)
     return;
 
   StateStore* store = GetStateStore();
@@ -237,6 +238,7 @@
   // is null. We only persist the default settings to disk, since per-tab
   // settings can't be persisted across browser sessions.
   bool for_default_tab = !web_contents;
+  // TODO(devlin): We should probably persist for TYPE_ACTION as well.
   if (browser_context_ == browser_context &&
       extension_action->action_type() == ActionInfo::TYPE_BROWSER &&
       for_default_tab) {
@@ -267,10 +269,9 @@
   if (!extension)
     return;
 
-  ExtensionAction* browser_action =
-      ExtensionActionManager::Get(browser_context_)->GetBrowserAction(
-          *extension);
-  if (!browser_action) {
+  ExtensionAction* action = ExtensionActionManager::Get(browser_context_)
+                                ->GetExtensionAction(*extension);
+  if (!action || action->action_type() != ActionInfo::TYPE_BROWSER) {
     // This can happen if the extension is updated between startup and when the
     // storage read comes back, and the update removes the browser action.
     // http://crbug.com/349371
@@ -281,7 +282,7 @@
   if (!value.get() || !value->GetAsDictionary(&dict))
     return;
 
-  SetDefaultsFromValue(dict, browser_action);
+  SetDefaultsFromValue(dict, action);
 }
 
 StateStore* ExtensionActionStorageManager::GetStateStore() {
diff --git a/chrome/browser/extensions/extension_action_test_util.cc b/chrome/browser/extensions/extension_action_test_util.cc
index 0327d41..0f88e70 100644
--- a/chrome/browser/extensions/extension_action_test_util.cc
+++ b/chrome/browser/extensions/extension_action_test_util.cc
@@ -42,8 +42,9 @@
   for (const ToolbarActionsModel::ActionId& action_id : toolbar_action_ids) {
     const Extension* extension = enabled_extensions.GetByID(action_id);
     ExtensionAction* extension_action =
-        action_manager->GetPageAction(*extension);
+        action_manager->GetExtensionAction(*extension);
     if (extension_action &&
+        extension_action->action_type() == ActionInfo::TYPE_PAGE &&
         (!only_count_visible || extension_action->GetIsVisible(tab_id.id()))) {
       ++count;
     }
diff --git a/chrome/browser/extensions/extension_keybinding_apitest.cc b/chrome/browser/extensions/extension_keybinding_apitest.cc
index 99b0142..3f68deb 100644
--- a/chrome/browser/extensions/extension_keybinding_apitest.cc
+++ b/chrome/browser/extensions/extension_keybinding_apitest.cc
@@ -258,10 +258,10 @@
   ASSERT_TRUE(WaitForPageActionVisibilityChangeTo(1));
   int tab_id = SessionTabHelper::FromWebContents(
       browser()->tab_strip_model()->GetActiveWebContents())->session_id().id();
-  ExtensionAction* action =
-      ExtensionActionManager::Get(browser()->profile())->
-      GetPageAction(*extension);
+  ExtensionAction* action = ExtensionActionManager::Get(browser()->profile())
+                                ->GetExtensionAction(*extension);
   ASSERT_TRUE(action);
+  EXPECT_EQ(ActionInfo::TYPE_PAGE, action->action_type());
   EXPECT_EQ("Send message", action->GetTitle(tab_id));
 
   ExtensionTestMessageListener test_listener(false);  // Won't reply.
diff --git a/chrome/browser/extensions/native_bindings_apitest.cc b/chrome/browser/extensions/native_bindings_apitest.cc
index 2acbf0a..0f8f8ca 100644
--- a/chrome/browser/extensions/native_bindings_apitest.cc
+++ b/chrome/browser/extensions/native_bindings_apitest.cc
@@ -90,26 +90,26 @@
   ASSERT_TRUE(listener.WaitUntilSatisfied());
 
   // The extension's page action should currently be hidden.
-  ExtensionAction* page_action =
-      ExtensionActionManager::Get(profile())->GetPageAction(*extension);
+  ExtensionAction* action =
+      ExtensionActionManager::Get(profile())->GetExtensionAction(*extension);
   content::WebContents* web_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
   int tab_id = SessionTabHelper::IdForTab(web_contents).id();
-  EXPECT_FALSE(page_action->GetIsVisible(tab_id));
-  EXPECT_TRUE(page_action->GetDeclarativeIcon(tab_id).IsEmpty());
+  EXPECT_FALSE(action->GetIsVisible(tab_id));
+  EXPECT_TRUE(action->GetDeclarativeIcon(tab_id).IsEmpty());
 
   // Navigating to example.com should show the page action.
   ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL(
                      "example.com", "/native_bindings/simple.html"));
   base::RunLoop().RunUntilIdle();
-  EXPECT_TRUE(page_action->GetIsVisible(tab_id));
-  EXPECT_FALSE(page_action->GetDeclarativeIcon(tab_id).IsEmpty());
+  EXPECT_TRUE(action->GetIsVisible(tab_id));
+  EXPECT_FALSE(action->GetDeclarativeIcon(tab_id).IsEmpty());
 
   // And the extension should be notified of the click.
   ExtensionTestMessageListener clicked_listener("clicked and removed", false);
   ExtensionActionAPI::Get(profile())->DispatchExtensionActionClicked(
-      *page_action, web_contents, extension);
+      *action, web_contents, extension);
   ASSERT_TRUE(clicked_listener.WaitUntilSatisfied());
 }
 
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 4e42b9f..6ee95ef 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -192,6 +192,11 @@
     "expiry_milestone": 82
   },
   {
+    "name": "auto-screen-brightness",
+    "owners": [ "jiameng", "napper" ],
+    "expiry_milestone": 79
+  },
+  {
     "name": "autofill-always-show-server-cards-in-sync-transport",
     "owners": [ "sebsg" ],
     "expiry_milestone": 74
@@ -695,7 +700,7 @@
   },
   {
     "name": "enable-app-banners",
-    // "owners": [ "your-team" ],
+    "owners": [ "desktop-pwas-team@google.com" ],
     "expiry_milestone": 76
   },
   {
@@ -769,11 +774,6 @@
     "expiry_milestone": 81
   },
   {
-    "name": "enable-async-image-decoding",
-    // "owners": [ "your-team" ],
-    "expiry_milestone": 76
-  },
-  {
     "name": "enable-audio-focus-enforcement",
     "owners": [ "beccahughes", "media-dev" ],
     "expiry_milestone": 76
@@ -1421,8 +1421,8 @@
   },
   {
     "name": "enable-quic",
-    // "owners": [ "your-team" ],
-    "expiry_milestone": 76
+    "owners": [ "rch" ],
+    "expiry_milestone": 78
   },
   {
     "name": "enable-reader-mode",
@@ -2901,8 +2901,8 @@
   },
   {
     "name": "top-chrome-touch-ui",
-    // "owners": [ "your-team" ],
-    "expiry_milestone": 76
+    "owners": [ "chrome-desktop-ui-sea@google.com" ],
+    "expiry_milestone": 81
   },
   {
     "name": "touch-events",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index f197354..a3a28de 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -94,10 +94,6 @@
 const char kEnableBloatedRendererDetectionDescription[] =
     "Enable bloated renderer detection";
 
-const char kAsyncImageDecodingName[] = "AsyncImageDecoding";
-const char kAsyncImageDecodingDescription[] =
-    "Enables asynchronous decoding of images from raster for web content";
-
 extern const char kAutofillAlwaysShowServerCardsInSyncTransportName[] =
     "AlwaysShowServerCardsInSyncTransport";
 extern const char kAutofillAlwaysShowServerCardsInSyncTransportDescription[] =
@@ -210,6 +206,11 @@
     "When enabled, the Autofill dropdown's suggestions' labels are displayed "
     "using the improved disambiguation format.";
 
+const char kAutoScreenBrightnessName[] = "Auto Screen Brightness model";
+const char kAutoScreenBrightnessDescription[] =
+    "Uses Auto Screen Brightness model to adjust screen brightness based on "
+    "ambient light";
+
 const char kAwaitOptimizationName[] = "Await optimization";
 const char kAwaitOptimizationDescription[] =
     "Enables await taking 1 tick on the microtask queue.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index ebf0fcf..036d5c5 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -82,9 +82,6 @@
 extern const char kEnableBloatedRendererDetectionName[];
 extern const char kEnableBloatedRendererDetectionDescription[];
 
-extern const char kAsyncImageDecodingName[];
-extern const char kAsyncImageDecodingDescription[];
-
 extern const char kAutofillAlwaysShowServerCardsInSyncTransportName[];
 extern const char kAutofillAlwaysShowServerCardsInSyncTransportDescription[];
 
@@ -144,6 +141,9 @@
 extern const char kAutofillUseImprovedLabelDisambiguationName[];
 extern const char kAutofillUseImprovedLabelDisambiguationDescription[];
 
+extern const char kAutoScreenBrightnessName[];
+extern const char kAutoScreenBrightnessDescription[];
+
 extern const char kAwaitOptimizationName[];
 extern const char kAwaitOptimizationDescription[];
 
diff --git a/chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc b/chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc
index 9e5d999..f95ccaf 100644
--- a/chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc
+++ b/chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc
@@ -29,6 +29,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
+#include "content/public/common/mime_handler_view_mode.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/find_test_utils.h"
 #include "content/public/test/hit_test_region_observer.h"
@@ -252,6 +253,11 @@
 // from the unattached guest. For more context see https://crbug.com/897465.
 IN_PROC_BROWSER_TEST_F(ChromeMimeHandlerViewBrowserPluginTest,
                        NoFindInPageForUnattachedGuest) {
+  if (content::MimeHandlerViewMode::UsesCrossProcessFrame()) {
+    // This test requires BrowserPlugin which does not exist in frame-based
+    // MimeHandlerView.
+    return;
+  }
   InitializeTestPage(embedded_test_server()->GetURL("/testBasic.csv"));
   auto* main_frame = embedder_web_contents()->GetMainFrame();
   auto* attached_guest_main_frame = guest_web_contents()->GetMainFrame();
@@ -295,6 +301,11 @@
 
 IN_PROC_BROWSER_TEST_F(ChromeMimeHandlerViewBrowserPluginTest,
                        UnderChildFrame) {
+  if (content::MimeHandlerViewMode::UsesCrossProcessFrame()) {
+    // This test requires BrowserPlugin which does not exist in frame-based
+    // MimeHandlerView.
+    return;
+  }
   // Create this frame tree structure.
   // main_frame_node
   //   |
@@ -328,6 +339,11 @@
 #endif
 IN_PROC_BROWSER_TEST_F(ChromeMimeHandlerViewBrowserPluginTest,
                        MAYBE_BP_AutoResizeMessages) {
+  if (content::MimeHandlerViewMode::UsesCrossProcessFrame()) {
+    // This test requires BrowserPlugin which does not exist in frame-based
+    // MimeHandlerView.
+    return;
+  }
   InitializeTestPage(embedded_test_server()->GetURL("/testBasic.csv"));
 
   // Helper function as this test requires inspecting a number of content::
@@ -350,6 +366,11 @@
 #endif
 IN_PROC_BROWSER_TEST_F(ChromeMimeHandlerViewBrowserPluginTest,
                        MAYBE_TouchFocusesEmbedder) {
+  if (content::MimeHandlerViewMode::UsesCrossProcessFrame()) {
+    // This test requires BrowserPlugin which does not exist in frame-based
+    // MimeHandlerView.
+    return;
+  }
   InitializeTestPage(embedded_test_server()->GetURL("/testBasic.csv"));
 
   content::RenderViewHost* embedder_rvh =
@@ -445,6 +466,11 @@
 
 IN_PROC_BROWSER_TEST_F(ChromeMimeHandlerViewBrowserPluginTest,
                        TouchFocusesBrowserPluginInEmbedder) {
+  if (content::MimeHandlerViewMode::UsesCrossProcessFrame()) {
+    // This test requires BrowserPlugin which does not exist in frame-based
+    // MimeHandlerView.
+    return;
+  }
   InitializeTestPage(embedded_test_server()->GetURL("/test_embedded.html"));
 
   auto embedder_rect = embedder_web_contents()->GetContainerBounds();
@@ -506,6 +532,11 @@
 #endif
 IN_PROC_BROWSER_TEST_F(ChromeMimeHandlerViewBrowserPluginScrollTest,
                        MAYBE_ScrollGuestContent) {
+  if (content::MimeHandlerViewMode::UsesCrossProcessFrame()) {
+    // This test requires BrowserPlugin which does not exist in frame-based
+    // MimeHandlerView.
+    return;
+  }
   InitializeTestPage(embedded_test_server()->GetURL("/test_embedded.html"));
 
   ASSERT_TRUE(ExecuteScript(guest_web_contents(), "ensurePageIsScrollable();"));
diff --git a/chrome/browser/media/android/cdm/per_device_provisioning_permission.cc b/chrome/browser/media/android/cdm/per_device_provisioning_permission.cc
index ce18249..8b5cf70 100644
--- a/chrome/browser/media/android/cdm/per_device_provisioning_permission.cc
+++ b/chrome/browser/media/android/cdm/per_device_provisioning_permission.cc
@@ -80,6 +80,11 @@
     return IDR_ANDROID_INFOBAR_PROTECTED_MEDIA_IDENTIFIER;
   }
 
+  base::string16 GetTitleText() const final {
+    return l10n_util::GetStringUTF16(
+        IDS_PROTECTED_MEDIA_IDENTIFIER_PERMISSION_TITLE);
+  }
+
   base::string16 GetMessageText() const final {
     // Note that the string is specific to per-device provisioning.
     return l10n_util::GetStringFUTF16(
diff --git a/chrome/browser/media/cast_transport_host_filter.cc b/chrome/browser/media/cast_transport_host_filter.cc
index b2b2383..5dbad65 100644
--- a/chrome/browser/media/cast_transport_host_filter.cc
+++ b/chrome/browser/media/cast_transport_host_filter.cc
@@ -11,7 +11,6 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/common/cast_messages.h"
-#include "components/net_log/chrome_net_log.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/common/service_manager_connection.h"
@@ -175,8 +174,7 @@
   }
 
   auto udp_transport = std::make_unique<media::cast::UdpTransportImpl>(
-      g_browser_process->net_log(), base::ThreadTaskRunnerHandle::Get(),
-      local_end_point, remote_end_point,
+      base::ThreadTaskRunnerHandle::Get(), local_end_point, remote_end_point,
       base::BindRepeating(&CastTransportHostFilter::OnStatusChanged,
                           weak_factory_.GetWeakPtr(), channel_id));
   udp_transport->SetUdpOptions(options);
diff --git a/chrome/browser/media/router/providers/cast/cast_internal_message_util.cc b/chrome/browser/media/router/providers/cast/cast_internal_message_util.cc
index 189883d..ba5cb31 100644
--- a/chrome/browser/media/router/providers/cast/cast_internal_message_util.cc
+++ b/chrome/browser/media/router/providers/cast/cast_internal_message_util.cc
@@ -24,34 +24,41 @@
 
 template <>
 const EnumTable<CastInternalMessage::Type>
-    EnumTable<CastInternalMessage::Type>::instance({
-        {CastInternalMessage::Type::kClientConnect, "client_connect"},
-        {CastInternalMessage::Type::kAppMessage, "app_message"},
-        {CastInternalMessage::Type::kV2Message, "v2_message"},
-        {CastInternalMessage::Type::kLeaveSession, "leave_session"},
-        {CastInternalMessage::Type::kReceiverAction, "receiver_action"},
-        {CastInternalMessage::Type::kNewSession, "new_session"},
-        {CastInternalMessage::Type::kUpdateSession, "update_session"},
-        {CastInternalMessage::Type::kError, "error"},
-        // kOther deliberately omitted
-    });
+    EnumTable<CastInternalMessage::Type>::instance(
+        {
+            {CastInternalMessage::Type::kClientConnect, "client_connect"},
+            {CastInternalMessage::Type::kAppMessage, "app_message"},
+            {CastInternalMessage::Type::kV2Message, "v2_message"},
+            {CastInternalMessage::Type::kLeaveSession, "leave_session"},
+            {CastInternalMessage::Type::kReceiverAction, "receiver_action"},
+            {CastInternalMessage::Type::kNewSession, "new_session"},
+            {CastInternalMessage::Type::kUpdateSession, "update_session"},
+            {CastInternalMessage::Type::kError, "error"},
+            {CastInternalMessage::Type::kOther},
+        },
+        CastInternalMessage::Type::kMaxValue);
 
 template <>
-const EnumTable<CastInternalMessage::ErrorCode> EnumTable<
-    CastInternalMessage::ErrorCode>::instance({
-    {CastInternalMessage::ErrorCode::kInternalError, "internal_error"},
-    {CastInternalMessage::ErrorCode::kCancel, "cancel"},
-    {CastInternalMessage::ErrorCode::kTimeout, "timeout"},
-    {CastInternalMessage::ErrorCode::kApiNotInitialized, "api_not_initialized"},
-    {CastInternalMessage::ErrorCode::kInvalidParameter, "invalid_parameter"},
-    {CastInternalMessage::ErrorCode::kExtensionNotCompatible,
-     "extension_not_compatible"},
-    {CastInternalMessage::ErrorCode::kReceiverUnavailable,
-     "receiver_unavailable"},
-    {CastInternalMessage::ErrorCode::kSessionError, "session_error"},
-    {CastInternalMessage::ErrorCode::kChannelError, "channel_error"},
-    {CastInternalMessage::ErrorCode::kLoadMediaFailed, "load_media_failed"},
-});
+const EnumTable<CastInternalMessage::ErrorCode>
+    EnumTable<CastInternalMessage::ErrorCode>::instance(
+        {
+            {CastInternalMessage::ErrorCode::kInternalError, "internal_error"},
+            {CastInternalMessage::ErrorCode::kCancel, "cancel"},
+            {CastInternalMessage::ErrorCode::kTimeout, "timeout"},
+            {CastInternalMessage::ErrorCode::kApiNotInitialized,
+             "api_not_initialized"},
+            {CastInternalMessage::ErrorCode::kInvalidParameter,
+             "invalid_parameter"},
+            {CastInternalMessage::ErrorCode::kExtensionNotCompatible,
+             "extension_not_compatible"},
+            {CastInternalMessage::ErrorCode::kReceiverUnavailable,
+             "receiver_unavailable"},
+            {CastInternalMessage::ErrorCode::kSessionError, "session_error"},
+            {CastInternalMessage::ErrorCode::kChannelError, "channel_error"},
+            {CastInternalMessage::ErrorCode::kLoadMediaFailed,
+             "load_media_failed"},
+        },
+        CastInternalMessage::ErrorCode::kMaxValue);
 
 }  // namespace cast_util
 
diff --git a/chrome/browser/media/router/providers/cast/cast_internal_message_util.h b/chrome/browser/media/router/providers/cast/cast_internal_message_util.h
index 9af4589..03e9cb2 100644
--- a/chrome/browser/media/router/providers/cast/cast_internal_message_util.h
+++ b/chrome/browser/media/router/providers/cast/cast_internal_message_util.h
@@ -36,8 +36,9 @@
     kUpdateSession,   // Message sent by MRP to inform SDK client of updated
                       // session.
     kError,
-    kOther  // All other types of messages which are not considered
-            // part of communication with Cast SDK.
+    kOther,  // All other types of messages which are not considered
+             // part of communication with Cast SDK.
+    kMaxValue = kOther,
   };
 
   // Errors that may be returned by the SDK.
@@ -54,6 +55,7 @@
     kSessionError,  // A session could not be created, or a session was invalid.
     kChannelError,  // A channel to the receiver is not available.
     kLoadMediaFailed,  // Load media failed.
+    kMaxValue = kLoadMediaFailed,
   };
 
   // Returns a CastInternalMessage for |message|, or nullptr is |message| is not
diff --git a/chrome/browser/net/variations_http_headers_browsertest.cc b/chrome/browser/net/variations_http_headers_browsertest.cc
index f105be8..c0d93fb 100644
--- a/chrome/browser/net/variations_http_headers_browsertest.cc
+++ b/chrome/browser/net/variations_http_headers_browsertest.cc
@@ -34,8 +34,6 @@
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
-#include "net/url_request/url_fetcher.h"
-#include "net/url_request/url_fetcher_delegate.h"
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/simple_url_loader.h"
@@ -256,24 +254,6 @@
   DISALLOW_COPY_AND_ASSIGN(VariationsHttpHeadersBrowserTest);
 };
 
-class BlockingURLFetcherDelegate : public net::URLFetcherDelegate {
- public:
-  BlockingURLFetcherDelegate() = default;
-  ~BlockingURLFetcherDelegate() override = default;
-
-  void OnURLFetchComplete(const net::URLFetcher* source) override {
-    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
-                                                  run_loop_.QuitClosure());
-  }
-
-  void AwaitResponse() { run_loop_.Run(); }
-
- private:
-  base::RunLoop run_loop_;
-
-  DISALLOW_COPY_AND_ASSIGN(BlockingURLFetcherDelegate);
-};
-
 std::unique_ptr<net::test_server::HttpResponse>
 VariationsHttpHeadersBrowserTest::RequestHandler(
     const net::test_server::HttpRequest& request) {
@@ -332,8 +312,7 @@
 }
 
 // Verify in an integration that that the variations header (X-Client-Data) is
-// correctly attached and stripped from network requests that are triggered via
-// a URLFetcher.
+// correctly attached and stripped from network requests.
 IN_PROC_BROWSER_TEST_F(VariationsHttpHeadersBrowserTest,
                        TestStrippingHeadersFromSubresourceRequest) {
   GURL url = server()->GetURL("/simple_page.html");
diff --git a/chrome/browser/offline_pages/prefetch/prefetch_service_factory.cc b/chrome/browser/offline_pages/prefetch/prefetch_service_factory.cc
index e0b3b15..a2c852f 100644
--- a/chrome/browser/offline_pages/prefetch/prefetch_service_factory.cc
+++ b/chrome/browser/offline_pages/prefetch/prefetch_service_factory.cc
@@ -7,11 +7,10 @@
 #include <memory>
 #include <utility>
 
-#include "base/bind_helpers.h"
 #include "base/files/file_path.h"
 #include "base/memory/singleton.h"
+#include "base/sequenced_task_runner.h"
 #include "base/task/post_task.h"
-#include "base/threading/sequenced_task_runner_handle.h"
 #include "chrome/browser/chrome_content_browser_client.h"
 #include "chrome/browser/download/download_service_factory.h"
 #include "chrome/browser/image_fetcher/image_fetcher_service_factory.h"
@@ -23,7 +22,6 @@
 #include "chrome/browser/offline_pages/prefetch/prefetch_instance_id_proxy.h"
 #include "chrome/browser/offline_pages/prefetch/thumbnail_fetcher_impl.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/transition_manager/full_browser_transition_manager.h"
 #include "chrome/common/channel_info.h"
 #include "chrome/common/chrome_constants.h"
 #include "components/feed/feed_feature_list.h"
@@ -31,7 +29,6 @@
 #include "components/image_fetcher/core/image_fetcher_impl.h"
 #include "components/image_fetcher/core/image_fetcher_service.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
-#include "components/offline_pages/core/offline_page_feature.h"
 #include "components/offline_pages/core/prefetch/prefetch_dispatcher_impl.h"
 #include "components/offline_pages/core/prefetch/prefetch_downloader_impl.h"
 #include "components/offline_pages/core/prefetch/prefetch_gcm_app_handler.h"
@@ -41,34 +38,10 @@
 #include "components/offline_pages/core/prefetch/store/prefetch_store.h"
 #include "components/offline_pages/core/prefetch/suggested_articles_observer.h"
 #include "content/public/browser/browser_context.h"
-#include "content/public/browser/browser_thread.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
 namespace offline_pages {
 
-namespace {
-
-void OnProfileCreated(PrefetchServiceImpl* service, Profile* profile) {
-  auto gcm_app_handler = std::make_unique<PrefetchGCMAppHandler>(
-      std::make_unique<PrefetchInstanceIDProxy>(kPrefetchingOfflinePagesAppId,
-                                                profile));
-  service->SetPrefetchGCMHandler(std::move(gcm_app_handler));
-  if (IsPrefetchingOfflinePagesEnabled()) {
-    // Trigger an update of the cached GCM token. This needs to be post tasked
-    // because otherwise leads to circular dependency between
-    // PrefetchServiceFactory and GCMProfileServiceFactory. See
-    // https://crbug.com/944952
-    // Update is not a priority so make sure it happens after the critical
-    // startup path.
-    content::BrowserThread::PostAfterStartupTask(
-        FROM_HERE, base::SequencedTaskRunnerHandle::Get(),
-        base::BindOnce(&PrefetchServiceImpl::GetGCMToken, service->GetWeakPtr(),
-                       base::DoNothing::Once<const std::string&>()));
-  }
-}
-
-}  // namespace
-
 PrefetchServiceFactory::PrefetchServiceFactory()
     : BrowserContextKeyedServiceFactory(
           "OfflinePagePrefetchService",
@@ -118,6 +91,10 @@
     url_loader_factory = nullptr;
   }
 
+  auto prefetch_gcm_app_handler = std::make_unique<PrefetchGCMAppHandler>(
+      std::make_unique<PrefetchInstanceIDProxy>(kPrefetchingOfflinePagesAppId,
+                                                context));
+
   auto prefetch_network_request_factory =
       std::make_unique<PrefetchNetworkRequestFactoryImpl>(
           url_loader_factory, chrome::GetChannel(), GetUserAgent(),
@@ -158,19 +135,14 @@
   auto prefetch_background_task_handler =
       std::make_unique<PrefetchBackgroundTaskHandlerImpl>(profile->GetPrefs());
 
-  auto* service = new PrefetchServiceImpl(
+  return new PrefetchServiceImpl(
       std::move(offline_metrics_collector), std::move(prefetch_dispatcher),
+      std::move(prefetch_gcm_app_handler),
       std::move(prefetch_network_request_factory), offline_page_model,
       std::move(prefetch_store), std::move(suggested_articles_observer),
       std::move(prefetch_downloader), std::move(prefetch_importer),
       std::move(prefetch_background_task_handler), std::move(thumbnail_fetcher),
       image_fetcher);
-
-  auto callback = base::BindOnce(&OnProfileCreated, service);
-  FullBrowserTransitionManager::Get()->RegisterCallbackOnProfileCreation(
-      profile->GetProfileKey(), std::move(callback));
-
-  return service;
 }
 
 }  // namespace offline_pages
diff --git a/chrome/browser/pdf/pdf_extension_test.cc b/chrome/browser/pdf/pdf_extension_test.cc
index 064fb90..d93a39b 100644
--- a/chrome/browser/pdf/pdf_extension_test.cc
+++ b/chrome/browser/pdf/pdf_extension_test.cc
@@ -1239,6 +1239,12 @@
     guest_contents_ = guest_contents;
   }
 
+  content::WebContents* GetWebContentsForInputRouting() {
+    return content::MimeHandlerViewMode::UsesCrossProcessFrame()
+               ? guest_contents_
+               : GetActiveWebContents();
+  }
+
  private:
   WebContents* guest_contents_;
 };
@@ -1251,9 +1257,9 @@
   content::WindowedNotificationObserver observer(
       chrome::NOTIFICATION_TAB_ADDED,
       content::NotificationService::AllSources());
-  content::SimulateMouseClickAt(web_contents, kDefaultKeyModifier,
-                                blink::WebMouseEvent::Button::kLeft,
-                                GetLinkPosition());
+  content::SimulateMouseClickAt(
+      GetWebContentsForInputRouting(), kDefaultKeyModifier,
+      blink::WebMouseEvent::Button::kLeft, GetLinkPosition());
   observer.Wait();
 
   int tab_count = browser()->tab_strip_model()->count();
@@ -1279,7 +1285,7 @@
   content::WindowedNotificationObserver observer(
       chrome::NOTIFICATION_TAB_ADDED,
       content::NotificationService::AllSources());
-  content::SimulateMouseClickAt(web_contents, 0,
+  content::SimulateMouseClickAt(GetWebContentsForInputRouting(), 0,
                                 blink::WebMouseEvent::Button::kMiddle,
                                 GetLinkPosition());
   observer.Wait();
@@ -1309,7 +1315,7 @@
   content::WindowedNotificationObserver observer(
       chrome::NOTIFICATION_TAB_ADDED,
       content::NotificationService::AllSources());
-  content::SimulateMouseClickAt(web_contents, modifiers,
+  content::SimulateMouseClickAt(GetWebContentsForInputRouting(), modifiers,
                                 blink::WebMouseEvent::Button::kLeft,
                                 GetLinkPosition());
   observer.Wait();
@@ -1332,9 +1338,9 @@
   content::WindowedNotificationObserver observer(
       chrome::NOTIFICATION_TAB_ADDED,
       content::NotificationService::AllSources());
-  content::SimulateMouseClickAt(web_contents, blink::WebInputEvent::kShiftKey,
-                                blink::WebMouseEvent::Button::kMiddle,
-                                GetLinkPosition());
+  content::SimulateMouseClickAt(
+      GetWebContentsForInputRouting(), blink::WebInputEvent::kShiftKey,
+      blink::WebMouseEvent::Button::kMiddle, GetLinkPosition());
   observer.Wait();
 
   int tab_count = browser()->tab_strip_model()->count();
@@ -1357,9 +1363,9 @@
   content::WindowedNotificationObserver observer(
       chrome::NOTIFICATION_BROWSER_OPENED,
       content::NotificationService::AllSources());
-  content::SimulateMouseClickAt(web_contents, blink::WebInputEvent::kShiftKey,
-                                blink::WebMouseEvent::Button::kLeft,
-                                GetLinkPosition());
+  content::SimulateMouseClickAt(
+      GetWebContentsForInputRouting(), blink::WebInputEvent::kShiftKey,
+      blink::WebMouseEvent::Button::kLeft, GetLinkPosition());
   observer.Wait();
 
   ASSERT_EQ(2U, chrome::GetTotalBrowserCount());
@@ -1403,9 +1409,9 @@
   content::WindowedNotificationObserver observer(
       chrome::NOTIFICATION_TAB_ADDED,
       content::NotificationService::AllSources());
-  content::SimulateMouseClickAt(web_contents, kDefaultKeyModifier,
-                                blink::WebMouseEvent::Button::kLeft,
-                                GetLinkPosition());
+  content::SimulateMouseClickAt(
+      GetWebContentsForInputRouting(), kDefaultKeyModifier,
+      blink::WebMouseEvent::Button::kLeft, GetLinkPosition());
   observer.Wait();
 
   // We should have two tabs now. One with the PDF and the second for
diff --git a/chrome/browser/permissions/mock_permission_request.cc b/chrome/browser/permissions/mock_permission_request.cc
index 720e357..e06e6aa 100644
--- a/chrome/browser/permissions/mock_permission_request.cc
+++ b/chrome/browser/permissions/mock_permission_request.cc
@@ -72,6 +72,9 @@
 }
 
 #if defined(OS_ANDROID)
+base::string16 MockPermissionRequest::GetTitleText() const {
+  return text_;
+}
 base::string16 MockPermissionRequest::GetMessageText() const {
   return text_;
 }
diff --git a/chrome/browser/permissions/mock_permission_request.h b/chrome/browser/permissions/mock_permission_request.h
index 4a7c695..c166b6f 100644
--- a/chrome/browser/permissions/mock_permission_request.h
+++ b/chrome/browser/permissions/mock_permission_request.h
@@ -27,6 +27,7 @@
 
   IconId GetIconId() const override;
 #if defined(OS_ANDROID)
+  base::string16 GetTitleText() const override;
   base::string16 GetMessageText() const override;
 #endif
   base::string16 GetMessageTextFragment() const override;
diff --git a/chrome/browser/permissions/permission_dialog_delegate.cc b/chrome/browser/permissions/permission_dialog_delegate.cc
index a8e1613d..9f98da2 100644
--- a/chrome/browser/permissions/permission_dialog_delegate.cc
+++ b/chrome/browser/permissions/permission_dialog_delegate.cc
@@ -65,6 +65,7 @@
       env, reinterpret_cast<uintptr_t>(this), tab->GetJavaObject(),
       base::android::ToJavaIntArray(env, content_settings_types),
       ResourceMapper::MapFromChromiumId(permission_prompt_->GetIconId()),
+      ConvertUTF16ToJavaString(env, permission_prompt_->GetTitleText()),
       ConvertUTF16ToJavaString(env, permission_prompt_->GetMessageText()),
       primaryButtonText, secondaryButtonText));
 }
diff --git a/chrome/browser/permissions/permission_prompt_android.cc b/chrome/browser/permissions/permission_prompt_android.cc
index 2c048dd..e123033 100644
--- a/chrome/browser/permissions/permission_prompt_android.cc
+++ b/chrome/browser/permissions/permission_prompt_android.cc
@@ -96,6 +96,15 @@
   return IDR_ANDROID_INFOBAR_MEDIA_STREAM_CAMERA;
 }
 
+base::string16 PermissionPromptAndroid::GetTitleText() const {
+  const std::vector<PermissionRequest*>& requests = delegate_->Requests();
+  if (requests.size() == 1)
+    return requests[0]->GetTitleText();
+  CheckValidRequestGroup(requests);
+  return l10n_util::GetStringUTF16(
+      IDS_MEDIA_CAPTURE_AUDIO_AND_VIDEO_PERMISSION_TITLE);
+}
+
 base::string16 PermissionPromptAndroid::GetMessageText() const {
   const std::vector<PermissionRequest*>& requests = delegate_->Requests();
   if (requests.size() == 1)
diff --git a/chrome/browser/permissions/permission_prompt_android.h b/chrome/browser/permissions/permission_prompt_android.h
index 4849b1d..6f56c94 100644
--- a/chrome/browser/permissions/permission_prompt_android.h
+++ b/chrome/browser/permissions/permission_prompt_android.h
@@ -35,6 +35,7 @@
   size_t PermissionCount() const;
   ContentSettingsType GetContentSettingType(size_t position) const;
   int GetIconId() const;
+  base::string16 GetTitleText() const;
   base::string16 GetMessageText() const;
 
  private:
diff --git a/chrome/browser/permissions/permission_request.h b/chrome/browser/permissions/permission_request.h
index 61522a7..5aeadb2 100644
--- a/chrome/browser/permissions/permission_request.h
+++ b/chrome/browser/permissions/permission_request.h
@@ -85,6 +85,10 @@
   virtual IconId GetIconId() const = 0;
 
 #if defined(OS_ANDROID)
+  // Returns the title of this permission as text. This is currently only used
+  // in touchless mode in Android.
+  virtual base::string16 GetTitleText() const = 0;
+
   // Returns the full prompt text for this permission. This is currently only
   // used on Android.
   virtual base::string16 GetMessageText() const = 0;
diff --git a/chrome/browser/permissions/permission_request_impl.cc b/chrome/browser/permissions/permission_request_impl.cc
index 3eff821..ff2d8360 100644
--- a/chrome/browser/permissions/permission_request_impl.cc
+++ b/chrome/browser/permissions/permission_request_impl.cc
@@ -90,6 +90,40 @@
 }
 
 #if defined(OS_ANDROID)
+base::string16 PermissionRequestImpl::GetTitleText() const {
+  int message_id;
+  switch (content_settings_type_) {
+    case CONTENT_SETTINGS_TYPE_GEOLOCATION:
+      message_id = IDS_GEOLOCATION_PERMISSION_TITLE;
+      break;
+    case CONTENT_SETTINGS_TYPE_NOTIFICATIONS:
+      message_id = IDS_NOTIFICATIONS_PERMISSION_TITLE;
+      break;
+    case CONTENT_SETTINGS_TYPE_MIDI_SYSEX:
+      message_id = IDS_MIDI_SYSEX_PERMISSION_TITLE;
+      break;
+    case CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER:
+      message_id = IDS_PROTECTED_MEDIA_IDENTIFIER_PERMISSION_TITLE;
+      break;
+    case CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC:
+      message_id = IDS_MEDIA_CAPTURE_AUDIO_ONLY_PERMISSION_TITLE;
+      break;
+    case CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA:
+      message_id = IDS_MEDIA_CAPTURE_VIDEO_ONLY_PERMISSION_TITLE;
+      break;
+    case CONTENT_SETTINGS_TYPE_ACCESSIBILITY_EVENTS:
+      message_id = IDS_ACCESSIBILITY_EVENTS_PERMISSION_TITLE;
+      break;
+    case CONTENT_SETTINGS_TYPE_CLIPBOARD_READ:
+      message_id = IDS_CLIPBOARD_PERMISSION_TITLE;
+      break;
+    default:
+      NOTREACHED();
+      return base::string16();
+  }
+  return l10n_util::GetStringUTF16(message_id);
+}
+
 base::string16 PermissionRequestImpl::GetMessageText() const {
   int message_id;
   switch (content_settings_type_) {
diff --git a/chrome/browser/permissions/permission_request_impl.h b/chrome/browser/permissions/permission_request_impl.h
index b991ff6..3524b3c 100644
--- a/chrome/browser/permissions/permission_request_impl.h
+++ b/chrome/browser/permissions/permission_request_impl.h
@@ -33,6 +33,7 @@
   // PermissionRequest:
   IconId GetIconId() const override;
 #if defined(OS_ANDROID)
+  base::string16 GetTitleText() const override;
   base::string16 GetMessageText() const override;
 #endif
   base::string16 GetMessageTextFragment() const override;
diff --git a/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc b/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
index bcd1b0f..f32ff1d 100644
--- a/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
+++ b/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
@@ -2979,3 +2979,39 @@
   EXPECT_EQ(overlay_window->muted_state_for_testing(),
             OverlayWindowViews::MutedState::kNoAudio);
 }
+
+// Tests that when closing the window after the player was reset, the <video>
+// element is still notified.
+IN_PROC_BROWSER_TEST_F(PictureInPictureWindowControllerBrowserTest,
+                       ResetPlayerCloseWindowNotifiesElement) {
+  LoadTabAndEnterPictureInPicture(browser());
+  content::WebContents* active_web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+
+  // Video should be in Picture-in-Picture.
+  {
+    bool in_picture_in_picture = false;
+    ASSERT_TRUE(ExecuteScriptAndExtractBool(active_web_contents,
+                                            "isInPictureInPicture();",
+                                            &in_picture_in_picture));
+    EXPECT_TRUE(in_picture_in_picture);
+  }
+
+  // Reset video source and wait for the notification.
+  ASSERT_TRUE(content::ExecuteScript(active_web_contents, "resetVideo();"));
+  base::string16 expected_title = base::ASCIIToUTF16("emptied");
+  EXPECT_EQ(expected_title,
+            content::TitleWatcher(active_web_contents, expected_title)
+                .WaitAndGetTitle());
+
+  window_controller()->Close(true /* should_pause_video */);
+
+  // Video should no longer be in Picture-in-Picture.
+  {
+    bool in_picture_in_picture = false;
+    ASSERT_TRUE(ExecuteScriptAndExtractBool(active_web_contents,
+                                            "isInPictureInPicture();",
+                                            &in_picture_in_picture));
+    EXPECT_FALSE(in_picture_in_picture);
+  }
+}
diff --git a/chrome/browser/previews/previews_lite_page_navigation_throttle_unittest.cc b/chrome/browser/previews/previews_lite_page_navigation_throttle_unittest.cc
index 13b739a..d240203 100644
--- a/chrome/browser/previews/previews_lite_page_navigation_throttle_unittest.cc
+++ b/chrome/browser/previews/previews_lite_page_navigation_throttle_unittest.cc
@@ -15,6 +15,7 @@
 #include "base/test/scoped_task_environment.h"
 #include "chrome/browser/previews/previews_lite_page_decider.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "components/data_reduction_proxy/core/common/data_reduction_proxy_features.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_switches.h"
 #include "components/previews/core/previews_features.h"
@@ -129,22 +130,17 @@
         test_case.experiment_cmd_line);
 
     base::test::ScopedFeatureList scoped_feature_list;
-    scoped_feature_list.InitAndEnableFeatureWithParameters(
-        previews::features::kLitePageServerPreviews,
-        {{"previews_host", test_case.previews_host}});
-
-    base::FieldTrialList::CreateFieldTrial(
-        data_reduction_proxy::params::
-            GetDataSaverServerExperimentsFieldTrialNameForTesting(),
-        "enabled");
-    std::map<std::string, std::string> server_experiment;
-    server_experiment[data_reduction_proxy::params::
-                          GetDataSaverServerExperimentsOptionName()] =
+    std::map<std::string, std::string> server_experiment_params;
+    server_experiment_params[data_reduction_proxy::params::
+                                 GetDataSaverServerExperimentsOptionName()] =
         test_case.experiment_variation;
-    variations::AssociateVariationParams(
-        data_reduction_proxy::params::
-            GetDataSaverServerExperimentsFieldTrialNameForTesting(),
-        "enabled", server_experiment);
+
+    scoped_feature_list.InitWithFeaturesAndParameters(
+        {{data_reduction_proxy::features::kDataReductionProxyServerExperiments,
+          {server_experiment_params}},
+         {previews::features::kLitePageServerPreviews,
+          {{"previews_host", test_case.previews_host}}}},
+        {});
 
     EXPECT_EQ(PreviewsLitePageNavigationThrottle::GetPreviewsURLForURL(
                   GURL(test_case.original_url)),
diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc
index fdc68a27..c3226565 100644
--- a/chrome/browser/profiles/profile_impl.cc
+++ b/chrome/browser/profiles/profile_impl.cc
@@ -551,10 +551,7 @@
   key_ = startup_data->TakeProfileKey();
   prefs_ = startup_data->TakeProfilePrefService();
   schema_registry_service_ = startup_data->TakeSchemaRegistryService();
-  configuration_policy_provider_ =
-      startup_data->TakeConfigurationPolicyProvider();
-  user_cloud_policy_manager_ = static_cast<policy::UserCloudPolicyManager*>(
-      configuration_policy_provider_.get());
+  user_cloud_policy_manager_ = startup_data->TakeUserCloudPolicyManager();
   profile_policy_connector_ = startup_data->TakeProfilePolicyConnector();
   pref_registry_ = startup_data->TakePrefRegistrySyncable();
 
@@ -575,25 +572,23 @@
   // policy data immediately.
   bool force_immediate_policy_load = !async_prefs;
 
+  policy::UserCloudPolicyManager* user_cloud_policy_manager;
 #if defined(OS_CHROMEOS)
   if (force_immediate_policy_load)
     chromeos::DeviceSettingsService::Get()->LoadImmediately();
-  configuration_policy_provider_ =
+  configuration_policy_provider_chromeos_ =
       policy::UserPolicyManagerFactoryChromeOS::CreateForProfile(
           this, force_immediate_policy_load, io_task_runner_);
-  user_cloud_policy_manager_ = nullptr;
+  user_cloud_policy_manager = nullptr;
 #else
-  std::unique_ptr<policy::UserCloudPolicyManager> user_cloud_policy_manager =
-      CreateUserCloudPolicyManager(
-          GetPath(), GetPolicySchemaRegistryService()->registry(),
-          force_immediate_policy_load, io_task_runner_);
-  user_cloud_policy_manager_ = user_cloud_policy_manager.get();
-
-  configuration_policy_provider_ = std::move(user_cloud_policy_manager);
+  user_cloud_policy_manager_ = CreateUserCloudPolicyManager(
+      GetPath(), GetPolicySchemaRegistryService()->registry(),
+      force_immediate_policy_load, io_task_runner_);
+  user_cloud_policy_manager = user_cloud_policy_manager_.get();
 #endif
   profile_policy_connector_ =
       policy::CreateProfilePolicyConnectorForBrowserContext(
-          schema_registry_service_->registry(), user_cloud_policy_manager_,
+          schema_registry_service_->registry(), user_cloud_policy_manager,
           g_browser_process->browser_policy_connector(),
           force_immediate_policy_load, this);
 
@@ -833,7 +828,7 @@
   // condition.
 #if !defined(OS_CHROMEOS)
   profile_policy_connector_->Shutdown();
-  configuration_policy_provider_->Shutdown();
+  configuration_policy_provider()->Shutdown();
 #endif
 
   // This causes the Preferences file to be written to disk.
@@ -1125,10 +1120,19 @@
 
 #if !defined(OS_CHROMEOS)
 policy::UserCloudPolicyManager* ProfileImpl::GetUserCloudPolicyManager() {
-  return user_cloud_policy_manager_;
+  return user_cloud_policy_manager_.get();
 }
 #endif
 
+policy::ConfigurationPolicyProvider*
+ProfileImpl::configuration_policy_provider() {
+#if defined(OS_CHROMEOS)
+  return configuration_policy_provider_chromeos_.get();
+#else
+  return user_cloud_policy_manager_.get();
+#endif
+}
+
 policy::ProfilePolicyConnector* ProfileImpl::GetProfilePolicyConnector() {
   return profile_policy_connector_.get();
 }
diff --git a/chrome/browser/profiles/profile_impl.h b/chrome/browser/profiles/profile_impl.h
index acc3245..1d736cb 100644
--- a/chrome/browser/profiles/profile_impl.h
+++ b/chrome/browser/profiles/profile_impl.h
@@ -225,6 +225,8 @@
 
   void GetMediaCacheParameters(base::FilePath* cache_path, int* max_size);
 
+  policy::ConfigurationPolicyProvider* configuration_policy_provider();
+
   PrefChangeRegistrar pref_change_registrar_;
 
   base::FilePath path_;
@@ -242,12 +244,17 @@
   // PolicyService that the |prefs_| depend on, and must outlive |prefs_|. This
   // can be removed once |prefs_| becomes a KeyedService too.
   // |profile_policy_connector_| in turn depends on
-  // |configuration_policy_provider_|, which depends on
-  // |schema_registry_service_|.
+  // |user_cloud_policy_manager_| or |configuration_policy_provider_chromeos_|,
+  // which depend on |schema_registry_service_|.
   std::unique_ptr<policy::SchemaRegistryService> schema_registry_service_;
+
+#if defined(OS_CHROMEOS)
   std::unique_ptr<policy::ConfigurationPolicyProvider>
-      configuration_policy_provider_;
-  policy::UserCloudPolicyManager* user_cloud_policy_manager_;
+      configuration_policy_provider_chromeos_;
+#else
+  std::unique_ptr<policy::UserCloudPolicyManager> user_cloud_policy_manager_;
+#endif
+
   std::unique_ptr<policy::ProfilePolicyConnector> profile_policy_connector_;
 
   // Keep |prefs_| on top for destruction order because |extension_prefs_|,
diff --git a/chrome/browser/resources/app_management/main_view.html b/chrome/browser/resources/app_management/main_view.html
index 0f83e37..364a670 100644
--- a/chrome/browser/resources/app_management/main_view.html
+++ b/chrome/browser/resources/app_management/main_view.html
@@ -48,7 +48,7 @@
         apps="[[appsList]]"
         list-title="$i18n{appListTitle}"
         collapsed-size="[[appsList.length]]">
-      <template is="dom-repeat" items="[[appsList]]" as="app" notify-dom-change>
+      <template is="dom-repeat" items="[[appsList]]" as="app">
         <app-management-app-item app="[[app]]">
           <cr-icon-button slot="right-content"
               class="subpage-arrow app-management-item-arrow" actionable>
diff --git a/chrome/browser/resources/app_management/notifications_view.html b/chrome/browser/resources/app_management/notifications_view.html
index e3bb6e6..20b54a3 100644
--- a/chrome/browser/resources/app_management/notifications_view.html
+++ b/chrome/browser/resources/app_management/notifications_view.html
@@ -38,8 +38,7 @@
     <app-management-expandable-app-list
         apps="[[appsList_]]"
         collapsed-size="[[getCollapsedSize_(appsList_)]]">
-      <template is="dom-repeat" items="[[appsList_]]"
-                as="app" notify-dom-change>
+      <template is="dom-repeat" items="[[appsList_]]" as="app">
         <app-management-app-item app="[[app]]">
           <app-management-permission-toggle slot="right-content"
               app="[[app]]"
diff --git a/chrome/browser/resources/extensions/item_list.html b/chrome/browser/resources/extensions/item_list.html
index 4723999..40c9027 100644
--- a/chrome/browser/resources/extensions/item_list.html
+++ b/chrome/browser/resources/extensions/item_list.html
@@ -104,8 +104,7 @@
             initial render time and total render time. -->
           <template is="dom-repeat" items="[[extensions]]" initial-count="3"
               filter="[[computedFilter_]]"
-              rendered-item-count="{{shownExtensionsCount_::dom-change}}"
-              notify-dom-change>
+              rendered-item-count="{{shownExtensionsCount_::dom-change}}">
             <extensions-item id="[[item.id]]" data="[[item]]"
                 delegate="[[delegate]]" in-dev-mode="[[inDevMode]]">
             </extensions-item>
@@ -118,8 +117,7 @@
           <div class="items-container">
             <template is="dom-repeat" items="[[apps]]" initial-count="3"
                 filter="[[computedFilter_]]"
-                rendered-item-count="{{shownAppsCount_::dom-change}}"
-                notify-dom-change>
+                rendered-item-count="{{shownAppsCount_::dom-change}}">
               <extensions-item id="[[item.id]]" data="[[item]]"
                   delegate="[[delegate]]" in-dev-mode="[[inDevMode]]">
               </extensions-item>
diff --git a/chrome/browser/resources/history/history_item.html b/chrome/browser/resources/history/history_item.html
index 7d5c39c..d8a261a 100644
--- a/chrome/browser/resources/history/history_item.html
+++ b/chrome/browser/resources/history/history_item.html
@@ -200,7 +200,7 @@
           <span id="domain">[[item.domain]]</span>
         </div>
         <div id="star-container">
-          <template is="dom-if" if="[[item.starred]]" notify-dom-change>
+          <template is="dom-if" if="[[item.starred]]">
             <cr-icon-button id="bookmark-star" iron-icon="cr:star"
                 focus-row-control focus-type="star"
                 title="$i18n{removeBookmark}" on-click="onRemoveBookmarkTap_">
diff --git a/chrome/browser/resources/history/synced_device_card.html b/chrome/browser/resources/history/synced_device_card.html
index d7bdbf0..a8a9f1d 100644
--- a/chrome/browser/resources/history/synced_device_card.html
+++ b/chrome/browser/resources/history/synced_device_card.html
@@ -98,8 +98,7 @@
 
       <iron-collapse opened="{{opened}}" id="collapse">
         <div id="tab-item-list">
-          <template is="dom-repeat" items="[[tabs]]" as="tab" id="tab-list"
-              notify-dom-change>
+          <template is="dom-repeat" items="[[tabs]]" as="tab" id="tab-list">
             <div class="item-container">
               <a href="[[tab.url]]" class="website-link" title="[[tab.title]]"
                   on-click="openTab_" on-contextmenu="onLinkRightClick_">
diff --git a/chrome/browser/resources/local_ntp/custom_backgrounds.js b/chrome/browser/resources/local_ntp/custom_backgrounds.js
index 0261b9e..d9c4afc 100644
--- a/chrome/browser/resources/local_ntp/custom_backgrounds.js
+++ b/chrome/browser/resources/local_ntp/custom_backgrounds.js
@@ -80,6 +80,7 @@
   BACKGROUNDS_BUTTON: 'backgrounds-button',
   BACKGROUNDS_IMAGE_MENU: 'backgrounds-image-menu',
   BACKGROUNDS_MENU: 'backgrounds-menu',
+  BACKGROUNDS_UPLOAD: 'backgrounds-upload',
   CANCEL: 'bg-sel-footer-cancel',
   COLORS_BUTTON: 'colors-button',
   CUSTOMIZATION_MENU: 'customization-menu',
@@ -208,6 +209,12 @@
  */
 customBackgrounds.hideCustomLinkNotification = null;
 
+/*
+ * The currently selected option in the richer picker.
+ * @type {?Element}
+ * @private
+ */
+customBackgrounds.richerPicker_selectedOption = null;
 
 /**
  * Sets the visibility of the settings menu and individual options depending on
@@ -301,6 +308,22 @@
 };
 
 /**
+ * Apply selected styling to |elem| and remove it from the previously selected
+ * element.
+ * @param {?Element} elem The element to apply styling to.
+ */
+customBackgrounds.richerPicker_toggleSelectedOption = function(elem) {
+  if (!elem) {
+    return;
+  }
+  customBackgrounds.richerPicker_selectedOption.classList.toggle(
+      customBackgrounds.CLASSES.SELECTED, false);
+  elem.classList.toggle(customBackgrounds.CLASSES.SELECTED, true);
+
+  customBackgrounds.richerPicker_selectedOption = elem;
+};
+
+/**
  * Remove image tiles and maybe swap back to main background menu.
  * @param {boolean} showMenu Whether the main background menu should be shown.
  */
@@ -316,6 +339,7 @@
   menu.classList.toggle(customBackgrounds.CLASSES.ON_IMAGE_MENU, false);
   backgroundMenu.classList.toggle(
       customBackgrounds.CLASSES.MENU_SHOWN, showMenu);
+  backgroundMenu.scrollTop = 0;
 
   // Reset done button state.
   $(customBackgrounds.IDS.MENU_DONE).disabled = true;
@@ -867,7 +891,11 @@
 
 /* Close dialog when an image is selected via the file picker. */
 customBackgrounds.closeCustomizationDialog = function() {
-  $(customBackgrounds.IDS.EDIT_BG_DIALOG).close();
+  if (configData.richerPicker) {
+    $(customBackgrounds.IDS.CUSTOMIZATION_MENU).close();
+  } else {
+    $(customBackgrounds.IDS.EDIT_BG_DIALOG).close();
+  }
 };
 
 /*
@@ -928,6 +956,9 @@
   $(customBackgrounds.IDS.MENU_TITLE).dataset.mainTitle =
       $(customBackgrounds.IDS.MENU_TITLE).textContent;
 
+  customBackgrounds.richerPicker_selectedOption =
+      $(customBackgrounds.IDS.BACKGROUNDS_BUTTON);
+
   $(customBackgrounds.IDS.EDIT_BG_ICON)
       .setAttribute(
           'aria-label', configData.translatedStrings.customizeThisPage);
@@ -1335,6 +1366,14 @@
     }
   };
 
+  $(customBackgrounds.IDS.BACKGROUNDS_UPLOAD).onclick = uploadImageInteraction;
+  $(customBackgrounds.IDS.BACKGROUNDS_UPLOAD).onkeydown = function(event) {
+    if (event.keyCode === customBackgrounds.KEYCODES.ENTER ||
+        event.keyCode === customBackgrounds.KEYCODES.SPACE) {
+      uploadImageInteraction();
+    }
+  };
+
   $(customBackgrounds.IDS.BACKGROUNDS_DEFAULT).onclick = function() {
     const tile = $(customBackgrounds.IDS.BACKGROUNDS_DEFAULT_ICON);
     tile.dataset.url = '';
@@ -1344,19 +1383,40 @@
     customBackgrounds.richerPicker_selectTile(tile);
   };
 
-  $(customBackgrounds.IDS.BACKGROUNDS_BUTTON).onclick = function() {
+  const richerPickerOpenBackgrounds = function() {
     $(customBackgrounds.IDS.BACKGROUNDS_MENU)
         .classList.toggle(customBackgrounds.CLASSES.MENU_SHOWN, true);
     $(customBackgrounds.IDS.SHORTCUTS_MENU)
         .classList.toggle(customBackgrounds.CLASSES.MENU_SHOWN, false);
+    customBackgrounds.richerPicker_toggleSelectedOption(
+        $(customBackgrounds.IDS.BACKGROUNDS_BUTTON));
   };
 
-  $(customBackgrounds.IDS.SHORTCUTS_BUTTON).onclick = function() {
+  $(customBackgrounds.IDS.BACKGROUNDS_BUTTON).onclick =
+      richerPickerOpenBackgrounds;
+  $(customBackgrounds.IDS.BACKGROUNDS_BUTTON).onkeydown = function(event) {
+    if (event.keyCode === customBackgrounds.KEYCODES.ENTER ||
+        event.keyCode === customBackgrounds.KEYCODES.SPACE) {
+      richerPickerOpenBackgrounds();
+    }
+  };
+
+  const richerPickerOpenShortcuts = function() {
     customBackgrounds.richerPicker_resetImageMenu(false);
     $(customBackgrounds.IDS.BACKGROUNDS_MENU)
         .classList.toggle(customBackgrounds.CLASSES.MENU_SHOWN, false);
     $(customBackgrounds.IDS.SHORTCUTS_MENU)
         .classList.toggle(customBackgrounds.CLASSES.MENU_SHOWN, true);
+    customBackgrounds.richerPicker_toggleSelectedOption(
+        $(customBackgrounds.IDS.SHORTCUTS_BUTTON));
+  };
+
+  $(customBackgrounds.IDS.SHORTCUTS_BUTTON).onclick = richerPickerOpenShortcuts;
+  $(customBackgrounds.IDS.SHORTCUTS_BUTTON).onkeydown = function(event) {
+    if (event.keyCode === customBackgrounds.KEYCODES.ENTER ||
+        event.keyCode === customBackgrounds.KEYCODES.SPACE) {
+      richerPickerOpenShortcuts();
+    }
   };
 
   $(customBackgrounds.IDS.COLORS_BUTTON).onclick = function() {
diff --git a/chrome/browser/resources/local_ntp/local_ntp.css b/chrome/browser/resources/local_ntp/local_ntp.css
index 0ede361..b0cd997 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.css
+++ b/chrome/browser/resources/local_ntp/local_ntp.css
@@ -884,7 +884,8 @@
   background-color: rgba(var(--GB900-rgb), .1);
 }
 
-.menu-option:active {
+.menu-option:active,
+.menu-option.selected {
   background-color: rgb(232, 240, 254);
   color: rgb(var(--GB600-rgb));
 }
@@ -909,7 +910,8 @@
   width: 20px;
 }
 
-.menu-option:active .menu-option-icon-wrapper .menu-option-icon {
+.menu-option:active .menu-option-icon-wrapper .menu-option-icon,
+.menu-option.selected .menu-option-icon-wrapper .menu-option-icon {
   background-color: rgb(var(--GB600-rgb));
 }
 
diff --git a/chrome/browser/resources/local_ntp/local_ntp.html b/chrome/browser/resources/local_ntp/local_ntp.html
index 9236f9d..7a12890 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.html
+++ b/chrome/browser/resources/local_ntp/local_ntp.html
@@ -179,7 +179,7 @@
       </div>
     </div>
     <div id="menu-nav-panel">
-      <div id="backgrounds-button" class="menu-option" tabindex="0">
+      <div id="backgrounds-button" class="menu-option selected" tabindex="0">
         <div class="menu-option-icon-wrapper">
           <div ids="backgrounds-icon" class="menu-option-icon"></div>
         </div>
diff --git a/chrome/browser/resources/local_ntp/local_ntp.js b/chrome/browser/resources/local_ntp/local_ntp.js
index b0ff36b..ce63121 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.js
+++ b/chrome/browser/resources/local_ntp/local_ntp.js
@@ -484,6 +484,8 @@
   message.customBackground = info.customBackgroundConfigured;
   message.useTitleContainer = info.useTitleContainer;
   message.isDarkMode = getUseDarkChips(info);
+  message.iconBackgroundColor = convertToRGBAColor(info.iconBackgroundColor);
+  message.useWhiteAddIcon = info.useWhiteAddIcon;
 
   let titleColor = NTP_DESIGN.titleColor;
   if (!info.usingDefaultTheme && info.textColorRgba) {
diff --git a/chrome/browser/resources/local_ntp/most_visited_single.css b/chrome/browser/resources/local_ntp/most_visited_single.css
index 4b0ef15..549c36b 100644
--- a/chrome/browser/resources/local_ntp/most_visited_single.css
+++ b/chrome/browser/resources/local_ntp/most_visited_single.css
@@ -25,6 +25,7 @@
   --md-title-max-height: 28px;
 
   /* May be overridden by themes (on the body element). */
+  --icon-background-color: rgb(var(--GG100-rgb));
   --tile-title-color: rgb(var(--GG800-rgb));
 }
 
@@ -217,7 +218,7 @@
 
 .md-icon-background {
   align-items: center;
-  background-color: rgb(var(--GG100-rgb));
+  background-color: var(--icon-background-color);
   border-radius: 50%;
   display: flex;
   height: var(--md-icon-size);
@@ -225,17 +226,13 @@
   width: var(--md-icon-size);
 }
 
-html[darkmode=true] .md-icon-background {
-  background-color: rgb(var(--GG900-rgb));
-}
-
 .md-add-icon {
   background: url(chrome-search://most-visited/add_link.svg) no-repeat center;
   height: var(--md-add-size);
   width: var(--md-add-size);
 }
 
-html[darkmode=true] .md-add-icon {
+.use-white-add-icon .md-add-icon {
   background: url(chrome-search://most-visited/add_link_white.svg) no-repeat center;
 }
 
diff --git a/chrome/browser/resources/local_ntp/most_visited_single.js b/chrome/browser/resources/local_ntp/most_visited_single.js
index 7f02c2e..4fc4e5d6 100644
--- a/chrome/browser/resources/local_ntp/most_visited_single.js
+++ b/chrome/browser/resources/local_ntp/most_visited_single.js
@@ -807,9 +807,12 @@
  */
 function updateTheme(info) {
   document.body.style.setProperty('--tile-title-color', info.tileTitleColor);
+  document.body.style.setProperty(
+      '--icon-background-color', info.iconBackgroundColor);
   document.body.classList.toggle('dark-theme', info.isThemeDark);
   document.body.classList.toggle('use-title-container', info.useTitleContainer);
   document.body.classList.toggle('custom-background', info.customBackground);
+  document.body.classList.toggle('use-white-add-icon', info.useWhiteAddIcon);
   document.documentElement.setAttribute('darkmode', info.isDarkMode);
 
   // Reduce font weight on the default(white) background for Mac and CrOS.
@@ -959,8 +962,6 @@
     }
 
     data.tid = data.rid;
-    // Use a dark icon if dark mode is enabled.
-    data.dark = args.darkMode;
     if (!data.faviconUrl) {
       data.faviconUrl = 'chrome-search://favicon/size/16@' +
           window.devicePixelRatio + 'x/' + data.renderViewId + '/' + data.tid;
@@ -1199,7 +1200,8 @@
   mdTileInner.className = CLASSES.MD_TILE_INNER;
 
   const mdIcon = document.createElement('div');
-  mdIcon.className = CLASSES.MD_ICON;
+  mdIcon.classList.add(CLASSES.MD_ICON);
+  mdIcon.classList.add(CLASSES.MD_ICON_BACKGROUND);
 
   if (data.isAddButton) {
     const mdAdd = document.createElement('div');
@@ -1215,7 +1217,6 @@
     fi.title = '';
     fi.alt = '';
     const url = new URL('chrome-search://ntpicon/');
-    url.searchParams.set('dark', data.dark);
     url.searchParams.set('size', '24@' + window.devicePixelRatio + 'x');
     url.searchParams.set('url', data.url);
     fi.src = url.toString();
diff --git a/chrome/browser/resources/settings/controls/settings_textarea.html b/chrome/browser/resources/settings/controls/settings_textarea.html
index 93c267b..04e44c9 100644
--- a/chrome/browser/resources/settings/controls/settings_textarea.html
+++ b/chrome/browser/resources/settings/controls/settings_textarea.html
@@ -14,6 +14,10 @@
       #input-container {
         background-color: var(--cr-input-background-color);
       }
+
+      #underline {
+        position: static;
+      }
     </style>
     <div id="label" hidden="[[!label]]">[[label]]</div>
     <div id="input-container">
diff --git a/chrome/browser/resources/settings/people_page/sync_page.html b/chrome/browser/resources/settings/people_page/sync_page.html
index c68b40c..2973ec9 100644
--- a/chrome/browser/resources/settings/people_page/sync_page.html
+++ b/chrome/browser/resources/settings/people_page/sync_page.html
@@ -121,7 +121,7 @@
 
     <template is="dom-if" if="[[shouldShowExistingPassphraseBelowAccount_(
         unifiedConsentEnabled, syncPrefs.passphraseRequired)]]"
-        notify-dom-change on-dom-change="focusPassphraseInput_">
+        on-dom-change="focusPassphraseInput_">
       <div id="existingPassphrase" class="list-frame">
         <div id="existingPassphraseTitle" class="list-item">
             <div class="start settings-box-text">
@@ -171,7 +171,7 @@
             Consent feature is launched. -->
         <template is="dom-if" if="[[shouldShowExistingPassphraseInSyncSection_(
             unifiedConsentEnabled, syncPrefs.passphraseRequired)]]"
-            notify-dom-change on-dom-change="focusPassphraseInput_">
+            on-dom-change="focusPassphraseInput_">
           <div id="existingPassphrase" class="list-frame">
             <div class="list-item">
               <div class="settings-box-text"
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.html b/chrome/browser/resources/settings/privacy_page/privacy_page.html
index e5bbd124..42a93f9 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_page.html
+++ b/chrome/browser/resources/settings/privacy_page/privacy_page.html
@@ -61,8 +61,7 @@
           on-close="onDialogClosed_">
       </settings-clear-browsing-data-dialog>
     </template>
-    <template id="doNotTrackDialogIf" is="dom-if"
-        if="[[showDoNotTrackDialog_]]" notify-dom-change
+    <template id="doNotTrackDialogIf" is="dom-if" if="[[showDoNotTrackDialog_]]"
         on-dom-change="onDoNotTrackDomChange_">
       <cr-dialog id="confirmDoNotTrackDialog"
           close-text="$i18n{close}" on-cancel="onDoNotTrackDialogCancel_"
diff --git a/chrome/browser/resources/unpack_pak.py b/chrome/browser/resources/unpack_pak.py
index d73c71f..9199e49e 100755
--- a/chrome/browser/resources/unpack_pak.py
+++ b/chrome/browser/resources/unpack_pak.py
@@ -4,9 +4,6 @@
 # found in the LICENSE file.
 
 import argparse
-import collections
-import cStringIO
-import gzip
 import os
 import re
 import sys
@@ -23,16 +20,9 @@
 sys.path.insert(1, os.path.join(_SRC_PATH, 'tools', 'grit'))
 from grit.format import data_pack
 
-ResourceFile = collections.namedtuple('ResourceFile',
-                                      ['path', 'gzipped'])
-
-def UngzipString(data):
-  # Ungzipping using Python's built in gzip.
-  with gzip.GzipFile(fileobj=cStringIO.StringIO(data)) as gzip_file:
-    return gzip_file.read()
 
 def ParseLine(line):
-  return re.match('  {"([^"]+)", ([^},]+)(?:, ([^},]+))?', line)
+  return re.match('  {"([^"]+)", ([^},]+)', line)
 
 
 def Unpack(pak_path, out_path):
@@ -53,29 +43,23 @@
   assert resource_ids
 
   # Associate numerical string IDs to files.
-  resource_files = dict()
+  resource_filenames = dict()
   resources_map_path = os.path.join(pak_dir, 'grit', pak_id + '_map.cc')
   with open(resources_map_path) as resources_map:
     for line in resources_map:
       res = ParseLine(line)
       if res:
-        resource_files[res.group(2)] = ResourceFile(
-          path=res.group(1),
-          gzipped=res.group(3) == 'true')
-  assert resource_files
+        resource_filenames[res.group(2)] = res.group(1)
+  assert resource_filenames
 
   # Extract packed files, while preserving directory structure.
   for (resource_id, text) in data.resources.iteritems():
-    resource_file = resource_files[resource_ids[resource_id]]
-    file_path = resource_file.path
-    file_gzipped = resource_file.gzipped
-    file_dir = os.path.join(out_path, os.path.dirname(file_path))
-    if not os.path.exists(file_dir):
-      os.makedirs(file_dir)
-    if file_gzipped:
-      text = UngzipString(text)
-    with open(os.path.join(out_path, file_path), 'w') as f:
-      f.write(text)
+    filename = resource_filenames[resource_ids[resource_id]]
+    dirname = os.path.join(out_path, os.path.dirname(filename))
+    if not os.path.exists(dirname):
+      os.makedirs(dirname)
+    with open(os.path.join(out_path, filename), 'w') as file:
+      file.write(text)
 
 
 def main():
diff --git a/chrome/browser/resources/unpack_pak_test.py b/chrome/browser/resources/unpack_pak_test.py
index cbec57a..ca5b87a 100755
--- a/chrome/browser/resources/unpack_pak_test.py
+++ b/chrome/browser/resources/unpack_pak_test.py
@@ -15,13 +15,6 @@
     self.assertTrue(unpack_pak.ParseLine('  {"path.js", IDR_PATH, false}'))
     self.assertTrue(unpack_pak.ParseLine('  {"path.js", IDR_PATH, true}'))
 
-  def testUngzipString(self):
-    self.assertEqual(
-      unpack_pak.UngzipString(
-        '\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\xcbH\xcd\xc9\xc9W' +
-        '(\xcf/\xcaI\x01\x00\x85\x11J\r\x0b\x00\x00\x00'),
-      'hello world')
-
 
 if __name__ == '__main__':
   unittest.main()
diff --git a/chrome/browser/search/ntp_icon_source.cc b/chrome/browser/search/ntp_icon_source.cc
index 8b0902d..2367e2b 100644
--- a/chrome/browser/search/ntp_icon_source.cc
+++ b/chrome/browser/search/ntp_icon_source.cc
@@ -65,9 +65,6 @@
 // changing the algorithm in RenderIconBitmap() that guarantees contrast.
 constexpr SkColor kFallbackIconLetterColor = SK_ColorWHITE;
 
-// Whether or not the requested icon is being rendered on a dark background.
-const char kDarkModeParameter[] = "dark";
-
 // The requested size of the icon.
 const char kSizeParameter[] = "size";
 
@@ -98,9 +95,6 @@
 
   // The device scale factor of the requested icon.
   float device_scale_factor = 1.0;
-
-  // True if a dark mode icon should be used.
-  bool is_dark_mode = false;
 };
 
 float GetMaxDeviceScaleFactor() {
@@ -127,9 +121,7 @@
 
   for (net::QueryIterator it(request); !it.IsAtEnd(); it.Advance()) {
     std::string key = it.GetKey();
-    if (key == kDarkModeParameter) {
-      parsed.is_dark_mode = it.GetValue() == "true";
-    } else if (key == kSizeParameter) {
+    if (key == kSizeParameter) {
       std::vector<std::string> pieces =
           base::SplitString(it.GetUnescapedValue(), "@", base::TRIM_WHITESPACE,
                             base::SPLIT_WANT_NONEMPTY);
@@ -221,24 +213,18 @@
   return SkColorSetRGB(hash[0], hash[1], hash[2]);
 }
 
-// For the given |icon_url|, will render |favicon| within a gray, circular
-// background (dark gray if |is_dark_mode|). If |favicon| is not specifed, will
-// use a colored circular monogram instead.
+// For the given |icon_url|, will render |favicon|. If |favicon| is not
+// specified, will use a colored circular monogram instead.
 std::vector<unsigned char> RenderIconBitmap(const GURL& icon_url,
                                             const SkBitmap& favicon,
                                             int icon_size,
-                                            int fallback_size,
-                                            bool is_dark_mode) {
+                                            int fallback_size) {
   SkBitmap bitmap;
   bitmap.allocN32Pixels(icon_size, icon_size, false);
   cc::SkiaPaintCanvas paint_canvas(bitmap);
   gfx::Canvas canvas(&paint_canvas, 1.f);
   canvas.DrawColor(SK_ColorTRANSPARENT, SkBlendMode::kSrc);
 
-  // Draw the gray background.
-  SkColor favicon_bg = is_dark_mode ? gfx::kGoogleGrey900 : gfx::kGoogleGrey100;
-  DrawCircleInCanvas(&canvas, icon_size, /*offset=*/0,
-                     /*background_color=*/favicon_bg);
   DrawFavicon(favicon, &canvas, icon_size);
 
   // If necessary, draw the colored fallback monogram.
@@ -263,13 +249,11 @@
   NtpIconRequest(const content::URLDataSource::GotDataCallback& cb,
                  const GURL& path,
                  int icon_size_in_pixels,
-                 float scale,
-                 bool is_dark_mode)
+                 float scale)
       : callback(cb),
         path(path),
         icon_size_in_pixels(icon_size_in_pixels),
-        device_scale_factor(scale),
-        is_dark_mode(is_dark_mode) {}
+        device_scale_factor(scale) {}
   NtpIconRequest(const NtpIconRequest& other) = default;
   ~NtpIconRequest() {}
 
@@ -277,7 +261,6 @@
   GURL path;
   int icon_size_in_pixels;
   float device_scale_factor;
-  bool is_dark_mode;
 };
 
 NtpIconSource::NtpIconSource(Profile* profile)
@@ -308,7 +291,7 @@
     int icon_size_in_pixels =
         std::ceil(parsed.size_in_dip * parsed.device_scale_factor);
     NtpIconRequest request(callback, parsed.url, icon_size_in_pixels,
-                           parsed.device_scale_factor, parsed.is_dark_mode);
+                           parsed.device_scale_factor);
 
     // Check if the requested URL is part of the prepopulated pages (currently,
     // only the Web Store).
@@ -478,6 +461,6 @@
       std::round(kFallbackSizeDip * request.device_scale_factor * 0.5) * 2.0;
   std::vector<unsigned char> bitmap_data =
       RenderIconBitmap(request.path, bitmap, desired_overall_size_in_pixel,
-                       desired_fallback_size_in_pixel, request.is_dark_mode);
+                       desired_fallback_size_in_pixel);
   request.callback.Run(base::RefCountedBytes::TakeVector(&bitmap_data));
 }
diff --git a/chrome/browser/serial/chrome_serial_browsertest.cc b/chrome/browser/serial/chrome_serial_browsertest.cc
new file mode 100644
index 0000000..40615c1
--- /dev/null
+++ b/chrome/browser/serial/chrome_serial_browsertest.cc
@@ -0,0 +1,63 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "chrome/browser/serial/serial_chooser_context.h"
+#include "chrome/browser/serial/serial_chooser_context_factory.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/bubble/bubble_manager.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "services/device/public/cpp/test/fake_serial_port_manager.h"
+#include "services/device/public/mojom/serial.mojom.h"
+#include "url/gurl.h"
+
+namespace {
+
+class SerialTest : public InProcessBrowserTest {
+ public:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    InProcessBrowserTest::SetUpCommandLine(command_line);
+    command_line->AppendSwitchASCII("enable-blink-features", "Serial");
+  }
+
+  void SetUpOnMainThread() override {
+    embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
+    ASSERT_TRUE(embedded_test_server()->Start());
+
+    device::mojom::SerialPortManagerPtr port_manager_ptr;
+    port_manager_.AddBinding(mojo::MakeRequest(&port_manager_ptr));
+    SerialChooserContextFactory::GetForProfile(browser()->profile())
+        ->SetPortManagerForTesting(std::move(port_manager_ptr));
+
+    GURL url = embedded_test_server()->GetURL("localhost", "/simple_page.html");
+    ui_test_utils::NavigateToURL(browser(), url);
+  }
+
+ private:
+  device::FakeSerialPortManager port_manager_;
+};
+
+IN_PROC_BROWSER_TEST_F(SerialTest, NavigateWithChooserCrossOrigin) {
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+
+  content::TestNavigationObserver observer(
+      web_contents, 1 /* number_of_navigations */,
+      content::MessageLoopRunner::QuitMode::DEFERRED);
+
+  EXPECT_TRUE(content::ExecJs(web_contents,
+                              R"(navigator.serial.requestPort({});
+         document.location.href = "https://google.com";)"));
+
+  observer.Wait();
+  EXPECT_EQ(0u, browser()->GetBubbleManager()->GetBubbleCountForTesting());
+}
+
+}  // namespace
diff --git a/chrome/browser/startup_data.cc b/chrome/browser/startup_data.cc
index a1560dc..824b5cd 100644
--- a/chrome/browser/startup_data.cc
+++ b/chrome/browser/startup_data.cc
@@ -86,9 +86,9 @@
   return std::move(schema_registry_service_);
 }
 
-std::unique_ptr<policy::ConfigurationPolicyProvider>
-StartupData::TakeConfigurationPolicyProvider() {
-  return std::move(configuration_policy_provider_);
+std::unique_ptr<policy::UserCloudPolicyManager>
+StartupData::TakeUserCloudPolicyManager() {
+  return std::move(user_cloud_policy_manager_);
 }
 
 std::unique_ptr<policy::ProfilePolicyConnector>
@@ -136,17 +136,16 @@
       std::move(schema_registry), browser_policy_connector->GetChromeSchema(),
       browser_policy_connector->GetSchemaRegistry());
 
-  configuration_policy_provider_ = CreateUserCloudPolicyManager(
+  user_cloud_policy_manager_ = CreateUserCloudPolicyManager(
       path, schema_registry_service_->registry(),
       true /* force_immediate_policy_load */, io_task_runner);
 
-  auto* user_cloud_policy_manager = static_cast<policy::CloudPolicyManager*>(
-      configuration_policy_provider_.get());
   profile_policy_connector_ = policy::CreateAndInitProfilePolicyConnector(
       schema_registry_service_->registry(),
       static_cast<policy::ChromeBrowserPolicyConnector*>(
           browser_policy_connector),
-      user_cloud_policy_manager, user_cloud_policy_manager->core()->store(),
+      user_cloud_policy_manager_.get(),
+      user_cloud_policy_manager_->core()->store(),
       true /* force_immediate_policy_load*/, nullptr /* user */);
 
   RegisterProfilePrefs(false /* is_signin_profile */,
diff --git a/chrome/browser/startup_data.h b/chrome/browser/startup_data.h
index fd50ab1..66dce52 100644
--- a/chrome/browser/startup_data.h
+++ b/chrome/browser/startup_data.h
@@ -16,9 +16,9 @@
 }
 
 namespace policy {
-class ConfigurationPolicyProvider;
 class ProfilePolicyConnector;
 class SchemaRegistryService;
+class UserCloudPolicyManager;
 }  // namespace policy
 
 namespace sync_preferences {
@@ -53,9 +53,8 @@
   // Passes ownership of the |schema_registry_service_| to the caller.
   std::unique_ptr<policy::SchemaRegistryService> TakeSchemaRegistryService();
 
-  // Passes ownership of the |configuration_policy_provider_| to the caller.
-  std::unique_ptr<policy::ConfigurationPolicyProvider>
-  TakeConfigurationPolicyProvider();
+  // Passes ownership of the |user_cloud_policy_manager_| to the caller.
+  std::unique_ptr<policy::UserCloudPolicyManager> TakeUserCloudPolicyManager();
 
   // Passes ownership of the |profile_policy_connector_| to the caller.
   std::unique_ptr<policy::ProfilePolicyConnector> TakeProfilePolicyConnector();
@@ -80,8 +79,7 @@
   std::unique_ptr<ProfileKey> key_;
 
   std::unique_ptr<policy::SchemaRegistryService> schema_registry_service_;
-  std::unique_ptr<policy::ConfigurationPolicyProvider>
-      configuration_policy_provider_;
+  std::unique_ptr<policy::UserCloudPolicyManager> user_cloud_policy_manager_;
   std::unique_ptr<policy::ProfilePolicyConnector> profile_policy_connector_;
 
   scoped_refptr<user_prefs::PrefRegistrySyncable> pref_registry_;
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index c75c9e1..1e1f6c8 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -94,14 +94,6 @@
     "blocked_content/tab_under_navigation_throttle.h",
     "blocked_content/url_list_manager.cc",
     "blocked_content/url_list_manager.h",
-    "bookmarks/bookmark_bar.h",
-    "bookmarks/bookmark_bubble_observer.h",
-    "bookmarks/bookmark_editor.cc",
-    "bookmarks/bookmark_editor.h",
-    "bookmarks/bookmark_utils.cc",
-    "bookmarks/bookmark_utils.h",
-    "bookmarks/recently_used_folders_combo_model.cc",
-    "bookmarks/recently_used_folders_combo_model.h",
     "browser_dialogs.cc",
     "browser_dialogs.h",
     "browser_navigator_params.cc",
@@ -188,9 +180,6 @@
     "startup/bad_flags_prompt.cc",
     "startup/bad_flags_prompt.h",
     "status_bubble.h",
-    "sync/bubble_sync_promo_delegate.h",
-    "sync/profile_signin_confirmation_helper.cc",
-    "sync/profile_signin_confirmation_helper.h",
     "sync/tab_contents_synced_tab_delegate.cc",
     "sync/tab_contents_synced_tab_delegate.h",
     "tab_contents/chrome_web_contents_view_delegate.h",
@@ -800,17 +789,25 @@
       "bluetooth/bluetooth_scanning_prompt_desktop.h",
       "bluetooth/chrome_extension_bluetooth_chooser.cc",
       "bluetooth/chrome_extension_bluetooth_chooser.h",
+      "bookmarks/bookmark_bar.h",
+      "bookmarks/bookmark_bubble_observer.h",
       "bookmarks/bookmark_context_menu_controller.cc",
       "bookmarks/bookmark_context_menu_controller.h",
       "bookmarks/bookmark_drag_drop.cc",
       "bookmarks/bookmark_drag_drop.h",
+      "bookmarks/bookmark_editor.cc",
+      "bookmarks/bookmark_editor.h",
       "bookmarks/bookmark_stats.cc",
       "bookmarks/bookmark_stats.h",
       "bookmarks/bookmark_tab_helper.cc",
       "bookmarks/bookmark_tab_helper.h",
       "bookmarks/bookmark_tab_helper_observer.h",
+      "bookmarks/bookmark_utils.cc",
+      "bookmarks/bookmark_utils.h",
       "bookmarks/bookmark_utils_desktop.cc",
       "bookmarks/bookmark_utils_desktop.h",
+      "bookmarks/recently_used_folders_combo_model.cc",
+      "bookmarks/recently_used_folders_combo_model.h",
       "browser.cc",
       "browser.h",
       "browser_command_controller.cc",
@@ -1033,6 +1030,9 @@
       "sync/browser_synced_window_delegate.h",
       "sync/browser_synced_window_delegates_getter.cc",
       "sync/browser_synced_window_delegates_getter.h",
+      "sync/bubble_sync_promo_delegate.h",
+      "sync/profile_signin_confirmation_helper.cc",
+      "sync/profile_signin_confirmation_helper.h",
       "sync/sync_promo_ui.cc",
       "sync/sync_promo_ui.h",
       "tab_contents/tab_contents_iterator.cc",
@@ -2429,6 +2429,9 @@
       "bubble_anchor_util.h",
       "manifest_web_app_browser_controller.cc",
       "manifest_web_app_browser_controller.h",
+      "send_tab_to_self/send_tab_to_self_bubble_controller.cc",
+      "send_tab_to_self/send_tab_to_self_bubble_controller.h",
+      "send_tab_to_self/send_tab_to_self_bubble_view.h",
 
       # This test header is included because it contains forward declarations
       # needed for "friend" statements for use in tests.
@@ -2871,8 +2874,10 @@
       "views/sad_tab_view.h",
       "views/safe_browsing/password_reuse_modal_warning_dialog.cc",
       "views/safe_browsing/password_reuse_modal_warning_dialog.h",
-      "views/send_tab_to_self/share_icon_view.cc",
-      "views/send_tab_to_self/share_icon_view.h",
+      "views/send_tab_to_self/send_tab_to_self_bubble_view_impl.cc",
+      "views/send_tab_to_self/send_tab_to_self_bubble_view_impl.h",
+      "views/send_tab_to_self/send_tab_to_self_icon_view.cc",
+      "views/send_tab_to_self/send_tab_to_self_icon_view.h",
       "views/session_crashed_bubble_view.cc",
       "views/session_crashed_bubble_view.h",
       "views/simple_message_box_views.cc",
diff --git a/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc b/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc
index dcaff1f..8533891 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc
+++ b/chrome/browser/ui/app_list/arc/arc_app_list_prefs.cc
@@ -14,6 +14,7 @@
 #include "base/files/file_util.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/sequenced_task_runner.h"
 #include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/task/post_task.h"
@@ -51,6 +52,7 @@
 namespace {
 
 constexpr char kActivity[] = "activity";
+constexpr char kFrameworkPackageName[] = "android";
 constexpr char kIconResourceId[] = "icon_resource_id";
 constexpr char kIconVersion[] = "icon_version";
 constexpr char kInstallTime[] = "install_time";
@@ -231,6 +233,8 @@
     user_prefs::PrefRegistrySyncable* registry) {
   registry->RegisterDictionaryPref(arc::prefs::kArcApps);
   registry->RegisterDictionaryPref(arc::prefs::kArcPackages);
+  registry->RegisterIntegerPref(arc::prefs::kArcFrameworkVersion,
+                                -1 /* default_value */);
   registry->RegisterDictionaryPref(
       arc::prefs::kArcSetNotificationsEnabledDeferred);
   ArcDefaultAppList::RegisterProfilePrefs(registry);
@@ -350,6 +354,9 @@
     : profile_(profile),
       prefs_(profile->GetPrefs()),
       app_connection_holder_for_testing_(app_connection_holder_for_testing),
+      file_task_runner_(base::CreateSequencedTaskRunnerWithTraits(
+          {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+           base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
       weak_ptr_factory_(this) {
   VLOG(1) << "ARC app list prefs created";
   DCHECK(profile);
@@ -1254,6 +1261,20 @@
     package_dict->RemoveKey(kPermissions);
   }
 
+  // TODO (crbug.com/xxxxx): Remove in M78. This is required to force updating
+  // icons for all packages in case framework version is changed. Prior to this
+  // change |InvalidatePackageIcons| for framework did not refresh all packages.
+  if (package_name == kFrameworkPackageName) {
+    const int last_framework_version =
+        profile_->GetPrefs()->GetInteger(arc::prefs::kArcFrameworkVersion);
+    if (last_framework_version != package.package_version) {
+      InvalidatePackageIcons(package_name);
+      profile_->GetPrefs()->SetInteger(arc::prefs::kArcFrameworkVersion,
+                                       package.package_version);
+    }
+    return;
+  }
+
   if (old_package_version == -1 ||
       old_package_version == package.package_version) {
     return;
@@ -1381,14 +1402,22 @@
 }
 
 void ArcAppListPrefs::InvalidatePackageIcons(const std::string& package_name) {
+  if (package_name == kFrameworkPackageName) {
+    VLOG(1)
+        << "Android framework was changed, refreshing icons for all packages";
+    for (const auto& package_name_to_invalidate : GetPackagesFromPrefs()) {
+      if (package_name_to_invalidate != kFrameworkPackageName)
+        InvalidatePackageIcons(package_name_to_invalidate);
+    }
+  }
   for (const std::string& app_id : GetAppsForPackage(package_name))
     InvalidateAppIcons(app_id);
 }
 
 void ArcAppListPrefs::ScheduleAppFolderDeletion(const std::string& app_id) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  base::PostTaskWithTraits(
-      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+  file_task_runner_->PostTask(
+      FROM_HERE,
       base::BindOnce(&DeleteAppFolderFromFileThread, GetAppPath(app_id)));
 }
 
@@ -1740,8 +1769,8 @@
                                   const ArcAppIconDescriptor& descriptor,
                                   const std::vector<uint8_t>& content_png) {
   const base::FilePath icon_path = GetIconPath(app_id, descriptor);
-  base::PostTaskWithTraitsAndReplyWithResult(
-      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+  base::PostTaskAndReplyWithResult(
+      file_task_runner_.get(), FROM_HERE,
       base::BindOnce(&InstallIconFromFileThread, icon_path, content_png),
       base::BindOnce(&ArcAppListPrefs::OnIconInstalled,
                      weak_ptr_factory_.GetWeakPtr(), app_id, descriptor));
diff --git a/chrome/browser/ui/app_list/arc/arc_app_list_prefs.h b/chrome/browser/ui/app_list/arc/arc_app_list_prefs.h
index 581127d1..e025b74 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_list_prefs.h
+++ b/chrome/browser/ui/app_list/arc/arc_app_list_prefs.h
@@ -17,6 +17,7 @@
 #include "base/callback.h"
 #include "base/files/file_path.h"
 #include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/optional.h"
@@ -40,6 +41,10 @@
 class ConnectionHolder;
 }  // namespace arc
 
+namespace base {
+class SequencedTaskRunner;
+}  // namespace base
+
 namespace content {
 class BrowserContext;
 }  // namespace content
@@ -549,6 +554,8 @@
   base::OneShotTimer detect_default_app_availability_timeout_;
   // Set of currently installing apps_.
   std::unordered_set<std::string> apps_installations_;
+  // To execute file operations in sequence.
+  scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
 
   arc::ArcPackageSyncableService* sync_service_ = nullptr;
 
diff --git a/chrome/browser/ui/app_list/arc/arc_app_unittest.cc b/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
index c3adbd7..1d73404 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
+++ b/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
@@ -78,6 +78,10 @@
 namespace {
 
 constexpr char kTestPackageName[] = "fake.package.name2";
+constexpr char kFrameworkPackageName[] = "android";
+
+constexpr int kFrameworkNycVersion = 25;
+constexpr int kFrameworkPiVersion = 28;
 
 class FakeAppIconLoaderDelegate : public AppIconLoaderDelegate {
  public:
@@ -142,15 +146,17 @@
   } while (!base::PathExists(icon_path));
 }
 
-void WaitForIconUpdates(Profile* profile,
-                        const std::string& app_id,
-                        size_t expected_updates) {
+void WaitForIconUpdates(Profile* profile, const std::string& app_id) {
   FakeAppIconLoaderDelegate delegate;
   ArcAppIconLoader icon_loader(
       profile, app_list::AppListConfig::instance().grid_icon_dimension(),
       &delegate);
+
+  // |FetchImage| ensures all supported factors are loaded.
+  const std::vector<ui::ScaleFactor>& scale_factors =
+      ui::GetSupportedScaleFactors();
   icon_loader.FetchImage(app_id);
-  delegate.WaitForIconUpdates(expected_updates);
+  delegate.WaitForIconUpdates(scale_factors.size());
 }
 
 enum class ArcState {
@@ -453,11 +459,17 @@
   }
 
   arc::mojom::ArcPackageInfoPtr CreatePackage(const std::string& package_name) {
+    return CreatePackageWithVersion(package_name, 1 /* package_version */);
+  }
+
+  arc::mojom::ArcPackageInfoPtr CreatePackageWithVersion(
+      const std::string& package_name,
+      int package_version) {
     base::flat_map<arc::mojom::AppPermission, bool> permissions;
     permissions.insert(std::make_pair(arc::mojom::AppPermission::CAMERA, 0));
     permissions.insert(std::make_pair(arc::mojom::AppPermission::LOCATION, 1));
     return arc::mojom::ArcPackageInfo::New(
-        package_name, 1 /* package_version */, 1 /* last_backup_android_id */,
+        package_name, package_version, 1 /* last_backup_android_id */,
         1 /* last_backup_time */, true /* sync */, false /* system */,
         false /* vpn_provider */, nullptr /* web_app_info */, permissions);
   }
@@ -572,17 +584,14 @@
     DCHECK(prefs);
 
     app_instance()->SendRefreshAppList({app});
-    auto package = CreatePackage(app.package_name);
-    package->package_version = package_version;
-    AddPackage(std::move(package));
+    AddPackage(CreatePackageWithVersion(app.package_name, package_version));
     return app_id;
   }
 
   // Simulates package of the test app is updated.
   void UpdatePackage(int package_version) {
     const arc::mojom::AppInfo app = test_app();
-    auto package = CreatePackage(app.package_name);
-    package->package_version = package_version;
+    auto package = CreatePackageWithVersion(app.package_name, package_version);
     app_instance()->SendPackageAppListRefreshed(package->package_name, {app});
     app_instance()->SendPackageModified(std::move(package));
   }
@@ -602,7 +611,7 @@
     ASSERT_EQ(GetAppListIconDimensionForScaleFactor(ui::SCALE_FACTOR_200P),
               icon_requests[1]->dimension());
 
-    WaitForIconUpdates(profile_.get(), app_id, 2);
+    WaitForIconUpdates(profile_.get(), app_id);
   }
 
   arc::mojom::AppInfo test_app() const { return fake_apps()[0]; }
@@ -734,8 +743,8 @@
   RemovePackage(kTestPackageName);
   ValidateHavePackages(fake_packages());
 
-  auto package = CreatePackage(kTestPackageName);
-  package->package_version = 2;
+  auto package =
+      CreatePackageWithVersion(kTestPackageName, 2 /* package_version */);
   package->last_backup_android_id = 2;
   package->last_backup_time = 2;
   AddPackage(package);
@@ -1122,9 +1131,10 @@
   std::set<int> expected_dimensions;
   ArcAppItem* app_item = FindArcItem(ArcAppTest::GetAppId(shortcut));
   ASSERT_NE(nullptr, app_item);
+  WaitForIconUpdates(profile_.get(), app_item->id());
+
   const std::vector<ui::ScaleFactor>& scale_factors =
       ui::GetSupportedScaleFactors();
-  WaitForIconUpdates(profile_.get(), app_item->id(), scale_factors.size());
   for (auto& scale_factor : scale_factors) {
     expected_dimensions.insert(
         GetAppListIconDimensionForScaleFactor(scale_factor));
@@ -1224,7 +1234,7 @@
   // This initiates async loading.
   app_item->icon().GetRepresentation(scale);
 
-  WaitForIconUpdates(profile_.get(), app_id, 1);
+  WaitForIconUpdates(profile_.get(), app_id);
 
   // Validate that icons are installed, have right content and icon is
   // refreshed for ARC app item.
@@ -1260,7 +1270,7 @@
   const base::FilePath app_path = prefs->GetAppPath(app_id);
 
   // Now send generated icon for the app.
-  WaitForIconUpdates(profile_.get(), app_id, 1);
+  WaitForIconUpdates(profile_.get(), app_id);
   EXPECT_TRUE(IsIconCreated(prefs, app_id, scale_factor));
 
   // Send empty app list. This will delete app and its folder.
@@ -2021,10 +2031,7 @@
   ASSERT_TRUE(prefs);
 
   const std::string app_id = StartApp(1 /* package_version */);
-  prefs->MaybeRequestIcon(app_id,
-                          GetAppListIconDescriptor(ui::SCALE_FACTOR_100P));
-
-  WaitForIconUpdates(profile_.get(), app_id, 1);
+  WaitForIconUpdates(profile_.get(), app_id);
 
   // Simulate ARC restart.
   RestartArc();
@@ -2046,6 +2053,49 @@
   EXPECT_TRUE(app_instance()->icon_requests().empty());
 }
 
+TEST_P(ArcAppModelIconTest, IconInvalidationOnFrameworkUpdate) {
+  ArcAppListPrefs* const prefs = ArcAppListPrefs::Get(profile_.get());
+  ASSERT_TRUE(prefs);
+
+  const arc::mojom::AppInfo app = test_app();
+  const std::string app_id = ArcAppTest::GetAppId(app);
+  app_instance()->SendRefreshAppList({app});
+
+  std::vector<arc::mojom::ArcPackageInfoPtr> packages;
+  packages.emplace_back(CreatePackage(app.package_name));
+  packages.emplace_back(
+      CreatePackageWithVersion(kFrameworkPackageName, kFrameworkNycVersion));
+  app_instance()->SendRefreshPackageList(std::move(packages));
+
+  WaitForIconUpdates(profile_.get(), app_id);
+
+  RestartArc();
+
+  app_instance()->SendRefreshAppList({app});
+
+  // Framework is the same, no update.
+  packages.emplace_back(CreatePackage(app.package_name));
+  packages.emplace_back(
+      CreatePackageWithVersion(kFrameworkPackageName, kFrameworkNycVersion));
+  app_instance()->SendRefreshPackageList(std::move(packages));
+
+  EXPECT_TRUE(app_instance()->icon_requests().empty());
+
+  RestartArc();
+
+  app_instance()->SendRefreshAppList({app});
+
+  // Framework was updated, app icons should be updated even app's package is
+  // the same.
+  packages.emplace_back(CreatePackage(app.package_name));
+  packages.emplace_back(
+      CreatePackageWithVersion(kFrameworkPackageName, kFrameworkPiVersion));
+  app_instance()->SendRefreshPackageList(std::move(packages));
+
+  EXPECT_FALSE(app_instance()->icon_requests().empty());
+  EnsureIconsUpdated();
+}
+
 // This verifies that app icons are invalidated in case icon version was
 // changed which means ARC sends icons using updated processing.
 TEST_P(ArcAppModelIconTest, IconInvalidationOnIconVersionUpdate) {
diff --git a/chrome/browser/ui/app_list/search/omnibox_result.cc b/chrome/browser/ui/app_list/search/omnibox_result.cc
index 8f3d2b4..731b8a3 100644
--- a/chrome/browser/ui/app_list/search/omnibox_result.cc
+++ b/chrome/browser/ui/app_list/search/omnibox_result.cc
@@ -34,8 +34,6 @@
 // #000 at 87% opacity.
 constexpr SkColor kListIconColor = SkColorSetARGB(0xDE, 0x00, 0x00, 0x00);
 
-constexpr SkColor kImageButtonColor = gfx::kGoogleGrey700;
-
 int ACMatchStyleToTagStyle(int styles) {
   int tag_styles = 0;
   if (styles & ACMatchClassification::URL)
@@ -299,14 +297,14 @@
     switch (button_action) {
       case ash::OmniBoxZeroStateAction::kRemoveSuggestion:
         button_image = gfx::CreateVectorIcon(
-            kSearchResultRemoveIcon, kImageButtonIconSize, kImageButtonColor);
+            kSearchResultRemoveIcon, kImageButtonIconSize, kListIconColor);
         button_tooltip = l10n_util::GetStringFUTF16(
             IDS_APP_LIST_REMOVE_SUGGESTION_ACCESSIBILITY_NAME, title());
         visible_on_hover = true;  // visible upon hovering
         break;
       case ash::OmniBoxZeroStateAction::kAppendSuggestion:
         button_image = gfx::CreateVectorIcon(
-            kSearchResultAppendIcon, kImageButtonIconSize, kImageButtonColor);
+            kSearchResultAppendIcon, kImageButtonIconSize, kListIconColor);
         button_tooltip = l10n_util::GetStringFUTF16(
             IDS_APP_LIST_APPEND_SUGGESTION_ACCESSIBILITY_NAME, title());
         visible_on_hover = false;  // always visible
diff --git a/chrome/browser/ui/ash/network/mobile_data_notifications_unittest.cc b/chrome/browser/ui/ash/network/mobile_data_notifications_unittest.cc
index 499f02a..3503d59 100644
--- a/chrome/browser/ui/ash/network/mobile_data_notifications_unittest.cc
+++ b/chrome/browser/ui/ash/network/mobile_data_notifications_unittest.cc
@@ -133,7 +133,7 @@
     service_test->ClearServices();
     service_test->AddService(kCellularServicePath, kCellularGuid,
                              "cellular1" /* name */, shill::kTypeCellular,
-                             "activated", true /* visible */);
+                             shill::kStateIdle, true /* visible */);
     service_test->SetServiceProperty(
         kCellularServicePath, shill::kActivationStateProperty,
         base::Value(shill::kActivationStateActivated));
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index b62cd84..1ede853 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -103,8 +103,6 @@
 #include "chrome/browser/ui/bluetooth/bluetooth_chooser_desktop.h"
 #include "chrome/browser/ui/bluetooth/bluetooth_scanning_prompt_controller.h"
 #include "chrome/browser/ui/bluetooth/bluetooth_scanning_prompt_desktop.h"
-#include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
-#include "chrome/browser/ui/bookmarks/bookmark_utils.h"
 #include "chrome/browser/ui/browser_command_controller.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_content_setting_bubble_model_delegate.h"
@@ -223,6 +221,10 @@
 #include "ui/gfx/geometry/point.h"
 #include "ui/shell_dialogs/selected_file_info.h"
 
+#if !defined(OS_ANDROID)
+#include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
+#include "chrome/browser/ui/bookmarks/bookmark_utils.h"
+#endif
 #if defined(OS_WIN)
 #include <shellapi.h>
 #include <windows.h>
@@ -428,7 +430,9 @@
       live_tab_context_(new BrowserLiveTabContext(this)),
       synced_window_delegate_(new BrowserSyncedWindowDelegate(this)),
       app_controller_(MaybeCreateWebAppController(this)),
+#if !defined(OS_ANDROID)
       bookmark_bar_state_(BookmarkBar::HIDDEN),
+#endif
       command_controller_(new chrome::BrowserCommandController(this)),
       window_has_shown_(false),
       chrome_updater_factory_(this),
@@ -462,11 +466,13 @@
       prefs::kDevToolsAvailability,
       base::BindRepeating(&Browser::OnDevToolsAvailabilityChanged,
                           base::Unretained(this)));
+#if !defined(OS_ANDROID)
   profile_pref_registrar_.Add(
       bookmarks::prefs::kShowBookmarkBar,
       base::BindRepeating(&Browser::UpdateBookmarkBarState,
                           base::Unretained(this),
                           BOOKMARK_BAR_STATE_CHANGE_PREF_CHANGE));
+#endif
 
   if (search::IsInstantExtendedAPIEnabled() && is_type_tabbed())
     instant_controller_.reset(new BrowserInstantController(this));
@@ -1923,11 +1929,13 @@
 ///////////////////////////////////////////////////////////////////////////////
 // Browser, BookmarkTabHelperObserver implementation:
 
+#if !defined(OS_ANDROID)
 void Browser::URLStarredChanged(content::WebContents* web_contents,
                                 bool starred) {
   if (web_contents == tab_strip_model_->GetActiveWebContents())
     window_->SetStarredState(starred);
 }
+#endif
 
 ///////////////////////////////////////////////////////////////////////////////
 // Browser, ZoomObserver implementation:
@@ -2632,6 +2640,7 @@
 }
 
 void Browser::UpdateBookmarkBarState(BookmarkBarStateChangeReason reason) {
+#if !defined(OS_ANDROID)
   BookmarkBar::State state =
       ShouldShowBookmarkBar() ? BookmarkBar::SHOW : BookmarkBar::HIDDEN;
 
@@ -2654,9 +2663,11 @@
   window_->BookmarkBarStateChanged(
       should_animate ? BookmarkBar::ANIMATE_STATE_CHANGE
                      : BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
+#endif
 }
 
 bool Browser::ShouldShowBookmarkBar() const {
+#if !defined(OS_ANDROID)
   if (profile_->IsGuestSession())
     return false;
 
@@ -2672,6 +2683,9 @@
   BookmarkTabHelper* bookmark_tab_helper =
       BookmarkTabHelper::FromWebContents(web_contents);
   return bookmark_tab_helper && bookmark_tab_helper->ShouldShowBookmarkBar();
+#else
+  return false;
+#endif
 }
 
 bool Browser::ShouldHideUIForFullscreen() const {
diff --git a/chrome/browser/ui/browser.h b/chrome/browser/ui/browser.h
index e91cedc..8f83b074 100644
--- a/chrome/browser/ui/browser.h
+++ b/chrome/browser/ui/browser.h
@@ -21,8 +21,6 @@
 #include "base/strings/string16.h"
 #include "build/build_config.h"
 #include "chrome/browser/devtools/devtools_toggle_action.h"
-#include "chrome/browser/ui/bookmarks/bookmark_bar.h"
-#include "chrome/browser/ui/bookmarks/bookmark_tab_helper_observer.h"
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/browser/ui/chrome_bubble_manager.h"
@@ -52,6 +50,8 @@
 #include "ui/shell_dialogs/select_file_dialog.h"
 
 #if !defined(OS_ANDROID)
+#include "chrome/browser/ui/bookmarks/bookmark_bar.h"
+#include "chrome/browser/ui/bookmarks/bookmark_tab_helper_observer.h"
 #include "components/zoom/zoom_observer.h"
 #endif  // !defined(OS_ANDROID)
 
@@ -114,8 +114,8 @@
 class Browser : public TabStripModelObserver,
                 public content::WebContentsDelegate,
                 public ChromeWebModalDialogManagerDelegate,
-                public BookmarkTabHelperObserver,
 #if !defined(OS_ANDROID)
+                public BookmarkTabHelperObserver,
                 public zoom::ZoomObserver,
 #endif  // !defined(OS_ANDROID)
                 public content::PageNavigator,
@@ -346,8 +346,10 @@
   // Returns true if a FindBarController exists for this browser.
   bool HasFindBarController() const;
 
+#if !defined(OS_ANDROID)
   // Returns the state of the bookmark bar.
   BookmarkBar::State bookmark_bar_state() const { return bookmark_bar_state_; }
+#endif
 
   // State Storage and Retrieval for UI ///////////////////////////////////////
 
@@ -784,11 +786,11 @@
   web_modal::WebContentsModalDialogHost* GetWebContentsModalDialogHost()
       override;
 
+#if !defined(OS_ANDROID)
   // Overridden from BookmarkTabHelperObserver:
   void URLStarredChanged(content::WebContents* web_contents,
                          bool starred) override;
 
-#if !defined(OS_ANDROID)
   // Overridden from ZoomObserver:
   void OnZoomChanged(
       const zoom::ZoomController::ZoomChangedEventData& data) override;
@@ -1087,7 +1089,9 @@
   // set of commands are enabled.
   std::unique_ptr<web_app::AppBrowserController> app_controller_;
 
+#if !defined(OS_ANDROID)
   BookmarkBar::State bookmark_bar_state_;
+#endif
 
   std::unique_ptr<ExclusiveAccessManager> exclusive_access_manager_;
 
diff --git a/chrome/browser/ui/browser_command_controller.cc b/chrome/browser/ui/browser_command_controller.cc
index 79d61d43..36b31c8 100644
--- a/chrome/browser/ui/browser_command_controller.cc
+++ b/chrome/browser/ui/browser_command_controller.cc
@@ -545,8 +545,7 @@
       ManagePasswordsForPage(browser_);
       break;
     case IDC_SEND_TAB_TO_SELF:
-      // TODO(crbug/950388): add implementation.
-      NOTIMPLEMENTED();
+      SendTabToSelfFromPageAction(browser_);
       break;
 
     // Clipboard commands
diff --git a/chrome/browser/ui/browser_commands.cc b/chrome/browser/ui/browser_commands.cc
index 2819711..db7a79d 100644
--- a/chrome/browser/ui/browser_commands.cc
+++ b/chrome/browser/ui/browser_commands.cc
@@ -51,6 +51,7 @@
 #include "chrome/browser/ui/location_bar/location_bar.h"
 #include "chrome/browser/ui/passwords/manage_passwords_ui_controller.h"
 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
+#include "chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.h"
 #include "chrome/browser/ui/status_bubble.h"
 #include "chrome/browser/ui/tab_contents/core_tab_helper.h"
 #include "chrome/browser/ui/tab_dialogs.h"
@@ -942,6 +943,15 @@
       ->ShowManagePasswordsBubble(!controller->IsAutomaticallyOpeningBubble());
 }
 
+void SendTabToSelfFromPageAction(Browser* browser) {
+  WebContents* web_contents =
+      browser->tab_strip_model()->GetActiveWebContents();
+  send_tab_to_self::SendTabToSelfBubbleController* controller =
+      send_tab_to_self::SendTabToSelfBubbleController::
+          CreateOrGetFromWebContents(web_contents);
+  controller->ShowBubble();
+}
+
 void SavePage(Browser* browser) {
   base::RecordAction(UserMetricsAction("SavePage"));
   WebContents* current_tab = browser->tab_strip_model()->GetActiveWebContents();
diff --git a/chrome/browser/ui/browser_commands.h b/chrome/browser/ui/browser_commands.h
index 6df3840..ac20902 100644
--- a/chrome/browser/ui/browser_commands.h
+++ b/chrome/browser/ui/browser_commands.h
@@ -119,6 +119,7 @@
 void MigrateLocalCards(Browser* browser);
 void Translate(Browser* browser);
 void ManagePasswordsForPage(Browser* browser);
+void SendTabToSelfFromPageAction(Browser* browser);
 void SavePage(Browser* browser);
 bool CanSavePage(const Browser* browser);
 void ShowFindBar(Browser* browser);
diff --git a/chrome/browser/ui/browser_window.h b/chrome/browser/ui/browser_window.h
index 0060404..a74d5c1 100644
--- a/chrome/browser/ui/browser_window.h
+++ b/chrome/browser/ui/browser_window.h
@@ -71,6 +71,11 @@
 enum class AccessPoint;
 }
 
+namespace send_tab_to_self {
+class SendTabToSelfBubbleController;
+class SendTabToSelfBubbleView;
+}  // namespace send_tab_to_self
+
 namespace web_modal {
 class WebContentsModalDialogHost;
 }
@@ -342,6 +347,12 @@
       autofill::SaveCardBubbleController* controller,
       bool is_user_gesture) = 0;
 
+  // Shows the "send tab to self" bubble.
+  virtual send_tab_to_self::SendTabToSelfBubbleView* ShowSendTabToSelfBubble(
+      content::WebContents* contents,
+      send_tab_to_self::SendTabToSelfBubbleController* controller,
+      bool is_user_gesture) = 0;
+
   // Shows the local card migration bubble.
   virtual autofill::LocalCardMigrationBubble* ShowLocalCardMigrationBubble(
       content::WebContents* contents,
diff --git a/chrome/browser/ui/search/local_ntp_browsertest.cc b/chrome/browser/ui/search/local_ntp_browsertest.cc
index 5731cd0..2a64453 100644
--- a/chrome/browser/ui/search/local_ntp_browsertest.cc
+++ b/chrome/browser/ui/search/local_ntp_browsertest.cc
@@ -675,15 +675,11 @@
   content::RenderFrameHost* cl_iframe =
       GetIframe(active_tab, kEditCustomLinkIframe);
 
-  // Dark mode should not be applied to the main page, iframes, and Most Visited
-  // icons.
+  // Dark mode should not be applied to the main page and iframes.
   ASSERT_FALSE(GetIsDarkModeApplied(active_tab));
   ASSERT_FALSE(GetIsDarkModeApplied(mv_iframe));
   ASSERT_FALSE(GetIsDarkModeApplied(cl_iframe));
   ASSERT_TRUE(GetIsLightChipsApplied(active_tab));
-  for (int i = 0; i < kDefaultMostVisitedItemCount; ++i) {
-    ASSERT_FALSE(GetIsDarkTile(mv_iframe, i));
-  }
 
   // Enable dark mode and wait until the MV tiles have updated.
   content::DOMMessageQueue msg_queue(active_tab);
@@ -697,9 +693,6 @@
   EXPECT_TRUE(GetIsDarkModeApplied(mv_iframe));
   EXPECT_TRUE(GetIsDarkModeApplied(cl_iframe));
   EXPECT_FALSE(GetIsLightChipsApplied(active_tab));
-  for (int i = 0; i < kDefaultMostVisitedItemCount; ++i) {
-    EXPECT_TRUE(GetIsDarkTile(mv_iframe, i));
-  }
 
   // Disable dark mode and wait until the MV tiles have updated.
   msg_queue.ClearQueue();
@@ -713,9 +706,6 @@
   EXPECT_FALSE(GetIsDarkModeApplied(mv_iframe));
   EXPECT_FALSE(GetIsDarkModeApplied(cl_iframe));
   EXPECT_TRUE(GetIsLightChipsApplied(active_tab));
-  for (int i = 0; i < kDefaultMostVisitedItemCount; ++i) {
-    EXPECT_FALSE(GetIsDarkTile(mv_iframe, i));
-  }
 }
 
 // Tests that dark mode styling is properly applied to the local NTP on start-
@@ -751,13 +741,10 @@
       GetIframe(active_tab, kEditCustomLinkIframe);
 
   // Check that dark mode, if enabled, has been properly applied to the main
-  // page, iframes, and Most Visited icons.
+  // page and iframes.
   EXPECT_EQ(kDarkModeEnabled, GetIsDarkModeApplied(active_tab));
   EXPECT_EQ(kDarkModeEnabled, GetIsDarkModeApplied(mv_iframe));
   EXPECT_EQ(kDarkModeEnabled, GetIsDarkModeApplied(cl_iframe));
-  for (int i = 0; i < kDefaultMostVisitedItemCount; ++i) {
-    EXPECT_EQ(kDarkModeEnabled, GetIsDarkTile(mv_iframe, i));
-  }
 }
 
 INSTANTIATE_TEST_SUITE_P(, LocalNTPDarkModeStartupTest, testing::Bool());
diff --git a/chrome/browser/ui/send_tab_to_self/OWNERS b/chrome/browser/ui/send_tab_to_self/OWNERS
new file mode 100644
index 0000000..59b3396
--- /dev/null
+++ b/chrome/browser/ui/send_tab_to_self/OWNERS
@@ -0,0 +1,3 @@
+ file://components/send_tab_to_self/OWNERS
+
+# COMPONENT: UI>Browser>Sharing
diff --git a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.cc b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.cc
new file mode 100644
index 0000000..d8a2685
--- /dev/null
+++ b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.cc
@@ -0,0 +1,95 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#include "chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.h"
+
+#include <vector>
+
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/send_tab_to_self/send_tab_to_self_desktop_util.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_view.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/send_tab_to_self/target_device_info.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace send_tab_to_self {
+
+SendTabToSelfBubbleController::~SendTabToSelfBubbleController() {
+  if (send_tab_to_self_bubble_view_) {
+    send_tab_to_self_bubble_view_->Hide();
+  }
+}
+
+// Static:
+SendTabToSelfBubbleController*
+SendTabToSelfBubbleController::CreateOrGetFromWebContents(
+    content::WebContents* web_contents) {
+  SendTabToSelfBubbleController::CreateForWebContents(web_contents);
+  SendTabToSelfBubbleController* controller =
+      SendTabToSelfBubbleController::FromWebContents(web_contents);
+  return controller;
+}
+
+void SendTabToSelfBubbleController::HideBubble() {
+  if (send_tab_to_self_bubble_view_) {
+    send_tab_to_self_bubble_view_->Hide();
+    send_tab_to_self_bubble_view_ = nullptr;
+  }
+}
+
+void SendTabToSelfBubbleController::ShowBubble() {
+  Browser* browser = chrome::FindBrowserWithWebContents(web_contents_);
+  send_tab_to_self_bubble_view_ =
+      browser->window()->ShowSendTabToSelfBubble(web_contents_, this, true);
+}
+
+SendTabToSelfBubbleView*
+SendTabToSelfBubbleController::send_tab_to_self_bubble_view() const {
+  return send_tab_to_self_bubble_view_;
+}
+
+base::string16 SendTabToSelfBubbleController::GetWindowTitle() const {
+  return l10n_util::GetStringUTF16(IDS_CONTEXT_MENU_SEND_TAB_TO_SELF);
+}
+
+std::map<std::string, TargetDeviceInfo>
+SendTabToSelfBubbleController::GetValidDevices() const {
+  return valid_devices_;
+}
+
+Profile* SendTabToSelfBubbleController::GetProfile() const {
+  if (!web_contents_) {
+    return nullptr;
+  }
+  return Profile::FromBrowserContext(web_contents_->GetBrowserContext());
+}
+
+void SendTabToSelfBubbleController::OnDeviceSelected(
+    std::string target_device_id) {
+  // TODO(crbug/959698): send current tab to target device; close the bubble
+  // and hide the icon.
+  NOTIMPLEMENTED();
+}
+
+void SendTabToSelfBubbleController::OnBubbleClosed() {
+  send_tab_to_self_bubble_view_ = nullptr;
+}
+
+SendTabToSelfBubbleController::SendTabToSelfBubbleController(
+    content::WebContents* web_contents)
+    : web_contents_(web_contents) {
+  this->FetchDeviceInfo();
+}
+
+void SendTabToSelfBubbleController::FetchDeviceInfo() {
+  // TODO(crbug/960595): get devices info map from SendTabToSelfModel.
+  NOTIMPLEMENTED();
+}
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(SendTabToSelfBubbleController)
+
+}  // namespace send_tab_to_self
diff --git a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.h b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.h
new file mode 100644
index 0000000..ecc4e1b
--- /dev/null
+++ b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.h
@@ -0,0 +1,74 @@
+// 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_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_BUBBLE_CONTROLLER_H_
+#define CHROME_BROWSER_UI_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_BUBBLE_CONTROLLER_H_
+
+#include <map>
+#include <memory>
+
+#include "base/macros.h"
+#include "content/public/browser/web_contents_user_data.h"
+
+class Profile;
+
+namespace content {
+class WebContents;
+}
+
+namespace send_tab_to_self {
+
+class SendTabToSelfBubbleView;
+struct TargetDeviceInfo;
+
+class SendTabToSelfBubbleController
+    : public content::WebContentsUserData<SendTabToSelfBubbleController> {
+ public:
+  ~SendTabToSelfBubbleController() override;
+
+  static SendTabToSelfBubbleController* CreateOrGetFromWebContents(
+      content::WebContents* web_contents);
+  // Hides send tab to self bubble.
+  void HideBubble();
+  // Displays send tab to self bubble.
+  void ShowBubble();
+
+  // Returns nullptr if no bubble is currently shown.
+  SendTabToSelfBubbleView* send_tab_to_self_bubble_view() const;
+  // Returns the title of send tab to self bubble.
+  base::string16 GetWindowTitle() const;
+  // Returns the valid devices info map.
+  std::map<std::string, TargetDeviceInfo> GetValidDevices() const;
+  // Returns current profile.
+  Profile* GetProfile() const;
+
+  // Handles the action when the user click on one valid device. Sends tab to
+  // the target device; closes the button and hides the omnibox icon.
+  void OnDeviceSelected(std::string target_device_id);
+  // Close the bubble when the user click on the close button.
+  void OnBubbleClosed();
+
+ protected:
+  explicit SendTabToSelfBubbleController(content::WebContents* web_contents);
+
+ private:
+  friend class content::WebContentsUserData<SendTabToSelfBubbleController>;
+  // Get information of valid devices.
+  void FetchDeviceInfo();
+
+  // The web_contents associated with this controller.
+  content::WebContents* web_contents_;
+  // Weak reference. Will be nullptr if no bubble is currently shown.
+  SendTabToSelfBubbleView* send_tab_to_self_bubble_view_ = nullptr;
+  // Valid devices data.
+  std::map<std::string, TargetDeviceInfo> valid_devices_;
+
+  WEB_CONTENTS_USER_DATA_KEY_DECL();
+
+  DISALLOW_COPY_AND_ASSIGN(SendTabToSelfBubbleController);
+};
+
+}  // namespace send_tab_to_self
+
+#endif  // CHROME_BROWSER_UI_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_BUBBLE_CONTROLLER_H_
diff --git a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_view.h b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_view.h
new file mode 100644
index 0000000..ac4d9dd
--- /dev/null
+++ b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_view.h
@@ -0,0 +1,21 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_BUBBLE_VIEW_H_
+#define CHROME_BROWSER_UI_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_BUBBLE_VIEW_H_
+
+namespace send_tab_to_self {
+
+// The cross-platform UI interface which displays the share bubble.
+// This object is responsible for its own lifetime.
+class SendTabToSelfBubbleView {
+ public:
+  // Called to close the bubble and prevent future callbacks into the
+  // controller.
+  virtual void Hide() = 0;
+};
+
+}  // namespace send_tab_to_self
+
+#endif  // CHROME_BROWSER_UI_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_BUBBLE_VIEW_H_
diff --git a/chrome/browser/ui/serial/serial_chooser_controller.cc b/chrome/browser/ui/serial/serial_chooser_controller.cc
index 3786b91..47563af 100644
--- a/chrome/browser/ui/serial/serial_chooser_controller.cc
+++ b/chrome/browser/ui/serial/serial_chooser_controller.cc
@@ -38,7 +38,7 @@
   DCHECK(chooser_context_);
 
   chooser_context_->GetPortManager()->GetDevices(base::BindOnce(
-      &SerialChooserController::OnGetDevices, base::Unretained(this)));
+      &SerialChooserController::OnGetDevices, weak_factory_.GetWeakPtr()));
 }
 
 SerialChooserController::~SerialChooserController() {
diff --git a/chrome/browser/ui/serial/serial_chooser_controller.h b/chrome/browser/ui/serial/serial_chooser_controller.h
index 8e654bb..876fbf3 100644
--- a/chrome/browser/ui/serial/serial_chooser_controller.h
+++ b/chrome/browser/ui/serial/serial_chooser_controller.h
@@ -25,7 +25,7 @@
 
 // SerialChooserController provides data for the Serial API permission prompt.
 // It is owned by ChooserBubbleDelegate.
-class SerialChooserController : public ChooserController {
+class SerialChooserController final : public ChooserController {
  public:
   SerialChooserController(
       content::RenderFrameHost* render_frame_host,
@@ -56,6 +56,8 @@
   base::WeakPtr<SerialChooserContext> chooser_context_;
   std::vector<device::mojom::SerialPortInfoPtr> ports_;
 
+  base::WeakPtrFactory<SerialChooserController> weak_factory_{this};
+
   DISALLOW_COPY_AND_ASSIGN(SerialChooserController);
 };
 
diff --git a/chrome/browser/ui/serial/serial_chooser_controller_unittest.cc b/chrome/browser/ui/serial/serial_chooser_controller_unittest.cc
new file mode 100644
index 0000000..949f324
--- /dev/null
+++ b/chrome/browser/ui/serial/serial_chooser_controller_unittest.cc
@@ -0,0 +1,56 @@
+// 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/serial/serial_chooser_controller.h"
+
+#include "base/test/bind_test_util.h"
+#include "chrome/browser/serial/serial_chooser_context.h"
+#include "chrome/browser/serial/serial_chooser_context_factory.h"
+#include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "chrome/test/base/testing_profile.h"
+#include "mojo/public/cpp/bindings/interface_request.h"
+#include "services/device/public/cpp/test/fake_serial_port_manager.h"
+#include "services/device/public/mojom/serial.mojom.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/mojom/serial/serial.mojom.h"
+
+class SerialChooserControllerTest : public ChromeRenderViewHostTestHarness {
+ public:
+  void SetUp() override {
+    ChromeRenderViewHostTestHarness::SetUp();
+
+    device::mojom::SerialPortManagerPtr port_manager_ptr;
+    port_manager_.AddBinding(mojo::MakeRequest(&port_manager_ptr));
+    SerialChooserContextFactory::GetForProfile(profile())
+        ->SetPortManagerForTesting(std::move(port_manager_ptr));
+  }
+
+ private:
+  device::FakeSerialPortManager port_manager_;
+};
+
+TEST_F(SerialChooserControllerTest, GetPortsLateResponse) {
+  std::vector<blink::mojom::SerialPortFilterPtr> filters;
+
+  bool callback_run = false;
+  auto callback = base::BindLambdaForTesting(
+      [&](device::mojom::SerialPortInfoPtr port_info) {
+        EXPECT_FALSE(port_info);
+        callback_run = true;
+      });
+
+  auto controller = std::make_unique<SerialChooserController>(
+      main_rfh(), std::move(filters), std::move(callback));
+  controller.reset();
+
+  // Allow any tasks posted by |controller| to run, such as asynchronous
+  // requests to the Device Service to get the list of available serial ports.
+  // These should be safely discarded since |controller| was destroyed.
+  base::RunLoop().RunUntilIdle();
+
+  // Even if |controller| is destroyed without user interaction the callback
+  // should be run.
+  EXPECT_TRUE(callback_run);
+}
diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_header_panel.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_header_panel.cc
index d75d572..1265e560 100644
--- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_header_panel.cc
+++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_header_panel.cc
@@ -76,7 +76,7 @@
   auto vertical_container_layout =
       std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical);
   vertical_container_layout->set_main_axis_alignment(
-      views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+      views::BoxLayout::MainAxisAlignment::kCenter);
   vertical_info_container->SetLayoutManager(
       std::move(vertical_container_layout));
   auto* vertical_info_container_ptr =
diff --git a/chrome/browser/ui/views/arc_app_dialog_view.cc b/chrome/browser/ui/views/arc_app_dialog_view.cc
index 49efa72..513c494 100644
--- a/chrome/browser/ui/views/arc_app_dialog_view.cc
+++ b/chrome/browser/ui/views/arc_app_dialog_view.cc
@@ -126,7 +126,7 @@
   auto text_container_layout =
       std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical);
   text_container_layout->set_main_axis_alignment(
-      views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+      views::BoxLayout::MainAxisAlignment::kCenter);
   text_container_layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kStart);
   text_container->SetLayoutManager(std::move(text_container_layout));
diff --git a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc
index d3366d4..7ae0827 100644
--- a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc
+++ b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc
@@ -826,7 +826,7 @@
       controller_(controller) {
   layout_ = SetLayoutManager(
       std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical));
-  layout_->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+  layout_->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
 
   CreateChildViews();
   SetBackground(views::CreateSolidBackground(GetBackgroundColor()));
@@ -916,7 +916,7 @@
     views::BoxLayout* body_layout = body_container->SetLayoutManager(
         std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical));
     body_layout->set_main_axis_alignment(
-        views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+        views::BoxLayout::MainAxisAlignment::kStart);
     for (auto* row : rows_) {
       body_container->AddChildView(row);
     }
@@ -955,7 +955,7 @@
     views::BoxLayout* footer_layout = footer_container->SetLayoutManager(
         std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical));
     footer_layout->set_main_axis_alignment(
-        views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+        views::BoxLayout::MainAxisAlignment::kStart);
 
     while (line_number < controller_->GetLineCount()) {
       rows_.push_back(AutofillPopupFooterView::Create(
diff --git a/chrome/browser/ui/views/autofill/payments/local_card_migration_dialog_view.cc b/chrome/browser/ui/views/autofill/payments/local_card_migration_dialog_view.cc
index f0d29e0b..411b76d5 100644
--- a/chrome/browser/ui/views/autofill/payments/local_card_migration_dialog_view.cc
+++ b/chrome/browser/ui/views/autofill/payments/local_card_migration_dialog_view.cc
@@ -154,7 +154,7 @@
               : provider->GetDistanceMetric(
                     views::DISTANCE_UNRELATED_CONTROL_VERTICAL)));
   card_list_view_layout->set_main_axis_alignment(
-      views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+      views::BoxLayout::MainAxisAlignment::kStart);
   for (size_t index = 0; index < migratable_credit_cards.size(); ++index) {
     card_list_view->AddChildView(new MigratableCardView(
         migratable_credit_cards[index], dialog_view, should_show_checkbox));
diff --git a/chrome/browser/ui/views/autofill/payments/local_card_migration_error_dialog_view.cc b/chrome/browser/ui/views/autofill/payments/local_card_migration_error_dialog_view.cc
index 9f036a0..6ede817 100644
--- a/chrome/browser/ui/views/autofill/payments/local_card_migration_error_dialog_view.cc
+++ b/chrome/browser/ui/views/autofill/payments/local_card_migration_error_dialog_view.cc
@@ -120,7 +120,7 @@
           provider->GetDistanceMetric(
               views::DISTANCE_UNRELATED_CONTROL_VERTICAL)));
   horizontal_layout->set_main_axis_alignment(
-      views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+      views::BoxLayout::MainAxisAlignment::kCenter);
   error_view->SetBorder(views::CreateEmptyBorder(kMigrationDialogInsets));
   auto* error_image = new views::ImageView();
   error_image->SetImage(
diff --git a/chrome/browser/ui/views/crostini/crostini_installer_view.cc b/chrome/browser/ui/views/crostini/crostini_installer_view.cc
index 6dbc372..30e1076 100644
--- a/chrome/browser/ui/views/crostini/crostini_installer_view.cc
+++ b/chrome/browser/ui/views/crostini/crostini_installer_view.cc
@@ -579,7 +579,7 @@
 
   // Make sure the lower_container_view is pinned to the bottom of the dialog.
   lower_container_layout->set_main_axis_alignment(
-      views::BoxLayout::MAIN_AXIS_ALIGNMENT_END);
+      views::BoxLayout::MainAxisAlignment::kEnd);
   layout->SetFlexForView(lower_container_view, 1, true);
 
   chrome::RecordDialogCreation(chrome::DialogIdentifier::CROSTINI_INSTALLER);
diff --git a/chrome/browser/ui/views/extensions/extension_action_platform_delegate_views.cc b/chrome/browser/ui/views/extensions/extension_action_platform_delegate_views.cc
index e3d1744..001e5cb 100644
--- a/chrome/browser/ui/views/extensions/extension_action_platform_delegate_views.cc
+++ b/chrome/browser/ui/views/extensions/extension_action_platform_delegate_views.cc
@@ -80,13 +80,12 @@
   // performs the flipping in RTL cases.
   views::BubbleBorder::Arrow arrow = views::BubbleBorder::TOP_RIGHT;
 
-  views::View* reference_view = GetDelegateViews()->GetReferenceViewForPopup();
-
   ExtensionPopup::ShowAction popup_show_action =
       show_action == ExtensionActionViewController::SHOW_POPUP ?
           ExtensionPopup::SHOW : ExtensionPopup::SHOW_AND_INSPECT;
-  ExtensionPopup::ShowPopup(std::move(host), reference_view, arrow,
-                            popup_show_action);
+  ExtensionPopup::ShowPopup(std::move(host),
+                            GetDelegateViews()->GetReferenceButtonForPopup(),
+                            arrow, popup_show_action);
 }
 
 void ExtensionActionPlatformDelegateViews::ShowContextMenu() {
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_button.cc b/chrome/browser/ui/views/extensions/extensions_menu_button.cc
index b496727..d4e06e5 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_button.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_button.cc
@@ -13,6 +13,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/paint_vector_icon.h"
+#include "ui/views/controls/button/button.h"
 #include "ui/views/controls/button/image_button.h"
 #include "ui/views/controls/button/menu_button.h"
 #include "ui/views/layout/box_layout.h"
@@ -71,7 +72,7 @@
   return GetFocusManager();
 }
 
-views::View* ExtensionsMenuButton::GetReferenceViewForPopup() {
+views::Button* ExtensionsMenuButton::GetReferenceButtonForPopup() {
   return BrowserView::GetBrowserViewForBrowser(browser_)
       ->toolbar()
       ->GetExtensionsButton();
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_button.h b/chrome/browser/ui/views/extensions/extensions_menu_button.h
index 2531dfd..f0087fb 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_button.h
+++ b/chrome/browser/ui/views/extensions/extensions_menu_button.h
@@ -14,6 +14,7 @@
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
 
 namespace views {
+class Button;
 class MenuButton;
 class MenuModelAdapter;
 class MenuRunner;
@@ -38,7 +39,7 @@
   // ToolbarActionViewDelegateViews:
   views::View* GetAsView() override;
   views::FocusManager* GetFocusManagerForAccelerator() override;
-  views::View* GetReferenceViewForPopup() override;
+  views::Button* GetReferenceButtonForPopup() override;
   content::WebContents* GetCurrentWebContents() const override;
   void UpdateState() override;
   bool IsMenuRunning() const override;
diff --git a/chrome/browser/ui/views/feature_promos/feature_promo_bubble_view.cc b/chrome/browser/ui/views/feature_promos/feature_promo_bubble_view.cc
index 343c1dd..e523867 100644
--- a/chrome/browser/ui/views/feature_promos/feature_promo_bubble_view.cc
+++ b/chrome/browser/ui/views/feature_promos/feature_promo_bubble_view.cc
@@ -89,7 +89,7 @@
   auto box_layout = std::make_unique<views::BoxLayout>(
       views::BoxLayout::kVertical, kBubbleContentsInsets, 0);
   box_layout->set_main_axis_alignment(
-      views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+      views::BoxLayout::MainAxisAlignment::kCenter);
   box_layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
   SetLayoutManager(std::move(box_layout));
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index cbedc0f..53f172f 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -59,6 +59,7 @@
 #include "chrome/browser/ui/layout_constants.h"
 #include "chrome/browser/ui/recently_audible_helper.h"
 #include "chrome/browser/ui/sad_tab_helper.h"
+#include "chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_view.h"
 #include "chrome/browser/ui/sync/bubble_sync_promo_delegate.h"
 #include "chrome/browser/ui/tabs/tab_menu_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -99,6 +100,7 @@
 #include "chrome/browser/ui/views/page_action/omnibox_page_action_icon_container_view.h"
 #include "chrome/browser/ui/views/profiles/profile_indicator_icon.h"
 #include "chrome/browser/ui/views/profiles/profile_menu_view_base.h"
+#include "chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.h"
 #include "chrome/browser/ui/views/status_bubble_views.h"
 #include "chrome/browser/ui/views/tab_contents/chrome_web_contents_view_focus_helper.h"
 #include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
@@ -1383,6 +1385,32 @@
   return bubble;
 }
 
+send_tab_to_self::SendTabToSelfBubbleView* BrowserView::ShowSendTabToSelfBubble(
+    content::WebContents* web_contents,
+    send_tab_to_self::SendTabToSelfBubbleController* controller,
+    bool is_user_gesture) {
+  if (!is_user_gesture) {
+    return nullptr;
+  }
+
+  LocationBarView* location_bar = GetLocationBarView();
+  PageActionIconView* icon_view =
+      (PageActionIconView*)location_bar->send_tab_to_self_icon_view();
+  views::View* anchor_view = location_bar;
+
+  send_tab_to_self::SendTabToSelfBubbleViewImpl* bubble =
+      new send_tab_to_self::SendTabToSelfBubbleViewImpl(
+          anchor_view, gfx::Point(), web_contents, controller);
+
+  if (icon_view) {
+    bubble->SetHighlightedButton(icon_view);
+  }
+  views::BubbleDialogDelegateView::CreateBubble(bubble);
+  bubble->Show(send_tab_to_self::SendTabToSelfBubbleViewImpl::USER_GESTURE);
+
+  return bubble;
+}
+
 autofill::LocalCardMigrationBubble* BrowserView::ShowLocalCardMigrationBubble(
     content::WebContents* web_contents,
     autofill::LocalCardMigrationBubbleController* controller,
diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h
index 7fa5abe..7045a8d 100644
--- a/chrome/browser/ui/views/frame/browser_view.h
+++ b/chrome/browser/ui/views/frame/browser_view.h
@@ -373,6 +373,10 @@
       content::WebContents* contents,
       autofill::SaveCardBubbleController* controller,
       bool is_user_gesture) override;
+  send_tab_to_self::SendTabToSelfBubbleView* ShowSendTabToSelfBubble(
+      content::WebContents* contents,
+      send_tab_to_self::SendTabToSelfBubbleController* controller,
+      bool is_user_gesture) override;
   autofill::LocalCardMigrationBubble* ShowLocalCardMigrationBubble(
       content::WebContents* contents,
       autofill::LocalCardMigrationBubbleController* controller,
diff --git a/chrome/browser/ui/views/frame/hosted_app_button_container.cc b/chrome/browser/ui/views/frame/hosted_app_button_container.cc
index fff65ce..9a87608 100644
--- a/chrome/browser/ui/views/frame/hosted_app_button_container.cc
+++ b/chrome/browser/ui/views/frame/hosted_app_button_container.cc
@@ -153,7 +153,7 @@
           views::LayoutProvider::Get()->GetDistanceMetric(
               views::DISTANCE_RELATED_CONTROL_HORIZONTAL)));
   // Right align to clip the leftmost items first when not enough space.
-  layout.set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_END);
+  layout.set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kEnd);
 
   std::vector<std::unique_ptr<ContentSettingImageModel>> models =
       ContentSettingImageModel::GenerateContentSettingImageModels();
@@ -196,7 +196,7 @@
                       right_margin.value_or(HorizontalPaddingBetweenItems())),
           HorizontalPaddingBetweenItems()));
   // Right align to clip the leftmost items first when not enough space.
-  layout.set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_END);
+  layout.set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kEnd);
   layout.set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
 
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.h b/chrome/browser/ui/views/location_bar/location_bar_view.h
index 1877f19..4a361f6 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_view.h
+++ b/chrome/browser/ui/views/location_bar/location_bar_view.h
@@ -57,6 +57,10 @@
 class SaveCardIconView;
 }
 
+namespace send_tab_to_self {
+class SendTabToSelfIconView;
+}
+
 namespace views {
 class ImageButton;
 class Label;
@@ -161,6 +165,12 @@
     return local_card_migration_icon_view_;
   }
 
+  // The send tab to self icon. It may not be visible.  It will be null when
+  // |browser_| is null.
+  send_tab_to_self::SendTabToSelfIconView* send_tab_to_self_icon_view() {
+    return send_tab_to_self_icon_view_;
+  }
+
   OmniboxPageActionIconContainerView*
   omnibox_page_action_icon_container_view() {
     return omnibox_page_action_icon_container_view_;
@@ -416,6 +426,10 @@
   autofill::LocalCardMigrationIconView* local_card_migration_icon_view_ =
       nullptr;
 
+  // The send tab to self icon. It will be null when |browser_| is null.
+  send_tab_to_self::SendTabToSelfIconView* send_tab_to_self_icon_view_ =
+      nullptr;
+
   // The intent picker for accessing apps.  It will be null when
   // |browser_| is null.
   IntentPickerView* intent_picker_view_ = nullptr;
diff --git a/chrome/browser/ui/views/location_bar/zoom_bubble_view.cc b/chrome/browser/ui/views/location_bar/zoom_bubble_view.cc
index c867ed7..b5f46fc 100644
--- a/chrome/browser/ui/views/location_bar/zoom_bubble_view.cc
+++ b/chrome/browser/ui/views/location_bar/zoom_bubble_view.cc
@@ -383,7 +383,7 @@
       views::BoxLayout::kHorizontal,
       provider->GetInsetsMetric(INSETS_TOAST) - margins(), spacing);
   box_layout->set_main_axis_alignment(
-      views::BoxLayout::MAIN_AXIS_ALIGNMENT_CENTER);
+      views::BoxLayout::MainAxisAlignment::kCenter);
   box_layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
   SetLayoutManager(std::move(box_layout));
diff --git a/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.cc b/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.cc
index eeae1f2..303fc9c 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.cc
@@ -219,6 +219,15 @@
   model_->SetSelectedLineState(OmniboxPopupModel::NORMAL);
 }
 
+void OmniboxPopupContentsView::ProvideButtonFocusHint(size_t line) {
+  OmniboxResultView* result = result_view_at(line);
+  result->ProvideButtonFocusHint();
+}
+
+bool OmniboxPopupContentsView::InExplicitExperimentalKeywordMode() {
+  return model_->edit_model()->InExplicitExperimentalKeywordMode();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // OmniboxPopupContentsView, OmniboxPopupView overrides:
 
@@ -322,11 +331,6 @@
   SetMouseHandler(nullptr);
 }
 
-void OmniboxPopupContentsView::ProvideButtonFocusHint(size_t line) {
-  OmniboxResultView* result = result_view_at(line);
-  result->ProvideButtonFocusHint();
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // OmniboxPopupContentsView, views::View overrides:
 
diff --git a/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.h b/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.h
index 570a310..66ca539 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.h
+++ b/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.h
@@ -66,6 +66,10 @@
   // Called to inform result view of button focus.
   void ProvideButtonFocusHint(size_t line);
 
+  // Returns whether we're in experimental keyword mode and the input gives
+  // sufficient confidence that the user wants keyword mode.
+  bool InExplicitExperimentalKeywordMode();
+
   // OmniboxPopupView:
   bool IsOpen() const override;
   void InvalidateLine(size_t line) override;
diff --git a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
index 57ecbab..d64998f 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
@@ -42,6 +42,8 @@
 #include "base/win/atl.h"
 #endif
 
+static size_t kKeywordSuggestionIndent = 70;
+
 ////////////////////////////////////////////////////////////////////////////////
 // OmniboxResultView, public:
 
@@ -326,8 +328,15 @@
       suggestion_tab_switch_button_->SetVisible(false);
     }
   }
-  keyword_view_->SetBounds(suggestion_width, 0, width(), height());
-  suggestion_view_->SetBounds(0, 0, suggestion_width, height());
+  keyword_view_->SetBounds(suggestion_width, 0, width() - suggestion_width,
+                           height());
+  if (popup_contents_view_->InExplicitExperimentalKeywordMode()) {
+    suggestion_view_->SetBounds(kKeywordSuggestionIndent, 0,
+                                suggestion_width - kKeywordSuggestionIndent,
+                                height());
+  } else {
+    suggestion_view_->SetBounds(0, 0, suggestion_width, height());
+  }
 }
 
 bool OmniboxResultView::OnMousePressed(const ui::MouseEvent& event) {
@@ -467,7 +476,11 @@
       popup_contents_view_->model()->result().match_at(model_index_);
 
   if (command_id == IDS_OMNIBOX_REMOVE_SUGGESTION) {
-    ShowRemoveSuggestion(this, raw_match,
+    TemplateURLService* template_url_service = popup_contents_view_->model()
+                                                   ->edit_model()
+                                                   ->client()
+                                                   ->GetTemplateURLService();
+    ShowRemoveSuggestion(template_url_service, this, raw_match,
                          base::BindOnce(&OmniboxResultView::RemoveSuggestion,
                                         weak_factory_.GetWeakPtr()));
   } else if (command_id == IDS_OMNIBOX_WHY_THIS_SUGGESTION) {
diff --git a/chrome/browser/ui/views/omnibox/remove_suggestion_bubble.cc b/chrome/browser/ui/views/omnibox/remove_suggestion_bubble.cc
index 8a8e297..6cfcb5f 100644
--- a/chrome/browser/ui/views/omnibox/remove_suggestion_bubble.cc
+++ b/chrome/browser/ui/views/omnibox/remove_suggestion_bubble.cc
@@ -10,6 +10,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/omnibox/browser/autocomplete_match.h"
+#include "components/search_engines/template_url_service.h"
 #include "components/strings/grit/components_strings.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
@@ -21,49 +22,64 @@
 class RemoveSuggestionBubbleDialogDelegateView
     : public views::BubbleDialogDelegateView {
  public:
-  RemoveSuggestionBubbleDialogDelegateView(views::View* anchor_view,
-                                           const AutocompleteMatch& match,
-                                           base::OnceClosure remove_closure)
+  RemoveSuggestionBubbleDialogDelegateView(
+      TemplateURLService* template_url_service,
+      views::View* anchor_view,
+      const AutocompleteMatch& match,
+      base::OnceClosure remove_closure)
       : views::BubbleDialogDelegateView(anchor_view,
                                         views::BubbleBorder::TOP_LEFT),
         match_(match),
         remove_closure_(std::move(remove_closure)) {
+    DCHECK(template_url_service);
+    DCHECK(match_.SupportsDeletion());
+
     auto* layout_manager = SetLayoutManager(
         std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical));
     layout_manager->set_cross_axis_alignment(
         views::BoxLayout::CrossAxisAlignment::kStart);
     // TODO(tommycli): Replace this with the real spacing from UX.
     layout_manager->set_between_child_spacing(16);
-    // TODO(tommycli): Replace this with the real translated string from UX.
-    views::Label* why_this_suggestion_label =
-        new views::Label(match.GetWhyThisSuggestionText());
-    why_this_suggestion_label->SetMultiLine(true);
-    why_this_suggestion_label->SetHorizontalAlignment(
-        gfx::HorizontalAlignment::ALIGN_LEFT);
-    AddChildView(why_this_suggestion_label);
 
-    AddChildView(new views::Label(base::ASCIIToUTF16(
-        match.SupportsDeletion() ? "Remove suggestion from history?"
-                                 : "This match cannot be removed.")));
+    // Get the Search Provider name associated with this match.
+    base::string16 search_provider_short_name;
+    const TemplateURL* template_url =
+        match.GetTemplateURL(template_url_service, false);
+    // If the match has no associated Search Provider, get the default one,
+    // although this may still fail if it's forbidden by policy.
+    if (!template_url) {
+      template_url = template_url_service->GetDefaultSearchProvider();
+    }
+    if (template_url) {
+      search_provider_short_name =
+          template_url->AdjustedShortNameForLocaleDirection();
+    }
+
+    views::Label* description_label =
+        new views::Label(l10n_util::GetStringFUTF16(
+            IDS_OMNIBOX_REMOVE_SUGGESTION_BUBBLE_DESCRIPTION,
+            search_provider_short_name));
+    description_label->SetMultiLine(true);
+    description_label->SetHorizontalAlignment(
+        gfx::HorizontalAlignment::ALIGN_LEFT);
+    AddChildView(description_label);
+
+    // TODO(tommycli): Indent and set a smaller font per UX suggestions.
+    views::Label* url_label = new views::Label(match.contents);
+    url_label->SetMultiLine(true);
+    url_label->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
+    AddChildView(url_label);
   }
 
   // views::DialogDelegateView:
   int GetDialogButtons() const override {
-    int buttons = ui::DIALOG_BUTTON_CANCEL;
-    if (match_.SupportsDeletion())
-      buttons |= ui::DIALOG_BUTTON_OK;
-    return buttons;
+    return ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL;
   }
   base::string16 GetDialogButtonLabel(ui::DialogButton button) const override {
-    // TODO(tommycli): Replace this with the real translated string from UX.
-    if (button == ui::DIALOG_BUTTON_OK)
-      return base::ASCIIToUTF16("Remove");
-
-    return l10n_util::GetStringUTF16(match_.SupportsDeletion() ? IDS_CANCEL
-                                                               : IDS_CLOSE);
+    return l10n_util::GetStringUTF16(
+        button == ui::DIALOG_BUTTON_OK ? IDS_REMOVE : IDS_CANCEL);
   }
   bool Accept() override {
-    DCHECK(match_.SupportsDeletion());
     std::move(remove_closure_).Run();
     return true;
   }
@@ -76,7 +92,10 @@
 
   // views::WidgetDelegate:
   ui::ModalType GetModalType() const override { return ui::MODAL_TYPE_WINDOW; }
-  base::string16 GetWindowTitle() const override { return match_.contents; }
+  base::string16 GetWindowTitle() const override {
+    return l10n_util::GetStringUTF16(
+        IDS_OMNIBOX_REMOVE_SUGGESTION_BUBBLE_TITLE);
+  }
 
  private:
   AutocompleteMatch match_;
@@ -85,12 +104,13 @@
 
 }  // namespace
 
-void ShowRemoveSuggestion(views::View* anchor_view,
+void ShowRemoveSuggestion(TemplateURLService* template_url_service,
+                          views::View* anchor_view,
                           const AutocompleteMatch& match,
                           base::OnceClosure remove_closure) {
   views::BubbleDialogDelegateView::CreateBubble(
-      new RemoveSuggestionBubbleDialogDelegateView(anchor_view, match,
-                                                   std::move(remove_closure)))
+      new RemoveSuggestionBubbleDialogDelegateView(
+          template_url_service, anchor_view, match, std::move(remove_closure)))
       ->Show();
 }
 
diff --git a/chrome/browser/ui/views/omnibox/remove_suggestion_bubble.h b/chrome/browser/ui/views/omnibox/remove_suggestion_bubble.h
index a8d9a4a..6a730ca 100644
--- a/chrome/browser/ui/views/omnibox/remove_suggestion_bubble.h
+++ b/chrome/browser/ui/views/omnibox/remove_suggestion_bubble.h
@@ -7,6 +7,7 @@
 
 #include "base/callback_forward.h"
 
+class TemplateURLService;
 struct AutocompleteMatch;
 
 namespace views {
@@ -16,7 +17,8 @@
 // Shows a confirmation bubble to remove a suggestion represented by |match|.
 // If the user clicks Remove, then |remove_closure| is executed, and the bubble
 // is closed.
-void ShowRemoveSuggestion(views::View* anchor_view,
+void ShowRemoveSuggestion(TemplateURLService* template_url_service,
+                          views::View* anchor_view,
                           const AutocompleteMatch& match,
                           base::OnceClosure remove_closure);
 
diff --git a/chrome/browser/ui/views/page_action/omnibox_page_action_icon_container_view.cc b/chrome/browser/ui/views/page_action/omnibox_page_action_icon_container_view.cc
index 6ddeb65..bf20875 100644
--- a/chrome/browser/ui/views/page_action/omnibox_page_action_icon_container_view.cc
+++ b/chrome/browser/ui/views/page_action/omnibox_page_action_icon_container_view.cc
@@ -10,7 +10,7 @@
 #include "chrome/browser/ui/views/page_action/pwa_install_view.h"
 #include "chrome/browser/ui/views/page_action/zoom_view.h"
 #include "chrome/browser/ui/views/passwords/manage_passwords_icon_views.h"
-#include "chrome/browser/ui/views/send_tab_to_self/share_icon_view.h"
+#include "chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_icon_view.h"
 #include "chrome/browser/ui/views/translate/translate_icon_view.h"
 #include "ui/views/layout/box_layout.h"
 
@@ -29,7 +29,7 @@
           views::BoxLayout::kHorizontal, gfx::Insets(),
           params.between_icon_spacing));
   // Right align to clip the leftmost items first when not enough space.
-  layout.set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_END);
+  layout.set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kEnd);
 
   for (PageActionIconType type : params.types_enabled) {
     switch (type) {
@@ -61,9 +61,10 @@
         page_action_icons_.push_back(zoom_view_);
         break;
       case PageActionIconType::kSendTabToSelf:
-        share_icon_view_ = new send_tab_to_self::ShareIconView(
-            params.command_updater, params.page_action_icon_delegate);
-        page_action_icons_.push_back(share_icon_view_);
+        send_tab_to_self_icon_view_ =
+            new send_tab_to_self::SendTabToSelfIconView(
+                params.command_updater, params.page_action_icon_delegate);
+        page_action_icons_.push_back(send_tab_to_self_icon_view_);
         break;
       case PageActionIconType::kLocalCardMigration:
       case PageActionIconType::kSaveCard:
@@ -104,7 +105,7 @@
     case PageActionIconType::kZoom:
       return zoom_view_;
     case PageActionIconType::kSendTabToSelf:
-      return share_icon_view_;
+      return send_tab_to_self_icon_view_;
     case PageActionIconType::kLocalCardMigration:
     case PageActionIconType::kSaveCard:
       NOTREACHED();
diff --git a/chrome/browser/ui/views/page_action/omnibox_page_action_icon_container_view.h b/chrome/browser/ui/views/page_action/omnibox_page_action_icon_container_view.h
index fbc0362..9c8aacb 100644
--- a/chrome/browser/ui/views/page_action/omnibox_page_action_icon_container_view.h
+++ b/chrome/browser/ui/views/page_action/omnibox_page_action_icon_container_view.h
@@ -24,7 +24,7 @@
 class ZoomView;
 
 namespace send_tab_to_self {
-class ShareIconView;
+class SendTabToSelfIconView;
 }
 
 class OmniboxPageActionIconContainerView
@@ -82,7 +82,8 @@
   FindBarIcon* find_bar_icon_ = nullptr;
   ManagePasswordsIconViews* manage_passwords_icon_ = nullptr;
   PwaInstallView* pwa_install_view_ = nullptr;
-  send_tab_to_self::ShareIconView* share_icon_view_ = nullptr;
+  send_tab_to_self::SendTabToSelfIconView* send_tab_to_self_icon_view_ =
+      nullptr;
   TranslateIconView* translate_icon_ = nullptr;
   std::vector<PageActionIconView*> page_action_icons_;
 
diff --git a/chrome/browser/ui/views/page_info/page_info_bubble_view.cc b/chrome/browser/ui/views/page_info/page_info_bubble_view.cc
index 0e6a3fe..c920182 100644
--- a/chrome/browser/ui/views/page_info/page_info_bubble_view.cc
+++ b/chrome/browser/ui/views/page_info/page_info_bubble_view.cc
@@ -349,7 +349,7 @@
       gfx::Insets(), kSpacingBetweenButtons);
   // Make buttons left-aligned. For RTL languages, buttons will automatically
   // become right-aligned.
-  layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
   password_reuse_button_container_->SetLayoutManager(std::move(layout));
 
 #if defined(OS_WIN) || defined(OS_CHROMEOS)
diff --git a/chrome/browser/ui/views/page_info/permission_selector_row.cc b/chrome/browser/ui/views/page_info/permission_selector_row.cc
index 6477463..ed0449d 100644
--- a/chrome/browser/ui/views/page_info/permission_selector_row.cc
+++ b/chrome/browser/ui/views/page_info/permission_selector_row.cc
@@ -126,7 +126,6 @@
   SetEnabled(enabled);
   UpdateSelectedIndex(use_default);
   set_size_to_largest_label(false);
-  ModelChanged();
 }
 
 PermissionCombobox::~PermissionCombobox() {}
diff --git a/chrome/browser/ui/views/payments/credit_card_editor_view_controller.cc b/chrome/browser/ui/views/payments/credit_card_editor_view_controller.cc
index c4483ab..c99255c 100644
--- a/chrome/browser/ui/views/payments/credit_card_editor_view_controller.cc
+++ b/chrome/browser/ui/views/payments/credit_card_editor_view_controller.cc
@@ -195,7 +195,7 @@
       views::BoxLayout::kVertical,
       gfx::Insets(kRowBottomPadding, kPaymentRequestRowHorizontalInsets),
       kRowVerticalSpacing);
-  layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kStart);
   view->SetLayoutManager(std::move(layout));
diff --git a/chrome/browser/ui/views/payments/editor_view_controller.cc b/chrome/browser/ui/views/payments/editor_view_controller.cc
index 500852c..e9b978a 100644
--- a/chrome/browser/ui/views/payments/editor_view_controller.cc
+++ b/chrome/browser/ui/views/payments/editor_view_controller.cc
@@ -51,7 +51,7 @@
 
   std::unique_ptr<views::BoxLayout> layout =
       std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical);
-  layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kStretch);
   // This is the space between the input field and the error label.
@@ -144,7 +144,7 @@
 
 void EditorViewController::FillContentView(views::View* content_view) {
   auto layout = std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical);
-  layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kStretch);
   content_view->SetLayoutManager(std::move(layout));
diff --git a/chrome/browser/ui/views/payments/error_message_view_controller.cc b/chrome/browser/ui/views/payments/error_message_view_controller.cc
index 93073cc..ada3e2ea 100644
--- a/chrome/browser/ui/views/payments/error_message_view_controller.cc
+++ b/chrome/browser/ui/views/payments/error_message_view_controller.cc
@@ -51,7 +51,7 @@
   auto layout = std::make_unique<views::BoxLayout>(
       views::BoxLayout::kVertical,
       gfx::Insets(0, kPaymentRequestRowHorizontalInsets), 0);
-  layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kStart);
   content_view->SetLayoutManager(std::move(layout));
diff --git a/chrome/browser/ui/views/payments/order_summary_view_controller.cc b/chrome/browser/ui/views/payments/order_summary_view_controller.cc
index 8fc05fe..4caf6ef 100644
--- a/chrome/browser/ui/views/payments/order_summary_view_controller.cc
+++ b/chrome/browser/ui/views/payments/order_summary_view_controller.cc
@@ -161,7 +161,7 @@
 
 void OrderSummaryViewController::FillContentView(views::View* content_view) {
   auto layout = std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical);
-  layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kStretch);
   content_view->SetLayoutManager(std::move(layout));
diff --git a/chrome/browser/ui/views/payments/payment_method_view_controller.cc b/chrome/browser/ui/views/payments/payment_method_view_controller.cc
index 8c2f833..9b4029e 100644
--- a/chrome/browser/ui/views/payments/payment_method_view_controller.cc
+++ b/chrome/browser/ui/views/payments/payment_method_view_controller.cc
@@ -210,7 +210,7 @@
 
 void PaymentMethodViewController::FillContentView(views::View* content_view) {
   auto layout = std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical);
-  layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kStretch);
   content_view->SetLayoutManager(std::move(layout));
diff --git a/chrome/browser/ui/views/payments/payment_request_views_util.cc b/chrome/browser/ui/views/payments/payment_request_views_util.cc
index a238a7c..82c129d 100644
--- a/chrome/browser/ui/views/payments/payment_request_views_util.cc
+++ b/chrome/browser/ui/views/payments/payment_request_views_util.cc
@@ -274,7 +274,7 @@
 
   auto layout = std::make_unique<views::BoxLayout>(
       views::BoxLayout::kHorizontal, gfx::Insets(), 0);
-  layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kStart);
   content_view->SetLayoutManager(std::move(layout));
@@ -431,7 +431,7 @@
       views::BoxLayout::kHorizontal,
       gfx::Insets(0, kPaymentRequestRowHorizontalInsets),
       kRowHorizontalSpacing);
-  layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kStretch);
   header_view->SetLayoutManager(std::move(layout));
diff --git a/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc b/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
index f7c2a01..4df767f 100644
--- a/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
+++ b/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
@@ -943,7 +943,7 @@
   auto layout = std::make_unique<views::BoxLayout>(
       views::BoxLayout::kVertical,
       gfx::Insets(0, kPaymentRequestRowHorizontalInsets));
-  layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kStart);
   content_view->SetLayoutManager(std::move(layout));
diff --git a/chrome/browser/ui/views/payments/profile_list_view_controller.cc b/chrome/browser/ui/views/payments/profile_list_view_controller.cc
index b5c0511..9549239 100644
--- a/chrome/browser/ui/views/payments/profile_list_view_controller.cc
+++ b/chrome/browser/ui/views/payments/profile_list_view_controller.cc
@@ -360,7 +360,7 @@
 
 void ProfileListViewController::FillContentView(views::View* content_view) {
   auto layout = std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical);
-  layout->set_main_axis_alignment(views::BoxLayout::MAIN_AXIS_ALIGNMENT_START);
+  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
   layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kStretch);
   content_view->SetLayoutManager(std::move(layout));
diff --git a/chrome/browser/ui/views/payments/validating_combobox.cc b/chrome/browser/ui/views/payments/validating_combobox.cc
index 7bbec03..7857d69 100644
--- a/chrome/browser/ui/views/payments/validating_combobox.cc
+++ b/chrome/browser/ui/views/payments/validating_combobox.cc
@@ -14,12 +14,10 @@
     std::unique_ptr<ui::ComboboxModel> model,
     std::unique_ptr<ValidationDelegate> delegate)
     : Combobox(std::move(model)), delegate_(std::move(delegate)) {
-  // No need to remove observer on owned model.
-  this->model()->AddObserver(this);
   SetFocusBehavior(FocusBehavior::ALWAYS);
 }
 
-ValidatingCombobox::~ValidatingCombobox() {}
+ValidatingCombobox::~ValidatingCombobox() = default;
 
 void ValidatingCombobox::OnBlur() {
   Combobox::OnBlur();
@@ -41,9 +39,8 @@
   Validate();
 }
 
-void ValidatingCombobox::OnComboboxModelChanged(
-    ui::ComboboxModel* unused_model) {
-  ModelChanged();
+void ValidatingCombobox::OnComboboxModelChanged(ui::ComboboxModel* model) {
+  views::Combobox::OnComboboxModelChanged(model);
   delegate_->ComboboxModelChanged(this);
 }
 
diff --git a/chrome/browser/ui/views/payments/validating_combobox.h b/chrome/browser/ui/views/payments/validating_combobox.h
index 1714ec2..fcc2660 100644
--- a/chrome/browser/ui/views/payments/validating_combobox.h
+++ b/chrome/browser/ui/views/payments/validating_combobox.h
@@ -14,8 +14,7 @@
 
 namespace payments {
 
-class ValidatingCombobox : public views::Combobox,
-                           public ui::ComboboxModelObserver {
+class ValidatingCombobox : public views::Combobox {
  public:
   ValidatingCombobox(std::unique_ptr<ui::ComboboxModel> model,
                      std::unique_ptr<ValidationDelegate> delegate);
@@ -31,7 +30,7 @@
   // Called when the combobox contents is changed. May do validation.
   void OnContentsChanged();
 
-  // ui::ComboboxModelObserver:
+  // views::Combobox:
   void OnComboboxModelChanged(ui::ComboboxModel* model) override;
 
   // Identifies whether the current content if valid or not.
diff --git a/chrome/browser/ui/views/plugin_vm/plugin_vm_launcher_view.cc b/chrome/browser/ui/views/plugin_vm/plugin_vm_launcher_view.cc
index 0539e0f..2c4c440 100644
--- a/chrome/browser/ui/views/plugin_vm/plugin_vm_launcher_view.cc
+++ b/chrome/browser/ui/views/plugin_vm/plugin_vm_launcher_view.cc
@@ -166,7 +166,7 @@
 
   // Make sure the lower_container_view is pinned to the bottom of the dialog.
   lower_container_layout->set_main_axis_alignment(
-      views::BoxLayout::MAIN_AXIS_ALIGNMENT_END);
+      views::BoxLayout::MainAxisAlignment::kEnd);
   layout->SetFlexForView(lower_container_view, 1, true);
 }
 
diff --git a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.cc b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.cc
new file mode 100644
index 0000000..d51b9801
--- /dev/null
+++ b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.cc
@@ -0,0 +1,110 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.h"
+#include "chrome/browser/ui/views/chrome_layout_provider.h"
+#include "chrome/grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/views/layout/fill_layout.h"
+
+namespace send_tab_to_self {
+
+SendTabToSelfBubbleViewImpl::SendTabToSelfBubbleViewImpl(
+    views::View* anchor_view,
+    const gfx::Point& anchor_point,
+    content::WebContents* web_contents,
+    SendTabToSelfBubbleController* controller)
+    : LocationBarBubbleDelegateView(anchor_view, anchor_point, web_contents),
+      controller_(controller) {
+  DCHECK(controller);
+}
+
+SendTabToSelfBubbleViewImpl::~SendTabToSelfBubbleViewImpl() {}
+
+void SendTabToSelfBubbleViewImpl::Hide() {
+  if (controller_) {
+    controller_->OnBubbleClosed();
+    controller_ = nullptr;
+  }
+  CloseBubble();
+}
+
+bool SendTabToSelfBubbleViewImpl::ShouldShowCloseButton() const {
+  return true;
+}
+
+base::string16 SendTabToSelfBubbleViewImpl::GetWindowTitle() const {
+  return controller_->GetWindowTitle();
+}
+
+void SendTabToSelfBubbleViewImpl::WindowClosing() {
+  if (controller_) {
+    controller_->OnBubbleClosed();
+    controller_ = nullptr;
+  }
+}
+
+int SendTabToSelfBubbleViewImpl::GetDialogButtons() const {
+  return ui::DIALOG_BUTTON_NONE;
+}
+
+bool SendTabToSelfBubbleViewImpl::Close() {
+  return Cancel();
+}
+
+void SendTabToSelfBubbleViewImpl::ButtonPressed(views::Button* sender,
+                                                const ui::Event& event) {
+  // TODO(crbug/959698): handle the action when a button has been clicked.
+  NOTIMPLEMENTED();
+}
+
+gfx::Size SendTabToSelfBubbleViewImpl::CalculatePreferredSize() const {
+  const int width = ChromeLayoutProvider::Get()->GetDistanceMetric(
+      DISTANCE_BUBBLE_PREFERRED_WIDTH);
+  return gfx::Size(width, GetHeightForWidth(width));
+}
+
+void SendTabToSelfBubbleViewImpl::OnPaint(gfx::Canvas* canvas) {
+  views::BubbleDialogDelegateView::OnPaint(canvas);
+}
+
+void SendTabToSelfBubbleViewImpl::Show(DisplayReason reason) {
+  ShowForReason(reason);
+}
+
+void SendTabToSelfBubbleViewImpl::Init() {
+  auto* provider = ChromeLayoutProvider::Get();
+  set_margins(
+      gfx::Insets(provider->GetDistanceMetric(
+                      views::DISTANCE_DIALOG_CONTENT_MARGIN_TOP_CONTROL),
+                  0,
+                  provider->GetDistanceMetric(
+                      views::DISTANCE_DIALOG_CONTENT_MARGIN_BOTTOM_CONTROL),
+                  0));
+  SetLayoutManager(std::make_unique<views::FillLayout>());
+}
+
+void SendTabToSelfBubbleViewImpl::ShowScrollView() {
+  // TODO(crbug/960595): show the scroll view.
+  NOTIMPLEMENTED();
+}
+
+void SendTabToSelfBubbleViewImpl::PopulateScrollView() {
+  // TODO(crbug/960595): get the valid devices data and populate the bubble
+  // scroll view.
+  NOTIMPLEMENTED();
+}
+
+void SendTabToSelfBubbleViewImpl::MaybeSizeToContents() {
+  // The widget may be null if this is called while the dialog is opening.
+  if (GetWidget()) {
+    SizeToContents();
+  }
+}
+
+}  // namespace send_tab_to_self
diff --git a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.h b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.h
new file mode 100644
index 0000000..385b030
--- /dev/null
+++ b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.h
@@ -0,0 +1,92 @@
+// 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_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_BUBBLE_VIEW_IMPL_H_
+#define CHROME_BROWSER_UI_VIEWS_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_BUBBLE_VIEW_IMPL_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "chrome/browser/ui/media_router/cast_dialog_controller.h"
+#include "chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_view.h"
+#include "chrome/browser/ui/views/location_bar/location_bar_bubble_delegate_view.h"
+#include "ui/views/controls/button/button.h"
+
+namespace gfx {
+class Canvas;
+}  // namespace gfx
+
+namespace content {
+class WebContents;
+}
+
+namespace send_tab_to_self {
+
+class SendTabToSelfBubbleController;
+
+// View component of the send tab to self bubble that allows users to choose
+// target device to send tab to.
+class SendTabToSelfBubbleViewImpl : public SendTabToSelfBubbleView,
+                                    public views::ButtonListener,
+                                    public LocationBarBubbleDelegateView {
+ public:
+  // Bubble will be anchored to |anchor_view|.
+  SendTabToSelfBubbleViewImpl(views::View* anchor_view,
+                              const gfx::Point& anchor_point,
+                              content::WebContents* web_contents,
+                              SendTabToSelfBubbleController* controller);
+
+  ~SendTabToSelfBubbleViewImpl() override;
+
+  // SendTabToSelfBubbleView:
+  void Hide() override;
+
+  // views::WidgetDelegateView:
+  bool ShouldShowCloseButton() const override;
+  base::string16 GetWindowTitle() const override;
+  void WindowClosing() override;
+
+  // views::DialogDelegate:
+  int GetDialogButtons() const override;
+  bool Close() override;
+
+  // views::ButtonListener:
+  void ButtonPressed(views::Button* sender, const ui::Event& event) override;
+
+  // views::View:
+  gfx::Size CalculatePreferredSize() const override;
+  void OnPaint(gfx::Canvas* canvas) override;
+
+  void Show(DisplayReason reason);
+
+ private:
+  // views::BubbleDialogDelegateView:
+  void Init() override;
+
+  // Shows the scroll view.
+  void ShowScrollView();
+
+  // Populates the scroll view containing valid devices.
+  void PopulateScrollView();
+
+  // Resizes and potentially moves the bubble to fit the content's preferred
+  // size.
+  void MaybeSizeToContents();
+
+  // Title shown at the top of the bubble.
+  base::string16 bubble_title_;
+
+  SendTabToSelfBubbleController* controller_;  // Weak reference.
+
+  // The device that the user has selected to share tab to.
+  base::Optional<size_t> selected_device_index_;
+
+  DISALLOW_COPY_AND_ASSIGN(SendTabToSelfBubbleViewImpl);
+};
+
+}  // namespace send_tab_to_self
+
+#endif  // CHROME_BROWSER_UI_VIEWS_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_BUBBLE_VIEW_IMPL_H_
diff --git a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_icon_view.cc b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_icon_view.cc
new file mode 100644
index 0000000..1ca5034
--- /dev/null
+++ b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_icon_view.cc
@@ -0,0 +1,87 @@
+// 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/send_tab_to_self/send_tab_to_self_icon_view.h"
+
+#include "chrome/app/chrome_command_ids.h"
+#include "chrome/app/vector_icons/vector_icons.h"
+#include "chrome/browser/send_tab_to_self/send_tab_to_self_util.h"
+#include "chrome/browser/ui/browser_command_controller.h"
+#include "chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.h"
+#include "chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.h"
+#include "chrome/grit/generated_resources.h"
+#include "components/omnibox/browser/omnibox_view.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace send_tab_to_self {
+
+SendTabToSelfIconView::SendTabToSelfIconView(
+    CommandUpdater* command_updater,
+    PageActionIconView::Delegate* delegate)
+    : PageActionIconView(command_updater, IDC_SEND_TAB_TO_SELF, delegate) {
+  SetVisible(false);
+  SetLabel(l10n_util::GetStringUTF16(IDS_OMNIBOX_ICON_SEND_TAB_TO_SELF));
+  SetUpForInOutAnimation();
+}
+
+SendTabToSelfIconView::~SendTabToSelfIconView() {}
+
+views::BubbleDialogDelegateView* SendTabToSelfIconView::GetBubble() const {
+  SendTabToSelfBubbleController* controller = GetController();
+  if (!controller) {
+    return nullptr;
+  }
+
+  return static_cast<SendTabToSelfBubbleViewImpl*>(
+      controller->send_tab_to_self_bubble_view());
+}
+
+bool SendTabToSelfIconView::Update() {
+  content::WebContents* web_contents = GetWebContents();
+  if (!web_contents) {
+    return false;
+  }
+
+  const bool was_visible = visible();
+  const OmniboxView* omnibox_view = delegate()->GetOmniboxView();
+
+  if (!send_tab_to_self::ShouldOfferFeature(web_contents) && was_visible) {
+    // Hide the icon.
+    ResetSlideAnimation(false);
+    SetVisible(false);
+  } else if (!was_visible && omnibox_view && omnibox_view->IsSelectAll()) {
+    // Show the animation the first time the valid url is highlighted.
+    AnimateIn(IDS_OMNIBOX_ICON_SEND_TAB_TO_SELF);
+    SetVisible(true);
+  }
+
+  return was_visible != visible();
+}
+
+void SendTabToSelfIconView::OnExecuting(
+    PageActionIconView::ExecuteSource execute_source) {}
+
+const gfx::VectorIcon& SendTabToSelfIconView::GetVectorIcon() const {
+  return kSendTabToSelfIcon;
+}
+
+base::string16 SendTabToSelfIconView::GetTextForTooltipAndAccessibleName()
+    const {
+  return l10n_util::GetStringUTF16(IDS_OMNIBOX_TOOLTIP_SEND_TAB_TO_SELF);
+}
+
+SendTabToSelfBubbleController* SendTabToSelfIconView::GetController() const {
+  content::WebContents* web_contents = GetWebContents();
+  if (!web_contents) {
+    return nullptr;
+  }
+  return SendTabToSelfBubbleController::CreateOrGetFromWebContents(
+      web_contents);
+}
+
+void SendTabToSelfIconView::AnimationEnded(const gfx::Animation* animation) {
+  IconLabelBubbleView::AnimationEnded(animation);
+}
+
+}  // namespace send_tab_to_self
diff --git a/chrome/browser/ui/views/send_tab_to_self/share_icon_view.h b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_icon_view.h
similarity index 61%
rename from chrome/browser/ui/views/send_tab_to_self/share_icon_view.h
rename to chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_icon_view.h
index e3a3a76..ff8de16 100644
--- a/chrome/browser/ui/views/send_tab_to_self/share_icon_view.h
+++ b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_icon_view.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_VIEWS_SEND_TAB_TO_SELF_SHARE_ICON_VIEW_H_
-#define CHROME_BROWSER_UI_VIEWS_SEND_TAB_TO_SELF_SHARE_ICON_VIEW_H_
+#ifndef CHROME_BROWSER_UI_VIEWS_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_ICON_VIEW_H_
+#define CHROME_BROWSER_UI_VIEWS_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_ICON_VIEW_H_
 
 #include "base/macros.h"
 #include "chrome/browser/ui/views/page_action/page_action_icon_view.h"
@@ -12,13 +12,15 @@
 
 namespace send_tab_to_self {
 
+class SendTabToSelfBubbleController;
+
 // The location bar icon to show the send tab to self bubble where the user can
 // choose to share the url to a target device.
-class ShareIconView : public PageActionIconView {
+class SendTabToSelfIconView : public PageActionIconView {
  public:
-  ShareIconView(CommandUpdater* command_updater,
-                PageActionIconView::Delegate* delegate);
-  ~ShareIconView() override;
+  SendTabToSelfIconView(CommandUpdater* command_updater,
+                        PageActionIconView::Delegate* delegate);
+  ~SendTabToSelfIconView() override;
 
   // PageActionIconView:
   views::BubbleDialogDelegateView* GetBubble() const override;
@@ -31,12 +33,13 @@
   const gfx::VectorIcon& GetVectorIcon() const override;
 
  private:
+  SendTabToSelfBubbleController* GetController() const;
   // gfx::AnimationDelegate:
   void AnimationEnded(const gfx::Animation* animation) override;
 
-  DISALLOW_COPY_AND_ASSIGN(ShareIconView);
+  DISALLOW_COPY_AND_ASSIGN(SendTabToSelfIconView);
 };
 
 }  // namespace send_tab_to_self
 
-#endif  // CHROME_BROWSER_UI_VIEWS_SEND_TAB_TO_SELF_SHARE_ICON_VIEW_H_
+#endif  // CHROME_BROWSER_UI_VIEWS_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_ICON_VIEW_H_
diff --git a/chrome/browser/ui/views/send_tab_to_self/share_icon_view.cc b/chrome/browser/ui/views/send_tab_to_self/share_icon_view.cc
deleted file mode 100644
index 9432c0f5..0000000
--- a/chrome/browser/ui/views/send_tab_to_self/share_icon_view.cc
+++ /dev/null
@@ -1,70 +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 "chrome/browser/ui/views/send_tab_to_self/share_icon_view.h"
-
-#include "chrome/app/chrome_command_ids.h"
-#include "chrome/app/vector_icons/vector_icons.h"
-#include "chrome/browser/send_tab_to_self/send_tab_to_self_util.h"
-#include "chrome/browser/ui/browser_command_controller.h"
-#include "chrome/grit/generated_resources.h"
-#include "components/omnibox/browser/omnibox_view.h"
-#include "ui/base/l10n/l10n_util.h"
-
-namespace send_tab_to_self {
-
-ShareIconView::ShareIconView(CommandUpdater* command_updater,
-                             PageActionIconView::Delegate* delegate)
-    : PageActionIconView(command_updater, IDC_SEND_TAB_TO_SELF, delegate) {
-  SetVisible(false);
-  SetLabel(l10n_util::GetStringUTF16(IDS_OMNIBOX_ICON_SEND_TAB_TO_SELF));
-  SetUpForInOutAnimation();
-}
-
-ShareIconView::~ShareIconView() {}
-
-views::BubbleDialogDelegateView* ShareIconView::GetBubble() const {
-  // TODO(crbug/950388): add implementation.
-  NOTIMPLEMENTED();
-  return nullptr;
-}
-
-bool ShareIconView::Update() {
-  content::WebContents* web_contents = GetWebContents();
-  if (!web_contents) {
-    return false;
-  }
-
-  const bool was_visible = visible();
-  const OmniboxView* omnibox_view = delegate()->GetOmniboxView();
-
-  if (!send_tab_to_self::ShouldOfferFeature(web_contents) && was_visible) {
-    // Hide the icon.
-    ResetSlideAnimation(false);
-    SetVisible(false);
-  } else if (!was_visible && omnibox_view && omnibox_view->IsSelectAll()) {
-    // Show the animation the first time the valid url is highlighted.
-    AnimateIn(IDS_OMNIBOX_ICON_SEND_TAB_TO_SELF);
-    SetVisible(true);
-  }
-
-  return was_visible != visible();
-}
-
-void ShareIconView::OnExecuting(
-    PageActionIconView::ExecuteSource execute_source) {}
-
-const gfx::VectorIcon& ShareIconView::GetVectorIcon() const {
-  return kSendTabToSelfIcon;
-}
-
-base::string16 ShareIconView::GetTextForTooltipAndAccessibleName() const {
-  return l10n_util::GetStringUTF16(IDS_OMNIBOX_TOOLTIP_SEND_TAB_TO_SELF);
-}
-
-void ShareIconView::AnimationEnded(const gfx::Animation* animation) {
-  IconLabelBubbleView::AnimationEnded(animation);
-}
-
-}  // namespace send_tab_to_self
diff --git a/chrome/browser/ui/views/toolbar/toolbar_action_view.cc b/chrome/browser/ui/views/toolbar/toolbar_action_view.cc
index 18ed7af..20aad40 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_action_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_action_view.cc
@@ -28,6 +28,7 @@
 #include "ui/gfx/image/image_skia_operations.h"
 #include "ui/gfx/image/image_skia_source.h"
 #include "ui/views/animation/ink_drop_impl.h"
+#include "ui/views/controls/button/button.h"
 #include "ui/views/controls/button/label_button_border.h"
 #include "ui/views/controls/menu/menu_controller.h"
 #include "ui/views/controls/menu/menu_model_adapter.h"
@@ -286,10 +287,10 @@
   return GetFocusManager();
 }
 
-views::View* ToolbarActionView::GetReferenceViewForPopup() {
+views::Button* ToolbarActionView::GetReferenceButtonForPopup() {
   // Browser actions in the overflow menu can still show popups, so we may need
   // a reference view other than this button's parent. If so, use the overflow
-  // view.
+  // view which is a BrowserAppMenuButton.
   return visible() ? this : delegate_->GetOverflowReferenceView();
 }
 
@@ -300,13 +301,13 @@
 void ToolbarActionView::OnPopupShown(bool by_user) {
   // If this was through direct user action, we press the menu button.
   if (by_user) {
-    // We set the state of the menu button we're using as a reference view,
-    // which is either this or the overflow reference view.
-    // This cast is safe because GetReferenceViewForPopup returns either |this|
-    // or delegate_->GetOverflowReferenceView(), which returns a MenuButton.
-    views::MenuButton* reference_view =
-        static_cast<views::MenuButton*>(GetReferenceViewForPopup());
-    pressed_lock_ = reference_view->button_controller()->TakeLock();
+    // GetReferenceButtonForPopup returns either |this| or
+    // delegate_->GetOverflowReferenceView() which is a BrowserAppMenuButton.
+    // This cast is safe because both will have a MenuButtonController.
+    views::MenuButtonController* reference_view_controller =
+        static_cast<views::MenuButtonController*>(
+            GetReferenceButtonForPopup()->button_controller());
+    pressed_lock_ = reference_view_controller->TakeLock();
   }
 }
 
diff --git a/chrome/browser/ui/views/toolbar/toolbar_action_view.h b/chrome/browser/ui/views/toolbar/toolbar_action_view.h
index c1d474a..2b6db8a 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_action_view.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_action_view.h
@@ -113,7 +113,7 @@
   // ToolbarActionViewDelegateViews:
   views::View* GetAsView() override;
   views::FocusManager* GetFocusManagerForAccelerator() override;
-  views::View* GetReferenceViewForPopup() override;
+  views::Button* GetReferenceButtonForPopup() override;
   bool IsMenuRunning() const override;
   void OnPopupShown(bool by_user) override;
   void OnPopupClosed() override;
diff --git a/chrome/browser/ui/views/toolbar/toolbar_action_view_delegate_views.h b/chrome/browser/ui/views/toolbar/toolbar_action_view_delegate_views.h
index 216be70..dec9466 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_action_view_delegate_views.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_action_view_delegate_views.h
@@ -8,6 +8,7 @@
 #include "chrome/browser/ui/toolbar/toolbar_action_view_delegate.h"
 
 namespace views {
+class Button;
 class FocusManager;
 class View;
 }
@@ -22,8 +23,13 @@
   // Returns the FocusManager to use when registering accelerators.
   virtual views::FocusManager* GetFocusManagerForAccelerator() = 0;
 
-  // Returns the reference view for the extension action's popup.
-  virtual views::View* GetReferenceViewForPopup() = 0;
+  // Returns the reference button for the extension action's popup. Rather than
+  // relying on the button being a MenuButton, the button returned should have a
+  // MenuButtonController. This is part of the ongoing work from
+  // http://crbug.com/901183 to simplify the button hierarchy by migrating
+  // controller logic into a separate class leaving MenuButton as an empty class
+  // to be deprecated.
+  virtual views::Button* GetReferenceButtonForPopup() = 0;
 
  protected:
   ~ToolbarActionViewDelegateViews() override {}
diff --git a/chrome/browser/ui/views/toolbar/toolbar_button.h b/chrome/browser/ui/views/toolbar/toolbar_button.h
index 0f3d1ba..8bcd8d2 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_button.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_button.h
@@ -33,8 +33,6 @@
 
 // This class provides basic drawing and mouse-over behavior for buttons
 // appearing in the toolbar.
-// TODO(cyan): Consider making ToolbarButton and AppMenuButton share a common
-// base class https://crbug.com/819854.
 class ToolbarButton : public views::LabelButton,
                       public views::ContextMenuController {
  public:
diff --git a/chrome/browser/ui/views/webauthn/authenticator_request_dialog_view.cc b/chrome/browser/ui/views/webauthn/authenticator_request_dialog_view.cc
index 0677674..2247c9c 100644
--- a/chrome/browser/ui/views/webauthn/authenticator_request_dialog_view.cc
+++ b/chrome/browser/ui/views/webauthn/authenticator_request_dialog_view.cc
@@ -26,6 +26,10 @@
 #include "ui/views/vector_icons.h"
 #include "ui/views/window/dialog_client_view.h"
 
+#if defined(USE_AURA)
+#include "ui/wm/core/window_animations.h"
+#endif
+
 // static
 void ShowAuthenticatorRequestDialog(
     content::WebContents* web_contents,
@@ -256,6 +260,21 @@
   if (!first_shown_) {
     constrained_window::ShowWebModalDialogViews(this, web_contents());
     DCHECK(GetWidget());
+
+#if defined(USE_AURA)
+    // TODO(agl): remove this to revert back to the default "rotate" animations.
+    //
+    // It appears that the rotate animation fails to handle layer visibility
+    // correctly and thus triggers DCHECKs and other issues. Using "fade" works
+    // around this until "rotate" can be fixed.
+    // https://chromium-review.googlesource.com/c/chromium/src/+/1610462
+    //
+    // When removing this, also remove the #include of window_animations.h.
+    wm::SetWindowVisibilityAnimationType(
+        GetWidget()->GetNativeWindow(),
+        wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
+#endif
+
     first_shown_ = true;
   } else {
     GetWidget()->Show();
diff --git a/chrome/browser/vr/webxr_vr_input_browser_test.cc b/chrome/browser/vr/webxr_vr_input_browser_test.cc
index dbe54d2..6b9e39e 100644
--- a/chrome/browser/vr/webxr_vr_input_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_input_browser_test.cc
@@ -235,8 +235,6 @@
       GetFileUrlForHtmlTestFile("test_webxr_input_same_object"));
   EnterSessionWithUserGestureOrFail();
 
-  RunJavaScriptOrFail("setupListeners()");
-
   // We should only have seen the first change indicating we have input sources.
   PollJavaScriptBooleanOrFail("inputChangeEvents === 1", kPollTimeoutShort);
 
@@ -312,6 +310,7 @@
 
   // The trigger should be button 0, and the first set of axes should have it's
   // value set.
+  ExecuteStepAndWait("validateMapping('xr-standard')");
   ExecuteStepAndWait("validateButtonPressed(0)");
   ExecuteStepAndWait("validateAxesValues(0, 0.5, -0.5)");
   RunJavaScriptOrFail("done()");
@@ -359,6 +358,9 @@
   my_mock.ToggleButtons(controller_index,
                         vr::ButtonMaskFromId(vr::k_EButton_Grip));
 
+  // Controller should meet the requirements for the 'xr-standard' mapping.
+  ExecuteStepAndWait("validateMapping('xr-standard')");
+
   // The secondary set of axes should be set appropriately.
   ExecuteStepAndWait("validateAxesValues(1, 0.25, -0.25)");
 
@@ -408,6 +410,7 @@
   // Index 2 and 3 are reserved for the grip and secondary joystick.
   // As our controller doesn't support them, they should be present but not
   // pressed, and our "extra" button should be index 4 and should be pressed.
+  ExecuteStepAndWait("validateMapping('xr-standard')");
   ExecuteStepAndWait("validateButtonPressed(0)");
   ExecuteStepAndWait("validateButtonPressed(1)");
   ExecuteStepAndWait("validateButtonNotPressed(2)");
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_uninstaller.cc b/chrome/browser/web_applications/extensions/bookmark_app_uninstaller.cc
index 48809d6..74e9021 100644
--- a/chrome/browser/web_applications/extensions/bookmark_app_uninstaller.cc
+++ b/chrome/browser/web_applications/extensions/bookmark_app_uninstaller.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/web_applications/extensions/bookmark_app_uninstaller.h"
 
 #include "base/optional.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/components/app_registrar.h"
@@ -21,19 +22,24 @@
 
 BookmarkAppUninstaller::~BookmarkAppUninstaller() = default;
 
-bool BookmarkAppUninstaller::UninstallApp(const GURL& app_url) {
+void BookmarkAppUninstaller::UninstallApp(const GURL& app_url,
+                                          UninstallCallback callback) {
   base::Optional<web_app::AppId> app_id =
       externally_installed_app_prefs_.LookupAppId(app_url);
   if (!app_id.has_value()) {
     LOG(WARNING) << "Couldn't uninstall app with url " << app_url
                  << "; No corresponding extension for url.";
-    return false;
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), false));
+    return;
   }
 
   if (!registrar_->IsInstalled(app_id.value())) {
     LOG(WARNING) << "Couldn't uninstall app with url " << app_url
                  << "; Extension not installed.";
-    return false;
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), false));
+    return;
   }
 
   base::string16 error;
@@ -45,7 +51,8 @@
     LOG(WARNING) << "Couldn't uninstall app with url " << app_url << ". "
                  << error;
   }
-  return uninstalled;
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), uninstalled));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_uninstaller.h b/chrome/browser/web_applications/extensions/bookmark_app_uninstaller.h
index a114cb1..a8ee388 100644
--- a/chrome/browser/web_applications/extensions/bookmark_app_uninstaller.h
+++ b/chrome/browser/web_applications/extensions/bookmark_app_uninstaller.h
@@ -18,13 +18,15 @@
 
 class BookmarkAppUninstaller {
  public:
+  using UninstallCallback = base::OnceCallback<void(bool uninstalled)>;
+
   BookmarkAppUninstaller(Profile* profile, web_app::AppRegistrar* registrar);
   virtual ~BookmarkAppUninstaller();
 
-  // Returns true if the app with |app_url| was successfully uninstalled.
-  // Returns false if the app doesn't not exist, or the app failed to be
-  // uninstalled.
-  virtual bool UninstallApp(const GURL& app_url);
+  // Runs |callback| with true if the app with |app_url| was successfully
+  // uninstalled. Runs callback with false if the app doesn't not exist, or the
+  // app failed to be uninstalled.
+  virtual void UninstallApp(const GURL& app_url, UninstallCallback callback);
 
  private:
   Profile* profile_;
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_uninstaller_unittest.cc b/chrome/browser/web_applications/extensions/bookmark_app_uninstaller_unittest.cc
index dfa52b5..754ba12c 100644
--- a/chrome/browser/web_applications/extensions/bookmark_app_uninstaller_unittest.cc
+++ b/chrome/browser/web_applications/extensions/bookmark_app_uninstaller_unittest.cc
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "base/scoped_observer.h"
+#include "base/test/bind_test_util.h"
 #include "chrome/browser/extensions/test_extension_system.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
 #include "chrome/browser/web_applications/extensions/bookmark_app_registrar.h"
@@ -115,6 +116,18 @@
         app_id, Manifest::EXTERNAL_POLICY, false /* external_uninstall */);
   }
 
+  bool UninstallAppAndWait(const GURL& app_url) {
+    base::RunLoop run_loop;
+    base::Optional<bool> result;
+    uninstaller().UninstallApp(
+        app_url, base::BindLambdaForTesting([&](bool uninstalled) {
+          result = uninstalled;
+          run_loop.Quit();
+        }));
+    run_loop.Run();
+    return result.value();
+  }
+
   void ResetResults() { test_extension_registry_observer_->ResetResults(); }
 
   BookmarkAppUninstaller& uninstaller() { return *uninstaller_; }
@@ -141,7 +154,7 @@
   SimulateInstalledApp(kFooWebAppUrl);
   ASSERT_EQ(1u, enabled_extensions().size());
 
-  EXPECT_TRUE(uninstaller().UninstallApp(kFooWebAppUrl));
+  EXPECT_TRUE(UninstallAppAndWait(kFooWebAppUrl));
   content::RunAllTasksUntilIdle();
 
   EXPECT_EQ(1u, uninstalled_extension_ids().size());
@@ -153,7 +166,7 @@
   auto bar_app_id = SimulateInstalledApp(kBarWebAppUrl);
   ASSERT_EQ(2u, enabled_extensions().size());
 
-  EXPECT_TRUE(uninstaller().UninstallApp(kBarWebAppUrl));
+  EXPECT_TRUE(UninstallAppAndWait(kBarWebAppUrl));
   content::RunAllTasksUntilIdle();
 
   EXPECT_EQ(1u, uninstalled_extension_ids().size());
@@ -163,7 +176,7 @@
 
   ResetResults();
 
-  EXPECT_TRUE(uninstaller().UninstallApp(kFooWebAppUrl));
+  EXPECT_TRUE(UninstallAppAndWait(kFooWebAppUrl));
   content::RunAllTasksUntilIdle();
 
   EXPECT_EQ(1u, uninstalled_extension_ids().size());
@@ -174,19 +187,19 @@
   SimulateInstalledApp(kFooWebAppUrl);
   SimulateExternalAppUninstalledByUser(kFooWebAppUrl);
 
-  EXPECT_FALSE(uninstaller().UninstallApp(kFooWebAppUrl));
+  EXPECT_FALSE(UninstallAppAndWait(kFooWebAppUrl));
 }
 
 // Tests trying to uninstall an app that was never installed.
 TEST_F(BookmarkAppUninstallerTest, Uninstall_FailsNeverInstalled) {
-  EXPECT_FALSE(uninstaller().UninstallApp(kFooWebAppUrl));
+  EXPECT_FALSE(UninstallAppAndWait(kFooWebAppUrl));
 }
 
 // Tests trying to uninstall an app that was previously uninstalled.
 TEST_F(BookmarkAppUninstallerTest, Uninstall_FailsAlreadyUninstalled) {
   SimulateInstalledApp(kFooWebAppUrl);
 
-  EXPECT_TRUE(uninstaller().UninstallApp(kFooWebAppUrl));
+  EXPECT_TRUE(UninstallAppAndWait(kFooWebAppUrl));
   content::RunAllTasksUntilIdle();
 
   EXPECT_EQ(1u, uninstalled_extension_ids().size());
@@ -194,7 +207,7 @@
 
   ResetResults();
 
-  EXPECT_FALSE(uninstaller().UninstallApp(kFooWebAppUrl));
+  EXPECT_FALSE(UninstallAppAndWait(kFooWebAppUrl));
   content::RunAllTasksUntilIdle();
 
   EXPECT_EQ(0u, uninstalled_extension_ids().size());
diff --git a/chrome/browser/web_applications/extensions/pending_bookmark_app_manager.cc b/chrome/browser/web_applications/extensions/pending_bookmark_app_manager.cc
index 49535fe..3be7d49 100644
--- a/chrome/browser/web_applications/extensions/pending_bookmark_app_manager.cc
+++ b/chrome/browser/web_applications/extensions/pending_bookmark_app_manager.cc
@@ -93,7 +93,11 @@
     std::vector<GURL> uninstall_urls,
     const UninstallCallback& callback) {
   for (auto& url : uninstall_urls) {
-    callback.Run(url, uninstaller_->UninstallApp(url));
+    uninstaller_->UninstallApp(
+        url, base::BindOnce(
+                 [](const UninstallCallback& callback, const GURL& app_url,
+                    bool uninstalled) { callback.Run(app_url, uninstalled); },
+                 callback, url));
   }
 }
 
@@ -210,22 +214,6 @@
   web_contents_.reset();
 }
 
-bool PendingBookmarkAppManager::UninstallPlaceholderIfNecessary(
-    const web_app::InstallOptions install_options) {
-  if (!install_options.reinstall_placeholder)
-    return true;
-
-  base::Optional<web_app::AppId> app_id =
-      externally_installed_app_prefs_.LookupPlaceholderAppId(
-          install_options.url);
-
-  if (app_id.has_value() && registrar_->IsInstalled(app_id.value())) {
-    return uninstaller_->UninstallApp(install_options.url);
-  }
-
-  return true;
-}
-
 void PendingBookmarkAppManager::StartInstallationTask(
     std::unique_ptr<TaskAndCallback> task) {
   DCHECK(!current_task_and_callback_);
@@ -254,18 +242,7 @@
       current_task_and_callback_->task->install_options();
 
   if (result == web_app::WebAppUrlLoader::Result::kUrlLoaded) {
-    if (!UninstallPlaceholderIfNecessary(install_options)) {
-      CurrentInstallationFinished(base::nullopt);
-      return;
-    }
-
-    current_task_and_callback_->task->Install(
-        web_contents_.get(),
-        base::BindOnce(&PendingBookmarkAppManager::OnInstalled,
-                       // Safe because the installation task will not run its
-                       // callback after being deleted and this class owns the
-                       // task.
-                       base::Unretained(this)));
+    UninstallPlaceholderIfNecessary();
     return;
   }
 
@@ -281,6 +258,42 @@
   CurrentInstallationFinished(base::nullopt);
 }
 
+void PendingBookmarkAppManager::UninstallPlaceholderIfNecessary() {
+  const auto& install_options =
+      current_task_and_callback_->task->install_options();
+
+  if (!install_options.reinstall_placeholder) {
+    OnPlaceholderUninstalled(true);
+    return;
+  }
+
+  base::Optional<web_app::AppId> app_id =
+      externally_installed_app_prefs_.LookupPlaceholderAppId(
+          install_options.url);
+
+  if (app_id.has_value() && registrar_->IsInstalled(app_id.value())) {
+    uninstaller_->UninstallApp(
+        install_options.url,
+        base::BindOnce(&PendingBookmarkAppManager::OnPlaceholderUninstalled,
+                       weak_ptr_factory_.GetWeakPtr()));
+    return;
+  }
+
+  OnPlaceholderUninstalled(true);
+}
+
+void PendingBookmarkAppManager::OnPlaceholderUninstalled(bool succeeded) {
+  if (!succeeded) {
+    CurrentInstallationFinished(base::nullopt);
+    return;
+  }
+
+  current_task_and_callback_->task->Install(
+      web_contents_.get(),
+      base::BindOnce(&PendingBookmarkAppManager::OnInstalled,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
 void PendingBookmarkAppManager::OnInstalled(
     BookmarkAppInstallationTask::Result result) {
   CurrentInstallationFinished(result.app_id);
diff --git a/chrome/browser/web_applications/extensions/pending_bookmark_app_manager.h b/chrome/browser/web_applications/extensions/pending_bookmark_app_manager.h
index 26401be3..428978f3 100644
--- a/chrome/browser/web_applications/extensions/pending_bookmark_app_manager.h
+++ b/chrome/browser/web_applications/extensions/pending_bookmark_app_manager.h
@@ -85,15 +85,16 @@
 
   void MaybeStartNextInstallation();
 
-  bool UninstallPlaceholderIfNecessary(
-      const web_app::InstallOptions install_options);
-
   void StartInstallationTask(std::unique_ptr<TaskAndCallback> task);
 
   void CreateWebContentsIfNecessary();
 
   void OnUrlLoaded(web_app::WebAppUrlLoader::Result result);
 
+  void UninstallPlaceholderIfNecessary();
+
+  void OnPlaceholderUninstalled(bool succeeded);
+
   void OnInstalled(BookmarkAppInstallationTask::Result result);
 
   void CurrentInstallationFinished(const base::Optional<std::string>& app_id);
diff --git a/chrome/browser/web_applications/extensions/pending_bookmark_app_manager_unittest.cc b/chrome/browser/web_applications/extensions/pending_bookmark_app_manager_unittest.cc
index 30f02f1..711b92b 100644
--- a/chrome/browser/web_applications/extensions/pending_bookmark_app_manager_unittest.cc
+++ b/chrome/browser/web_applications/extensions/pending_bookmark_app_manager_unittest.cc
@@ -168,19 +168,24 @@
   }
 
   // BookmarkAppUninstaller
-  bool UninstallApp(const GURL& app_url) override {
+  void UninstallApp(const GURL& app_url, UninstallCallback callback) override {
     DCHECK(base::ContainsKey(next_result_map_, app_url));
 
     ++uninstall_call_count_;
     uninstalled_app_urls_.push_back(app_url);
 
-    bool result = next_result_map_[app_url];
-    next_result_map_.erase(app_url);
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(base::BindLambdaForTesting(
+                                      [&, app_url](UninstallCallback callback) {
+                                        bool result = next_result_map_[app_url];
+                                        next_result_map_.erase(app_url);
 
-    if (result)
-      registrar_->RemoveAsInstalled(GenerateFakeAppId(app_url));
-
-    return result;
+                                        if (result)
+                                          registrar_->RemoveAsInstalled(
+                                              GenerateFakeAppId(app_url));
+                                        std::move(callback).Run(result);
+                                      }),
+                                  std::move(callback)));
   }
 
  private:
diff --git a/chrome/common/extensions/api/file_manager_private.idl b/chrome/common/extensions/api/file_manager_private.idl
index 9909bb7..cd9fd74 100644
--- a/chrome/common/extensions/api/file_manager_private.idl
+++ b/chrome/common/extensions/api/file_manager_private.idl
@@ -559,6 +559,8 @@
   boolean searchSuggestEnabled;
   boolean use24hourClock;
   DOMString timezone;
+  boolean arcEnabled;
+  boolean arcRemovableMediaAccessEnabled;
 };
 
 dictionary PreferencesChange {
diff --git a/chrome/common/media_router/providers/cast/cast_media_source.cc b/chrome/common/media_router/providers/cast/cast_media_source.cc
index 8998da3..9e463a7 100644
--- a/chrome/common/media_router/providers/cast/cast_media_source.cc
+++ b/chrome/common/media_router/providers/cast/cast_media_source.cc
@@ -27,17 +27,21 @@
 using media_router::DefaultActionPolicy;
 
 template <>
-const EnumTable<AutoJoinPolicy> EnumTable<AutoJoinPolicy>::instance({
-    {AutoJoinPolicy::kPageScoped, "page_scoped"},
-    {AutoJoinPolicy::kTabAndOriginScoped, "tab_and_origin_scoped"},
-    {AutoJoinPolicy::kOriginScoped, "origin_scoped"},
-});
+const EnumTable<AutoJoinPolicy> EnumTable<AutoJoinPolicy>::instance(
+    {
+        {AutoJoinPolicy::kPageScoped, "page_scoped"},
+        {AutoJoinPolicy::kTabAndOriginScoped, "tab_and_origin_scoped"},
+        {AutoJoinPolicy::kOriginScoped, "origin_scoped"},
+    },
+    AutoJoinPolicy::kMaxValue);
 
 template <>
-const EnumTable<DefaultActionPolicy> EnumTable<DefaultActionPolicy>::instance({
-    {DefaultActionPolicy::kCreateSession, "create_session"},
-    {DefaultActionPolicy::kCastThisTab, "cast_this_tab"},
-});
+const EnumTable<DefaultActionPolicy> EnumTable<DefaultActionPolicy>::instance(
+    {
+        {DefaultActionPolicy::kCreateSession, "create_session"},
+        {DefaultActionPolicy::kCastThisTab, "cast_this_tab"},
+    },
+    DefaultActionPolicy::kMaxValue);
 
 template <>
 const EnumTable<CastDeviceCapability> EnumTable<CastDeviceCapability>::instance(
@@ -50,7 +54,7 @@
         {CastDeviceCapability::VIDEO_OUT, "video_out"},
         // NONE deliberately omitted
     },
-    UnsortedEnumTable);
+    NonConsecutiveEnumTable);
 
 }  // namespace cast_util
 
diff --git a/chrome/common/media_router/providers/cast/cast_media_source.h b/chrome/common/media_router/providers/cast/cast_media_source.h
index 81e2b34..d7fdf25 100644
--- a/chrome/common/media_router/providers/cast/cast_media_source.h
+++ b/chrome/common/media_router/providers/cast/cast_media_source.h
@@ -75,12 +75,16 @@
 enum class AutoJoinPolicy {
   // No automatic connection.  This is the default when no policy is specified.
   kPageScoped,
+
   // Automatically connects when the session was started with the same app ID,
   // in the same tab and page origin.
   kTabAndOriginScoped,
+
   // Automatically connects when the session was started with the same app ID
   // and the same page origin (regardless of tab).
   kOriginScoped,
+
+  kMaxValue = kOriginScoped,
 };
 
 // Default action policy determines when the SDK will automatically create a
@@ -96,6 +100,8 @@
   // being cast.  The Cast dialog prompts the user to mirror the tab (mirror,
   // not cast, despite the name).
   kCastThisTab,
+
+  kMaxValue = kCastThisTab,
 };
 
 // Tests whether a sender specified by (origin1, tab_id1) is allowed by |policy|
diff --git a/chrome/renderer/searchbox/searchbox_extension.cc b/chrome/renderer/searchbox/searchbox_extension.cc
index 714a7cf..7a962078 100644
--- a/chrome/renderer/searchbox/searchbox_extension.cc
+++ b/chrome/renderer/searchbox/searchbox_extension.cc
@@ -44,6 +44,7 @@
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/base/window_open_disposition.h"
 #include "ui/events/keycodes/keyboard_codes.h"
+#include "ui/gfx/color_palette.h"
 #include "ui/gfx/color_utils.h"
 #include "ui/gfx/text_constants.h"
 #include "ui/gfx/text_elider.h"
@@ -75,6 +76,54 @@
   return !color_utils::IsDark(ntp_text);
 }
 
+// Calculate icon color for given background color.
+SkColor CalculateIconColor(SkColor bg_color) {
+  color_utils::HSL hsl;
+  SkColorToHSL(bg_color, &hsl);
+
+  // If luminosity is 0, it means |bg_color| color is black. Use white icon
+  // color for black backgrounds.
+  if (hsl.l == 0)
+    return SK_ColorWHITE;
+
+  // Decrease luminosity by 20%, unless color is already dark.
+  float change = -0.2;
+  if (hsl.l <= 0.15)
+    change = 0.2;
+
+  hsl.l *= 1 + change;
+  if (hsl.l >= 0.0f && hsl.l <= 1.0f)
+    return HSLToSkColor(hsl, 255);
+  return bg_color;
+}
+
+// TODO(gayane): Consider removing RGBAColor struct and replacing it with
+// SkColor.
+// Converts RGBAColor to SkColor.
+SkColor RGBAColorToSkColor(const RGBAColor& color) {
+  return SkColorSetARGB(color.a, color.r, color.g, color.b);
+}
+
+// Use dark icon when in dark mode and no background. Otherwise, use
+// light icon for NTPs with images, and themed icon for NTPs with solid color.
+SkColor GetIconColor(const ThemeBackgroundInfo& theme_info) {
+  bool has_background_image =
+      crx_file::id_util::IdIsValid(theme_info.theme_id) ||
+      !theme_info.custom_background_url.is_empty();
+  if (has_background_image)
+    return gfx::kGoogleGrey100;
+
+  if (theme_info.using_dark_mode && theme_info.using_default_theme)
+    return gfx::kGoogleGrey900;
+
+  SkColor bg_color = RGBAColorToSkColor(theme_info.background_color);
+  SkColor icon_color = gfx::kGoogleGrey100;
+  if (!theme_info.using_default_theme && bg_color != SK_ColorWHITE)
+    icon_color = CalculateIconColor(bg_color);
+
+  return icon_color;
+}
+
 }  // namespace internal
 
 namespace {
@@ -199,15 +248,16 @@
   return maybe_int.ToLocalChecked()->Value();
 }
 
-// TODO(gayane): Consider removing RGBAColor struct and replacing it with
-// SkColor.
-// Converts RGBAColor to SkColor.
-SkColor RGBAColorToSkColor(const RGBAColor& color) {
-  return SkColorSetARGB(color.a, color.r, color.g, color.b);
+// Converts SkColor to RGBAColor
+RGBAColor SkColorToRGBAColor(const SkColor& sKColor) {
+  RGBAColor color;
+  color.r = SkColorGetR(sKColor);
+  color.g = SkColorGetG(sKColor);
+  color.b = SkColorGetB(sKColor);
+  color.a = SkColorGetA(sKColor);
+  return color;
 }
 
-// TODO(gayane): Consider moving any non-trivial logic up to |InstantService|
-// and do only mapping here.
 v8::Local<v8::Object> GenerateThemeBackgroundInfo(
     v8::Isolate* isolate,
     const ThemeBackgroundInfo& theme_info) {
@@ -332,11 +382,18 @@
   builder.Set("textColorRgba", internal::RGBAColorToArray(isolate, ntp_text));
 
   // Generate fields for themeing NTP elements.
-  builder.Set("isNtpBackgroundDark",
-              internal::IsNtpBackgroundDark(RGBAColorToSkColor(ntp_text)));
+  builder.Set(
+      "isNtpBackgroundDark",
+      internal::IsNtpBackgroundDark(internal::RGBAColorToSkColor(ntp_text)));
   builder.Set("useTitleContainer",
               crx_file::id_util::IdIsValid(theme_info.theme_id));
 
+  SkColor icon_color = internal::GetIconColor(theme_info);
+  builder.Set(
+      "iconBackgroundColor",
+      internal::RGBAColorToArray(isolate, SkColorToRGBAColor(icon_color)));
+  builder.Set("useWhiteAddIcon", color_utils::IsDark(icon_color));
+
   return builder.Build();
 }
 
diff --git a/chrome/renderer/searchbox/searchbox_extension_unittest.cc b/chrome/renderer/searchbox/searchbox_extension_unittest.cc
index a8aee483..bec21b9 100644
--- a/chrome/renderer/searchbox/searchbox_extension_unittest.cc
+++ b/chrome/renderer/searchbox/searchbox_extension_unittest.cc
@@ -4,13 +4,17 @@
 
 #include "chrome/renderer/searchbox/searchbox_extension.h"
 
+#include "chrome/common/search/instant_types.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/color_palette.h"
 #include "ui/gfx/color_utils.h"
 
 namespace internal {
 
 // Defined in searchbox_extension.cc
 bool IsNtpBackgroundDark(SkColor ntp_text);
+SkColor CalculateIconColor(SkColor bg_color);
+SkColor GetIconColor(const ThemeBackgroundInfo& theme_info);
 
 TEST(SearchboxExtensionTest, TestIsNtpBackgroundDark) {
   // Dark font means light background.
@@ -23,4 +27,70 @@
   EXPECT_TRUE(IsNtpBackgroundDark(SkColorSetARGB(255, 30, 144, 255)));
 }
 
+TEST(SearchboxExtensionTest, TestCalculateIconColor) {
+  // White icon for black background.
+  EXPECT_EQ(SK_ColorWHITE, CalculateIconColor(SK_ColorBLACK));
+
+  // Lighter icon for too dark colors.
+  SkColor dark_background = SkColorSetARGB(255, 50, 0, 50);
+  EXPECT_LT(
+      color_utils::GetRelativeLuminance(dark_background),
+      color_utils::GetRelativeLuminance(CalculateIconColor(dark_background)));
+
+  // Darker icon for light backgrounds.
+  EXPECT_GT(
+      color_utils::GetRelativeLuminance(SK_ColorWHITE),
+      color_utils::GetRelativeLuminance(CalculateIconColor(SK_ColorWHITE)));
+
+  SkColor light_background = SkColorSetARGB(255, 100, 0, 100);
+  EXPECT_GT(
+      color_utils::GetRelativeLuminance(light_background),
+      color_utils::GetRelativeLuminance(CalculateIconColor(light_background)));
+}
+
+TEST(SearchboxExtensionTest, TestGetIconColor) {
+  constexpr SkColor kLightIconColor = gfx::kGoogleGrey100;
+  constexpr SkColor kDarkIconColor = gfx::kGoogleGrey900;
+
+  ThemeBackgroundInfo theme_info;
+  theme_info.using_default_theme = true;
+  theme_info.using_dark_mode = false;
+  theme_info.background_color = RGBAColor(255, 0, 0, 255);  // red
+
+  // // Default theme in light mode.
+  EXPECT_EQ(kLightIconColor, GetIconColor(theme_info));
+
+  // // Default theme in dark mode.
+  theme_info.using_dark_mode = true;
+  EXPECT_EQ(kDarkIconColor, GetIconColor(theme_info));
+
+  // Default theme with custom background, in dark mode.
+  theme_info.custom_background_url = GURL("https://www.foo.com");
+  EXPECT_EQ(kLightIconColor, GetIconColor(theme_info));
+
+  // Default theme with custom background.
+  theme_info.using_dark_mode = false;
+  EXPECT_EQ(kLightIconColor, GetIconColor(theme_info));
+
+  // Theme with image.
+  theme_info.using_default_theme = false;
+  theme_info.custom_background_url = GURL();
+  theme_info.theme_id = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+  EXPECT_EQ(kLightIconColor, GetIconColor(theme_info));
+
+  // Theme with image in dark mode.
+  theme_info.using_dark_mode = true;
+  EXPECT_EQ(kLightIconColor, GetIconColor(theme_info));
+
+  SkColor red_icon_color = CalculateIconColor(SK_ColorRED);
+
+  // Theme with no image, in dark mode.
+  theme_info.theme_id = "";
+  EXPECT_EQ(red_icon_color, GetIconColor(theme_info));
+
+  // Theme with no image.
+  theme_info.using_dark_mode = false;
+  EXPECT_EQ(red_icon_color, GetIconColor(theme_info));
+}
+
 }  // namespace internal
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index ffab7a9..b483fe4 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2977,15 +2977,11 @@
     "../browser/ui/android/tab_model/tab_model_list_unittest.cc",
     "../browser/ui/android/tab_model/tab_model_unittest.cc",
     "../browser/ui/autofill/autofill_popup_layout_model_unittest.cc",
-    "../browser/ui/autofill/payments/autofill_dialog_models_unittest.cc",
     "../browser/ui/autofill/popup_view_common_unittest.cc",
     "../browser/ui/autofill/popup_view_test_helpers.cc",
     "../browser/ui/autofill/popup_view_test_helpers.h",
     "../browser/ui/blocked_content/popup_opener_tab_helper_unittest.cc",
     "../browser/ui/blocked_content/safe_browsing_triggered_popup_blocker_unittest.cc",
-    "../browser/ui/bookmarks/bookmark_editor_unittest.cc",
-    "../browser/ui/bookmarks/bookmark_ui_utils_desktop_unittest.cc",
-    "../browser/ui/bookmarks/recently_used_folders_combo_model_unittest.cc",
     "../browser/ui/chrome_select_file_policy_unittest.cc",
     "../browser/ui/find_bar/find_backend_unittest.cc",
     "../browser/ui/login/login_handler_unittest.cc",
@@ -2996,8 +2992,6 @@
     "../browser/ui/passwords/settings/password_manager_porter_unittest.cc",
     "../browser/ui/passwords/settings/password_manager_presenter_unittest.cc",
     "../browser/ui/search_engines/keyword_editor_controller_unittest.cc",
-    "../browser/ui/sync/profile_signin_confirmation_helper_unittest.cc",
-    "../browser/ui/sync/sync_promo_ui_unittest.cc",
     "../browser/ui/sync/tab_contents_synced_tab_delegate_unittest.cc",
     "../browser/ui/webui/favicon_source_unittest.cc",
     "../browser/ui/webui/fileicon_source_unittest.cc",
@@ -3085,11 +3079,17 @@
   }
 
   if (!is_android) {
-    # CRLSets are not supported on Android or iOS, but available on all other
-    # platforms.
     sources += [
+      # CRLSets are not supported on Android or iOS, but available on all other
+      # platforms.
       "../browser/component_updater/crl_set_component_installer_unittest.cc",
+      "../browser/ui/autofill/payments/autofill_dialog_models_unittest.cc",
+      "../browser/ui/bookmarks/bookmark_editor_unittest.cc",
+      "../browser/ui/bookmarks/bookmark_ui_utils_desktop_unittest.cc",
+      "../browser/ui/bookmarks/recently_used_folders_combo_model_unittest.cc",
       "../browser/ui/hats/hats_unittest.cc",
+      "../browser/ui/sync/profile_signin_confirmation_helper_unittest.cc",
+      "../browser/ui/sync/sync_promo_ui_unittest.cc",
       "../browser/ui/thumbnails/thumbnail_utils_unittest.cc",
       "../browser/ui/toolbar/app_menu_icon_controller_unittest.cc",
     ]
@@ -3499,6 +3499,7 @@
       "../browser/ui/search/search_ipc_router_policy_unittest.cc",
       "../browser/ui/search/search_ipc_router_unittest.cc",
       "../browser/ui/search/search_tab_helper_unittest.cc",
+      "../browser/ui/serial/serial_chooser_controller_unittest.cc",
       "../browser/ui/tab_contents/tab_contents_iterator_unittest.cc",
       "../browser/ui/tabs/pinned_tab_codec_unittest.cc",
       "../browser/ui/tabs/pinned_tab_service_unittest.cc",
@@ -3595,6 +3596,7 @@
       "//components/send_tab_to_self:test_support",
       "//components/signin/core/browser:signin_buildflags",
       "//components/sync:test_support",
+      "//services/device/public/cpp/test:test_support",
       "//services/metrics/public/cpp:ukm_builders",
       "//third_party/libaddressinput",
       "//ui/native_theme:test_support",
@@ -4587,13 +4589,6 @@
       sources += [ "../browser/google/google_update_win_unittest.cc" ]
     }
   }
-  if (is_android) {
-    sources -= [
-      "../browser/ui/bookmarks/bookmark_ui_utils_desktop_unittest.cc",
-      "../browser/ui/sync/sync_promo_ui_unittest.cc",
-      "../browser/ui/sync/tab_contents_synced_tab_delegate_unittest.cc",
-    ]
-  }
   if (!is_android && !is_chromeos) {
     sources += [
       "../browser/media/webrtc/native_desktop_media_list_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 7ab92a4..0c7ed6c 100644
--- a/chrome/test/base/browser_with_test_window_test.cc
+++ b/chrome/test/base/browser_with_test_window_test.cc
@@ -52,7 +52,6 @@
   testing::Test::SetUp();
 #if defined(OS_CHROMEOS)
   ash_test_helper_.SetUp(true);
-  ash_test_helper_.SetRunningOutsideAsh();
 #elif defined(TOOLKIT_VIEWS)
   views_test_helper_.reset(new views::ScopedViewsTestHelper());
 #endif
diff --git a/chrome/test/base/test_browser_window.cc b/chrome/test/base/test_browser_window.cc
index b7b501e..ef7983b 100644
--- a/chrome/test/base/test_browser_window.cc
+++ b/chrome/test/base/test_browser_window.cc
@@ -206,6 +206,14 @@
   return nullptr;
 }
 
+send_tab_to_self::SendTabToSelfBubbleView*
+TestBrowserWindow::ShowSendTabToSelfBubble(
+    content::WebContents* contents,
+    send_tab_to_self::SendTabToSelfBubbleController* controller,
+    bool is_user_gesture) {
+  return nullptr;
+}
+
 bool TestBrowserWindow::IsDownloadShelfVisible() const {
   return false;
 }
diff --git a/chrome/test/base/test_browser_window.h b/chrome/test/base/test_browser_window.h
index 7f67a77..95db497 100644
--- a/chrome/test/base/test_browser_window.h
+++ b/chrome/test/base/test_browser_window.h
@@ -30,6 +30,11 @@
 class Extension;
 }
 
+namespace send_tab_to_self {
+class SendTabToSelfBubbleController;
+class SendTabToSelfBubbleView;
+}  // namespace send_tab_to_self
+
 // An implementation of BrowserWindow used for testing. TestBrowserWindow only
 // contains a valid LocationBar, all other getters return NULL.
 // See BrowserWithTestWindowTest for an example of using this class.
@@ -130,6 +135,10 @@
       content::WebContents* contents,
       autofill::LocalCardMigrationBubbleController* controller,
       bool user_gesture) override;
+  send_tab_to_self::SendTabToSelfBubbleView* ShowSendTabToSelfBubble(
+      content::WebContents* contents,
+      send_tab_to_self::SendTabToSelfBubbleController* controller,
+      bool is_user_gesture) override;
   ShowTranslateBubbleResult ShowTranslateBubble(
       content::WebContents* contents,
       translate::TranslateStep step,
diff --git a/chrome/test/chromedriver/test/test_expectations b/chrome/test/chromedriver/test/test_expectations
index 68495f6..0fc5f4d 100644
--- a/chrome/test/chromedriver/test/test_expectations
+++ b/chrome/test/chromedriver/test/test_expectations
@@ -18,9 +18,6 @@
     # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2653
     'ReferrerTest.navigationWhenProxyInterceptsASpecificUrl',
 
-    # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2661
-    'WindowTest.testSetsTheSizeOfTheCurrentWindowFromIframe',
-
     # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2854
     'ChromeOptionsFunctionalTest.canSetAcceptInsecureCerts',
 
@@ -51,27 +48,14 @@
 
 _OS_NEGATIVE_FILTER = {}
 _OS_NEGATIVE_FILTER['win'] = [
-    # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2855
-    'WindowTest.canFullscreenTheWindow',
-    'WindowTest.canFullscreenTheWindowFromIframe',
-    'WindowTest.testCanMaximizeTheWindowFromIframe',
-    'WindowTest.testCanMaximizeTheWindow',
-    'BasicMouseInterfaceTest.testMovingMouseByRelativeOffset',
 ]
 _OS_NEGATIVE_FILTER['linux'] = [
 ]
 _OS_NEGATIVE_FILTER['mac'] = [
     # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2663
-    'WindowTest.canFullscreenTheWindow',
+    # Test 'WindowTest.testSetsTheSizeOfTheCurrentWindowFromIframe' passes while it is run
+    # standalone and fails in a test suit run
     'WindowTest.testSetsTheSizeOfTheCurrentWindowFromIframe',
-    'WindowTest.testSetsTheSizeOfTheCurrentWindowFromFrame',
-    'WindowTest.canFullscreenTheWindowFromFrame',
-    'WindowTest.canFullscreenTheWindowFromIframe',
-    'WindowTest.testSetsTheSizeOfTheCurrentWindow',
-    'WindowTest.testCanMaximizeTheWindowFromFrame',
-    'WindowTest.testCanMaximizeTheWindowFromIframe',
-    'WindowTest.testCanMaximizeTheWindow',
-    'BasicMouseInterfaceTest.testMovingMouseByRelativeOffset',
 
     # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2856
     'ClickScrollingTest.testShouldBeAbleToClickElementThatIsOutOfViewInANestedFrameThatIsOutOfView',
diff --git a/chrome/test/data/local_ntp/local_ntp_browsertest.html b/chrome/test/data/local_ntp/local_ntp_browsertest.html
index 57b5aa3..726edc8 100644
--- a/chrome/test/data/local_ntp/local_ntp_browsertest.html
+++ b/chrome/test/data/local_ntp/local_ntp_browsertest.html
@@ -165,7 +165,7 @@
         </div>
       </div>
       <div id="menu-nav-panel">
-        <div id="backgrounds-button" class="menu-option" tabindex="0">
+        <div id="backgrounds-button" class="menu-option selected" tabindex="0">
           <div class="menu-option-icon-wrapper">
             <div ids="backgrounds-icon" class="menu-option-icon"></div>
           </div>
diff --git a/chrome/test/data/media/picture-in-picture/window-size.html b/chrome/test/data/media/picture-in-picture/window-size.html
index 50c954c..1443db9 100644
--- a/chrome/test/data/media/picture-in-picture/window-size.html
+++ b/chrome/test/data/media/picture-in-picture/window-size.html
@@ -145,5 +145,12 @@
       .catch(e => { document.title = 'failed to enter Picture-in-Picture after leaving'; });
     });
   }
+
+  function resetVideo() {
+    video.addEventListener('emptied', () => {
+      document.title = 'emptied';
+    }, { once: true });
+    video.src = '';
+  }
 </script>
 </html>
diff --git a/chrome/test/data/webui/print_preview/custom_margins_test.js b/chrome/test/data/webui/print_preview/custom_margins_test.js
index 6e78007..982cf21 100644
--- a/chrome/test/data/webui/print_preview/custom_margins_test.js
+++ b/chrome/test/data/webui/print_preview/custom_margins_test.js
@@ -83,7 +83,6 @@
     function finishSetup() {
       // Wait for the control elements to be created before updating the state.
       document.body.appendChild(container);
-      container.$$('template').notifyDomChange = true;
       let controlsAdded = test_util.eventToPromise('dom-change', container);
       return controlsAdded.then(() => {
         // 8.5 x 11, in pixels
diff --git a/chrome/test/data/webui/settings/cr_settings_browsertest.js b/chrome/test/data/webui/settings/cr_settings_browsertest.js
index e96a147..4bc3860 100644
--- a/chrome/test/data/webui/settings/cr_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/cr_settings_browsertest.js
@@ -2300,7 +2300,7 @@
   ]),
 };
 
-TEST_F('CrSettingsCrostiniPageTest', 'All', function() {
+TEST_F('CrSettingsCrostiniPageTest', 'DISABLED_All', function() {
   mocha.run();
 });
 
diff --git a/chrome/test/data/xr/e2e_test_files/html/test_webxr_gamepad_support.html b/chrome/test/data/xr/e2e_test_files/html/test_webxr_gamepad_support.html
index 10bf14b..a56efa1 100644
--- a/chrome/test/data/xr/e2e_test_files/html/test_webxr_gamepad_support.html
+++ b/chrome/test/data/xr/e2e_test_files/html/test_webxr_gamepad_support.html
@@ -17,10 +17,19 @@
       // always null.
       window.addEventListener("gamepadconnected", function(e) {});
 
+      // TODO(https://crbug.com/961774): On some Windows machines, a phantom
+      // gamepad shows up in the navigator.getGamepads() array once a WebXR
+      // gamepad sends any input. Until that's fixed, filter out such devices
+      // when checking if the number of gamepads on the navigator array matches
+      // expectations.
+      function isPhantomGamepad(gamepad) {
+        return gamepad.id == "Unknown Gamepad (Vendor: 0000 Product: 0000)";
+      }
+
       function assertNumNavigatorGamepadsMatchesExpectation(numExpectedGamepads) {
         let numGamepads = 0;
         for (gamepad of navigator.getGamepads()) {
-          if (gamepad !== null) {
+          if ((gamepad !== null) && !isPhantomGamepad(gamepad)) {
             numGamepads++;
           }
         }
@@ -28,6 +37,24 @@
             "Number of returned gamepads matches expectation");
       }
 
+      function assertNumInputSourceGamepadsMatchesExpectation(numExpectedGamepads) {
+        let currentSession = sessionInfos[sessionTypes.IMMERSIVE].currentSession;
+        let numGamepads = 0;
+        for (source of currentSession.getInputSources()) {
+          if (source.gamepad !== null) {
+            numGamepads++;
+          }
+        }
+        assert_equals(numGamepads, numExpectedGamepads,
+            "Number of XRInputSources with Gamepads on XRSession matches expectation");
+      }
+
+      function assertNumInputSourcesMatchesExpectation(numExpectedSources) {
+        let currentSession = sessionInfos[sessionTypes.IMMERSIVE].currentSession;
+        assert_equals(currentSession.getInputSources().length, numExpectedSources,
+            "Number of XRInputSources on XRSession matches expectation");
+      }
+
       function validateInputSourceHasNoGamepad() {
         assert_equals(onImmersiveXRFrameCallback, null);
         onImmersiveXRFrameCallback = function(session) {
@@ -41,11 +68,11 @@
           // We don't expect to have attached any non-vr gamepads, and vr
           // gamepads shouldn't show up in navigator.getGamepads()
           assertNumNavigatorGamepadsMatchesExpectation(0);
-          let input_sources = session.getInputSources();
-          assert_equals(input_sources.length, 1, "There should only be one input source");
-          assert_equals(input_sources[0].gamepad, null,
-              "There should not be enough data to make a gamepad");
 
+          // There should only be one input source, but there should not be
+          // enough data to make a gamepad.
+          assertNumInputSourcesMatchesExpectation(1);
+          assertNumInputSourceGamepadsMatchesExpectation(0);
           finishJavaScriptStep();
         }
       }
@@ -63,19 +90,28 @@
           // We don't expect to have attached any non-vr gamepads, and vr
           // gamepads shouldn't show up in navigator.getGamepads()
           assertNumNavigatorGamepadsMatchesExpectation(0);
-          let input_sources = session.getInputSources();
-          assert_equals(input_sources.length, 1, "There should only be one input source");
-          assert_not_equals(input_sources[0].gamepad, null,
-              "There should be enough data to make a gamepad");
-          assert_equals(input_sources[0].gamepad.mapping, "xr-standard");
+
+          // There should only be one input source and it should have a gamepad.
+          assertNumInputSourcesMatchesExpectation(1);
+          assertNumInputSourceGamepadsMatchesExpectation(1);
+
+          let gamepad = session.getInputSources()[0].gamepad;
           if (validationFunction) {
-            validationFunction(input_sources[0].gamepad);
+            validationFunction(gamepad);
           }
 
           finishJavaScriptStep();
         }
       }
 
+      function validateMapping(expected_mapping) {
+        let validationFunction = function(gamepad) {
+          assert_equals(gamepad.mapping, expected_mapping);
+        }
+
+        validateFirstGamepad(validationFunction);
+      }
+
       function validateButtonNotPressed(button_index) {
         let validationFunction = function(gamepad) {
           assert_less_than(button_index, gamepad.buttons.length,
diff --git a/chrome/test/data/xr/e2e_test_files/html/test_webxr_gamepad_support_nowebxr.html b/chrome/test/data/xr/e2e_test_files/html/test_webxr_gamepad_support_nowebxr.html
deleted file mode 100644
index 8ddb77e..0000000
--- a/chrome/test/data/xr/e2e_test_files/html/test_webxr_gamepad_support_nowebxr.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<!doctype html>
-<!--
-Tests that a Gamepad API gamepad is or is not returned under different
-circumstances. Does not have WebXR code.
--->
-<html>
-  <head>
-    <link rel="stylesheet" type="text/css" href="../resources/webxr_e2e.css">
-  </head>
-  <body>
-    <canvas id="webgl-canvas"></canvas>
-    <script src="../../../../../../third_party/blink/web_tests/resources/testharness.js"></script>
-    <script src="../resources/webxr_e2e.js"></script>
-    <script>
-      // We apparently need to register a listener, otherwise all gamepads are
-      // always null.
-      window.addEventListener("gamepadconnected", function(e) {});
-
-      function assertNumNavigatorGamepadsMatchesExpectation(numExpectedGamepads) {
-        var numGamepads = 0;
-        for (gamepad of navigator.getGamepads()) {
-          if (gamepad !== null) {
-            numGamepads++;
-          }
-        }
-        assert_equals(numGamepads, numExpectedGamepads,
-            "Number of returned gamepads matches expectation");
-      }
-    </script>
-  </body>
-</html>
diff --git a/chrome/test/data/xr/e2e_test_files/html/test_webxr_input.html b/chrome/test/data/xr/e2e_test_files/html/test_webxr_input.html
index 1cae513..d94a57d 100644
--- a/chrome/test/data/xr/e2e_test_files/html/test_webxr_input.html
+++ b/chrome/test/data/xr/e2e_test_files/html/test_webxr_input.html
@@ -21,7 +21,7 @@
       var finishAfterEachInput = true;
 
       function onSelectStart() {
-        // selectstart should always be fired first, so check that
+        // selectstart should always be fired first, so check that.
         assert_true(selectStartCount == selectEndCount,
                     'selectstart fired before selectend');
         assert_true(selectStartCount == selectCount,
@@ -29,22 +29,22 @@
         selectStartCount++;
       }
 
-      function onSelectEnd() {
-        // selectend should always be fired between selectstart and select
-        assert_true(selectEndCount + 1 == selectStartCount,
-                    'selectend fired after selectstart');
-        assert_true(selectEndCount == selectCount,
-                    'selectend fired before select');
-        selectEndCount++;
-      }
-
       function onSelect() {
-        // select should always be fired last
-        selectCount++;
-        assert_true(selectCount == selectStartCount,
+        // select should always be fired between selectstart and selectend.
+        assert_true(selectCount + 1 == selectStartCount,
                     'select fired after selectstart');
         assert_true(selectCount == selectEndCount,
                     'select fired after selectend');
+        selectCount++;
+      }
+
+      function onSelectEnd() {
+        // selectend should always be fired last.
+        selectEndCount++;
+        assert_true(selectEndCount == selectStartCount,
+                    'selectend fired after selectstart');
+        assert_true(selectEndCount == selectCount,
+                    'selectend fired before select');
         currentIteration++;
         if (currentIteration == iterations) {
           done();
diff --git a/chrome/test/data/xr/e2e_test_files/html/test_webxr_input_same_object.html b/chrome/test/data/xr/e2e_test_files/html/test_webxr_input_same_object.html
index 6758434..218d149 100644
--- a/chrome/test/data/xr/e2e_test_files/html/test_webxr_input_same_object.html
+++ b/chrome/test/data/xr/e2e_test_files/html/test_webxr_input_same_object.html
@@ -18,9 +18,10 @@
         inputChangeEvents++;
       }
 
-      function setupListeners() {
-        let currentSession = sessionInfos[sessionTypes.IMMERSIVE].currentSession;
-        currentSession.addEventListener('inputsourceschange', onInputSourcesChange, false);
+      onSessionStartedCallback = function(session) {
+        if (session.mode === "immersive-vr") {
+          session.addEventListener('inputsourceschange', onInputSourcesChange, false);
+        }
       }
 
       function getCurrentInputSources() {
diff --git a/chrome/test/data/xr/e2e_test_files/resources/webxr_boilerplate.js b/chrome/test/data/xr/e2e_test_files/resources/webxr_boilerplate.js
index 24534fd..e91f05b 100644
--- a/chrome/test/data/xr/e2e_test_files/resources/webxr_boilerplate.js
+++ b/chrome/test/data/xr/e2e_test_files/resources/webxr_boilerplate.js
@@ -19,6 +19,7 @@
 var gl = null;
 var onMagicWindowXRFrameCallback = null;
 var onImmersiveXRFrameCallback = null;
+var onSessionStartedCallback = null;
 var onPoseCallback = null;
 var shouldSubmitFrame = true;
 var hasPresentedFrame = false;
@@ -129,6 +130,10 @@
     gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
   }
 
+  if (onSessionStartedCallback) {
+    onSessionStartedCallback(session);
+  }
+
   session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
   session.requestReferenceSpace(referenceSpaceMap[getSessionType(session)])
       .then( (refSpace) => {
diff --git a/chromecast/renderer/cast_content_renderer_client.cc b/chromecast/renderer/cast_content_renderer_client.cc
index 453e1f0..ad26b8e 100644
--- a/chromecast/renderer/cast_content_renderer_client.cc
+++ b/chromecast/renderer/cast_content_renderer_client.cc
@@ -24,6 +24,7 @@
 #include "content/public/renderer/render_frame.h"
 #include "content/public/renderer/render_thread.h"
 #include "content/public/renderer/render_view.h"
+#include "media/base/audio_parameters.h"
 #include "media/base/media.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
 #include "services/service_manager/public/cpp/connector.h"
@@ -306,5 +307,11 @@
   return std::make_unique<CastURLLoaderThrottleProvider>(type);
 }
 
+base::Optional<::media::AudioRendererAlgorithmParameters>
+CastContentRendererClient::GetAudioRendererAlgorithmParameters(
+    ::media::AudioParameters audio_parameters) {
+  return base::nullopt;
+}
+
 }  // namespace shell
 }  // namespace chromecast
diff --git a/chromecast/renderer/cast_content_renderer_client.h b/chromecast/renderer/cast_content_renderer_client.h
index 650c6f1e..b45c074 100644
--- a/chromecast/renderer/cast_content_renderer_client.h
+++ b/chromecast/renderer/cast_content_renderer_client.h
@@ -14,6 +14,7 @@
 #include "chromecast/common/mojom/application_media_capabilities.mojom.h"
 #include "content/public/renderer/content_renderer_client.h"
 #include "media/base/audio_codecs.h"
+#include "media/base/audio_parameters.h"
 #include "mojo/public/cpp/bindings/binding.h"
 
 namespace extensions {
@@ -71,6 +72,9 @@
   std::unique_ptr<content::URLLoaderThrottleProvider>
   CreateURLLoaderThrottleProvider(
       content::URLLoaderThrottleProviderType type) override;
+  base::Optional<::media::AudioRendererAlgorithmParameters>
+  GetAudioRendererAlgorithmParameters(
+      ::media::AudioParameters audio_parameters) override;
 
  protected:
   CastContentRendererClient();
diff --git a/chromeos/components/tether/active_host_network_state_updater_unittest.cc b/chromeos/components/tether/active_host_network_state_updater_unittest.cc
index 1b35432..277277e 100644
--- a/chromeos/components/tether/active_host_network_state_updater_unittest.cc
+++ b/chromeos/components/tether/active_host_network_state_updater_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/message_loop/message_loop.h"
 #include "chromeos/components/tether/fake_active_host.h"
 #include "chromeos/network/network_state.h"
+#include "chromeos/network/network_state_handler.h"
 #include "chromeos/network/network_state_test_helper.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/chromeos/components/tether/tether_network_disconnection_handler_unittest.cc b/chromeos/components/tether/tether_network_disconnection_handler_unittest.cc
index 102c7c0..571356f 100644
--- a/chromeos/components/tether/tether_network_disconnection_handler_unittest.cc
+++ b/chromeos/components/tether/tether_network_disconnection_handler_unittest.cc
@@ -17,6 +17,7 @@
 #include "chromeos/components/tether/network_configuration_remover.h"
 #include "chromeos/components/tether/tether_session_completion_logger.h"
 #include "chromeos/network/network_state.h"
+#include "chromeos/network/network_state_handler.h"
 #include "chromeos/network/network_state_test_helper.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
diff --git a/chromeos/network/BUILD.gn b/chromeos/network/BUILD.gn
index 4ca2ad8..f14d80d 100644
--- a/chromeos/network/BUILD.gn
+++ b/chromeos/network/BUILD.gn
@@ -176,6 +176,8 @@
     "//base",
     "//chromeos:test_utils",
     "//chromeos/dbus:test_support",
+    "//chromeos/services/network_config/public/cpp",
+    "//chromeos/services/network_config/public/mojom",
     "//dbus",
     "//net:test_support",
     "//testing/gmock",
diff --git a/chromeos/network/network_state.cc b/chromeos/network/network_state.cc
index 2163810..0aaa409 100644
--- a/chromeos/network/network_state.cc
+++ b/chromeos/network/network_state.cc
@@ -385,7 +385,27 @@
 
 std::string NetworkState::connection_state() const {
   if (!visible())
-    return shill::kStateDisconnect;
+    return shill::kStateIdle;
+  DCHECK(connection_state_ == shill::kStateIdle ||
+         connection_state_ == shill::kStateAssociation ||
+         connection_state_ == shill::kStateConfiguration ||
+         connection_state_ == shill::kStateReady ||
+         connection_state_ == shill::kStatePortal ||
+         connection_state_ == shill::kStateNoConnectivity ||
+         connection_state_ == shill::kStateRedirectFound ||
+         connection_state_ == shill::kStatePortalSuspected ||
+         // TODO(https://crbug.com/552190): Remove kStateOffline from this list
+         // when occurrences in chromium code have been eliminated.
+         connection_state_ == shill::kStateOffline ||
+         connection_state_ == shill::kStateOnline ||
+         connection_state_ == shill::kStateFailure ||
+         // TODO(https://crbug.com/552190): Remove kStateActivationFailure from
+         // this list when occurrences in chromium code have been eliminated.
+         connection_state_ == shill::kStateActivationFailure ||
+         // TODO(https://crbug.com/552190): Empty should not be a valid state,
+         // but e.g. new tether NetworkStates and unit tests use it currently.
+         connection_state_.empty());
+
   return connection_state_;
 }
 
@@ -570,8 +590,7 @@
 // static
 bool NetworkState::StateIsConnecting(const std::string& connection_state) {
   return (connection_state == shill::kStateAssociation ||
-          connection_state == shill::kStateConfiguration ||
-          connection_state == shill::kStateCarrier);
+          connection_state == shill::kStateConfiguration);
 }
 
 // static
diff --git a/chromeos/network/network_state.h b/chromeos/network/network_state.h
index 189b722..a77c55c 100644
--- a/chromeos/network/network_state.h
+++ b/chromeos/network/network_state.h
@@ -82,7 +82,7 @@
   void clear_last_error() { last_error_.clear(); }
   ::onc::ONCSource onc_source() const { return onc_source_; }
 
-  // Returns |connection_state_| if visible, kStateDisconnect otherwise.
+  // Returns |connection_state_| if visible, kStateIdle otherwise.
   std::string connection_state() const;
 
   // Updates the connection state and saves the previous connection state.
diff --git a/chromeos/network/network_state_handler.cc b/chromeos/network/network_state_handler.cc
index 932bbcf..5627101 100644
--- a/chromeos/network/network_state_handler.cc
+++ b/chromeos/network/network_state_handler.cc
@@ -44,12 +44,9 @@
   if (network->is_captive_portal() != prev_is_captive_portal)
     return true;
   std::string connection_state = network->connection_state();
-  // Treat 'idle' and 'disconnect' the same.
   bool prev_idle = prev_connection_state.empty() ||
-                   prev_connection_state == shill::kStateIdle ||
-                   prev_connection_state == shill::kStateDisconnect;
-  bool cur_idle = connection_state == shill::kStateIdle ||
-                  connection_state == shill::kStateDisconnect;
+                   prev_connection_state == shill::kStateIdle;
+  bool cur_idle = connection_state == shill::kStateIdle;
   if (prev_idle || cur_idle)
     return prev_idle != cur_idle;
   return connection_state != prev_connection_state;
@@ -858,7 +855,7 @@
 
 void NetworkStateHandler::SetTetherNetworkStateDisconnected(
     const std::string& guid) {
-  SetTetherNetworkStateConnectionState(guid, shill::kStateDisconnect);
+  SetTetherNetworkStateConnectionState(guid, shill::kStateIdle);
 }
 
 void NetworkStateHandler::SetTetherNetworkStateConnecting(
diff --git a/chromeos/network/network_state_test_helper.cc b/chromeos/network/network_state_test_helper.cc
index e5c18c2..ab06779 100644
--- a/chromeos/network/network_state_test_helper.cc
+++ b/chromeos/network/network_state_test_helper.cc
@@ -10,6 +10,7 @@
 #include "chromeos/dbus/shill/shill_clients.h"
 #include "chromeos/network/network_state_handler.h"
 #include "chromeos/network/onc/onc_utils.h"
+#include "chromeos/services/network_config/public/cpp/cros_network_config_util.h"
 #include "dbus/object_path.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/cros_system_api/dbus/service_constants.h"
@@ -135,19 +136,52 @@
   base::RunLoop().RunUntilIdle();
 }
 
-std::unique_ptr<NetworkState>
-NetworkStateTestHelper::CreateStandaloneNetworkState(
+network_config::mojom::NetworkStatePropertiesPtr
+NetworkStateTestHelper::CreateStandaloneNetworkProperties(
     const std::string& id,
-    const std::string& type,
-    const std::string& connection_state,
+    network_config::mojom::NetworkType type,
+    network_config::mojom::ConnectionStateType connection_state,
     int signal_strength) {
-  auto network = std::make_unique<NetworkState>(id);
-  network->SetGuid(id);
-  network->set_name(id);
-  network->set_type(type);
-  network->set_visible(true);
-  network->SetConnectionState(connection_state);
-  network->set_signal_strength(signal_strength);
+  auto network = network_config::mojom::NetworkStateProperties::New();
+  network->guid = id;
+  network->name = id;
+  network->type = type;
+  network->connection_state = connection_state;
+  switch (type) {
+    case network_config::mojom::NetworkType::kAll:
+    case network_config::mojom::NetworkType::kMobile:
+    case network_config::mojom::NetworkType::kWireless:
+      NOTREACHED();
+      break;
+    case network_config::mojom::NetworkType::kCellular: {
+      auto cellular = network_config::mojom::CellularStateProperties::New();
+      cellular->signal_strength = signal_strength;
+      network->cellular = std::move(cellular);
+      break;
+    }
+    case network_config::mojom::NetworkType::kEthernet:
+      break;
+    case network_config::mojom::NetworkType::kTether: {
+      auto tether = network_config::mojom::TetherStateProperties::New();
+      tether->signal_strength = signal_strength;
+      network->tether = std::move(tether);
+      break;
+    }
+    case network_config::mojom::NetworkType::kVPN:
+      break;
+    case network_config::mojom::NetworkType::kWiFi: {
+      auto wifi = network_config::mojom::WiFiStateProperties::New();
+      wifi->signal_strength = signal_strength;
+      network->wifi = std::move(wifi);
+      break;
+    }
+    case network_config::mojom::NetworkType::kWiMAX: {
+      auto wimax = network_config::mojom::WiMAXStateProperties::New();
+      wimax->signal_strength = signal_strength;
+      network->wimax = std::move(wimax);
+      break;
+    }
+  }
   return network;
 }
 
diff --git a/chromeos/network/network_state_test_helper.h b/chromeos/network/network_state_test_helper.h
index 43b830c..c8078fc 100644
--- a/chromeos/network/network_state_test_helper.h
+++ b/chromeos/network/network_state_test_helper.h
@@ -13,10 +13,12 @@
 #include "chromeos/dbus/shill/shill_manager_client.h"
 #include "chromeos/dbus/shill/shill_profile_client.h"
 #include "chromeos/dbus/shill/shill_service_client.h"
-#include "chromeos/network/network_state_handler.h"
+#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
 
 namespace chromeos {
 
+class NetworkStateHandler;
+
 // Helper class for tests that use NetworkStateHandler. Handles initialization,
 // shutdown, and adds default profiles and a wifi device (but no services).
 // NOTE: This is not intended to be used with NetworkHandler::Initialize()
@@ -57,10 +59,11 @@
                           const std::string& key,
                           const base::Value& value);
 
-  std::unique_ptr<NetworkState> CreateStandaloneNetworkState(
+  network_config::mojom::NetworkStatePropertiesPtr
+  CreateStandaloneNetworkProperties(
       const std::string& id,
-      const std::string& type,
-      const std::string& connection_state,
+      network_config::mojom::NetworkType type,
+      network_config::mojom::ConnectionStateType connection_state,
       int signal_strength);
 
   // Returns the path used for the shared and user profiles.
diff --git a/chromeos/network/network_state_unittest.cc b/chromeos/network/network_state_unittest.cc
index be2231c..cbade5e 100644
--- a/chromeos/network/network_state_unittest.cc
+++ b/chromeos/network/network_state_unittest.cc
@@ -309,15 +309,15 @@
   network_state_.set_visible(false);
 
   network_state_.SetConnectionState(shill::kStateConfiguration);
-  EXPECT_EQ(network_state_.connection_state(), shill::kStateDisconnect);
+  EXPECT_EQ(network_state_.connection_state(), shill::kStateIdle);
   EXPECT_FALSE(network_state_.IsConnectingState());
 
   network_state_.SetConnectionState(shill::kStateOnline);
-  EXPECT_EQ(network_state_.connection_state(), shill::kStateDisconnect);
+  EXPECT_EQ(network_state_.connection_state(), shill::kStateIdle);
   EXPECT_FALSE(network_state_.IsConnectedState());
 
   network_state_.SetConnectionState(shill::kStateConfiguration);
-  EXPECT_EQ(network_state_.connection_state(), shill::kStateDisconnect);
+  EXPECT_EQ(network_state_.connection_state(), shill::kStateIdle);
   EXPECT_FALSE(network_state_.IsConnectingState());
 }
 
diff --git a/chromeos/network/prohibited_technologies_handler_unittest.cc b/chromeos/network/prohibited_technologies_handler_unittest.cc
index 0d3c50c..f7b3f7c 100644
--- a/chromeos/network/prohibited_technologies_handler_unittest.cc
+++ b/chromeos/network/prohibited_technologies_handler_unittest.cc
@@ -17,6 +17,7 @@
 #include "chromeos/network/managed_network_configuration_handler_impl.h"
 #include "chromeos/network/network_configuration_handler.h"
 #include "chromeos/network/network_profile_handler.h"
+#include "chromeos/network/network_state_handler.h"
 #include "chromeos/network/network_state_test_helper.h"
 #include "chromeos/network/onc/onc_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/chromeos/services/machine_learning/public/mojom/graph_executor.mojom b/chromeos/services/machine_learning/public/mojom/graph_executor.mojom
index 787dff0..42d5c04 100644
--- a/chromeos/services/machine_learning/public/mojom/graph_executor.mojom
+++ b/chromeos/services/machine_learning/public/mojom/graph_executor.mojom
@@ -2,26 +2,34 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// NOTE: This mojom should be kept in sync with the copy in Chromium OS's repo
-// in src/platform2/ml/mojom/.
-// Example: A backwards-compatible interface change can be made in Chromium OS
-// first, then replicated and used here later.
+// NOTE: This mojom exists in two places and must be kept in sync:
+//       Chromium:  //chromeos/services/machine_learning/public/mojom/
+//       Chrome OS: src/platform2/ml/mojom/
+// Example: A backwards-compatible mojom change (and corresponding
+// implementation change) can be made in Chrome OS first, then replicated to the
+// client (Chromium) later.
 
 module chromeos.machine_learning.mojom;
 
+// NOTE: The base directory for 'import' statements is expected to differ
+//       between Chromium and Chrome OS versions of this file.
 import "chromeos/services/machine_learning/public/mojom/tensor.mojom";
 
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
 enum ExecuteResult {
   OK = 0,
-  INPUT_MISSING_ERROR,
-  UNKNOWN_INPUT_ERROR,
-  INPUT_TYPE_ERROR,
-  INPUT_SHAPE_ERROR,
-  INPUT_FORMAT_ERROR,
-  OUTPUT_MISSING_ERROR,
-  UNKNOWN_OUTPUT_ERROR,
-  DUPLICATE_OUTPUT_ERROR,
-  EXECUTION_ERROR,
+  INPUT_MISSING_ERROR = 1,
+  UNKNOWN_INPUT_ERROR = 2,
+  INPUT_TYPE_ERROR = 3,
+  INPUT_SHAPE_ERROR = 4,
+  INPUT_FORMAT_ERROR = 5,
+  OUTPUT_MISSING_ERROR = 6,
+  UNKNOWN_OUTPUT_ERROR = 7,
+  DUPLICATE_OUTPUT_ERROR = 8,
+  EXECUTION_ERROR = 9,
+  // Remove kMax and use builtin kMaxValue after Mojo uprev (crbug.com/909719).
+  kMax = EXECUTION_ERROR,
 };
 
 // API for performing inference on a TensorFlow graph. A given graph can be
diff --git a/chromeos/services/machine_learning/public/mojom/machine_learning_service.mojom b/chromeos/services/machine_learning/public/mojom/machine_learning_service.mojom
index 6ce9f5e..68f9e00 100644
--- a/chromeos/services/machine_learning/public/mojom/machine_learning_service.mojom
+++ b/chromeos/services/machine_learning/public/mojom/machine_learning_service.mojom
@@ -4,19 +4,27 @@
 
 // Top-level API of the Machine Learning Service: loading models for inference.
 
-// NOTE: This mojom should be kept in sync with the copy in Chromium OS's repo
-// in src/platform2/ml/mojom/.
-// Example: A backwards-compatible interface change can be made in Chromium OS
-// first, then replicated and used here later.
+// NOTE: This mojom exists in two places and must be kept in sync:
+//       Chromium:  //chromeos/services/machine_learning/public/mojom/
+//       Chrome OS: src/platform2/ml/mojom/
+// Example: A backwards-compatible mojom change (and corresponding
+// implementation change) can be made in Chrome OS first, then replicated to the
+// client (Chromium) later.
 
 module chromeos.machine_learning.mojom;
 
+// NOTE: The base directory for 'import' statements is expected to differ
+//       between Chromium and Chrome OS versions of this file.
 import "chromeos/services/machine_learning/public/mojom/model.mojom";
 
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
 enum LoadModelResult {
   OK = 0,
-  MODEL_SPEC_ERROR,
-  LOAD_MODEL_ERROR,
+  MODEL_SPEC_ERROR = 1,
+  LOAD_MODEL_ERROR = 2,
+  // Remove kMax and use builtin kMaxValue after Mojo uprev (crbug.com/909719).
+  kMax = LOAD_MODEL_ERROR,
 };
 
 // Top-level interface between Chromium and the ML Service daemon.
diff --git a/chromeos/services/machine_learning/public/mojom/model.mojom b/chromeos/services/machine_learning/public/mojom/model.mojom
index 4b53322..7f833df 100644
--- a/chromeos/services/machine_learning/public/mojom/model.mojom
+++ b/chromeos/services/machine_learning/public/mojom/model.mojom
@@ -4,25 +4,35 @@
 
 // Datatypes and interfaces of models for the Machine Learning API.
 
-// NOTE: This mojom should be kept in sync with the copy in Chromium OS's repo
-// in src/platform2/ml/mojom/.
-// Example: A backwards-compatible interface change can be made in Chromium OS
-// first, then replicated and used here later.
+// NOTE: This mojom exists in two places and must be kept in sync:
+//       Chromium:  //chromeos/services/machine_learning/public/mojom/
+//       Chrome OS: src/platform2/ml/mojom/
+// Example: A backwards-compatible mojom change (and corresponding
+// implementation change) can be made in Chrome OS first, then replicated to the
+// client (Chromium) later.
 
 module chromeos.machine_learning.mojom;
 
+// NOTE: The base directory for 'import' statements is expected to differ
+//       between Chromium and Chrome OS versions of this file.
 import "chromeos/services/machine_learning/public/mojom/graph_executor.mojom";
 
 enum ModelId {
-  UNKNOWN,
-  TEST_MODEL,  // Only available in tests.
-  SMART_DIM,
+  UNKNOWN = 0,
+  TEST_MODEL = 1,
+  SMART_DIM = 2,
+  // Remove kMax and use builtin kMaxValue after Mojo uprev (crbug.com/909719).
+  kMax = SMART_DIM,
 };
 
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
 enum CreateGraphExecutorResult {
   OK = 0,
-  MODEL_INTERPRETATION_ERROR,
-  MEMORY_ALLOCATION_ERROR,
+  MODEL_INTERPRETATION_ERROR = 1,
+  MEMORY_ALLOCATION_ERROR = 2,
+  // Remove kMax and use builtin kMaxValue after Mojo uprev (crbug.com/909719).
+  kMax = MEMORY_ALLOCATION_ERROR,
 };
 
 struct ModelSpec {
diff --git a/chromeos/services/machine_learning/public/mojom/tensor.mojom b/chromeos/services/machine_learning/public/mojom/tensor.mojom
index 786eaac2..1b10456 100644
--- a/chromeos/services/machine_learning/public/mojom/tensor.mojom
+++ b/chromeos/services/machine_learning/public/mojom/tensor.mojom
@@ -6,10 +6,12 @@
 // This module is derived from the Feature proto definition of TensorFlow.
 // See /tensorflow/core/example/feature.proto in the TensorFlow code.
 
-// NOTE: This mojom should be kept in sync with the copy in Chromium OS's repo
-// in src/platform2/ml/mojom/.
-// Example: A backwards-compatible interface change can be made in Chromium OS
-// first, then replicated and used here later.
+// NOTE: This mojom exists in two places and must be kept in sync:
+//       Chromium:  //chromeos/services/machine_learning/public/mojom/
+//       Chrome OS: src/platform2/ml/mojom/
+// Example: A backwards-compatible mojom change (and corresponding
+// implementation change) can be made in Chrome OS first, then replicated to the
+// client (Chromium) later.
 
 module chromeos.machine_learning.mojom;
 
diff --git a/chromeos/services/network_config/cros_network_config.cc b/chromeos/services/network_config/cros_network_config.cc
index 4401128..2dbcf8d 100644
--- a/chromeos/services/network_config/cros_network_config.cc
+++ b/chromeos/services/network_config/cros_network_config.cc
@@ -117,9 +117,8 @@
   return mojom::DeviceStateType::kUnavailable;
 }
 
-mojom::NetworkStatePropertiesPtr NetworkStateToMojo(
-    NetworkStateHandler* network_state_handler,
-    const NetworkState* network) {
+mojom::NetworkStatePropertiesPtr NetworkStateToMojo(const NetworkState* network,
+                                                    const DeviceState* device) {
   mojom::NetworkType type = ShillTypeToMojo(network->type());
   if (type == mojom::NetworkType::kAll) {
     NET_LOG(ERROR) << "Unexpected network type: " << network->type()
@@ -157,8 +156,6 @@
                                                 onc::kNetworkTechnologyTable);
       cellular->roaming = network->IndicateRoaming();
       cellular->signal_strength = network->signal_strength();
-      const DeviceState* device =
-          network_state_handler->GetDeviceState(network->device_path());
       if (device) {
         cellular->scanning = device->scanning();
         cellular->sim_absent = device->IsSimAbsent();
@@ -223,8 +220,8 @@
 }
 
 mojom::DeviceStatePropertiesPtr DeviceStateToMojo(
-    NetworkStateHandler* network_state_handler,
-    const DeviceState* device) {
+    const DeviceState* device,
+    mojom::DeviceStateType technology_state) {
   mojom::NetworkType type = ShillTypeToMojo(device->type());
   if (type == mojom::NetworkType::kAll) {
     NET_LOG(ERROR) << "Unexpected device type: " << device->type()
@@ -232,17 +229,10 @@
     return nullptr;
   }
 
-  mojom::DeviceStateType state =
-      GetMojoDeviceStateType(network_state_handler->GetTechnologyState(
-          NetworkTypePattern::Primitive(device->type())));
-  if (state == mojom::DeviceStateType::kUnavailable) {
-    NET_LOG(ERROR) << "Device state unavailable";
-    return nullptr;
-  }
   auto result = mojom::DeviceStateProperties::New();
   result->type = type;
   result->scanning = device->scanning();
-  result->state = state;
+  result->state = technology_state;
   result->managed_network_available =
       !device->available_managed_network_path().empty();
 
@@ -290,7 +280,8 @@
     std::move(callback).Run(nullptr);
     return;
   }
-  std::move(callback).Run(NetworkStateToMojo(network_state_handler_, network));
+  std::move(callback).Run(NetworkStateToMojo(
+      network, network_state_handler_->GetDeviceState(network->device_path())));
 }
 
 void CrosNetworkConfig::GetNetworkStateList(
@@ -323,8 +314,9 @@
   }
   std::vector<mojom::NetworkStatePropertiesPtr> result;
   for (const NetworkState* network : networks) {
-    mojom::NetworkStatePropertiesPtr mojo_network =
-        NetworkStateToMojo(network_state_handler_, network);
+    mojom::NetworkStatePropertiesPtr mojo_network = NetworkStateToMojo(
+        network,
+        network_state_handler_->GetDeviceState(network->device_path()));
     if (mojo_network)
       result.emplace_back(std::move(mojo_network));
   }
@@ -337,8 +329,15 @@
   network_state_handler_->GetDeviceList(&devices);
   std::vector<mojom::DeviceStatePropertiesPtr> result;
   for (const DeviceState* device : devices) {
+    mojom::DeviceStateType technology_state =
+        GetMojoDeviceStateType(network_state_handler_->GetTechnologyState(
+            NetworkTypePattern::Primitive(device->type())));
+    if (technology_state == mojom::DeviceStateType::kUnavailable) {
+      NET_LOG(ERROR) << "Device state unavailable: " << device->name();
+      continue;
+    }
     mojom::DeviceStatePropertiesPtr mojo_device =
-        DeviceStateToMojo(network_state_handler_, device);
+        DeviceStateToMojo(device, technology_state);
     if (mojo_device)
       result.emplace_back(std::move(mojo_device));
   }
@@ -362,8 +361,9 @@
     const std::vector<const NetworkState*>& active_networks) {
   std::vector<mojom::NetworkStatePropertiesPtr> result;
   for (const NetworkState* network : active_networks) {
-    mojom::NetworkStatePropertiesPtr mojo_network =
-        NetworkStateToMojo(network_state_handler_, network);
+    mojom::NetworkStatePropertiesPtr mojo_network = NetworkStateToMojo(
+        network,
+        network_state_handler_->GetDeviceState(network->device_path()));
     if (mojo_network)
       result.emplace_back(std::move(mojo_network));
   }
diff --git a/chromeos/services/network_config/network_config_service_unittest.cc b/chromeos/services/network_config/network_config_service_unittest.cc
index 32c5683..dad6dc5 100644
--- a/chromeos/services/network_config/network_config_service_unittest.cc
+++ b/chromeos/services/network_config/network_config_service_unittest.cc
@@ -5,6 +5,7 @@
 #include "chromeos/services/network_config/network_config_service.h"
 
 #include "base/message_loop/message_loop.h"
+#include "chromeos/network/network_state_handler.h"
 #include "chromeos/services/network_config/public/cpp/cros_network_config_test_helper.h"
 #include "chromeos/services/network_config/public/cpp/cros_network_config_test_observer.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/components/arc/BUILD.gn b/components/arc/BUILD.gn
index 6833e10..bd7b23f 100644
--- a/components/arc/BUILD.gn
+++ b/components/arc/BUILD.gn
@@ -123,6 +123,7 @@
     "//ui/display/manager",
     "//ui/events",
     "//ui/events:dom_keycode_converter",
+    "//ui/events/ozone:events_ozone_evdev",
     "//url:url",
   ]
 }
diff --git a/components/arc/arc_prefs.cc b/components/arc/arc_prefs.cc
index caf5df5..75a1811 100644
--- a/components/arc/arc_prefs.cc
+++ b/components/arc/arc_prefs.cc
@@ -75,6 +75,9 @@
 const char kArcFastAppReinstallStarted[] = "arc.fast.app.reinstall.started";
 // A preference to keep list of Play Fast App Reinstall packages.
 const char kArcFastAppReinstallPackages[] = "arc.fast.app.reinstall.packages";
+// A preference to keep the current Android framework version. Note, that value
+// is only available after first packages update.
+const char kArcFrameworkVersion[] = "arc.framework.version";
 // A preference that holds the list of apps that the admin requested to be
 // push-installed.
 const char kArcPushInstallAppsRequested[] = "arc.push_install.requested";
diff --git a/components/arc/arc_prefs.h b/components/arc/arc_prefs.h
index 6c34a5d..552ad69 100644
--- a/components/arc/arc_prefs.h
+++ b/components/arc/arc_prefs.h
@@ -23,6 +23,7 @@
 ARC_EXPORT extern const char kArcEnabled[];
 ARC_EXPORT extern const char kArcFastAppReinstallPackages[];
 ARC_EXPORT extern const char kArcFastAppReinstallStarted[];
+ARC_EXPORT extern const char kArcFrameworkVersion[];
 ARC_EXPORT extern const char kArcHasAccessToRemovableMedia[];
 ARC_EXPORT extern const char kArcInitialSettingsPending[];
 ARC_EXPORT extern const char kArcLocationServiceEnabled[];
diff --git a/components/arc/metrics/DEPS b/components/arc/metrics/DEPS
index ce83197d..7b41bba 100644
--- a/components/arc/metrics/DEPS
+++ b/components/arc/metrics/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
   "+ui/aura",
+  "+ui/events/ozone/gamepad",
   "+ui/wm/public",
 ]
diff --git a/components/arc/metrics/arc_metrics_constants.h b/components/arc/metrics/arc_metrics_constants.h
index 81a8dad..69e7332 100644
--- a/components/arc/metrics/arc_metrics_constants.h
+++ b/components/arc/metrics/arc_metrics_constants.h
@@ -89,7 +89,10 @@
   // User started an app from the Kiosk Next Home app.
   APP_STARTED_FROM_KIOSK_NEXT_HOME = 18,
 
-  kMaxValue = APP_STARTED_FROM_KIOSK_NEXT_HOME,
+  // User interacted with an app using a gamepad.
+  GAMEPAD_INTERACTION = 19,
+
+  kMaxValue = GAMEPAD_INTERACTION,
 };
 
 }  // namespace arc
diff --git a/components/arc/metrics/arc_metrics_service.cc b/components/arc/metrics/arc_metrics_service.cc
index 6c2c1c0..cfc8161d 100644
--- a/components/arc/metrics/arc_metrics_service.cc
+++ b/components/arc/metrics/arc_metrics_service.cc
@@ -29,6 +29,7 @@
 #include "components/session_manager/core/session_manager.h"
 #include "components/user_prefs/user_prefs.h"
 #include "content/public/browser/browser_context.h"
+#include "ui/events/ozone/gamepad/gamepad_provider_ozone.h"
 
 namespace arc {
 
@@ -151,6 +152,7 @@
   arc_window_delegate_->RegisterActivationChangeObserver();
   session_manager::SessionManager::Get()->AddObserver(this);
   chromeos::PowerManagerClient::Get()->AddObserver(this);
+  ui::GamepadProviderOzone::GetInstance()->AddGamepadObserver(this);
 
   DCHECK(pref_service_);
   RestoreEngagementTimeFromPrefs();
@@ -172,6 +174,7 @@
   UpdateEngagementTime();
   SaveEngagementTimeToPrefs();
 
+  ui::GamepadProviderOzone::GetInstance()->RemoveGamepadObserver(this);
   chromeos::PowerManagerClient::Get()->RemoveObserver(this);
   session_manager::SessionManager::Get()->RemoveObserver(this);
   arc_window_delegate_->UnregisterActivationChangeObserver();
@@ -325,8 +328,10 @@
     aura::Window* lost_active) {
   UpdateEngagementTime();
   was_arc_window_active_ = arc_window_delegate_->IsArcAppWindow(gained_active);
-  if (!was_arc_window_active_)
+  if (!was_arc_window_active_) {
+    gamepad_interaction_recorded_ = false;
     return;
+  }
   UMA_HISTOGRAM_ENUMERATION(
       "Arc.UserInteraction",
       UserInteractionType::APP_CONTENT_WINDOW_INTERACTION);
@@ -345,6 +350,16 @@
   was_screen_dimmed_ = proto.dimmed();
 }
 
+void ArcMetricsService::OnGamepadEvent(const ui::GamepadEvent& event) {
+  if (!was_arc_window_active_)
+    return;
+  if (gamepad_interaction_recorded_)
+    return;
+  gamepad_interaction_recorded_ = true;
+  UMA_HISTOGRAM_ENUMERATION("Arc.UserInteraction",
+                            UserInteractionType::GAMEPAD_INTERACTION);
+}
+
 void ArcMetricsService::OnTaskCreated(int32_t task_id,
                                       const std::string& package_name,
                                       const std::string& activity,
diff --git a/components/arc/metrics/arc_metrics_service.h b/components/arc/metrics/arc_metrics_service.h
index b3ee74b..ba3a11f 100644
--- a/components/arc/metrics/arc_metrics_service.h
+++ b/components/arc/metrics/arc_metrics_service.h
@@ -21,6 +21,7 @@
 #include "components/arc/session/connection_observer.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/session_manager/core/session_manager_observer.h"
+#include "ui/events/ozone/gamepad/gamepad_observer.h"
 #include "ui/wm/public/activation_change_observer.h"
 
 class BrowserContextKeyedServiceFactory;
@@ -48,7 +49,8 @@
                           public wm::ActivationChangeObserver,
                           public session_manager::SessionManagerObserver,
                           public chromeos::PowerManagerClient::Observer,
-                          public mojom::MetricsHost {
+                          public mojom::MetricsHost,
+                          public ui::GamepadObserver {
  public:
   // Delegate for handling window focus observation that is used to track ARC
   // app usage metrics.
@@ -107,6 +109,9 @@
   void ScreenIdleStateChanged(
       const power_manager::ScreenIdleState& proto) override;
 
+  // ui::GamepadObserver overrides.
+  void OnGamepadEvent(const ui::GamepadEvent& event) override;
+
   // ArcAppListPrefs::Observer callbacks which are called through
   // ArcMetricsServiceProxy.
   void OnTaskCreated(int32_t task_id,
@@ -192,6 +197,8 @@
   base::TimeDelta engagement_time_foreground_;
   base::TimeDelta engagement_time_background_;
 
+  bool gamepad_interaction_recorded_ = false;
+
   // Always keep this the last member of this class to make sure it's the
   // first thing to be destructed.
   base::WeakPtrFactory<ArcMetricsService> weak_ptr_factory_;
diff --git a/components/cast_channel/cast_message_util.cc b/components/cast_channel/cast_message_util.cc
index 5ebf3a9..329faa7 100644
--- a/components/cast_channel/cast_message_util.cc
+++ b/components/cast_channel/cast_message_util.cc
@@ -24,48 +24,57 @@
 using namespace cast_channel;
 
 template <>
-const EnumTable<CastMessageType> EnumTable<CastMessageType>::instance({
-    {CastMessageType::kPing, "PING"},
-    {CastMessageType::kPong, "PONG"},
-    {CastMessageType::kGetAppAvailability, "GET_APP_AVAILABILITY"},
-    {CastMessageType::kReceiverStatusRequest, "GET_STATUS"},
-    {CastMessageType::kConnect, "CONNECT"},
-    {CastMessageType::kCloseConnection, "CLOSE"},
-    {CastMessageType::kBroadcast, "APPLICATION_BROADCAST"},
-    {CastMessageType::kLaunch, "LAUNCH"},
-    {CastMessageType::kStop, "STOP"},
-    {CastMessageType::kReceiverStatus, "RECEIVER_STATUS"},
-    {CastMessageType::kMediaStatus, "MEDIA_STATUS"},
-    {CastMessageType::kLaunchError, "LAUNCH_ERROR"},
-});
+const EnumTable<CastMessageType> EnumTable<CastMessageType>::instance(
+    {
+        {CastMessageType::kPing, "PING"},
+        {CastMessageType::kPong, "PONG"},
+        {CastMessageType::kGetAppAvailability, "GET_APP_AVAILABILITY"},
+        {CastMessageType::kReceiverStatusRequest, "GET_STATUS"},
+        {CastMessageType::kConnect, "CONNECT"},
+        {CastMessageType::kCloseConnection, "CLOSE"},
+        {CastMessageType::kBroadcast, "APPLICATION_BROADCAST"},
+        {CastMessageType::kLaunch, "LAUNCH"},
+        {CastMessageType::kStop, "STOP"},
+        {CastMessageType::kReceiverStatus, "RECEIVER_STATUS"},
+        {CastMessageType::kMediaStatus, "MEDIA_STATUS"},
+        {CastMessageType::kLaunchError, "LAUNCH_ERROR"},
+        {CastMessageType::kOther},
+    },
+    CastMessageType::kMaxValue);
 
 template <>
-const EnumTable<V2MessageType> EnumTable<V2MessageType>::instance({
-    {V2MessageType::kEditTracksInfo, "EDIT_TRACKS_INFO"},
-    {V2MessageType::kGetStatus, "GET_STATUS"},
-    {V2MessageType::kLoad, "LOAD"},
-    {V2MessageType::kMediaGetStatus, "MEDIA_GET_STATUS"},
-    {V2MessageType::kMediaSetVolume, "MEDIA_SET_VOLUME"},
-    {V2MessageType::kPause, "PAUSE"},
-    {V2MessageType::kPlay, "PLAY"},
-    {V2MessageType::kPrecache, "PRECACHE"},
-    {V2MessageType::kQueueInsert, "QUEUE_INSERT"},
-    {V2MessageType::kQueueLoad, "QUEUE_LOAD"},
-    {V2MessageType::kQueueRemove, "QUEUE_REMOVE"},
-    {V2MessageType::kQueueReorder, "QUEUE_REORDER"},
-    {V2MessageType::kQueueUpdate, "QUEUE_UPDATE"},
-    {V2MessageType::kSeek, "SEEK"},
-    {V2MessageType::kSetVolume, "SET_VOLUME"},
-    {V2MessageType::kStop, "STOP"},
-    {V2MessageType::kStopMedia, "STOP_MEDIA"},
-});
+const EnumTable<V2MessageType> EnumTable<V2MessageType>::instance(
+    {
+        {V2MessageType::kEditTracksInfo, "EDIT_TRACKS_INFO"},
+        {V2MessageType::kGetStatus, "GET_STATUS"},
+        {V2MessageType::kLoad, "LOAD"},
+        {V2MessageType::kMediaGetStatus, "MEDIA_GET_STATUS"},
+        {V2MessageType::kMediaSetVolume, "MEDIA_SET_VOLUME"},
+        {V2MessageType::kPause, "PAUSE"},
+        {V2MessageType::kPlay, "PLAY"},
+        {V2MessageType::kPrecache, "PRECACHE"},
+        {V2MessageType::kQueueInsert, "QUEUE_INSERT"},
+        {V2MessageType::kQueueLoad, "QUEUE_LOAD"},
+        {V2MessageType::kQueueRemove, "QUEUE_REMOVE"},
+        {V2MessageType::kQueueReorder, "QUEUE_REORDER"},
+        {V2MessageType::kQueueUpdate, "QUEUE_UPDATE"},
+        {V2MessageType::kSeek, "SEEK"},
+        {V2MessageType::kSetVolume, "SET_VOLUME"},
+        {V2MessageType::kStop, "STOP"},
+        {V2MessageType::kStopMedia, "STOP_MEDIA"},
+        {V2MessageType::kOther},
+    },
+    V2MessageType::kMaxValue);
 
 template <>
 const EnumTable<GetAppAvailabilityResult>
-    EnumTable<GetAppAvailabilityResult>::instance({
-        {GetAppAvailabilityResult::kAvailable, "APP_AVAILABLE"},
-        {GetAppAvailabilityResult::kUnavailable, "APP_UNAVAILABLE"},
-    });
+    EnumTable<GetAppAvailabilityResult>::instance(
+        {
+            {GetAppAvailabilityResult::kAvailable, "APP_AVAILABLE"},
+            {GetAppAvailabilityResult::kUnavailable, "APP_UNAVAILABLE"},
+            {GetAppAvailabilityResult::kUnknown},
+        },
+        GetAppAvailabilityResult::kMaxValue);
 
 }  // namespace cast_util
 
@@ -485,7 +494,7 @@
   if (!type_value)
     return LaunchSessionResponse();
 
-  CastMessageType type = CastMessageTypeFromString(type_value->GetString());
+  const auto type = CastMessageTypeFromString(type_value->GetString());
   if (type != CastMessageType::kReceiverStatus &&
       type != CastMessageType::kLaunchError)
     return LaunchSessionResponse();
diff --git a/components/cast_channel/cast_message_util.h b/components/cast_channel/cast_message_util.h
index 9f12167..ee1e5ab 100644
--- a/components/cast_channel/cast_message_util.h
+++ b/components/cast_channel/cast_message_util.h
@@ -48,7 +48,8 @@
   kReceiverStatus,
   kMediaStatus,
   kLaunchError,
-  kOther  // Add new types above |kOther|.
+  kOther,  // Add new types above |kOther|.
+  kMaxValue = kOther,
 };
 
 enum class V2MessageType {
@@ -69,7 +70,8 @@
   kSetVolume,
   kStop,
   kStopMedia,
-  kOther  // Add new types above |kOther|.
+  kOther,  // Add new types above |kOther|.
+  kMaxValue = kOther,
 };
 
 // Checks if the contents of |message_proto| are valid.
@@ -198,6 +200,7 @@
   kAvailable,
   kUnavailable,
   kUnknown,
+  kMaxValue = kUnknown,
 };
 
 const char* ToString(GetAppAvailabilityResult result);
@@ -213,7 +216,7 @@
 
 // Result of a session launch.
 struct LaunchSessionResponse {
-  enum Result { kOk, kError, kTimedOut, kUnknown };
+  enum Result { kOk, kError, kTimedOut, kUnknown, kMaxValue = kUnknown };
 
   LaunchSessionResponse();
   LaunchSessionResponse(LaunchSessionResponse&& other);
diff --git a/components/cast_channel/enum_table.cc b/components/cast_channel/enum_table.cc
index a47a2a2..d40033c 100644
--- a/components/cast_channel/enum_table.cc
+++ b/components/cast_channel/enum_table.cc
@@ -35,7 +35,7 @@
     std::size_t size,
     int value) {
   for (std::size_t i = 0; i < size; i++) {
-    if (data[i].value == value)
+    if (data[i].value == value && data[i].has_str())
       return data[i].str();
   }
   return base::nullopt;
diff --git a/components/cast_channel/enum_table.h b/components/cast_channel/enum_table.h
index e0551953..7957f5b 100644
--- a/components/cast_channel/enum_table.h
+++ b/components/cast_channel/enum_table.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_CAST_CHANNEL_ENUM_TABLE_H_
 
 #include <cstdint>
+#include <cstring>
 
 #include "base/logging.h"
 #include "base/optional.h"
@@ -25,17 +26,18 @@
 // a table of enum values and their corresponding strings:
 //
 //     // In .h file:
-//     enum class MyEnum { kFoo, kBar, ..., kOther };
+//     enum class MyEnum { kFoo, kBar, ..., kOther, kMaxValue = kOther };
 //
 //     // In .cc file:
 //
 //     template <>
 //     constexpr EnumTable<MyEnum> EnumTable<MyEnum>::instance({
-//       {MyEnum::kFoo, "FOO"},
-//       {MyEnum::kBar, "BAR"},
-//       ...
-//       // No entry for kOther because it has no string representation.
-//     });
+//         {MyEnum::kFoo, "FOO"},
+//         {MyEnum::kBar, "BAR"},
+//         ...
+//         {MyEnum::kOther},  // Not all values need strings.
+//       },
+//       MyEnum::kMaxValue);  // Needed to detect missing table entries.
 //
 // The functions EnumToString() and StringToEnum() are used to look up values
 // in the table.  In some cases, it may be useful to wrap these functions for
@@ -65,6 +67,40 @@
 // string literals or define each enum string as a named constant.
 //
 //
+// Consecutive Enum Tables
+// -----------------------
+//
+// When using an EnumTable, it's generally best for the following conditions
+// to be true:
+//
+// - The enum is defined with "enum class" syntax.
+//
+// - The members have the default values assigned by the compiler.
+//
+// - There is an extra member named kMaxValue which is set equal to the highest
+//   ordinary value.  (The Chromium style checker will verify that kMaxValue
+//   really is the maximum value.)
+//
+// - The values in the EnumTable constructor appear in sorted order.
+//
+// - Every member of the enum (other than kMaxValue) is included in the table.
+//
+// When these conditions are met, the enum's |kMaxValue| member should be passed
+// as the second argument of the EnumTable.  This will create a table where enum
+// values can be converted to strings in constant time.  It will also reliably
+// detect an incomplete enum table during startup of a debug build.
+//
+//
+// Non-Consecutive Enum Tables
+// ---------------------------
+//
+// When the conditions in the previous section cannot be satisfied, the second
+// argument of the EnumTable constructor should be the special constant
+// |NonConsecutiveEnumTable|.  Doing so has some unfortunate side-effects: there
+// is no automatic way to detect when an enum value is missing from the table,
+// and looking up a non-constant enum value requires a linear search.
+//
+//
 // Why use an EnumTable?
 // ---------------------
 //
@@ -146,6 +182,7 @@
 #endif
         GenericEnumTableEntry {
  public:
+  inline constexpr GenericEnumTableEntry(int32_t value);
   inline constexpr GenericEnumTableEntry(int32_t value, base::StringPiece str);
 
  private:
@@ -156,7 +193,12 @@
   static base::Optional<base::StringPiece>
   FindByValue(const GenericEnumTableEntry data[], std::size_t size, int value);
 
-  constexpr base::StringPiece str() const { return {chars, length}; }
+  constexpr base::StringPiece str() const {
+    DCHECK(has_str());
+    return {chars, length};
+  }
+
+  constexpr bool has_str() const { return chars != nullptr; }
 
   // Instead of storing a base::StringPiece, it's broken apart and stored as a
   // pointer and an unsigned int (rather than a std::size_t) so that table
@@ -171,9 +213,13 @@
   DISALLOW_COPY_AND_ASSIGN(GenericEnumTableEntry);
 };
 
-// Yes, this really needs to be inlined.  Even though it looks "complex" to the
-// style checker, everything is executed at compile time, so an EnumTable
-// instance can be fully initialized without executing any code.
+// Yes, these constructors really needs to be inlined.  Even though they look
+// "complex" to the style checker, everything is executed at compile time, so an
+// EnumTable instance can be fully initialized without executing any code.
+
+inline constexpr GenericEnumTableEntry::GenericEnumTableEntry(int32_t value)
+    : chars(nullptr), length(UINT32_MAX), value(value) {}
+
 inline constexpr GenericEnumTableEntry::GenericEnumTableEntry(
     int32_t value,
     base::StringPiece str)
@@ -181,8 +227,8 @@
       length(static_cast<uint32_t>(str.length())),
       value(value) {}
 
-struct UnsortedEnumTable_t {};
-constexpr UnsortedEnumTable_t UnsortedEnumTable;
+struct NonConsecutiveEnumTable_t {};
+constexpr NonConsecutiveEnumTable_t NonConsecutiveEnumTable;
 
 // A table for associating enum values with string literals.  This class is
 // designed for use as an initialized global variable, so it has a trivial
@@ -190,11 +236,16 @@
 template <typename E>
 class EnumTable {
  public:
-  // DO NOT add any members to this class, or it will break the
+  // DO NOT add any data members to this class, or it will break the
   // reinterpret_casts below.  If necessary, add new members to
   // GenericEnumTableEntry instead.
   class Entry : public GenericEnumTableEntry {
    public:
+    // Constructor for placeholder entries with no string value.
+    constexpr Entry(E value)
+        : GenericEnumTableEntry(static_cast<int32_t>(value)) {}
+
+    // Constructor for regular entries.
     constexpr Entry(E value, base::StringPiece str)
         : GenericEnumTableEntry(static_cast<int32_t>(value), str) {}
 
@@ -209,7 +260,14 @@
   // Creates an EnumTable where data[i].value == i for all i.  When a table is
   // created with this constructor, the GetString() method is a simple array
   // lookup that runs in constant time.
-  constexpr EnumTable(std::initializer_list<Entry> data)
+  //
+  // If |max_value| is specified, all enum values in |data| must be less than or
+  // equal to |max_value|.  This feature is intended to help catch errors cause
+  // by a new value being added to an enum without the new value being added to
+  // the corresponding table.  For best results, use an enum class and create a
+  // constant named kMaxValue.  For more details, see
+  // https://www.chromium.org/developers/coding-style/chromium-style-checker-errors#TOC-Enumerator-max-values
+  constexpr EnumTable(std::initializer_list<Entry> data, E max_value)
       : EnumTable(data, true) {
 #ifndef NDEBUG
     // NOTE(jrw): This is compiled out when NDEBUG is defined, even if DCHECKS
@@ -217,20 +275,25 @@
     // this is that if DCHECKs are enabled, global EnumTable instances will have
     // a nontrivial constructor, which is flagged as a style violation by the
     // linux-rel trybot.
-    auto found = FindUnsortedEntry(data);
+    auto found = FindNonConsecutiveEntry(data);
     DCHECK(!found) << "Entries' numerical values must be consecutive "
-                   << "integers starting at 0; found problem at index "
+                   << "integers starting from 0; found problem at index "
                    << *found;
+
+    const auto int_max_value = static_cast<int32_t>(max_value);
+    DCHECK(data.end()[-1].value == int_max_value)
+        << "Missing entry for enum value " << int_max_value;
 #endif  // NDEBUG
   }
 
   // Creates an EnumTable where data[i].value != i for some values of i.  When
   // a table is created with this constructor, the GetString() method must
   // perform a linear search to find the correct string.
-  constexpr EnumTable(std::initializer_list<Entry> data, UnsortedEnumTable_t)
+  constexpr EnumTable(std::initializer_list<Entry> data,
+                      NonConsecutiveEnumTable_t)
       : EnumTable(data, false) {
 #ifndef NDEBUG
-    DCHECK(FindUnsortedEntry(data))
+    DCHECK(FindNonConsecutiveEntry(data))
         << "Don't use this constructor for sorted entries.";
 #endif  // NDEBUG
   }
@@ -242,9 +305,10 @@
       const std::size_t index = static_cast<std::size_t>(value);
       if (ANALYZER_ASSUME_TRUE(index < data_.size())) {
         const auto& entry = data_.begin()[index];
-        return entry.str();
+        if (ANALYZER_ASSUME_TRUE(entry.has_str()))
+          return entry.str();
       }
-      return {};
+      return base::nullopt;
     }
     return GenericEnumTableEntry::FindByValue(
         reinterpret_cast<const GenericEnumTableEntry*>(data_.begin()),
@@ -262,7 +326,7 @@
   template <E Value>
   constexpr base::StringPiece GetString() const {
     for (const auto& entry : data_) {
-      if (entry.value == static_cast<int32_t>(Value))
+      if (entry.value == static_cast<int32_t>(Value) && entry.has_str())
         return entry.str();
     }
 
@@ -308,7 +372,7 @@
         const Entry& ej = data.begin()[j];
         DCHECK(ei.value != ej.value)
             << "Found duplicate enum values at indices " << i << " and " << j;
-        DCHECK(ei.str() != ej.str())
+        DCHECK(!(ei.has_str() && ej.has_str() && ei.str() == ej.str()))
             << "Found duplicate strings at indices " << i << " and " << j;
       }
     }
@@ -317,11 +381,11 @@
 
 #ifndef NDEBUG
   // Finds and returns the first i for which data[i].value != i;
-  constexpr static base::Optional<std::size_t> FindUnsortedEntry(
+  constexpr static base::Optional<std::size_t> FindNonConsecutiveEntry(
       std::initializer_list<Entry> data) {
-    std::size_t counter = 0;
+    int32_t counter = 0;
     for (const auto& entry : data) {
-      if (static_cast<std::size_t>(entry.value) != counter) {
+      if (entry.value != counter) {
         return counter;
       }
       ++counter;
diff --git a/components/cast_channel/enum_table_unittest.cc b/components/cast_channel/enum_table_unittest.cc
index d9f900f..22cf674f 100644
--- a/components/cast_channel/enum_table_unittest.cc
+++ b/components/cast_channel/enum_table_unittest.cc
@@ -5,22 +5,34 @@
 #include "components/cast_channel/enum_table.h"
 
 #include "base/macros.h"
+#include "base/optional.h"
 #include "base/test/gtest_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cast_util {
 namespace {
 
-enum class MyEnum { kZero, kOne, kTwo, kOther };
+enum class MyEnum { kZero, kOne, kTwo, kMaxValue = kTwo };
 
 const EnumTable<MyEnum> kSorted({{MyEnum::kZero, "ZERO"},
                                  {MyEnum::kOne, "ONE"},
-                                 {MyEnum::kTwo, "TWO"}});
+                                 {MyEnum::kTwo, "TWO"}},
+                                MyEnum::kMaxValue);
 
 const EnumTable<MyEnum> kUnsorted({{MyEnum::kOne, "ONE"},
                                    {MyEnum::kZero, "ZERO"},
                                    {MyEnum::kTwo, "TWO"}},
-                                  UnsortedEnumTable);
+                                  NonConsecutiveEnumTable);
+
+const EnumTable<MyEnum> kSortedMissing({{MyEnum::kZero, "ZERO"},
+                                        {MyEnum::kOne},
+                                        {MyEnum::kTwo, "TWO"}},
+                                       MyEnum::kMaxValue);
+
+const EnumTable<MyEnum> kUnsortedMissing({{MyEnum::kOne, "ONE"},
+                                          {MyEnum::kZero},
+                                          {MyEnum::kTwo, "TWO"}},
+                                         NonConsecutiveEnumTable);
 
 }  // namespace
 
@@ -28,7 +40,8 @@
 const EnumTable<MyEnum> EnumTable<MyEnum>::instance(
     {{MyEnum::kZero, "ZERO_DEFAULT"},
      {MyEnum::kOne, "ONE_DEFAULT"},
-     {MyEnum::kTwo, "TWO_DEFAULT"}});
+     {MyEnum::kTwo, "TWO_DEFAULT"}},
+    MyEnum::kMaxValue);
 
 namespace {
 
@@ -36,21 +49,30 @@
   EXPECT_EQ("ZERO", kSorted.GetString(MyEnum::kZero));
   EXPECT_EQ("ONE", kSorted.GetString(MyEnum::kOne));
   EXPECT_EQ("TWO", kSorted.GetString(MyEnum::kTwo));
-  EXPECT_EQ(base::nullopt, kSorted.GetString(MyEnum::kOther));
 }
 
 TEST(EnumTableTest, TestGetStringUnsorted) {
   EXPECT_EQ("ZERO", kUnsorted.GetString(MyEnum::kZero));
   EXPECT_EQ("ONE", kUnsorted.GetString(MyEnum::kOne));
   EXPECT_EQ("TWO", kUnsorted.GetString(MyEnum::kTwo));
-  EXPECT_EQ(base::nullopt, kUnsorted.GetString(MyEnum::kOther));
+}
+
+TEST(EnumTableTest, TestGetMissingString) {
+  EXPECT_EQ("ZERO", kSortedMissing.GetString(MyEnum::kZero));
+  EXPECT_EQ(base::nullopt, kSortedMissing.GetString(MyEnum::kOne));
+  EXPECT_EQ("TWO", kSortedMissing.GetString(MyEnum::kTwo));
+}
+
+TEST(EnumTableTest, TestGetMissingStringUnsorted) {
+  EXPECT_EQ(base::nullopt, kUnsortedMissing.GetString(MyEnum::kZero));
+  EXPECT_EQ("ONE", kUnsortedMissing.GetString(MyEnum::kOne));
+  EXPECT_EQ("TWO", kUnsortedMissing.GetString(MyEnum::kTwo));
 }
 
 TEST(EnumTableTest, TestEnumToStringGlobal) {
   EXPECT_EQ("ZERO_DEFAULT", EnumToString(MyEnum::kZero));
   EXPECT_EQ("ONE_DEFAULT", EnumToString(MyEnum::kOne));
   EXPECT_EQ("TWO_DEFAULT", EnumToString(MyEnum::kTwo));
-  EXPECT_EQ(base::nullopt, EnumToString(MyEnum::kOther));
 }
 
 TEST(EnumTableTest, TestStaticGetString) {
@@ -86,15 +108,27 @@
 // out when NDEBUG is defined.
 #ifndef NDEBUG
 
+TEST(EnumTableDeathTest, MaxValueTooSmall) {
+  EXPECT_DCHECK_DEATH(EnumTable<MyEnum>(
+      {{MyEnum::kZero, "ZERO"}, {MyEnum::kOne, "ONE"}, {MyEnum::kTwo, "TWO"}},
+      MyEnum::kOne));
+}
+
+TEST(EnumTableDeathTest, MaxValueTooLarge) {
+  EXPECT_DCHECK_DEATH(EnumTable<MyEnum>(
+      {{MyEnum::kZero, "ZERO"}, {MyEnum::kOne, "ONE"}}, MyEnum::kTwo));
+}
+
 TEST(EnumTableDeathTest, Sorted) {
   EXPECT_DCHECK_DEATH(EnumTable<MyEnum>(
-      {{MyEnum::kZero, "ZERO"}, {MyEnum::kTwo, "TWO"}, {MyEnum::kOne, "ONE"}}));
+      {{MyEnum::kZero, "ZERO"}, {MyEnum::kTwo, "TWO"}, {MyEnum::kOne, "ONE"}},
+      MyEnum::kMaxValue));
 }
 
 TEST(EnumTableDeathTest, Unsorted) {
   EXPECT_DCHECK_DEATH(EnumTable<MyEnum>(
       {{MyEnum::kZero, "ZERO"}, {MyEnum::kOne, "ONE"}, {MyEnum::kTwo, "TWO"}},
-      UnsortedEnumTable));
+      NonConsecutiveEnumTable));
 }
 
 TEST(EnumTableDeathTest, DuplicateEnums) {
@@ -102,17 +136,24 @@
                                          {MyEnum::kTwo, "TWO"},
                                          {MyEnum::kOne, "ONE"},
                                          {MyEnum::kZero, "ZERO"}},
-                                        UnsortedEnumTable));
+                                        NonConsecutiveEnumTable));
 }
 
 TEST(EnumTableDeathTest, DuplicateStrings) {
   EXPECT_DCHECK_DEATH(EnumTable<MyEnum>(
-      {{MyEnum::kZero, "FOO"}, {MyEnum::kOne, "ONE"}, {MyEnum::kTwo, "FOO"}}));
+      {{MyEnum::kZero, "FOO"}, {MyEnum::kOne, "ONE"}, {MyEnum::kTwo, "FOO"}},
+      MyEnum::kMaxValue));
+}
+
+constexpr MyEnum kInvalid =
+    static_cast<MyEnum>(static_cast<int>(MyEnum::kMaxValue) + 1);
+
+TEST(EnumTableDeathTest, EnumToString) {
+  EXPECT_DCHECK_DEATH(kSorted.GetString<kInvalid>());
 }
 
 TEST(EnumTableDeathTest, StaticEnumToString) {
-  EXPECT_DCHECK_DEATH(kSorted.GetString<MyEnum::kOther>());
-  EXPECT_DCHECK_DEATH((EnumToString<MyEnum, MyEnum::kOther>()));
+  EXPECT_DCHECK_DEATH((EnumToString<MyEnum, kInvalid>()));
 }
 
 enum class HugeEnum {
@@ -149,22 +190,31 @@
   k30,
   k31,
   k32,
+  kMaxValue = k32,
 };
 
 TEST(EnumTableDeathTest, HugeEnum) {
-  EXPECT_DCHECK_DEATH(EnumTable<HugeEnum>({
-      {HugeEnum::k0, "k0"},   {HugeEnum::k1, "k1"},   {HugeEnum::k2, "k2"},
-      {HugeEnum::k3, "k3"},   {HugeEnum::k4, "k4"},   {HugeEnum::k5, "k5"},
-      {HugeEnum::k6, "k6"},   {HugeEnum::k7, "k7"},   {HugeEnum::k8, "k8"},
-      {HugeEnum::k9, "k9"},   {HugeEnum::k10, "k10"}, {HugeEnum::k11, "k11"},
-      {HugeEnum::k12, "k12"}, {HugeEnum::k13, "k13"}, {HugeEnum::k14, "k14"},
-      {HugeEnum::k15, "k15"}, {HugeEnum::k16, "k16"}, {HugeEnum::k17, "k17"},
-      {HugeEnum::k18, "k18"}, {HugeEnum::k19, "k19"}, {HugeEnum::k20, "k20"},
-      {HugeEnum::k21, "k21"}, {HugeEnum::k22, "k22"}, {HugeEnum::k23, "k23"},
-      {HugeEnum::k24, "k24"}, {HugeEnum::k25, "k25"}, {HugeEnum::k26, "k26"},
-      {HugeEnum::k27, "k27"}, {HugeEnum::k28, "k28"}, {HugeEnum::k29, "k29"},
-      {HugeEnum::k30, "k30"}, {HugeEnum::k31, "k31"}, {HugeEnum::k32, "k32"},
-  }));
+  EXPECT_DCHECK_DEATH(EnumTable<HugeEnum>(
+      {
+          {HugeEnum::k0, "k0"},   {HugeEnum::k1, "k1"},
+          {HugeEnum::k2, "k2"},   {HugeEnum::k3, "k3"},
+          {HugeEnum::k4, "k4"},   {HugeEnum::k5, "k5"},
+          {HugeEnum::k6, "k6"},   {HugeEnum::k7, "k7"},
+          {HugeEnum::k8, "k8"},   {HugeEnum::k9, "k9"},
+          {HugeEnum::k10, "k10"}, {HugeEnum::k11, "k11"},
+          {HugeEnum::k12, "k12"}, {HugeEnum::k13, "k13"},
+          {HugeEnum::k14, "k14"}, {HugeEnum::k15, "k15"},
+          {HugeEnum::k16, "k16"}, {HugeEnum::k17, "k17"},
+          {HugeEnum::k18, "k18"}, {HugeEnum::k19, "k19"},
+          {HugeEnum::k20, "k20"}, {HugeEnum::k21, "k21"},
+          {HugeEnum::k22, "k22"}, {HugeEnum::k23, "k23"},
+          {HugeEnum::k24, "k24"}, {HugeEnum::k25, "k25"},
+          {HugeEnum::k26, "k26"}, {HugeEnum::k27, "k27"},
+          {HugeEnum::k28, "k28"}, {HugeEnum::k29, "k29"},
+          {HugeEnum::k30, "k30"}, {HugeEnum::k31, "k31"},
+          {HugeEnum::k32, "k32"},
+      },
+      HugeEnum::kMaxValue));
 }
 
 #endif  // NDEBUG
diff --git a/components/components_strings.grd b/components/components_strings.grd
index 71e8f62..33c5baf 100644
--- a/components/components_strings.grd
+++ b/components/components_strings.grd
@@ -250,6 +250,9 @@
       <message name="IDS_ADD" desc="Used for Add on buttons">
         Add
       </message>
+      <message name="IDS_REMOVE" desc="Used for Remove on buttons">
+        Remove
+      </message>
       <message name="IDS_SAVE" desc="Used on a button to save information you are editing.">
         Save
       </message>
diff --git a/components/components_strings_grd/IDS_REMOVE.png.sha1 b/components/components_strings_grd/IDS_REMOVE.png.sha1
new file mode 100644
index 0000000..88c435d
--- /dev/null
+++ b/components/components_strings_grd/IDS_REMOVE.png.sha1
@@ -0,0 +1 @@
+4f7bd70a3a0304338ef3af278686c8cd75162ee9
\ No newline at end of file
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_request_options_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_request_options_unittest.cc
index d64bf8f..7aafebc 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_request_options_unittest.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_request_options_unittest.cc
@@ -14,10 +14,12 @@
 #include "base/command_line.h"
 #include "base/metrics/field_trial.h"
 #include "base/strings/string_util.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_task_environment.h"
 #include "build/build_config.h"
 #include "components/data_reduction_proxy/core/browser/data