diff --git a/.vpython b/.vpython
index f210f49..712ddf1 100644
--- a/.vpython
+++ b/.vpython
@@ -226,3 +226,55 @@
   name: "infra/python/wheels/mock-py2_py3"
   version: "version:2.0.0"
 >
+
+# Used by:
+#   chrome/test/chromedriver/test/run_webdriver_tests.py
+
+wheel <
+  name: "infra/python/wheels/pytest-py2_py3"
+  version: "version:3.5.0"
+>
+
+wheel <
+  name: "infra/python/wheels/attrs-py2_py3"
+  version: "version:17.4.0"
+>
+
+wheel <
+  name: "infra/python/wheels/six-py2_py3"
+  version: "version:1.10.0"
+>
+
+wheel <
+  name: "infra/python/wheels/more-itertools-py2_py3"
+  version: "version:4.1.0"
+>
+
+wheel <
+  name: "infra/python/wheels/scandir/${vpython_platform}"
+  version: "version:1.7"
+>
+
+wheel <
+  name: "infra/python/wheels/pluggy-py2_py3"
+  version: "version:0.6.0"
+>
+
+wheel <
+  name: "infra/python/wheels/py-py2_py3"
+  version: "version:1.5.3"
+>
+
+wheel <
+  name: "infra/python/wheels/funcsigs-py2_py3"
+  version: "version:1.0.2"
+>
+wheel: <
+  name: "infra/python/wheels/psutil/${vpython_platform}"
+  version: "version:5.2.2"
+>
+
+wheel: <
+  name: "infra/python/wheels/colorama-py2_py3"
+  version: "version:0.4.1"
+>
diff --git a/BUILD.gn b/BUILD.gn
index 53155d0..3bf0898 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -927,6 +927,27 @@
     ]
   }
 
+  if (!is_chromeos && !is_ios && !is_fuchsia && !is_android) {
+    # WPT Webdriver tests runner
+    # chrome/test/chromedriver/test/run_webdriver_tests.py
+    group("webdriver_wpt_tests") {
+      testonly = true
+      data = [
+        "//third_party/blink/tools/blinkpy/third_party/wpt/wpt/tools/webdriver/",
+        "//third_party/blink/web_tests/external/wpt/webdriver/",
+        "//chrome/test/chromedriver/test/run_webdriver_tests.py",
+        "//chrome/test/chromedriver/server/server.py",
+        "//chrome/test/chromedriver/util.py",
+        "//chrome/test/chromedriver/chrome_paths.py",
+        "//testing/xvfb.py",
+      ]
+      data_deps = [
+        "//chrome:chrome",
+        "//chrome/test/chromedriver",
+      ]
+    }
+  }
+
   # https://www.chromium.org/developers/testing/webkit-layout-tests
 
   # The _exparchive at the end of the name indicates to the isolate recipe
diff --git a/DEPS b/DEPS
index dbd6eabd..312d05f 100644
--- a/DEPS
+++ b/DEPS
@@ -126,11 +126,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': '98385ba6c5c691c092372b9010f8375c005e0448',
+  'skia_revision': 'f36ad269e88a1b2fec16e6404cd6cd36bc202115',
   # 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': '003f99ea6ea916e527e8eacd733cdd5c919e6ae5',
+  'v8_revision': '69a830d35c1947dcc8d5570bbb023263651e1e46',
   # 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.
@@ -138,7 +138,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '740db7fd279529530019546457f048f06f4fbc8e',
+  'angle_revision': '603ad164a6fcfac0fce282c604b292db5046b870',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -146,7 +146,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '1b8d2cc9056a4e22480963cdbf8bc34edd2dcd0b',
+  'pdfium_revision': '82105494f27a592f628749bdf252d13e73e84cf2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -186,7 +186,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': '11e283fea2536277797e09893a547cbd094e4426',
+  'catapult_revision': '38769c1f96a321e99a04743d2326cab6ff8fa6fb',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -250,7 +250,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '7771f58c7f2429b9f1c532f1f3196423e5b9fb80',
+  'dawn_revision': '01a3e9b6383f2169d46e64488a4adb5a0273c15c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -449,7 +449,7 @@
   },
 
   'src/ios/third_party/webkit/src': {
-      'url': 'https://chromium.googlesource.com/external/Webkit',
+      'url': Var('chromium_git') + '/external/github.com/WebKit/webkit.git' + '@' + 'c56dd8a91c62afcfd0de4a5fe6dbb46484ea1fb5',
       'condition': 'checkout_ios and checkout_ios_webkit'
   },
 
@@ -729,7 +729,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '971e4cb2b43d928c59a3e0e58d30dec03e8f2a9e',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'f0a00cd7f17bdf5627cdb5da8faf8302cb243bf6',
       'condition': 'checkout_linux',
   },
 
@@ -754,7 +754,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '61d0c292535e8e6c1102f198ec1ef47f50075ceb',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '06d1040fab75709fd0cbea3d3cbfa8774cb826f0',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -1089,7 +1089,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '10ef8b33f128b6def49291c2c0de2e0e2eb84437',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '2af00334929272b1ec4ce4dd17480a50bbbc589b',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + 'ac0d98b5cee6c024b0cffeb4f8f45b6fc5ccdb78',
@@ -1252,7 +1252,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'db52df17f0d012983dc281e4864c71485a86bd0e',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'e98954c35e97dfe4d50fe6bd75587b14ffb6568e',
+    Var('webrtc_git') + '/src.git' + '@' + '7ca375c8ca54f5ab093204b9aa2bf446be10bcbd',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1293,7 +1293,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@96982ab4da6c91f6c47c129af5b91d76977c03c3',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@8200b1f0ea5598d7828f69506f53bbad91bf3238',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 8bb4787..74b6c02 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -709,6 +709,8 @@
     "system/model/update_model.h",
     "system/model/virtual_keyboard_model.cc",
     "system/model/virtual_keyboard_model.h",
+    "system/network/active_network_icon.cc",
+    "system/network/active_network_icon.h",
     "system/network/auto_connect_notifier.cc",
     "system/network/auto_connect_notifier.h",
     "system/network/network_feature_pod_button.cc",
diff --git a/ash/app_list/model/search/search_model.cc b/ash/app_list/model/search/search_model.cc
index 1710c6ac..63a7385 100644
--- a/ash/app_list/model/search/search_model.cc
+++ b/ash/app_list/model/search/search_model.cc
@@ -8,6 +8,8 @@
 #include <string>
 #include <utility>
 
+#include "base/bind.h"
+
 namespace app_list {
 
 SearchModel::SearchModel()
@@ -29,11 +31,27 @@
     SearchResult::DisplayType display_type,
     const std::set<std::string>& excludes,
     size_t max_results) {
+  base::RepeatingCallback<bool(const SearchResult&)> filter_function =
+      base::BindRepeating(
+          [](const SearchResult::DisplayType& display_type,
+             const std::set<std::string>& excludes,
+             const SearchResult& r) -> bool {
+            return excludes.count(r.id()) == 0 &&
+                   display_type == r.display_type();
+          },
+          display_type, excludes);
+  return SearchModel::FilterSearchResultsByFunction(results, filter_function,
+                                                    max_results);
+}
+
+std::vector<SearchResult*> SearchModel::FilterSearchResultsByFunction(
+    SearchResults* results,
+    const base::RepeatingCallback<bool(const SearchResult&)>& result_filter,
+    size_t max_results) {
   std::vector<SearchResult*> matches;
   for (size_t i = 0; i < results->item_count(); ++i) {
     SearchResult* item = results->GetItemAt(i);
-    if (item->display_type() == display_type &&
-        excludes.count(item->id()) == 0) {
+    if (result_filter.Run(*item)) {
       matches.push_back(item);
       if (matches.size() == max_results)
         break;
diff --git a/ash/app_list/model/search/search_model.h b/ash/app_list/model/search/search_model.h
index 2216bd2b..115856f9 100644
--- a/ash/app_list/model/search/search_model.h
+++ b/ash/app_list/model/search/search_model.h
@@ -13,6 +13,7 @@
 #include "ash/app_list/model/app_list_model_export.h"
 #include "ash/app_list/model/search/search_box_model.h"
 #include "ash/app_list/model/search/search_result.h"
+#include "base/callback.h"
 #include "ui/base/models/list_model.h"
 
 namespace app_list {
@@ -47,6 +48,13 @@
       const std::set<std::string>& excludes,
       size_t max_results);
 
+  // Filter the given |results| by those which |result_filter| returns true for.
+  // The returned list is truncated to |max_results|.
+  static std::vector<SearchResult*> FilterSearchResultsByFunction(
+      SearchResults* results,
+      const base::RepeatingCallback<bool(const SearchResult&)>& result_filter,
+      size_t max_results);
+
   SearchBoxModel* search_box() { return search_box_.get(); }
   SearchResults* results() { return results_.get(); }
 
diff --git a/ash/app_list/views/search_result_tile_item_list_view.cc b/ash/app_list/views/search_result_tile_item_list_view.cc
index 5127752..4567fba 100644
--- a/ash/app_list/views/search_result_tile_item_list_view.cc
+++ b/ash/app_list/views/search_result_tile_item_list_view.cc
@@ -6,6 +6,7 @@
 
 #include <stddef.h>
 
+#include <algorithm>
 #include <memory>
 
 #include "ash/app_list/app_list_util.h"
@@ -16,6 +17,8 @@
 #include "ash/public/cpp/app_list/app_list_config.h"
 #include "ash/public/cpp/app_list/app_list_features.h"
 #include "ash/public/cpp/app_list/internal_app_id_constants.h"
+#include "base/bind.h"
+#include "base/callback.h"
 #include "base/i18n/rtl.h"
 #include "ui/gfx/color_palette.h"
 #include "ui/gfx/geometry/insets.h"
@@ -50,13 +53,16 @@
     : search_result_page_view_(search_result_page_view),
       search_box_(search_box),
       is_play_store_app_search_enabled_(
-          app_list_features::IsPlayStoreAppSearchEnabled()) {
+          app_list_features::IsPlayStoreAppSearchEnabled()),
+      is_app_reinstall_recommendation_enabled_(
+          app_list_features::IsAppReinstallZeroStateEnabled()) {
   SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::kHorizontal,
       gfx::Insets(kItemListVerticalSpacing, kItemListHorizontalSpacing),
       kBetweenItemSpacing));
   for (size_t i = 0; i < kMaxNumSearchResultTiles; ++i) {
-    if (is_play_store_app_search_enabled_) {
+    if (is_app_reinstall_recommendation_enabled_ ||
+        is_play_store_app_search_enabled_) {
       views::Separator* separator = new views::Separator;
       separator->SetVisible(false);
       separator->SetBorder(views::CreateEmptyBorder(
@@ -97,23 +103,12 @@
 }
 
 int SearchResultTileItemListView::DoUpdate() {
-  base::string16 raw_query = search_box_->text();
-  base::string16 query;
-  base::TrimWhitespace(raw_query, base::TRIM_ALL, &query);
-
-  SearchResult::DisplayType display_type =
-      app_list_features::IsZeroStateSuggestionsEnabled()
-          ? (query.empty() ? ash::SearchResultDisplayType::kRecommendation
-                           : ash::SearchResultDisplayType::kTile)
-          : ash::SearchResultDisplayType::kTile;
-  // Do not display the continue reading app in the search result list.
-  std::vector<SearchResult*> display_results =
-      SearchModel::FilterSearchResultsByDisplayType(
-          results(), display_type,
-          /*excludes=*/{app_list::kInternalAppIdContinueReading},
-          kMaxNumSearchResultTiles);
+  std::vector<SearchResult*> display_results = GetDisplayResults();
 
   SearchResult::ResultType previous_type = ash::SearchResultType::kUnknown;
+  ash::SearchResultDisplayType previous_display_type =
+      ash::SearchResultDisplayType::kNone;
+
   for (size_t i = 0; i < kMaxNumSearchResultTiles; ++i) {
     if (i >= display_results.size()) {
       if (is_play_store_app_search_enabled_)
@@ -125,8 +120,10 @@
     SearchResult* item = display_results[i];
     tile_views_[i]->SetResult(item);
 
-    if (is_play_store_app_search_enabled_) {
-      if (i > 0 && item->result_type() != previous_type) {
+    if (is_play_store_app_search_enabled_ ||
+        is_app_reinstall_recommendation_enabled_) {
+      if (i > 0 && (item->result_type() != previous_type ||
+                    item->display_type() != previous_display_type)) {
         // Add a separator to separate search results of different types.
         // The strategy here is to only add a separator only if current search
         // result type is different from the previous one. The strategy is
@@ -139,6 +136,7 @@
     }
 
     previous_type = item->result_type();
+    previous_display_type = item->display_type();
   }
 
   set_container_score(
@@ -147,6 +145,53 @@
   return display_results.size();
 }
 
+std::vector<SearchResult*> SearchResultTileItemListView::GetDisplayResults() {
+  base::string16 raw_query = search_box_->text();
+  base::string16 query;
+  base::TrimWhitespace(raw_query, base::TRIM_ALL, &query);
+
+  // We ask for kMaxNumSearchResultTiles total results, and we prefer reinstall
+  // candidates if appropriate. we fetch |reinstall_results| first, and
+  // front-fill the rest from the regular result types.
+  auto reinstall_filter =
+      base::BindRepeating([](const SearchResult& r) -> bool {
+        return r.display_type() ==
+                   ash::SearchResultDisplayType::kRecommendation &&
+               r.result_type() == ash::SearchResultType::kPlayStoreReinstallApp;
+      });
+  std::vector<SearchResult*> reinstall_results =
+      is_app_reinstall_recommendation_enabled_ && query.empty()
+          ? SearchModel::FilterSearchResultsByFunction(
+                results(), reinstall_filter, kMaxNumSearchResultTiles)
+          : std::vector<SearchResult*>();
+
+  SearchResult::DisplayType display_type =
+      app_list_features::IsZeroStateSuggestionsEnabled()
+          ? (query.empty() ? ash::SearchResultDisplayType::kRecommendation
+                           : ash::SearchResultDisplayType::kTile)
+          : ash::SearchResultDisplayType::kTile;
+  size_t display_num = kMaxNumSearchResultTiles - reinstall_results.size();
+
+  // Do not display the continue reading app in the search result list.
+  auto non_reinstall_filter = base::BindRepeating(
+      [](const SearchResult::DisplayType& display_type,
+         const SearchResult& r) -> bool {
+        return r.display_type() == display_type &&
+               r.result_type() !=
+                   ash::SearchResultType::kPlayStoreReinstallApp &&
+               r.id() != app_list::kInternalAppIdContinueReading;
+      },
+      display_type);
+  std::vector<SearchResult*> display_results =
+      SearchModel::FilterSearchResultsByFunction(
+          results(), non_reinstall_filter, display_num);
+
+  // Append the reinstalls to the display results.
+  display_results.insert(display_results.end(), reinstall_results.begin(),
+                         reinstall_results.end());
+  return display_results;
+}
+
 bool SearchResultTileItemListView::OnKeyPressed(const ui::KeyEvent& event) {
   // Let the FocusManager handle Left/Right keys.
   if (!IsUnhandledUpDownKeyEvent(event))
diff --git a/ash/app_list/views/search_result_tile_item_list_view.h b/ash/app_list/views/search_result_tile_item_list_view.h
index b180fab..27cd79f 100644
--- a/ash/app_list/views/search_result_tile_item_list_view.h
+++ b/ash/app_list/views/search_result_tile_item_list_view.h
@@ -47,6 +47,8 @@
   // Overridden from SearchResultContainerView:
   int DoUpdate() override;
 
+  std::vector<SearchResult*> GetDisplayResults();
+
   std::vector<SearchResultTileItemView*> tile_views_;
 
   std::vector<views::Separator*> separator_views_;
@@ -57,6 +59,8 @@
 
   const bool is_play_store_app_search_enabled_;
 
+  const bool is_app_reinstall_recommendation_enabled_;
+
   DISALLOW_COPY_AND_ASSIGN(SearchResultTileItemListView);
 };
 
diff --git a/ash/app_list/views/search_result_tile_item_list_view_unittest.cc b/ash/app_list/views/search_result_tile_item_list_view_unittest.cc
index 93297ac7..f3d6a70a 100644
--- a/ash/app_list/views/search_result_tile_item_list_view_unittest.cc
+++ b/ash/app_list/views/search_result_tile_item_list_view_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "ash/app_list/views/search_result_tile_item_list_view.h"
 
+#include <algorithm>
 #include <memory>
 #include <utility>
 
@@ -28,34 +29,47 @@
 constexpr int kMaxNumSearchResultTiles = 6;
 constexpr int kInstalledApps = 4;
 constexpr int kPlayStoreApps = 2;
+constexpr int kRecommendedApps = 1;
 }  // namespace
 
 class SearchResultTileItemListViewTest
     : public views::ViewsTestBase,
-      public ::testing::WithParamInterface<bool> {
+      public ::testing::WithParamInterface<std::pair<bool, bool>> {
  public:
   SearchResultTileItemListViewTest() = default;
   ~SearchResultTileItemListViewTest() override = default;
 
  protected:
   void CreateSearchResultTileItemListView() {
+    std::vector<base::Feature> enabled_features, disabled_features;
     // Enable fullscreen app list for parameterized Play Store app search
     // feature.
     // Zero State affects the UI behavior significantly. This test tests the
     // UI behavior with zero state being disable.
     // TODO(crbug.com/925195): Write new test cases for zero state.
     if (IsPlayStoreAppSearchEnabled()) {
-      scoped_feature_list_.InitWithFeatures(
-          {app_list_features::kEnablePlayStoreAppSearch},
-          {app_list_features::kEnableZeroStateSuggestions});
+      enabled_features.push_back(app_list_features::kEnablePlayStoreAppSearch);
     } else {
-      scoped_feature_list_.InitWithFeatures(
-          {}, {app_list_features::kEnablePlayStoreAppSearch,
-               app_list_features::kEnableZeroStateSuggestions});
+      disabled_features.push_back(app_list_features::kEnablePlayStoreAppSearch);
     }
+    if (IsReinstallAppRecommendationEnabled()) {
+      enabled_features.push_back(
+          app_list_features::kEnableAppReinstallZeroState);
+    } else {
+      disabled_features.push_back(
+          app_list_features::kEnableAppReinstallZeroState);
+    }
+
+    disabled_features.push_back(app_list_features::kEnableZeroStateSuggestions);
+
+    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
+
     ASSERT_EQ(IsPlayStoreAppSearchEnabled(),
               app_list_features::IsPlayStoreAppSearchEnabled());
 
+    ASSERT_EQ(IsReinstallAppRecommendationEnabled(),
+              app_list_features::IsAppReinstallZeroStateEnabled());
+
     // Sets up the views.
     textfield_ = std::make_unique<views::Textfield>();
     view_ = std::make_unique<SearchResultTileItemListView>(
@@ -63,7 +77,9 @@
     view_->SetResults(view_delegate_.GetSearchModel()->results());
   }
 
-  bool IsPlayStoreAppSearchEnabled() const { return GetParam(); }
+  bool IsPlayStoreAppSearchEnabled() const { return GetParam().first; }
+
+  bool IsReinstallAppRecommendationEnabled() const { return GetParam().second; }
 
   SearchResultTileItemListView* view() { return view_.get(); }
 
@@ -103,6 +119,20 @@
       }
     }
 
+    if (IsReinstallAppRecommendationEnabled()) {
+      for (int i = 0; i < kRecommendedApps; ++i) {
+        std::unique_ptr<TestSearchResult> result =
+            std::make_unique<TestSearchResult>();
+        result->set_result_id(base::StringPrintf("RecommendedApp %d", i));
+        result->set_display_type(ash::SearchResultDisplayType::kRecommendation);
+        result->set_result_type(ash::SearchResultType::kPlayStoreReinstallApp);
+        result->set_title(
+            base::UTF8ToUTF16(base::StringPrintf("RecommendedApp %d", i)));
+        result->SetRating(1 + i);
+        results->Add(std::move(result));
+      }
+    }
+
     // Adding results calls SearchResultContainerView::ScheduleUpdate().
     // It will post a delayed task to update the results and relayout.
     RunPendingMessages();
@@ -138,20 +168,30 @@
   SetUpSearchResults();
 
   const int results = GetResultCount();
-  const int expected_results = IsPlayStoreAppSearchEnabled()
-                                   ? kInstalledApps + kPlayStoreApps
-                                   : kInstalledApps;
+  int expected_results = kInstalledApps;
+
+  if (IsPlayStoreAppSearchEnabled()) {
+    expected_results += kPlayStoreApps;
+  }
+  if (IsReinstallAppRecommendationEnabled()) {
+    expected_results += kRecommendedApps;
+  }
+  expected_results = std::min(kMaxNumSearchResultTiles, expected_results);
+
   EXPECT_EQ(expected_results, results);
-  // When the Play Store app search feature is enabled, for each results,
-  // we added a separator for result type grouping.
-  const int expected_child_count = IsPlayStoreAppSearchEnabled()
+
+  const bool separators_enabled =
+      IsPlayStoreAppSearchEnabled() || IsReinstallAppRecommendationEnabled();
+  // When the Play Store app search feature or app reinstallation feature is
+  // enabled, for each result, we added a separator for result type grouping.
+  const int expected_child_count = separators_enabled
                                        ? kMaxNumSearchResultTiles * 2
                                        : kMaxNumSearchResultTiles;
   EXPECT_EQ(expected_child_count, view()->child_count());
 
   /// Test accessibility descriptions of tile views.
-  const int first_child = IsPlayStoreAppSearchEnabled() ? 1 : 0;
-  const int child_step = IsPlayStoreAppSearchEnabled() ? 2 : 1;
+  const int first_child = separators_enabled ? 1 : 0;
+  const int child_step = separators_enabled ? 2 : 1;
 
   for (int i = 0; i < kInstalledApps; ++i) {
     ui::AXNodeData node_data;
@@ -163,7 +203,12 @@
               node_data.GetStringAttribute(ax::mojom::StringAttribute::kName));
   }
 
-  for (int i = kInstalledApps; i < expected_results; ++i) {
+  const int expected_install_apps =
+      expected_results -
+      (IsReinstallAppRecommendationEnabled() ? kRecommendedApps : 0) -
+      kInstalledApps;
+  for (int i = kInstalledApps; i < (kInstalledApps + expected_install_apps);
+       ++i) {
     ui::AXNodeData node_data;
     view()
         ->child_at(first_child + i * child_step)
@@ -175,15 +220,42 @@
               node_data.GetStringAttribute(ax::mojom::StringAttribute::kName));
   }
 
+  // Recommendations.
+  const int start_index = kInstalledApps + expected_install_apps;
+  for (int i = kInstalledApps + expected_install_apps; i < expected_results;
+       ++i) {
+    ui::AXNodeData node_data;
+    view()
+        ->child_at(first_child + i * child_step)
+        ->GetAccessibleNodeData(&node_data);
+    EXPECT_EQ(ax::mojom::Role::kButton, node_data.role);
+    EXPECT_EQ(base::StringPrintf("RecommendedApp %d, Star rating %d.0",
+                                 i - start_index, i + 1 - start_index),
+              node_data.GetStringAttribute(ax::mojom::StringAttribute::kName));
+  }
+
   ResetOpenResultCount();
   for (int i = 0; i < results; ++i) {
     ui::KeyEvent event(ui::ET_KEY_PRESSED, ui::VKEY_RETURN, ui::EF_NONE);
     for (int j = 0; j <= i; ++j)
       view()->tile_views_for_test()[i]->OnKeyEvent(&event);
-    EXPECT_EQ(i + 1, GetOpenResultCount(i));
+    // When both app reinstalls and play store apps are enabled, we actually
+    // instantiate 7 results, but only show 6. So we have to look, for exactly 1
+    // result, a "skip" ahead for the reinstall result.
+    if (IsReinstallAppRecommendationEnabled() &&
+        IsPlayStoreAppSearchEnabled() && i == (results - 1)) {
+      EXPECT_EQ(i + 1, GetOpenResultCount(i + 1));
+    } else {
+      EXPECT_EQ(i + 1, GetOpenResultCount(i));
+    }
   }
 }
 
-INSTANTIATE_TEST_CASE_P(, SearchResultTileItemListViewTest, testing::Bool());
+INSTANTIATE_TEST_CASE_P(,
+                        SearchResultTileItemListViewTest,
+                        testing::ValuesIn({std::make_pair(false, false),
+                                           std::make_pair(false, true),
+                                           std::make_pair(true, false),
+                                           std::make_pair(true, true)}));
 
 }  // namespace app_list
diff --git a/ash/app_list/views/search_result_tile_item_view.cc b/ash/app_list/views/search_result_tile_item_view.cc
index 7939782..0880836c 100644
--- a/ash/app_list/views/search_result_tile_item_view.cc
+++ b/ash/app_list/views/search_result_tile_item_view.cc
@@ -72,6 +72,8 @@
       pagination_model_(pagination_model),
       is_play_store_app_search_enabled_(
           app_list_features::IsPlayStoreAppSearchEnabled()),
+      is_app_reinstall_recommendation_enabled_(
+          app_list_features::IsAppReinstallZeroStateEnabled()),
       show_in_apps_page_(show_in_apps_page),
       weak_ptr_factory_(this) {
   SetFocusBehavior(FocusBehavior::ALWAYS);
@@ -87,7 +89,8 @@
   AddChildView(icon_);
 
   if (is_play_store_app_search_enabled_ ||
-      app_list_features::IsAppShortcutSearchEnabled()) {
+      app_list_features::IsAppShortcutSearchEnabled() ||
+      is_app_reinstall_recommendation_enabled_) {
     badge_ = new views::ImageView;
     badge_->set_can_process_events_within_subtree(false);
     badge_->SetVerticalAlignment(views::ImageView::LEADING);
@@ -104,7 +107,8 @@
   title_->SetAllowCharacterBreak(true);
   AddChildView(title_);
 
-  if (is_play_store_app_search_enabled_) {
+  if (is_play_store_app_search_enabled_ ||
+      is_app_reinstall_recommendation_enabled_) {
     rating_ = new views::Label;
     rating_->SetEnabledColor(kSearchAppRatingColor);
     rating_->SetLineHeight(kTileTextLineHeight);
@@ -263,7 +267,6 @@
   if (event.key_code() == ui::VKEY_RETURN) {
     if (IsSuggestedAppTile())
       LogAppLaunch();
-
     RecordSearchResultOpenSource(result(), view_delegate_->GetModel(),
                                  view_delegate_->GetSearchModel());
     view_delegate_->OpenSearchResult(result()->id(), event.flags());
diff --git a/ash/app_list/views/search_result_tile_item_view.h b/ash/app_list/views/search_result_tile_item_view.h
index 92c3920..2fc1926 100644
--- a/ash/app_list/views/search_result_tile_item_view.h
+++ b/ash/app_list/views/search_result_tile_item_view.h
@@ -115,6 +115,7 @@
   SkColor parent_background_color_ = SK_ColorTRANSPARENT;
 
   const bool is_play_store_app_search_enabled_;
+  const bool is_app_reinstall_recommendation_enabled_;
   const bool show_in_apps_page_;  // True if shown in app list's apps page.
 
   std::unique_ptr<AppListMenuModelAdapter> context_menu_;
diff --git a/ash/app_list/views/suggestion_chip_container_view.cc b/ash/app_list/views/suggestion_chip_container_view.cc
index 5e2e044e..b77c84d 100644
--- a/ash/app_list/views/suggestion_chip_container_view.cc
+++ b/ash/app_list/views/suggestion_chip_container_view.cc
@@ -13,6 +13,8 @@
 #include "ash/app_list/views/search_result_suggestion_chip_view.h"
 #include "ash/public/cpp/app_list/app_list_config.h"
 #include "ash/public/cpp/app_list/app_list_features.h"
+#include "base/bind.h"
+#include "base/callback.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/controls/textfield/textfield.h"
 #include "ui/views/focus/focus_manager.h"
@@ -61,10 +63,14 @@
   if (IgnoreUpdateAndLayout())
     return num_results();
 
+  auto exclude_reinstall_filter = [](const SearchResult& r) -> bool {
+    return r.display_type() == ash::SearchResultDisplayType::kRecommendation &&
+           r.result_type() != ash::SearchResultType::kPlayStoreReinstallApp;
+  };
   std::vector<SearchResult*> display_results =
-      SearchModel::FilterSearchResultsByDisplayType(
-          results(), ash::SearchResultDisplayType::kRecommendation,
-          /*excludes=*/{}, AppListConfig::instance().num_start_page_tiles());
+      SearchModel::FilterSearchResultsByFunction(
+          results(), base::BindRepeating(exclude_reinstall_filter),
+          AppListConfig::instance().num_start_page_tiles());
 
   // Update search results here, but wait until layout to add them as child
   // views when we know this view's bounds.
diff --git a/ash/display/display_configuration_controller.cc b/ash/display/display_configuration_controller.cc
index ec6f43e..207114a 100644
--- a/ash/display/display_configuration_controller.cc
+++ b/ash/display/display_configuration_controller.cc
@@ -16,13 +16,10 @@
 #include "base/bind.h"
 #include "base/time/time.h"
 #include "chromeos/system/devicemode.h"
-#include "ui/base/class_property.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/display/display_layout.h"
 #include "ui/display/manager/display_manager.h"
 
-DEFINE_UI_CLASS_PROPERTY_TYPE(ash::ScreenRotationAnimator*);
-
 namespace ash {
 
 namespace {
@@ -37,12 +34,6 @@
 const int64_t kCycleDisplayThrottleTimeoutMs = 4000;
 const int64_t kSetPrimaryDisplayThrottleTimeoutMs = 500;
 
-// A property key to store the ScreenRotationAnimator of the window; Used for
-// screen rotation.
-DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(ash::ScreenRotationAnimator,
-                                   kScreenRotationAnimatorKey,
-                                   nullptr);
-
 bool g_disable_animator_for_test = false;
 
 display::DisplayPositionInUnifiedMatrix GetUnifiedModeShelfCellPosition() {
@@ -216,13 +207,6 @@
     display_animator_.reset(new DisplayAnimator());
 }
 
-void DisplayConfigurationController::SetScreenRotationAnimatorForTest(
-    int64_t display_id,
-    std::unique_ptr<ScreenRotationAnimator> animator) {
-  aura::Window* root_window = Shell::GetRootWindowForDisplayId(display_id);
-  root_window->SetProperty(kScreenRotationAnimatorKey, animator.release());
-}
-
 // Private
 
 void DisplayConfigurationController::SetThrottleTimeout(int64_t throttle_ms) {
@@ -267,13 +251,7 @@
 DisplayConfigurationController::GetScreenRotationAnimatorForDisplay(
     int64_t display_id) {
   aura::Window* root_window = Shell::GetRootWindowForDisplayId(display_id);
-  ScreenRotationAnimator* animator =
-      root_window->GetProperty(kScreenRotationAnimatorKey);
-  if (!animator) {
-    animator = new ScreenRotationAnimator(root_window);
-    root_window->SetProperty(kScreenRotationAnimatorKey, animator);
-  }
-  return animator;
+  return ScreenRotationAnimator::GetForRootWindow(root_window);
 }
 
 }  // namespace ash
diff --git a/ash/display/display_configuration_controller.h b/ash/display/display_configuration_controller.h
index cfd39495..2c81305 100644
--- a/ash/display/display_configuration_controller.h
+++ b/ash/display/display_configuration_controller.h
@@ -96,10 +96,6 @@
   // Allow tests to enable or disable animations.
   void SetAnimatorForTest(bool enable);
 
-  void SetScreenRotationAnimatorForTest(
-      int64_t display_id,
-      std::unique_ptr<ScreenRotationAnimator> animator);
-
  private:
   class DisplayChangeLimiter;
 
diff --git a/ash/display/display_configuration_controller_test_api.cc b/ash/display/display_configuration_controller_test_api.cc
index a15c127..fa0266b 100644
--- a/ash/display/display_configuration_controller_test_api.cc
+++ b/ash/display/display_configuration_controller_test_api.cc
@@ -6,6 +6,7 @@
 
 #include "ash/display/display_configuration_controller.h"
 #include "ash/rotator/screen_rotation_animator.h"
+#include "ash/shell.h"
 
 namespace ash {
 
@@ -26,8 +27,9 @@
 void DisplayConfigurationControllerTestApi::SetScreenRotationAnimatorForDisplay(
     int64_t display_id,
     std::unique_ptr<ScreenRotationAnimator> animator) {
-  controller_->SetScreenRotationAnimatorForTest(display_id,
-                                                std::move(animator));
+  aura::Window* root_window = Shell::GetRootWindowForDisplayId(display_id);
+  ScreenRotationAnimator::SetScreenRotationAnimatorForTest(root_window,
+                                                           std::move(animator));
 }
 
 }  // namespace ash
diff --git a/ash/focus_cycler.cc b/ash/focus_cycler.cc
index 7046cdb..30697217 100644
--- a/ash/focus_cycler.cc
+++ b/ash/focus_cycler.cc
@@ -107,7 +107,7 @@
 bool FocusCycler::FocusWidget(views::Widget* widget) {
   // If the target is PIP window, temporarily make it activatable.
   if (wm::GetWindowState(widget->GetNativeWindow())->IsPip())
-    widget->widget_delegate()->set_can_activate(true);
+    widget->widget_delegate()->SetCanActivate(true);
 
   // Note: It is not necessary to set the focus directly to the pane since that
   // will be taken care of by the widget activation.
diff --git a/ash/ime/ime_mode_indicator_view.cc b/ash/ime/ime_mode_indicator_view.cc
index ff4af7f3..5f477dd 100644
--- a/ash/ime/ime_mode_indicator_view.cc
+++ b/ash/ime/ime_mode_indicator_view.cc
@@ -51,7 +51,7 @@
 ImeModeIndicatorView::ImeModeIndicatorView(const gfx::Rect& cursor_bounds,
                                            const base::string16& label)
     : cursor_bounds_(cursor_bounds), label_view_(new views::Label(label)) {
-  set_can_activate(false);
+  SetCanActivate(false);
   set_accept_events(false);
   set_shadow(views::BubbleBorder::BIG_SHADOW);
   SetArrow(views::BubbleBorder::TOP_CENTER);
diff --git a/ash/public/cpp/app_list/app_list_struct_traits.h b/ash/public/cpp/app_list/app_list_struct_traits.h
index e7394aa..b644a72 100644
--- a/ash/public/cpp/app_list/app_list_struct_traits.h
+++ b/ash/public/cpp/app_list/app_list_struct_traits.h
@@ -113,6 +113,8 @@
         return ash::mojom::SearchResultType::kLauncher;
       case ash::SearchResultType::kAnswerCard:
         return ash::mojom::SearchResultType::kAnswerCard;
+      case ash::SearchResultType::kPlayStoreReinstallApp:
+        return ash::mojom::SearchResultType::kPlayStoreReinstallApp;
       case ash::SearchResultType::kUnknown:
         break;
     }
@@ -150,6 +152,9 @@
       case ash::mojom::SearchResultType::kAnswerCard:
         *out = ash::SearchResultType::kAnswerCard;
         return true;
+      case ash::mojom::SearchResultType::kPlayStoreReinstallApp:
+        *out = ash::SearchResultType::kPlayStoreReinstallApp;
+        return true;
     }
     NOTREACHED();
     return false;
diff --git a/ash/public/cpp/app_list/app_list_types.h b/ash/public/cpp/app_list/app_list_types.h
index 13e4212..b8e78a7 100644
--- a/ash/public/cpp/app_list/app_list_types.h
+++ b/ash/public/cpp/app_list/app_list_types.h
@@ -47,6 +47,7 @@
   kOmnibox,         // Results from Omnibox.
   kLauncher,        // Results from launcher search (currently only from Files).
   kAnswerCard,      // WebContents based answer card.
+  kPlayStoreReinstallApp,  // Reinstall recommendations from PlayStore.
   // Add new values here.
 };
 
diff --git a/ash/public/cpp/ash_pref_names.cc b/ash/public/cpp/ash_pref_names.cc
index 1e46c3b..91f6ee8 100644
--- a/ash/public/cpp/ash_pref_names.cc
+++ b/ash/public/cpp/ash_pref_names.cc
@@ -183,6 +183,12 @@
 const char kNightLightCustomStartTime[] = "ash.night_light.custom_start_time";
 const char kNightLightCustomEndTime[] = "ash.night_light.custom_end_time";
 
+// Double prefs storing the most recent valid geoposition, which is only used
+// when the device lacks connectivity and we're unable to retrieve a valid
+// geoposition to calculate the sunset / sunrise times.
+const char kNightLightCachedLatitude[] = "ash.night_light.cached_latitude";
+const char kNightLightCachedLongitude[] = "ash.night_light.cached_longitude";
+
 // Whether the Chrome OS lock screen is allowed.
 const char kAllowScreenLock[] = "allow_screen_lock";
 
diff --git a/ash/public/cpp/ash_pref_names.h b/ash/public/cpp/ash_pref_names.h
index 143550e..c1bae25 100644
--- a/ash/public/cpp/ash_pref_names.h
+++ b/ash/public/cpp/ash_pref_names.h
@@ -72,6 +72,8 @@
 ASH_PUBLIC_EXPORT extern const char kNightLightScheduleType[];
 ASH_PUBLIC_EXPORT extern const char kNightLightCustomStartTime[];
 ASH_PUBLIC_EXPORT extern const char kNightLightCustomEndTime[];
+ASH_PUBLIC_EXPORT extern const char kNightLightCachedLatitude[];
+ASH_PUBLIC_EXPORT extern const char kNightLightCachedLongitude[];
 
 ASH_PUBLIC_EXPORT extern const char kAllowScreenLock[];
 ASH_PUBLIC_EXPORT extern const char kEnableAutoScreenLock[];
diff --git a/ash/public/interfaces/app_list.mojom b/ash/public/interfaces/app_list.mojom
index 112067d..315e34e8 100644
--- a/ash/public/interfaces/app_list.mojom
+++ b/ash/public/interfaces/app_list.mojom
@@ -119,6 +119,7 @@
   kOmnibox,         // Results from Omninbox.
   kLauncher,        // Results from launcher search (currently only from Files).
   kAnswerCard,      // WebContents based answer card.
+  kPlayStoreReinstallApp, // Reinstall recommendations from PlayStore.
   // Add new values here.
 };
 
diff --git a/ash/rotator/screen_rotation_animator.cc b/ash/rotator/screen_rotation_animator.cc
index 4aa542ee..a3e6e73 100644
--- a/ash/rotator/screen_rotation_animator.cc
+++ b/ash/rotator/screen_rotation_animator.cc
@@ -20,6 +20,7 @@
 #include "components/viz/common/frame_sinks/copy_output_result.h"
 #include "third_party/khronos/GLES2/gl2.h"
 #include "ui/aura/window.h"
+#include "ui/base/class_property.h"
 #include "ui/compositor/callback_layer_animation_observer.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_animation_element.h"
@@ -40,6 +41,8 @@
 #include "ui/gfx/transform_util.h"
 #include "ui/wm/core/window_util.h"
 
+DEFINE_UI_CLASS_PROPERTY_TYPE(ash::ScreenRotationAnimator*);
+
 namespace ash {
 
 namespace {
@@ -54,6 +57,12 @@
 const int kCounterClockWiseRotationFactor = 1;
 const int kClockWiseRotationFactor = -1;
 
+// A property key to store the ScreenRotationAnimator of the window; Used for
+// screen rotation.
+DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(ScreenRotationAnimator,
+                                   kScreenRotationAnimatorKey,
+                                   nullptr);
+
 display::Display::Rotation GetCurrentScreenRotation(int64_t display_id) {
   return Shell::Get()
       ->display_manager()
@@ -161,6 +170,24 @@
 
 }  // namespace
 
+// static
+ScreenRotationAnimator* ScreenRotationAnimator::GetForRootWindow(
+    aura::Window* root_window) {
+  auto* animator = root_window->GetProperty(kScreenRotationAnimatorKey);
+  if (!animator) {
+    animator = new ScreenRotationAnimator(root_window);
+    root_window->SetProperty(kScreenRotationAnimatorKey, animator);
+  }
+  return animator;
+}
+
+// static
+void ScreenRotationAnimator::SetScreenRotationAnimatorForTest(
+    aura::Window* root_window,
+    std::unique_ptr<ScreenRotationAnimator> animator) {
+  root_window->SetProperty(kScreenRotationAnimatorKey, animator.release());
+}
+
 ScreenRotationAnimator::ScreenRotationAnimator(aura::Window* root_window)
     : root_window_(root_window),
       screen_rotation_state_(IDLE),
@@ -299,6 +326,10 @@
   animation_scale_mode_ =
       std::make_unique<ui::ScopedAnimationDurationScaleMode>(
           ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
+
+  for (auto& observer : screen_rotation_animator_observers_)
+    observer.OnScreenCopiedBeforeRotation();
+
   SetRotation(rotation_request->display_id, rotation_request->old_rotation,
               rotation_request->new_rotation, rotation_request->source);
 
@@ -312,8 +343,10 @@
     std::unique_ptr<ScreenRotationRequest> rotation_request,
     std::unique_ptr<viz::CopyOutputResult> result) {
   animation_scale_mode_.reset();
-  if (IgnoreCopyResult(rotation_request->id, rotation_request_id_))
+  if (IgnoreCopyResult(rotation_request->id, rotation_request_id_)) {
+    NotifyAnimationFinished(/*canceled=*/true);
     return;
+  }
   // In the following cases, abort animation:
   // 1) if the display was removed,
   // 2) if the |root_window| was changed for |display_id|,
@@ -495,12 +528,12 @@
   }
 }
 
-void ScreenRotationAnimator::AddScreenRotationAnimatorObserver(
+void ScreenRotationAnimator::AddObserver(
     ScreenRotationAnimatorObserver* observer) {
   screen_rotation_animator_observers_.AddObserver(observer);
 }
 
-void ScreenRotationAnimator::RemoveScreenRotationAnimatorObserver(
+void ScreenRotationAnimator::RemoveObserver(
     ScreenRotationAnimatorObserver* observer) {
   screen_rotation_animator_observers_.RemoveObserver(observer);
 }
@@ -518,9 +551,7 @@
     return;
   }
 
-  // This is only used in test to notify animator observer.
-  for (auto& observer : screen_rotation_animator_observers_)
-    observer.OnScreenRotationAnimationFinished(this);
+  NotifyAnimationFinished(/*canceled=*/false);
 }
 
 bool ScreenRotationAnimator::IsRotating() const {
@@ -541,4 +572,9 @@
   mask_layer_tree_owner_.reset();
 }
 
+void ScreenRotationAnimator::NotifyAnimationFinished(bool canceled) {
+  for (auto& observer : screen_rotation_animator_observers_)
+    observer.OnScreenRotationAnimationFinished(this, canceled);
+}
+
 }  // namespace ash
diff --git a/ash/rotator/screen_rotation_animator.h b/ash/rotator/screen_rotation_animator.h
index d11aa37..ccbc7cc 100644
--- a/ash/rotator/screen_rotation_animator.h
+++ b/ash/rotator/screen_rotation_animator.h
@@ -33,12 +33,13 @@
 }  // namespace ui
 
 namespace ash {
-
 class ScreenRotationAnimatorObserver;
 
 // Utility to perform a screen rotation with an animation.
 class ASH_EXPORT ScreenRotationAnimator {
  public:
+  static ScreenRotationAnimator* GetForRootWindow(aura::Window* root_window);
+
   explicit ScreenRotationAnimator(aura::Window* root_window);
   virtual ~ScreenRotationAnimator();
 
@@ -53,10 +54,8 @@
               display::Display::RotationSource source,
               DisplayConfigurationController::RotationAnimation mode);
 
-  void AddScreenRotationAnimatorObserver(
-      ScreenRotationAnimatorObserver* observer);
-  void RemoveScreenRotationAnimatorObserver(
-      ScreenRotationAnimatorObserver* observer);
+  void AddObserver(ScreenRotationAnimatorObserver* observer);
+  void RemoveObserver(ScreenRotationAnimatorObserver* observer);
 
   // When screen rotation animation is ended or aborted, calls |Rotate()| with
   // the pending rotation request if the request queue is not empty. Otherwise
@@ -70,6 +69,10 @@
   // orientation if |IsRotating()| is false.
   display::Display::Rotation GetTargetRotation() const;
 
+  static void SetScreenRotationAnimatorForTest(
+      aura::Window* root_window,
+      std::unique_ptr<ScreenRotationAnimator> animator);
+
  protected:
   using CopyCallback =
       base::OnceCallback<void(std::unique_ptr<viz::CopyOutputResult> result)>;
@@ -158,6 +161,8 @@
   // |rotation_degrees| arc.
   void AnimateRotation(std::unique_ptr<ScreenRotationRequest> rotation_request);
 
+  void NotifyAnimationFinished(bool canceled);
+
   void set_disable_animation_timers_for_test(bool disable_timers) {
     disable_animation_timers_for_test_ = disable_timers;
   }
diff --git a/ash/rotator/screen_rotation_animator_observer.h b/ash/rotator/screen_rotation_animator_observer.h
index 5689a96..9f6963d3 100644
--- a/ash/rotator/screen_rotation_animator_observer.h
+++ b/ash/rotator/screen_rotation_animator_observer.h
@@ -15,9 +15,13 @@
  public:
   ScreenRotationAnimatorObserver() {}
 
+  // This will be called when the screen is copied before rotation.
+  virtual void OnScreenCopiedBeforeRotation() = 0;
+
   // This will be called when the animation is ended or aborted.
   virtual void OnScreenRotationAnimationFinished(
-      ScreenRotationAnimator* animator) = 0;
+      ScreenRotationAnimator* animator,
+      bool canceled) = 0;
 
  protected:
   virtual ~ScreenRotationAnimatorObserver() {}
diff --git a/ash/rotator/screen_rotation_animator_unittest.cc b/ash/rotator/screen_rotation_animator_unittest.cc
index f73657a8..c0d8f3a 100644
--- a/ash/rotator/screen_rotation_animator_unittest.cc
+++ b/ash/rotator/screen_rotation_animator_unittest.cc
@@ -60,15 +60,18 @@
  public:
   AnimationObserver() = default;
 
-  bool notified() const { return notified_; }
+  bool copy_notified() const { return copy_notified_; }
+  bool finish_notified() const { return finish_notified_; }
 
-  void OnScreenRotationAnimationFinished(
-      ScreenRotationAnimator* animator) override {
-    notified_ = true;
+  void OnScreenCopiedBeforeRotation() override { copy_notified_ = true; }
+  void OnScreenRotationAnimationFinished(ScreenRotationAnimator* animator,
+                                         bool canceled) override {
+    finish_notified_ = true;
   }
 
  private:
-  bool notified_ = false;
+  bool copy_notified_ = false;
+  bool finish_notified_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(AnimationObserver);
 };
@@ -261,40 +264,46 @@
 TEST_F(ScreenRotationAnimatorSlowAnimationTest, ShouldNotifyObserver) {
   SetDisplayRotation(display_id(), display::Display::ROTATE_0);
   AnimationObserver observer;
-  animator()->AddScreenRotationAnimatorObserver(&observer);
-  EXPECT_FALSE(observer.notified());
+  animator()->AddObserver(&observer);
+  EXPECT_FALSE(observer.copy_notified());
+  EXPECT_FALSE(observer.finish_notified());
 
   animator()->Rotate(display::Display::ROTATE_90,
                      display::Display::RotationSource::USER,
                      DisplayConfigurationController::ANIMATION_SYNC);
-  EXPECT_FALSE(observer.notified());
+  EXPECT_FALSE(observer.copy_notified());
+  EXPECT_FALSE(observer.finish_notified());
 
   test_api()->CompleteAnimations();
-  EXPECT_TRUE(observer.notified());
+  EXPECT_FALSE(observer.copy_notified());
+  EXPECT_TRUE(observer.finish_notified());
   EXPECT_FALSE(test_api()->HasActiveAnimations());
-  animator()->RemoveScreenRotationAnimatorObserver(&observer);
+  animator()->RemoveObserver(&observer);
 }
 
 TEST_F(ScreenRotationAnimatorSlowAnimationTest, ShouldNotifyObserverOnce) {
   SetDisplayRotation(display_id(), display::Display::ROTATE_0);
   AnimationObserver observer;
-  animator()->AddScreenRotationAnimatorObserver(&observer);
-  EXPECT_FALSE(observer.notified());
+  animator()->AddObserver(&observer);
+  EXPECT_FALSE(observer.copy_notified());
+  EXPECT_FALSE(observer.finish_notified());
 
   animator()->Rotate(display::Display::ROTATE_90,
                      display::Display::RotationSource::USER,
                      DisplayConfigurationController::ANIMATION_SYNC);
-  EXPECT_FALSE(observer.notified());
+  EXPECT_FALSE(observer.copy_notified());
+  EXPECT_FALSE(observer.finish_notified());
 
   animator()->Rotate(display::Display::ROTATE_180,
                      display::Display::RotationSource::USER,
                      DisplayConfigurationController::ANIMATION_SYNC);
-  EXPECT_FALSE(observer.notified());
+  EXPECT_FALSE(observer.finish_notified());
 
   test_api()->CompleteAnimations();
-  EXPECT_TRUE(observer.notified());
+  EXPECT_FALSE(observer.copy_notified());
+  EXPECT_TRUE(observer.finish_notified());
   EXPECT_FALSE(test_api()->HasActiveAnimations());
-  animator()->RemoveScreenRotationAnimatorObserver(&observer);
+  animator()->RemoveObserver(&observer);
 }
 
 TEST_F(ScreenRotationAnimatorSlowAnimationTest, RotatesToDifferentRotation) {
@@ -389,6 +398,41 @@
   EXPECT_FALSE(GetTray()->visible());
 }
 
+TEST_F(ScreenRotationAnimatorSmoothAnimationTest, Observer) {
+  const int64_t display_id = display_manager()->GetDisplayAt(0).id();
+
+  SetScreenRotationAnimator(
+      Shell::GetRootWindowForDisplayId(display_id),
+      base::BindRepeating(
+          &ScreenRotationAnimatorSmoothAnimationTest::QuitWaitForCopyCallback,
+          base::Unretained(this)),
+      base::BindRepeating(
+          &ScreenRotationAnimatorSmoothAnimationTest::QuitWaitForCopyCallback,
+          base::Unretained(this)));
+  AnimationObserver observer;
+  animator()->AddObserver(&observer);
+  EXPECT_FALSE(observer.copy_notified());
+  EXPECT_FALSE(observer.finish_notified());
+
+  SetDisplayRotation(display_id, display::Display::ROTATE_0);
+  animator()->Rotate(display::Display::ROTATE_90,
+                     display::Display::RotationSource::USER,
+                     DisplayConfigurationController::ANIMATION_ASYNC);
+  EXPECT_TRUE(animator()->IsRotating());
+  WaitForCopyCallback();
+  EXPECT_TRUE(observer.copy_notified());
+  EXPECT_FALSE(observer.finish_notified());
+
+  WaitForCopyCallback();
+  EXPECT_TRUE(observer.copy_notified());
+  EXPECT_FALSE(observer.finish_notified());
+  test_api()->CompleteAnimations();
+  EXPECT_FALSE(test_api()->HasActiveAnimations());
+  EXPECT_EQ(display::Display::ROTATE_90, GetDisplayRotation(display_id));
+  EXPECT_TRUE(observer.copy_notified());
+  EXPECT_TRUE(observer.finish_notified());
+}
+
 // Test enable smooth screen rotation code path.
 TEST_F(ScreenRotationAnimatorSmoothAnimationTest,
        RotatesToDifferentRotationWithCopyCallback) {
@@ -399,6 +443,11 @@
       base::Bind(
           &ScreenRotationAnimatorSmoothAnimationTest::QuitWaitForCopyCallback,
           base::Unretained(this)));
+  AnimationObserver observer;
+  animator()->AddObserver(&observer);
+  EXPECT_FALSE(observer.copy_notified());
+  EXPECT_FALSE(observer.finish_notified());
+
   SetDisplayRotation(display_id, display::Display::ROTATE_0);
   animator()->Rotate(display::Display::ROTATE_90,
                      display::Display::RotationSource::USER,
@@ -407,9 +456,14 @@
 
   EXPECT_EQ(display::Display::ROTATE_90, animator()->GetTargetRotation());
   EXPECT_NE(display::Display::ROTATE_90, GetDisplayRotation(display_id));
+  EXPECT_FALSE(observer.copy_notified());
+  EXPECT_FALSE(observer.finish_notified());
 
   WaitForCopyCallback();
   EXPECT_TRUE(test_api()->HasActiveAnimations());
+  EXPECT_TRUE(observer.copy_notified());
+  EXPECT_FALSE(observer.finish_notified());
+
   EXPECT_EQ(display::Display::ROTATE_90, animator()->GetTargetRotation());
   // Once copy is made, the rotation is set to the target, with the
   // image that was rotated to the original orientation.
@@ -418,6 +472,8 @@
   test_api()->CompleteAnimations();
   EXPECT_FALSE(test_api()->HasActiveAnimations());
   EXPECT_EQ(display::Display::ROTATE_90, GetDisplayRotation(display_id));
+  EXPECT_TRUE(observer.copy_notified());
+  EXPECT_TRUE(observer.finish_notified());
 }
 
 // If the rotating external secondary display is removed before the first copy
diff --git a/ash/shelf/shelf_tooltip_bubble.cc b/ash/shelf/shelf_tooltip_bubble.cc
index 1812ca3..e0b3374 100644
--- a/ash/shelf/shelf_tooltip_bubble.cc
+++ b/ash/shelf/shelf_tooltip_bubble.cc
@@ -37,7 +37,7 @@
     : ShelfBubble(anchor, alignment, background_color) {
   set_margins(gfx::Insets(kTooltipTopBottomMargin, kTooltipLeftRightMargin));
   set_close_on_deactivate(false);
-  set_can_activate(false);
+  SetCanActivate(false);
   set_accept_events(false);
   set_shadow(views::BubbleBorder::NO_ASSETS);
   SetLayoutManager(std::make_unique<views::FillLayout>());
diff --git a/ash/system/message_center/arc/arc_notification_content_view.cc b/ash/system/message_center/arc/arc_notification_content_view.cc
index 2f17e5c..79db8beb 100644
--- a/ash/system/message_center/arc/arc_notification_content_view.cc
+++ b/ash/system/message_center/arc/arc_notification_content_view.cc
@@ -743,7 +743,7 @@
 
   // Make the widget active.
   if (activate) {
-    GetWidget()->widget_delegate()->set_can_activate(true);
+    GetWidget()->widget_delegate()->SetCanActivate(true);
     GetWidget()->Activate();
 
     if (surface_)
@@ -751,7 +751,7 @@
     else
       activate_on_attach_ = true;
   } else {
-    GetWidget()->widget_delegate()->set_can_activate(false);
+    GetWidget()->widget_delegate()->SetCanActivate(false);
   }
 }
 
diff --git a/ash/system/model/system_tray_model.cc b/ash/system/model/system_tray_model.cc
index 3489fc0..8d0f44f 100644
--- a/ash/system/model/system_tray_model.cc
+++ b/ash/system/model/system_tray_model.cc
@@ -13,6 +13,7 @@
 #include "ash/system/model/tracing_model.h"
 #include "ash/system/model/update_model.h"
 #include "ash/system/model/virtual_keyboard_model.h"
+#include "ash/system/network/active_network_icon.h"
 #include "ash/system/status_area_widget.h"
 #include "ash/system/unified/unified_system_tray.h"
 #include "base/logging.h"
@@ -26,7 +27,8 @@
       session_length_limit_(std::make_unique<SessionLengthLimitModel>()),
       tracing_(std::make_unique<TracingModel>()),
       update_model_(std::make_unique<UpdateModel>()),
-      virtual_keyboard_(std::make_unique<VirtualKeyboardModel>()) {}
+      virtual_keyboard_(std::make_unique<VirtualKeyboardModel>()),
+      active_network_icon_(std::make_unique<ActiveNetworkIcon>()) {}
 
 SystemTrayModel::~SystemTrayModel() = default;
 
diff --git a/ash/system/model/system_tray_model.h b/ash/system/model/system_tray_model.h
index 74721f9..7a5d70aa 100644
--- a/ash/system/model/system_tray_model.h
+++ b/ash/system/model/system_tray_model.h
@@ -13,6 +13,7 @@
 
 namespace ash {
 
+class ActiveNetworkIcon;
 class ClockModel;
 class EnterpriseDomainModel;
 class LocaleModel;
@@ -62,6 +63,9 @@
   TracingModel* tracing() { return tracing_.get(); }
   UpdateModel* update_model() { return update_model_.get(); }
   VirtualKeyboardModel* virtual_keyboard() { return virtual_keyboard_.get(); }
+  ActiveNetworkIcon* active_network_icon() {
+    return active_network_icon_.get();
+  }
 
   const mojom::SystemTrayClientPtr& client_ptr() { return client_ptr_; }
 
@@ -73,6 +77,7 @@
   std::unique_ptr<TracingModel> tracing_;
   std::unique_ptr<UpdateModel> update_model_;
   std::unique_ptr<VirtualKeyboardModel> virtual_keyboard_;
+  std::unique_ptr<ActiveNetworkIcon> active_network_icon_;
 
   // TODO(tetsui): Add following as a sub-model of SystemTrayModel:
   // * BluetoothModel
diff --git a/ash/system/network/active_network_icon.cc b/ash/system/network/active_network_icon.cc
new file mode 100644
index 0000000..9473d0e
--- /dev/null
+++ b/ash/system/network/active_network_icon.cc
@@ -0,0 +1,248 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/system/network/active_network_icon.h"
+
+#include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/strings/grit/ash_strings.h"
+#include "ash/system/network/network_icon.h"
+#include "ash/system/tray/tray_constants.h"
+#include "chromeos/network/network_handler.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/base/l10n/l10n_util.h"
+#include "ui/gfx/paint_vector_icon.h"
+
+using chromeos::NetworkState;
+using chromeos::NetworkTypePattern;
+
+namespace ash {
+
+namespace {
+
+bool IsTrayIcon(network_icon::IconType icon_type) {
+  return icon_type == network_icon::ICON_TYPE_TRAY_REGULAR ||
+         icon_type == network_icon::ICON_TYPE_TRAY_OOBE;
+}
+
+SkColor GetDefaultColorForIconType(network_icon::IconType icon_type) {
+  if (icon_type == network_icon::ICON_TYPE_TRAY_REGULAR)
+    return kTrayIconColor;
+  if (icon_type == network_icon::ICON_TYPE_TRAY_OOBE)
+    return kOobeTrayIconColor;
+  return kUnifiedMenuIconColor;
+}
+
+const NetworkState* GetConnectingOrConnected(
+    const NetworkState* connecting_network,
+    const NetworkState* connected_network) {
+  if (connecting_network &&
+      (!connected_network || connecting_network->IsReconnecting() ||
+       connecting_network->connect_requested())) {
+    // If we are connecting to a network, and there is either no connected
+    // network, or the connection was user requested, or shill triggered a
+    // reconnection, use the connecting network.
+    return connecting_network;
+  }
+  return connected_network;
+}
+
+}  // namespace
+
+ActiveNetworkIcon::ActiveNetworkIcon() {
+  // NetworkHandler may not be initialized in tests.
+  if (!chromeos::NetworkHandler::IsInitialized())
+    return;
+  network_state_handler_ =
+      chromeos::NetworkHandler::Get()->network_state_handler();
+  DCHECK(network_state_handler_);
+  network_state_handler_->AddObserver(this, FROM_HERE);
+  UpdateActiveNetworks();
+}
+
+ActiveNetworkIcon::~ActiveNetworkIcon() {
+  if (network_state_handler_)
+    network_state_handler_->RemoveObserver(this, FROM_HERE);
+}
+
+base::string16 ActiveNetworkIcon::GetDefaultLabel(
+    network_icon::IconType icon_type) {
+  if (!default_network_) {
+    if (cellular_uninitialized_msg_ != 0)
+      return l10n_util::GetStringUTF16(cellular_uninitialized_msg_);
+    return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NETWORK_NOT_CONNECTED);
+  }
+  return network_icon::GetLabelForNetwork(default_network_, icon_type);
+}
+
+gfx::ImageSkia ActiveNetworkIcon::GetSingleImage(
+    network_icon::IconType icon_type,
+    bool* animating) {
+  // If no network, check for cellular initializing.
+  if (!default_network_ && cellular_uninitialized_msg_ != 0) {
+    if (animating)
+      *animating = true;
+    return network_icon::GetConnectingImageForNetworkType(shill::kTypeCellular,
+                                                          icon_type);
+  }
+  return GetDefaultImageImpl(default_network_, icon_type, animating);
+}
+
+gfx::ImageSkia ActiveNetworkIcon::GetDualImagePrimary(
+    network_icon::IconType icon_type,
+    bool* animating) {
+  const NetworkState* default_network = default_network_;
+  if (default_network &&
+      default_network->Matches(NetworkTypePattern::Cellular())) {
+    if (default_network->IsConnectedState()) {
+      // TODO: Show proper technology badges.
+      if (animating)
+        *animating = false;
+      return gfx::CreateVectorIcon(kNetworkBadgeTechnologyLteIcon,
+                                   GetDefaultColorForIconType(icon_type));
+    }
+    // If Cellular is connecting, use the active non cellular network.
+    default_network = active_non_cellular_;
+  }
+  return GetDefaultImageImpl(default_network, icon_type, animating);
+}
+
+gfx::ImageSkia ActiveNetworkIcon::GetDualImageCellular(
+    network_icon::IconType icon_type,
+    bool* animating) {
+  if (!network_state_handler_->IsTechnologyAvailable(
+          NetworkTypePattern::Cellular())) {
+    if (animating)
+      *animating = false;
+    return gfx::ImageSkia();
+  }
+
+  if (cellular_uninitialized_msg_ != 0) {
+    if (animating)
+      *animating = true;
+    return network_icon::GetConnectingImageForNetworkType(shill::kTypeCellular,
+                                                          icon_type);
+  }
+
+  if (!active_cellular_) {
+    if (animating)
+      *animating = false;
+    return network_icon::GetDisconnectedImageForNetworkType(
+        shill::kTypeCellular);
+  }
+
+  return network_icon::GetImageForNonVirtualNetwork(
+      active_cellular_, icon_type, false /* show_vpn_badge */, animating);
+}
+
+gfx::ImageSkia ActiveNetworkIcon::GetDefaultImageImpl(
+    const NetworkState* default_network,
+    network_icon::IconType icon_type,
+    bool* animating) {
+  if (!default_network_)
+    return GetDefaultImageForNoNetwork(icon_type, animating);
+
+  // Don't show connected Ethernet in the tray unless a VPN is present.
+  if (default_network->Matches(NetworkTypePattern::Ethernet()) &&
+      IsTrayIcon(icon_type) && !active_vpn_) {
+    if (animating)
+      *animating = false;
+    return gfx::ImageSkia();
+  }
+
+  // Connected network with a connecting VPN.
+  if (default_network->IsConnectedState() && active_vpn_ &&
+      active_vpn_->IsConnectingState()) {
+    if (animating)
+      *animating = true;
+    return network_icon::GetConnectedNetworkWithConnectingVpnImage(
+        default_network, icon_type);
+  }
+
+  // Default behavior: connected or connecting network, possibly with VPN badge.
+  bool show_vpn_badge = !!active_vpn_;
+  return network_icon::GetImageForNonVirtualNetwork(default_network, icon_type,
+                                                    show_vpn_badge, animating);
+}
+
+gfx::ImageSkia ActiveNetworkIcon::GetDefaultImageForNoNetwork(
+    network_icon::IconType icon_type,
+    bool* animating) {
+  if (animating)
+    *animating = false;
+  if (network_state_handler_ &&
+      network_state_handler_->IsTechnologyEnabled(NetworkTypePattern::WiFi())) {
+    // WiFi is enabled but disconnected, show an empty wedge.
+    return network_icon::GetBasicImage(icon_type, shill::kTypeWifi,
+                                       false /* connected */);
+  }
+  // WiFi is disabled, show a full icon with a strikethrough.
+  return network_icon::GetImageForWiFiEnabledState(false /* not enabled*/,
+                                                   icon_type);
+}
+
+void ActiveNetworkIcon::UpdateActiveNetworks() {
+  std::vector<const NetworkState*> active_networks;
+  network_state_handler_->GetActiveNetworkListByType(
+      NetworkTypePattern::Default(), &active_networks);
+  ActiveNetworksChanged(active_networks);
+}
+
+void ActiveNetworkIcon::DeviceListChanged() {
+  UpdateActiveNetworks();
+}
+
+void ActiveNetworkIcon::ActiveNetworksChanged(
+    const std::vector<const NetworkState*>& active_networks) {
+  active_cellular_ = nullptr;
+  active_vpn_ = nullptr;
+
+  const NetworkState* connected_network = nullptr;
+  const NetworkState* connected_non_cellular = nullptr;
+  const NetworkState* connecting_network = nullptr;
+  const NetworkState* connecting_non_cellular = nullptr;
+  for (const NetworkState* network : active_networks) {
+    if (network->Matches(NetworkTypePattern::VPN())) {
+      if (!active_vpn_)
+        active_vpn_ = network;
+      continue;
+    }
+    if (network->Matches(NetworkTypePattern::Cellular())) {
+      if (!active_cellular_)
+        active_cellular_ = network;
+    }
+    if (network->IsConnectedState()) {
+      if (!connected_network)
+        connected_network = network;
+      if (!connected_non_cellular &&
+          !network->Matches(NetworkTypePattern::Cellular())) {
+        connected_non_cellular = network;
+      }
+      continue;
+    }
+    if (network->Matches(NetworkTypePattern::Wireless()) &&
+        network->IsActive()) {
+      if (!connecting_network)
+        connecting_network = network;
+      if (!connecting_non_cellular &&
+          !network->Matches(NetworkTypePattern::Cellular())) {
+        connecting_non_cellular = network;
+      }
+    }
+  }
+
+  default_network_ =
+      GetConnectingOrConnected(connecting_network, connected_network);
+  active_non_cellular_ =
+      GetConnectingOrConnected(connecting_non_cellular, connected_non_cellular);
+
+  cellular_uninitialized_msg_ = network_icon::GetCellularUninitializedMsg();
+}
+
+void ActiveNetworkIcon::OnShuttingDown() {
+  network_state_handler_ = nullptr;
+}
+
+}  // namespace ash
diff --git a/ash/system/network/active_network_icon.h b/ash/system/network/active_network_icon.h
new file mode 100644
index 0000000..7dfe0f67
--- /dev/null
+++ b/ash/system/network/active_network_icon.h
@@ -0,0 +1,93 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_SYSTEM_NETWORK_ACTIVE_NETWORK_ICON_H_
+#define ASH_SYSTEM_NETWORK_ACTIVE_NETWORK_ICON_H_
+
+#include <memory>
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "ash/system/network/network_icon.h"
+#include "base/macros.h"
+#include "base/strings/string16.h"
+#include "chromeos/network/network_state_handler_observer.h"
+
+namespace chromeos {
+class NetworkState;
+class NetworkStateHandler;
+}  // namespace chromeos
+
+namespace gfx {
+class ImageSkia;
+}  // namespace gfx
+
+namespace ash {
+
+// Tracks changes to the active networks and provides an interface to
+// network_icon for the default network. This class supports two interfaces:
+// * Single: A single icon is shown to represent the active network state.
+// * Dual: One or two icons are shown to represent the active network state:
+// ** Primary: The state of the primary active network. If Cellular, a
+//    a technology badge is used to represent the network.
+// ** Cellular (enabled devices only): The state of the Cellular connection if
+//    available regardless of whether it is the active network.
+// NOTE : GetSingleDefaultImage and GetDefaultLabel are tested in
+// network_icon_unittest.cc. TODO(stevenjb): Test other public methods.
+class ASH_EXPORT ActiveNetworkIcon
+    : public chromeos::NetworkStateHandlerObserver {
+ public:
+  ActiveNetworkIcon();
+  ~ActiveNetworkIcon() override;
+
+  // Returns the label for the primary active network..
+  base::string16 GetDefaultLabel(network_icon::IconType icon_type);
+
+  // Single image mode. Returns a network icon (which may be empty) and sets
+  // |animating| if provided.
+  gfx::ImageSkia GetSingleImage(network_icon::IconType icon_type,
+                                bool* animating);
+
+  // Dual image mode. Returns the primary icon (which may be empty) and sets
+  // |animating| if provided.
+  gfx::ImageSkia GetDualImagePrimary(network_icon::IconType icon_type,
+                                     bool* animating);
+
+  // Dual image mode. Returns the Cellular icon (which may be empty) and sets
+  // |animating| if provided.
+  gfx::ImageSkia GetDualImageCellular(network_icon::IconType icon_type,
+                                      bool* animating);
+
+ private:
+  gfx::ImageSkia GetDefaultImageImpl(
+      const chromeos::NetworkState* default_network,
+      network_icon::IconType icon_type,
+      bool* animating);
+
+  // Called when there is no default network., Provides an empty or disabled
+  // wifi icon and sets |animating| if provided to false.
+  gfx::ImageSkia GetDefaultImageForNoNetwork(network_icon::IconType icon_type,
+                                             bool* animating);
+
+  void UpdateActiveNetworks();
+
+  // chromeos::NetworkStateHandlerObserver
+  void DeviceListChanged() override;
+  void ActiveNetworksChanged(const std::vector<const chromeos::NetworkState*>&
+                                 active_networks) override;
+  void OnShuttingDown() override;
+
+  chromeos::NetworkStateHandler* network_state_handler_ = nullptr;
+  const chromeos::NetworkState* default_network_ = nullptr;
+  const chromeos::NetworkState* active_non_cellular_ = nullptr;
+  const chromeos::NetworkState* active_cellular_ = nullptr;
+  const chromeos::NetworkState* active_vpn_ = nullptr;
+  int cellular_uninitialized_msg_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(ActiveNetworkIcon);
+};
+
+}  // namespace ash
+
+#endif  // ASH_SYSTEM_NETWORK_ACTIVE_NETWORK_ICON_H_
diff --git a/ash/system/network/network_feature_pod_button.cc b/ash/system/network/network_feature_pod_button.cc
index 31b7e52..d92db02 100644
--- a/ash/system/network/network_feature_pod_button.cc
+++ b/ash/system/network/network_feature_pod_button.cc
@@ -6,6 +6,8 @@
 
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/system/model/system_tray_model.h"
+#include "ash/system/network/active_network_icon.h"
 #include "ash/system/network/network_icon.h"
 #include "ash/system/network/network_icon_animation.h"
 #include "ash/system/tray/system_tray_notifier.h"
@@ -86,11 +88,10 @@
 }
 
 void NetworkFeaturePodButton::Update() {
-  gfx::ImageSkia image;
   bool animating = false;
-  network_icon::GetDefaultNetworkImageAndLabel(
-      network_icon::ICON_TYPE_DEFAULT_VIEW, &image, nullptr, &animating);
-
+  gfx::ImageSkia image =
+      Shell::Get()->system_tray_model()->active_network_icon()->GetSingleImage(
+          network_icon::ICON_TYPE_DEFAULT_VIEW, &animating);
   if (animating)
     network_icon::NetworkIconAnimation::GetInstance()->AddObserver(this);
   else
diff --git a/ash/system/network/network_icon.cc b/ash/system/network/network_icon.cc
index 7880b50..d5cb0986 100644
--- a/ash/system/network/network_icon.cc
+++ b/ash/system/network/network_icon.cc
@@ -219,16 +219,6 @@
       GetSizeForIconType(icon_type), index, GetPaddingForIconType(icon_type));
 }
 
-// Returns an image to represent either a fully connected network or a
-// disconnected network.
-const gfx::ImageSkia GetBasicImage(bool connected,
-                                   IconType icon_type,
-                                   const std::string& network_type) {
-  DCHECK_NE(shill::kTypeVPN, network_type);
-  return GetImageForIndex(ImageTypeForNetworkType(network_type), icon_type,
-                          connected ? kNumNetworkImages - 1 : 0);
-}
-
 gfx::ImageSkia* ConnectingWirelessImage(ImageType image_type,
                                         IconType icon_type,
                                         double animation) {
@@ -393,10 +383,13 @@
 
 bool NetworkIconImpl::UpdateCellularState(const NetworkState* network) {
   bool dirty = false;
-  const Badge technology_badge = BadgeForNetworkTechnology(network, icon_type_);
-  if (technology_badge != technology_badge_) {
-    technology_badge_ = technology_badge;
-    dirty = true;
+  if (!features::IsSeparateNetworkIconsEnabled()) {
+    const Badge technology_badge =
+        BadgeForNetworkTechnology(network, icon_type_);
+    if (technology_badge != technology_badge_) {
+      technology_badge_ = technology_badge;
+      dirty = true;
+    }
   }
   bool is_roaming = network->IndicateRoaming();
   if (is_roaming != is_roaming_) {
@@ -467,6 +460,14 @@
 //------------------------------------------------------------------------------
 // Public interface
 
+const gfx::ImageSkia GetBasicImage(IconType icon_type,
+                                   const std::string& network_type,
+                                   bool connected) {
+  DCHECK_NE(shill::kTypeVPN, network_type);
+  return GetImageForIndex(ImageTypeForNetworkType(network_type), icon_type,
+                          connected ? kNumNetworkImages - 1 : 0);
+}
+
 gfx::ImageSkia GetImageForNonVirtualNetwork(const NetworkState* network,
                                             IconType icon_type,
                                             bool show_vpn_badge,
@@ -478,7 +479,7 @@
   if (!network->visible()) {
     if (animating)
       *animating = false;
-    return GetBasicImage(false /* is_connected */, icon_type, network_type);
+    return GetBasicImage(icon_type, network_type, false /* connected */);
   }
 
   if (network->IsConnectingState()) {
@@ -520,7 +521,7 @@
   }
 
   gfx::ImageSkia image =
-      GetBasicImage(true /* connected */, icon_type, shill::kTypeWifi);
+      GetBasicImage(icon_type, shill::kTypeWifi, true /* connected */);
   Badges badges;
   if (!enabled) {
     badges.center = {&kNetworkBadgeOffIcon,
@@ -555,7 +556,7 @@
 
 gfx::ImageSkia GetDisconnectedImageForNetworkType(
     const std::string& network_type) {
-  return GetBasicImage(false /* not connected */, ICON_TYPE_LIST, network_type);
+  return GetBasicImage(ICON_TYPE_LIST, network_type, false /* connected */);
 }
 
 gfx::ImageSkia GetImageForNewWifiNetwork(SkColor icon_color,
@@ -571,13 +572,7 @@
 
 base::string16 GetLabelForNetwork(const chromeos::NetworkState* network,
                                   IconType icon_type) {
-  if (!network) {
-    int uninitialized_msg = GetCellularUninitializedMsg();
-    if (uninitialized_msg != 0)
-      return l10n_util::GetStringUTF16(uninitialized_msg);
-    return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NETWORK_NOT_CONNECTED);
-  }
-
+  DCHECK(network);
   std::string activation_state = network->activation_state();
   if (icon_type == ICON_TYPE_LIST || icon_type == ICON_TYPE_MENU_LIST) {
     // Show "<network>: [Connecting|Activating|Reconnecting]..."
@@ -665,107 +660,6 @@
   return 0;
 }
 
-const NetworkState* GetDefaultNetworkForIcon() {
-  NetworkStateHandler* network_state_handler =
-      NetworkHandler::Get()->network_state_handler();
-  const NetworkState* connected_network =
-      network_state_handler->ConnectedNetworkByType(
-          NetworkTypePattern::NonVirtual());
-  const NetworkState* connecting_network =
-      network_state_handler->ConnectingNetworkByType(
-          NetworkTypePattern::Wireless());
-
-  // If we are connecting to a network, and there is either no connected
-  // network, or the connection was user requested, or shill triggered a
-  // reconnection, use the connecting network.
-  if (connecting_network &&
-      (!connected_network || connecting_network->IsReconnecting() ||
-       connecting_network->connect_requested())) {
-    return connecting_network;
-  }
-
-  if (connected_network)
-    return connected_network;
-
-  const NetworkState* cellular =
-      network_state_handler->FirstNetworkByType(NetworkTypePattern::Cellular());
-  if (cellular &&
-      cellular->activation_state() == shill::kActivationStateActivating) {
-    return cellular;
-  }
-
-  return nullptr;
-}
-
-void GetDefaultNetworkImageAndLabel(IconType icon_type,
-                                    gfx::ImageSkia* image,
-                                    base::string16* label,
-                                    bool* animating) {
-  NetworkStateHandler* network_state_handler =
-      NetworkHandler::Get()->network_state_handler();
-
-  const NetworkState* network = GetDefaultNetworkForIcon();
-  if (label)
-    *label = GetLabelForNetwork(network, icon_type);
-
-  if (!network) {
-    // If no network, check for cellular initializing.
-    if (GetCellularUninitializedMsg() != 0) {
-      *image =
-          GetConnectingImageForNetworkType(shill::kTypeCellular, icon_type);
-      if (animating)
-        *animating = true;
-      return;
-    }
-    // Otherwise show a WiFi icon.
-    if (network_state_handler->IsTechnologyEnabled(
-            NetworkTypePattern::WiFi())) {
-      // WiFi is enabled but disconnected, show an empty wedge.
-      *image =
-          GetBasicImage(false /* not connected */, icon_type, shill::kTypeWifi);
-    } else {
-      // WiFi is disabled, show a full icon with a strikethrough.
-      *image = GetImageForWiFiEnabledState(false /* not enabled*/, icon_type);
-    }
-    if (animating)
-      *animating = false;
-    return;
-  }
-
-  // Get the active (connecting or connected) VPN for badging and determining
-  // whether to show the Ethernet icon.
-  const NetworkState* active_vpn = nullptr;
-  if (network->IsConnectedState()) {
-    active_vpn =
-        network_state_handler->FirstNetworkByType(NetworkTypePattern::VPN());
-    if (active_vpn && !active_vpn->IsConnectingOrConnected())
-      active_vpn = nullptr;
-  }
-
-  // Don't show connected Ethernet in the tray unless a VPN is present.
-  if (IsTrayIcon(icon_type) &&
-      network->Matches(NetworkTypePattern::Ethernet()) && !active_vpn) {
-    *image = gfx::ImageSkia();
-    if (animating)
-      *animating = false;
-    return;
-  }
-
-  // Connected network with a connecting VPN.
-  if (network->IsConnectedState() && active_vpn &&
-      active_vpn->IsConnectingState()) {
-    *image = GetConnectedNetworkWithConnectingVpnImage(network, icon_type);
-    if (animating)
-      *animating = true;
-    return;
-  }
-
-  // Default behavior: connected or connecting network, possibly with VPN badge.
-  bool show_vpn_badge = !!active_vpn;
-  *image = GetImageForNonVirtualNetwork(network, icon_type, show_vpn_badge,
-                                        animating);
-}
-
 void PurgeNetworkIconCache() {
   NetworkStateHandler::NetworkStateList networks;
   NetworkHandler::Get()->network_state_handler()->GetVisibleNetworkList(
diff --git a/ash/system/network/network_icon.h b/ash/system/network/network_icon.h
index c6fca38..f0eba5c 100644
--- a/ash/system/network/network_icon.h
+++ b/ash/system/network/network_icon.h
@@ -32,6 +32,12 @@
 // Strength of a wireless signal.
 enum class SignalStrength { NONE, WEAK, MEDIUM, STRONG, NOT_WIRELESS };
 
+// Returns an image to represent either a fully connected network or a
+// disconnected network.
+const gfx::ImageSkia GetBasicImage(IconType icon_type,
+                                   const std::string& network_type,
+                                   bool connected);
+
 // Returns and caches an image for non VPN |network| which must not be null.
 // Use this for non virtual networks and for the default (tray) icon.
 // |icon_type| determines the color theme.
@@ -50,22 +56,28 @@
                                          IconType icon_type,
                                          bool* animating = nullptr);
 
-// Gets an image for a Wi-Fi network, either full strength or strike-through
+// Returns an image for a Wi-Fi network, either full strength or strike-through
 // based on |enabled|.
 ASH_EXPORT gfx::ImageSkia GetImageForWiFiEnabledState(
     bool enabled,
     IconType = ICON_TYPE_DEFAULT_VIEW);
 
-// Gets the connecting image for a shill network non-VPN type.
+// Returns the connecting image for a shill network non-VPN type.
 gfx::ImageSkia GetConnectingImageForNetworkType(const std::string& network_type,
                                                 IconType icon_type);
 
-// Gets the disconnected image for a shill network type.
+// Returns the connected image for |connected_network| and |network_type| with a
+// connecting VPN badge.
+gfx::ImageSkia GetConnectedNetworkWithConnectingVpnImage(
+    const chromeos::NetworkState* connected_network,
+    IconType icon_type);
+
+// Returns the disconnected image for a shill network type.
 gfx::ImageSkia GetDisconnectedImageForNetworkType(
     const std::string& network_type);
 
-// Gets the full strength image for a Wi-Fi network using |icon_color| for the
-// main icon and |badge_color| for the badge.
+// Returns the full strength image for a Wi-Fi network using |icon_color| for
+// the main icon and |badge_color| for the badge.
 ASH_EXPORT gfx::ImageSkia GetImageForNewWifiNetwork(SkColor icon_color,
                                                     SkColor badge_color);
 
@@ -79,13 +91,6 @@
 // is uninitialized.
 ASH_EXPORT int GetCellularUninitializedMsg();
 
-// Sets the |icon| and |label| for |icon_type|. |animating| is an optional out
-// parameter that is set to true when the returned image should be animated.
-ASH_EXPORT void GetDefaultNetworkImageAndLabel(IconType icon_type,
-                                               gfx::ImageSkia* image,
-                                               base::string16* label,
-                                               bool* animating = nullptr);
-
 // Called when the list of networks changes. Retrieves the list of networks
 // from the global NetworkStateHandler instance and removes cached entries
 // that are no longer in the list.
diff --git a/ash/system/network/network_icon_unittest.cc b/ash/system/network/network_icon_unittest.cc
index eebc61aa..2843a6f 100644
--- a/ash/system/network/network_icon_unittest.cc
+++ b/ash/system/network/network_icon_unittest.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/system/network/active_network_icon.h"
 #include "base/logging.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
@@ -23,6 +24,9 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/image/image_unittest_util.h"
 
+// This tests both the helper functions in network_icon, and ActiveNetworkIcon
+// which is a primary consumer of the helper functions.
+
 namespace ash {
 
 namespace network_icon {
@@ -47,15 +51,18 @@
 
     chromeos::NetworkHandler::Initialize();
     base::RunLoop().RunUntilIdle();
+
+    active_network_icon_ = std::make_unique<ActiveNetworkIcon>();
   }
 
   void TearDown() override {
+    active_network_icon_.reset();
     PurgeNetworkIconCache();
+
     chromeos::NetworkHandler::Shutdown();
 
     ShutdownNetworkState();
     chromeos::NetworkStateTest::TearDown();
-
     chromeos::DBusThreadManager::Shutdown();
   }
 
@@ -113,6 +120,14 @@
     return gfx::Image(image_skia);
   }
 
+  void GetDefaultNetworkImageAndLabel(IconType icon_type,
+                                      gfx::ImageSkia* image,
+                                      base::string16* label,
+                                      bool* animating) {
+    *image = active_network_icon_->GetSingleImage(icon_type, animating);
+    *label = active_network_icon_->GetDefaultLabel(icon_type);
+  }
+
   // The icon for a Tether network should be the same as one for a cellular
   // network. The icon for a Tether network should be different from one for a
   // Wi-Fi network. The icon for a cellular network should be different from one
@@ -184,6 +199,8 @@
   std::string wifi2_path_;
   std::string cellular_path_;
 
+  std::unique_ptr<ActiveNetworkIcon> active_network_icon_;
+
   DISALLOW_COPY_AND_ASSIGN(NetworkIconTest);
 };
 
@@ -280,8 +297,8 @@
   gfx::ImageSkia default_image;
   base::string16 label;
   bool animating = false;
-  ash::network_icon::GetDefaultNetworkImageAndLabel(icon_type_, &default_image,
-                                                    &label, &animating);
+  GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
+                                 &animating);
   ASSERT_FALSE(default_image.isNull());
   EXPECT_TRUE(animating);
   EXPECT_EQ(
@@ -356,8 +373,8 @@
   gfx::ImageSkia default_image;
   base::string16 label;
   bool animating = false;
-  ash::network_icon::GetDefaultNetworkImageAndLabel(icon_type_, &default_image,
-                                                    &label, &animating);
+  GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
+                                 &animating);
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
 
@@ -378,8 +395,8 @@
   gfx::ImageSkia default_image;
   base::string16 label;
   bool animating = false;
-  ash::network_icon::GetDefaultNetworkImageAndLabel(icon_type_, &default_image,
-                                                    &label, &animating);
+  GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
+                                 &animating);
   ASSERT_FALSE(default_image.isNull());
   EXPECT_TRUE(animating);
 
@@ -413,8 +430,8 @@
   gfx::ImageSkia default_image;
   base::string16 label;
   bool animating = false;
-  ash::network_icon::GetDefaultNetworkImageAndLabel(icon_type_, &default_image,
-                                                    &label, &animating);
+  GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
+                                 &animating);
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
 
@@ -450,8 +467,8 @@
   // Verify that the default network is connecting icon for the initial default
   // network (even though the default network as reported by shill actually
   // changed).
-  ash::network_icon::GetDefaultNetworkImageAndLabel(icon_type_, &default_image,
-                                                    &label, &animating);
+  GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
+                                 &animating);
   ASSERT_FALSE(default_image.isNull());
   EXPECT_TRUE(animating);
 
@@ -474,8 +491,8 @@
   std::unique_ptr<chromeos::NetworkState> reference_network_2 =
       CreateStandaloneNetworkState("reference2", shill::kTypeCellular,
                                    shill::kStateOnline, 65);
-  ash::network_icon::GetDefaultNetworkImageAndLabel(icon_type_, &default_image,
-                                                    &label, &animating);
+  GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
+                                 &animating);
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
 
@@ -490,8 +507,8 @@
   std::unique_ptr<chromeos::NetworkState> reference_network_3 =
       CreateStandaloneNetworkState("reference3", shill::kTypeWifi,
                                    shill::kStateOnline, 45);
-  ash::network_icon::GetDefaultNetworkImageAndLabel(icon_type_, &default_image,
-                                                    &label, &animating);
+  GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
+                                 &animating);
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
 
@@ -518,8 +535,8 @@
   gfx::ImageSkia default_image;
   base::string16 label;
   bool animating = false;
-  ash::network_icon::GetDefaultNetworkImageAndLabel(icon_type_, &default_image,
-                                                    &label, &animating);
+  GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
+                                 &animating);
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
 
@@ -555,8 +572,8 @@
   // another network connected and used as default.
   // TODO(tbarzic): Consider changing network icon logic to use a connected
   //     network icon if a network is connected while a network is reconnecting.
-  ash::network_icon::GetDefaultNetworkImageAndLabel(icon_type_, &default_image,
-                                                    &label, &animating);
+  GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
+                                 &animating);
   ASSERT_FALSE(default_image.isNull());
   EXPECT_TRUE(animating);
 
@@ -571,8 +588,8 @@
   SetServiceProperty(cellular_path(), shill::kStateProperty,
                      base::Value(shill::kStateReady));
 
-  ash::network_icon::GetDefaultNetworkImageAndLabel(icon_type_, &default_image,
-                                                    &label, &animating);
+  GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
+                                 &animating);
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
   std::unique_ptr<chromeos::NetworkState> reference_network_2 =
@@ -586,8 +603,8 @@
   SetServiceProperty(cellular_path(), shill::kStateProperty,
                      base::Value(shill::kStateOnline));
 
-  ash::network_icon::GetDefaultNetworkImageAndLabel(icon_type_, &default_image,
-                                                    &label, &animating);
+  GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
+                                 &animating);
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
   EXPECT_TRUE(gfx::test::AreImagesEqual(
@@ -596,7 +613,7 @@
 
 // Tests that the default network image shows a cellular network icon if
 // cellular network is connected while wifi is connecting.
-TEST_F(NetworkIconTest, DefaultImageConnectingToWifiWileCellularConnected) {
+TEST_F(NetworkIconTest, DefaultImageConnectingToWifiWhileCellularConnected) {
   // Connect cellular network, and set the wifi as connecting.
   SetServiceProperty(wifi1_path(), shill::kSignalStrengthProperty,
                      base::Value(45));
@@ -611,8 +628,8 @@
   gfx::ImageSkia default_image;
   base::string16 label;
   bool animating = false;
-  ash::network_icon::GetDefaultNetworkImageAndLabel(icon_type_, &default_image,
-                                                    &label, &animating);
+  GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
+                                 &animating);
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
 
@@ -623,7 +640,7 @@
       gfx::Image(default_image), ImageForNetwork(reference_network.get())));
 }
 
-// Test that a cellular icon is displayed when activating cellular
+// Test that a connecting cellular icon is displayed when activating a cellular
 // network (if other networks are not connected).
 TEST_F(NetworkIconTest, DefaultNetworkImageActivatingCellularNetwork) {
   SetServiceProperty(cellular_path(), shill::kSignalStrengthProperty,
@@ -634,8 +651,8 @@
   gfx::ImageSkia default_image;
   base::string16 label;
   bool animating = false;
-  ash::network_icon::GetDefaultNetworkImageAndLabel(icon_type_, &default_image,
-                                                    &label, &animating);
+  GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
+                                 &animating);
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
 
@@ -663,8 +680,8 @@
   gfx::ImageSkia default_image;
   base::string16 label;
   bool animating = false;
-  ash::network_icon::GetDefaultNetworkImageAndLabel(icon_type_, &default_image,
-                                                    &label, &animating);
+  GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
+                                 &animating);
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
 
@@ -692,8 +709,8 @@
                      base::Value(45));
 
   // With Ethernet and WiFi connected, the default icon should be empty.
-  ash::network_icon::GetDefaultNetworkImageAndLabel(icon_type_, &default_image,
-                                                    &label, &animating);
+  GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
+                                 &animating);
   ASSERT_TRUE(default_image.isNull());
   EXPECT_FALSE(animating);
 
@@ -703,8 +720,8 @@
   ASSERT_FALSE(vpn_path.empty());
 
   // When a VPN is connected, the default icon should be Ethernet with a badge.
-  ash::network_icon::GetDefaultNetworkImageAndLabel(icon_type_, &default_image,
-                                                    &label, &animating);
+  GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
+                                 &animating);
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
 
@@ -724,8 +741,8 @@
   // Disconnect Ethernet. The default icon should become WiFi with a badge.
   SetServiceProperty(ethernet_path, shill::kStateProperty,
                      base::Value(shill::kStateIdle));
-  ash::network_icon::GetDefaultNetworkImageAndLabel(icon_type_, &default_image,
-                                                    &label, &animating);
+  GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
+                                 &animating);
   ASSERT_FALSE(default_image.isNull());
   EXPECT_FALSE(animating);
 
@@ -740,8 +757,8 @@
   // Set the VPN to connecting; the default icon should be animating.
   SetServiceProperty(vpn_path, shill::kStateProperty,
                      base::Value(shill::kStateAssociation));
-  ash::network_icon::GetDefaultNetworkImageAndLabel(icon_type_, &default_image,
-                                                    &label, &animating);
+  GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
+                                 &animating);
   ASSERT_FALSE(default_image.isNull());
   EXPECT_TRUE(animating);
 }
@@ -760,8 +777,8 @@
   gfx::ImageSkia default_image;
   base::string16 label;
   bool animating = false;
-  ash::network_icon::GetDefaultNetworkImageAndLabel(icon_type_, &default_image,
-                                                    &label, &animating);
+  GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
+                                 &animating);
   ASSERT_FALSE(default_image.isNull());
   EXPECT_TRUE(animating);
 
@@ -787,8 +804,8 @@
   gfx::ImageSkia default_image;
   base::string16 label;
   bool animating = false;
-  ash::network_icon::GetDefaultNetworkImageAndLabel(icon_type_, &default_image,
-                                                    &label, &animating);
+  GetDefaultNetworkImageAndLabel(icon_type_, &default_image, &label,
+                                 &animating);
   ASSERT_FALSE(default_image.isNull());
   EXPECT_TRUE(animating);
 
diff --git a/ash/system/network/network_tray_icon_strategy.cc b/ash/system/network/network_tray_icon_strategy.cc
index b653342fc..c6f41503 100644
--- a/ash/system/network/network_tray_icon_strategy.cc
+++ b/ash/system/network/network_tray_icon_strategy.cc
@@ -6,6 +6,8 @@
 
 #include "ash/session/session_controller.h"
 #include "ash/shell.h"
+#include "ash/system/model/system_tray_model.h"
+#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"
@@ -24,30 +26,6 @@
 
 namespace {
 
-const NetworkState* GetConnectingOrConnectedNetwork(
-    NetworkTypePattern pattern) {
-  NetworkStateHandler* state_handler =
-      NetworkHandler::Get()->network_state_handler();
-  const NetworkState* connecting_network =
-      state_handler->ConnectingNetworkByType(pattern);
-  const NetworkState* connected_network =
-      state_handler->ConnectedNetworkByType(pattern);
-  // If we are connecting to a network, and there is either no connected
-  // network, or the connection was user requested, or shill triggered a
-  // reconnection, use the connecting network.
-  if (connecting_network &&
-      (!connected_network || connecting_network->IsReconnecting() ||
-       connecting_network->connect_requested())) {
-    return connecting_network;
-  }
-  return connected_network;
-}
-
-bool NetworkTypeEnabled(NetworkTypePattern pattern) {
-  return NetworkHandler::Get()->network_state_handler()->IsTechnologyEnabled(
-      pattern);
-}
-
 // OOBE has a white background that makes regular tray icons not visible.
 network_icon::IconType GetIconType() {
   if (Shell::Get()->session_controller()->GetSessionState() ==
@@ -60,55 +38,24 @@
 }  // namespace
 
 gfx::ImageSkia DefaultNetworkTrayIconStrategy::GetNetworkIcon(bool* animating) {
-  if (!NetworkTypeEnabled(NetworkTypePattern::WiFi()))
-    return gfx::ImageSkia();
-
-  auto icon_type = GetIconType();
-  const NetworkState* network =
-      GetConnectingOrConnectedNetwork(NetworkTypePattern::WiFi());
-  if (network) {
-    bool show_vpn_badge =
-        network->IsConnectedState() &&
-        NetworkHandler::Get()->network_state_handler()->ConnectedNetworkByType(
-            NetworkTypePattern::VPN());
-    return network_icon::GetImageForNonVirtualNetwork(
-        network, icon_type, show_vpn_badge, animating);
-  }
-  *animating = false;
-  return network_icon::GetDisconnectedImageForNetworkType(shill::kTypeWifi);
+  return Shell::Get()
+      ->system_tray_model()
+      ->active_network_icon()
+      ->GetDualImagePrimary(GetIconType(), animating);
 }
 
 gfx::ImageSkia MobileNetworkTrayIconStrategy::GetNetworkIcon(bool* animating) {
-  if (!NetworkTypeEnabled(NetworkTypePattern::Mobile()))
-    return gfx::ImageSkia();
-
-  auto icon_type = GetIconType();
-  // Check if we are initializing a mobile network.
-  if (network_icon::GetCellularUninitializedMsg()) {
-    *animating = true;
-    return network_icon::GetConnectingImageForNetworkType(shill::kTypeCellular,
-                                                          icon_type);
-  }
-
-  const NetworkState* network =
-      NetworkHandler::Get()->network_state_handler()->FirstNetworkByType(
-          NetworkTypePattern::Mobile());
-
-  if (network && network->IsConnectingOrConnected()) {
-    return network_icon::GetImageForNonVirtualNetwork(
-        network, icon_type, false /* show_vpn_badge */, animating);
-  }
-
-  *animating = false;
-  return network_icon::GetDisconnectedImageForNetworkType(shill::kTypeCellular);
+  return Shell::Get()
+      ->system_tray_model()
+      ->active_network_icon()
+      ->GetDualImageCellular(GetIconType(), animating);
 }
 
 gfx::ImageSkia SingleNetworkTrayIconStrategy::GetNetworkIcon(bool* animating) {
-  auto icon_type = GetIconType();
-  gfx::ImageSkia image;
-  network_icon::GetDefaultNetworkImageAndLabel(icon_type, &image,
-                                               /*label=*/nullptr, animating);
-  return image;
+  return Shell::Get()
+      ->system_tray_model()
+      ->active_network_icon()
+      ->GetSingleImage(GetIconType(), animating);
 }
 
 }  // namespace tray
diff --git a/ash/system/night_light/night_light_controller.cc b/ash/system/night_light/night_light_controller.cc
index 697badf..c428eb9 100644
--- a/ash/system/night_light/night_light_controller.cc
+++ b/ash/system/night_light/night_light_controller.cc
@@ -70,34 +70,39 @@
   base::Time GetSunsetTime() const override { return GetSunRiseSet(false); }
   base::Time GetSunriseTime() const override { return GetSunRiseSet(true); }
   void SetGeoposition(mojom::SimpleGeopositionPtr position) override {
-    position_ = std::move(position);
+    geoposition_ = std::move(position);
   }
+  bool HasGeoposition() const override { return !!geoposition_; }
 
  private:
+  // Note that the below computation is intentionally performed every time
+  // GetSunsetTime() or GetSunriseTime() is called rather than once whenever we
+  // receive a geoposition (which happens at least once a day). This increases
+  // the chances of getting accurate values, especially around DST changes.
   base::Time GetSunRiseSet(bool sunrise) const {
-    if (!ValidatePosition()) {
+    if (!HasGeoposition()) {
       LOG(ERROR) << "Invalid geoposition. Using default time for "
                  << (sunrise ? "sunrise." : "sunset.");
       return sunrise ? TimeOfDay(kDefaultEndTimeOffsetMinutes).ToTimeToday()
                      : TimeOfDay(kDefaultStartTimeOffsetMinutes).ToTimeToday();
     }
 
-    icu::CalendarAstronomer astro(position_->longitude, position_->latitude);
+    icu::CalendarAstronomer astro(geoposition_->longitude,
+                                  geoposition_->latitude);
     // For sunset and sunrise times calculations to be correct, the time of the
     // icu::CalendarAstronomer object should be set to a time near local noon.
     // This avoids having the computation flopping over into an adjacent day.
     // See the documentation of icu::CalendarAstronomer::getSunRiseSet().
     // Note that the icu calendar works with milliseconds since epoch, and
     // base::Time::FromDoubleT() / ToDoubleT() work with seconds since epoch.
-    const double noon_today_sec = TimeOfDay(12 * 60).ToTimeToday().ToDoubleT();
-    astro.setTime(noon_today_sec * 1000.0);
+    const double midday_today_sec =
+        TimeOfDay(12 * 60).ToTimeToday().ToDoubleT();
+    astro.setTime(midday_today_sec * 1000.0);
     const double sun_rise_set_ms = astro.getSunRiseSet(sunrise);
     return base::Time::FromDoubleT(sun_rise_set_ms / 1000.0);
   }
 
-  bool ValidatePosition() const { return !!position_; }
-
-  mojom::SimpleGeopositionPtr position_;
+  mojom::SimpleGeopositionPtr geoposition_;
 
   DISALLOW_COPY_AND_ASSIGN(NightLightControllerDelegateImpl);
 };
@@ -329,6 +334,10 @@
   registry->RegisterIntegerPref(prefs::kNightLightCustomEndTime,
                                 kDefaultEndTimeOffsetMinutes,
                                 PrefRegistry::PUBLIC);
+
+  // Non-public prefs, only meant to be used by ash.
+  registry->RegisterDoublePref(prefs::kNightLightCachedLatitude, 0.0);
+  registry->RegisterDoublePref(prefs::kNightLightCachedLongitude, 0.0);
 }
 
 // static
@@ -472,6 +481,9 @@
 void NightLightController::SetCurrentGeoposition(
     mojom::SimpleGeopositionPtr position) {
   VLOG(1) << "Received new geoposition.";
+
+  is_current_geoposition_from_cache_ = false;
+  StoreCachedGeoposition(position);
   delegate_->SetGeoposition(std::move(position));
 
   // If the schedule type is sunset to sunrise, then a potential change in the
@@ -502,6 +514,52 @@
   delegate_ = std::move(delegate);
 }
 
+void NightLightController::LoadCachedGeopositionIfNeeded() {
+  DCHECK(active_user_pref_service_);
+
+  // Even if there is a geoposition, but it's coming from a previously cached
+  // value, switching users should load the currently saved values for the
+  // new user. This is to keep users' prefs completely separate. We only ignore
+  // the cached values once we have a valid non-cached geoposition from any
+  // user in the same session.
+  if (delegate_->HasGeoposition() && !is_current_geoposition_from_cache_)
+    return;
+
+  if (!active_user_pref_service_->HasPrefPath(
+          prefs::kNightLightCachedLatitude) ||
+      !active_user_pref_service_->HasPrefPath(
+          prefs::kNightLightCachedLongitude)) {
+    VLOG(1) << "No valid current geoposition and no valid cached geoposition"
+               " are available. Will use default times for sunset / sunrise.";
+    return;
+  }
+
+  VLOG(1) << "Temporarily using a previously cached geoposition.";
+  delegate_->SetGeoposition(mojom::SimpleGeoposition::New(
+      active_user_pref_service_->GetDouble(prefs::kNightLightCachedLatitude),
+      active_user_pref_service_->GetDouble(prefs::kNightLightCachedLongitude)));
+  is_current_geoposition_from_cache_ = true;
+}
+
+void NightLightController::StoreCachedGeoposition(
+    const mojom::SimpleGeopositionPtr& position) {
+  DCHECK(position);
+
+  const SessionController* session_controller =
+      Shell::Get()->session_controller();
+  for (const auto& user_session : session_controller->GetUserSessions()) {
+    PrefService* pref_service = session_controller->GetUserPrefServiceForUser(
+        user_session->user_info->account_id);
+    if (!pref_service)
+      continue;
+
+    pref_service->SetDouble(prefs::kNightLightCachedLatitude,
+                            position->latitude);
+    pref_service->SetDouble(prefs::kNightLightCachedLongitude,
+                            position->longitude);
+  }
+}
+
 void NightLightController::RefreshLayersTemperature() {
   const float new_temperature = GetEnabled() ? GetColorTemperature() : 0.0f;
   temperature_animation_->AnimateToNewValue(
@@ -545,10 +603,15 @@
       prefs::kNightLightCustomEndTime,
       base::BindRepeating(&NightLightController::OnCustomSchedulePrefsChanged,
                           base::Unretained(this)));
+
+  // Note: No need to observe changes in the cached latitude/longitude since
+  // they're only accessed here in ash. We only load them when the active user
+  // changes, and store them whenever we receive an updated geoposition.
 }
 
 void NightLightController::InitFromUserPrefs() {
   StartWatchingPrefsChanges();
+  LoadCachedGeopositionIfNeeded();
   Refresh(true /* did_schedule_change */);
   NotifyStatusChanged();
   NotifyClientWithScheduleChange();
diff --git a/ash/system/night_light/night_light_controller.h b/ash/system/night_light/night_light_controller.h
index 1452b41..dd01ee03 100644
--- a/ash/system/night_light/night_light_controller.h
+++ b/ash/system/night_light/night_light_controller.h
@@ -74,6 +74,9 @@
     // Provides the delegate with the geoposition so that it can be used to
     // calculate sunset and sunrise times.
     virtual void SetGeoposition(mojom::SimpleGeopositionPtr position) = 0;
+
+    // Returns true if a geoposition value is available.
+    virtual bool HasGeoposition() const = 0;
   };
 
   class Observer {
@@ -119,6 +122,9 @@
     return last_animation_duration_;
   }
   base::OneShotTimer* timer() { return &timer_; }
+  bool is_current_geoposition_from_cache() const {
+    return is_current_geoposition_from_cache_;
+  }
 
   void BindRequest(mojom::NightLightControllerRequest request);
 
@@ -163,6 +169,15 @@
   void SetDelegateForTesting(std::unique_ptr<Delegate> delegate);
 
  private:
+  // Called only when the active user changes in order to see if we need to use
+  // a previously cached geoposition value from the active user's prefs.
+  void LoadCachedGeopositionIfNeeded();
+
+  // Called whenever we receive a new geoposition update to cache it in all
+  // logged-in users' prefs so that it can be used later in the event of not
+  // being able to retrieve a valid geoposition.
+  void StoreCachedGeoposition(const mojom::SimpleGeopositionPtr& position);
+
   void RefreshLayersTemperature();
 
   void StartWatchingPrefsChanges();
@@ -224,6 +239,15 @@
   // type is either kSunsetToSunrise or kCustom.
   base::OneShotTimer timer_;
 
+  // True if the current geoposition value used by the Delegate is from a
+  // previously cached value in the user prefs of any of the users in the
+  // current session. It is reset to false once we receive a newly-updated
+  // geoposition from the client.
+  // This is used to treat the current geoposition as temporary until we receive
+  // a valid geoposition update, and also not to let a cached geoposition value
+  // to leak to another user for privacy reasons.
+  bool is_current_geoposition_from_cache_ = false;
+
   // The registrar used to watch NightLight prefs changes in the above
   // |active_user_pref_service_| from outside ash.
   // NOTE: Prefs are how Chrome communicates changes to the NightLight settings
diff --git a/ash/system/night_light/night_light_controller_unittest.cc b/ash/system/night_light/night_light_controller_unittest.cc
index 4d9b6510..e3feb4f 100644
--- a/ash/system/night_light/night_light_controller_unittest.cc
+++ b/ash/system/night_light/night_light_controller_unittest.cc
@@ -117,6 +117,7 @@
   base::Time GetSunsetTime() const override { return fake_sunset_; }
   base::Time GetSunriseTime() const override { return fake_sunrise_; }
   void SetGeoposition(mojom::SimpleGeopositionPtr position) override {
+    has_geoposition_ = true;
     if (position.Equals(mojom::SimpleGeoposition::New(
             kFakePosition1_Latitude, kFakePosition1_Longitude))) {
       // Set sunset and sunrise times associated with fake position 1.
@@ -129,11 +130,13 @@
       SetFakeSunrise(TimeOfDay(kFakePosition2_SunriseOffset));
     }
   }
+  bool HasGeoposition() const override { return has_geoposition_; }
 
  private:
   base::Time fake_now_;
   base::Time fake_sunset_;
   base::Time fake_sunrise_;
+  bool has_geoposition_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(TestDelegate);
 };
@@ -638,6 +641,101 @@
             controller->timer()->GetCurrentDelay());
 }
 
+// Tests the behavior when there is no valid geoposition for example due to lack
+// of connectivity.
+TEST_F(NightLightTest, AbsentValidGeoposition) {
+  NightLightController* controller = GetController();
+  ASSERT_FALSE(delegate()->HasGeoposition());
+
+  // Initially, no values are stored in either of the two users' prefs.
+  ASSERT_FALSE(
+      user1_pref_service()->HasPrefPath(prefs::kNightLightCachedLatitude));
+  ASSERT_FALSE(
+      user1_pref_service()->HasPrefPath(prefs::kNightLightCachedLongitude));
+  ASSERT_FALSE(
+      user2_pref_service()->HasPrefPath(prefs::kNightLightCachedLatitude));
+  ASSERT_FALSE(
+      user2_pref_service()->HasPrefPath(prefs::kNightLightCachedLongitude));
+
+  // Store fake geoposition 2 in user 2's prefs.
+  user2_pref_service()->SetDouble(prefs::kNightLightCachedLatitude,
+                                  kFakePosition2_Latitude);
+  user2_pref_service()->SetDouble(prefs::kNightLightCachedLongitude,
+                                  kFakePosition2_Longitude);
+
+  // Switch to user 2 and expect that the delegate now has a geoposition, but
+  // the controller knows that it's from a cached value.
+  SwitchActiveUser(kUser2Email);
+  EXPECT_TRUE(delegate()->HasGeoposition());
+  EXPECT_TRUE(controller->is_current_geoposition_from_cache());
+  const TimeOfDay kSunset2{kFakePosition2_SunsetOffset};
+  const TimeOfDay kSunrise2{kFakePosition2_SunriseOffset};
+  EXPECT_EQ(delegate()->GetSunsetTime(), kSunset2.ToTimeToday());
+  EXPECT_EQ(delegate()->GetSunriseTime(), kSunrise2.ToTimeToday());
+
+  // Store fake geoposition 1 in user 1's prefs.
+  user1_pref_service()->SetDouble(prefs::kNightLightCachedLatitude,
+                                  kFakePosition1_Latitude);
+  user1_pref_service()->SetDouble(prefs::kNightLightCachedLongitude,
+                                  kFakePosition1_Longitude);
+
+  // Switching to user 1 should ignore the current geoposition since it's
+  // a cached value from user 2's prefs rather than a newly-updated value.
+  // User 1's cached values should be loaded.
+  SwitchActiveUser(kUser1Email);
+  EXPECT_TRUE(delegate()->HasGeoposition());
+  EXPECT_TRUE(controller->is_current_geoposition_from_cache());
+  const TimeOfDay kSunset1{kFakePosition1_SunsetOffset};
+  const TimeOfDay kSunrise1{kFakePosition1_SunriseOffset};
+  EXPECT_EQ(delegate()->GetSunsetTime(), kSunset1.ToTimeToday());
+  EXPECT_EQ(delegate()->GetSunriseTime(), kSunrise1.ToTimeToday());
+
+  // Now simulate receiving a geoposition update of fake geoposition 2.
+  controller->SetCurrentGeoposition(mojom::SimpleGeoposition::New(
+      kFakePosition2_Latitude, kFakePosition2_Longitude));
+  EXPECT_TRUE(delegate()->HasGeoposition());
+  EXPECT_FALSE(controller->is_current_geoposition_from_cache());
+  EXPECT_EQ(delegate()->GetSunsetTime(), kSunset2.ToTimeToday());
+  EXPECT_EQ(delegate()->GetSunriseTime(), kSunrise2.ToTimeToday());
+
+  // Update user 2's prefs with fake geoposition 1.
+  user2_pref_service()->SetDouble(prefs::kNightLightCachedLatitude,
+                                  kFakePosition1_Latitude);
+  user2_pref_service()->SetDouble(prefs::kNightLightCachedLongitude,
+                                  kFakePosition1_Longitude);
+
+  // Now switching to user 2 should completely ignore their cached geopsoition,
+  // since from now on we have a valid newly-retrieved value.
+  SwitchActiveUser(kUser2Email);
+  EXPECT_TRUE(delegate()->HasGeoposition());
+  EXPECT_FALSE(controller->is_current_geoposition_from_cache());
+  EXPECT_EQ(delegate()->GetSunsetTime(), kSunset2.ToTimeToday());
+  EXPECT_EQ(delegate()->GetSunriseTime(), kSunrise2.ToTimeToday());
+
+  // Clear all cached geoposition prefs for all users, just to make sure getting
+  // a new geoposition with persist it for all users not just the active one.
+  user1_pref_service()->ClearPref(prefs::kNightLightCachedLatitude);
+  user1_pref_service()->ClearPref(prefs::kNightLightCachedLongitude);
+  user2_pref_service()->ClearPref(prefs::kNightLightCachedLatitude);
+  user2_pref_service()->ClearPref(prefs::kNightLightCachedLongitude);
+
+  // Now simulate receiving a geoposition update of fake geoposition 1.
+  controller->SetCurrentGeoposition(mojom::SimpleGeoposition::New(
+      kFakePosition1_Latitude, kFakePosition1_Longitude));
+  EXPECT_TRUE(delegate()->HasGeoposition());
+  EXPECT_FALSE(controller->is_current_geoposition_from_cache());
+  EXPECT_EQ(delegate()->GetSunsetTime(), kSunset1.ToTimeToday());
+  EXPECT_EQ(delegate()->GetSunriseTime(), kSunrise1.ToTimeToday());
+  EXPECT_EQ(kFakePosition1_Latitude,
+            user1_pref_service()->GetDouble(prefs::kNightLightCachedLatitude));
+  EXPECT_EQ(kFakePosition1_Longitude,
+            user1_pref_service()->GetDouble(prefs::kNightLightCachedLongitude));
+  EXPECT_EQ(kFakePosition1_Latitude,
+            user2_pref_service()->GetDouble(prefs::kNightLightCachedLatitude));
+  EXPECT_EQ(kFakePosition1_Longitude,
+            user2_pref_service()->GetDouble(prefs::kNightLightCachedLongitude));
+}
+
 // Tests that on device resume from sleep, the NightLight status is updated
 // correctly if the time has changed meanwhile.
 TEST_F(NightLightTest, TestCustomScheduleOnResume) {
diff --git a/ash/system/overview/overview_button_tray.cc b/ash/system/overview/overview_button_tray.cc
index e4735d7..e5056f34 100644
--- a/ash/system/overview/overview_button_tray.cc
+++ b/ash/system/overview/overview_button_tray.cc
@@ -22,6 +22,7 @@
 #include "base/metrics/user_metrics_action.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/paint_vector_icon.h"
+#include "ui/views/animation/ink_drop.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/image_view.h"
 #include "ui/wm/core/window_util.h"
@@ -63,6 +64,10 @@
   UpdateIconVisibility();
 }
 
+void OverviewButtonTray::SnapRippleToActivated() {
+  GetInkDrop()->SnapToActivated();
+}
+
 void OverviewButtonTray::OnGestureEvent(ui::GestureEvent* event) {
   Button::OnGestureEvent(event);
   if (event->type() == ui::ET_GESTURE_LONG_PRESS) {
diff --git a/ash/system/overview/overview_button_tray.h b/ash/system/overview/overview_button_tray.h
index 47097fd..0d16b4b 100644
--- a/ash/system/overview/overview_button_tray.h
+++ b/ash/system/overview/overview_button_tray.h
@@ -45,6 +45,9 @@
   // state of TabletMode
   virtual void UpdateAfterLoginStatusChange(LoginStatus status);
 
+  // Sets the ink drop ripple to ACTIVATED immediately with no animations.
+  void SnapRippleToActivated();
+
   // views::Button:
   void OnGestureEvent(ui::GestureEvent* event) override;
 
diff --git a/ash/system/palette/palette_welcome_bubble.cc b/ash/system/palette/palette_welcome_bubble.cc
index 474e6ac..2659adc 100644
--- a/ash/system/palette/palette_welcome_bubble.cc
+++ b/ash/system/palette/palette_welcome_bubble.cc
@@ -37,7 +37,7 @@
   WelcomeBubbleView(views::View* anchor, views::BubbleBorder::Arrow arrow)
       : views::BubbleDialogDelegateView(anchor, arrow) {
     set_close_on_deactivate(true);
-    set_can_activate(false);
+    SetCanActivate(false);
     set_accept_events(true);
     set_parent_window(
         anchor_widget()->GetNativeWindow()->GetRootWindow()->GetChildById(
diff --git a/ash/system/tray/tray_bubble_view.cc b/ash/system/tray/tray_bubble_view.cc
index cd24bb4..3bd89d9b 100644
--- a/ash/system/tray/tray_bubble_view.cc
+++ b/ash/system/tray/tray_bubble_view.cc
@@ -171,7 +171,7 @@
       (key_code == ui::VKEY_ESCAPE && flags == ui::EF_NONE)) {
     // Make TrayBubbleView activatable as the following Widget::OnKeyEvent might
     // try to activate it.
-    tray_bubble_view_->set_can_activate(true);
+    tray_bubble_view_->SetCanActivate(true);
 
     tray_bubble_view_->GetWidget()->OnKeyEvent(event);
 
@@ -218,7 +218,7 @@
   if (init_params.corner_radius)
     bubble_border_->SetCornerRadius(init_params.corner_radius.value());
   set_parent_window(params_.parent_window);
-  set_can_activate(false);
+  SetCanActivate(false);
   set_notify_enter_exit_on_child(true);
   set_close_on_deactivate(init_params.close_on_deactivate);
   set_margins(gfx::Insets());
@@ -480,7 +480,7 @@
 
   // No need to explicitly activate the widget. View::RequestFocus will activate
   // it if necessary.
-  set_can_activate(true);
+  SetCanActivate(true);
 
   view->RequestFocus();
 }
diff --git a/ash/system/unified/unified_system_tray_bubble.cc b/ash/system/unified/unified_system_tray_bubble.cc
index 99a14d1f..aab15ac 100644
--- a/ash/system/unified/unified_system_tray_bubble.cc
+++ b/ash/system/unified/unified_system_tray_bubble.cc
@@ -158,7 +158,7 @@
 
   if (bubble_widget_->IsClosed())
     return;
-  bubble_widget_->widget_delegate()->set_can_activate(true);
+  bubble_widget_->widget_delegate()->SetCanActivate(true);
   bubble_widget_->Activate();
 }
 
diff --git a/ash/wallpaper/OWNERS b/ash/wallpaper/OWNERS
index f8b0297..c4a642c 100644
--- a/ash/wallpaper/OWNERS
+++ b/ash/wallpaper/OWNERS
@@ -1,4 +1,5 @@
 achuith@chromium.org
+maybelle@chromium.org
 wzang@chromium.org
 xdai@chromium.org
 
diff --git a/ash/wm/immersive_fullscreen_controller_unittest.cc b/ash/wm/immersive_fullscreen_controller_unittest.cc
index 66e68ae..4115bf7 100644
--- a/ash/wm/immersive_fullscreen_controller_unittest.cc
+++ b/ash/wm/immersive_fullscreen_controller_unittest.cc
@@ -1021,14 +1021,14 @@
 
   views::BubbleDialogDelegateView* bubble_delegate4(
       new TestBubbleDialogDelegate(child_view));
-  bubble_delegate4->set_can_activate(false);
+  bubble_delegate4->SetCanActivate(false);
   views::Widget* bubble_widget4(
       views::BubbleDialogDelegateView::CreateBubble(bubble_delegate4));
   bubble_widget4->Show();
 
   views::BubbleDialogDelegateView* bubble_delegate5(
       new TestBubbleDialogDelegate(child_view));
-  bubble_delegate5->set_can_activate(false);
+  bubble_delegate5->SetCanActivate(false);
   views::Widget* bubble_widget5(
       views::BubbleDialogDelegateView::CreateBubble(bubble_delegate5));
   bubble_widget5->Show();
diff --git a/ash/wm/overview/overview_controller.cc b/ash/wm/overview/overview_controller.cc
index 4e361502..3213665 100644
--- a/ash/wm/overview/overview_controller.cc
+++ b/ash/wm/overview/overview_controller.cc
@@ -541,6 +541,12 @@
   return windows;
 }
 
+void OverviewController::DelayedUpdateMaskAndShadow() {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(&OverviewController::UpdateMaskAndShadow,
+                                weak_ptr_factory_.GetWeakPtr()));
+}
+
 // TODO(flackr): Make OverviewController observe the activation of
 // windows, so we can remove OverviewDelegate.
 // TODO(sammiequon): Rename to something like EndOverview() and refactor to use
@@ -634,6 +640,11 @@
     OnStartingAnimationComplete(/*canceled=*/false);
 }
 
+void OverviewController::UpdateMaskAndShadow() {
+  if (overview_session_)
+    overview_session_->UpdateMaskAndShadow();
+}
+
 // static
 void OverviewController::SetDoNotChangeWallpaperBlurForTests() {
   g_disable_wallpaper_blur_for_tests = true;
diff --git a/ash/wm/overview/overview_controller.h b/ash/wm/overview/overview_controller.h
index 659cffb..5e88d1d 100644
--- a/ash/wm/overview/overview_controller.h
+++ b/ash/wm/overview/overview_controller.h
@@ -68,6 +68,9 @@
   // overview mode is active for testing.
   std::vector<aura::Window*> GetWindowsListInOverviewGridsForTesting();
 
+  // Post a task to update the shadow and mask of overview windows.
+  void DelayedUpdateMaskAndShadow();
+
   // OverviewDelegate:
   void OnSelectionEnded() override;
   void AddDelayedAnimationObserver(
@@ -115,6 +118,8 @@
   void OnEndingAnimationComplete(bool canceled);
   void ResetPauser();
 
+  void UpdateMaskAndShadow();
+
   // Collection of DelayedAnimationObserver objects that own widgets that may be
   // still animating after overview mode ends. If shell needs to shut down while
   // those animations are in progress, the animations are shut down and the
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index 8357e06c..9946dfb 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -17,6 +17,7 @@
 #include "ash/public/cpp/window_properties.h"
 #include "ash/public/cpp/window_state_type.h"
 #include "ash/root_window_controller.h"
+#include "ash/rotator/screen_rotation_animator.h"
 #include "ash/screen_util.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shelf/shelf_constants.h"
@@ -395,6 +396,8 @@
 }
 
 void OverviewGrid::Shutdown() {
+  ScreenRotationAnimator::GetForRootWindow(root_window_)->RemoveObserver(this);
+
   for (const auto& window : window_list_)
     window->Shutdown();
 
@@ -426,6 +429,11 @@
   for (const auto& window : window_list_)
     window->PrepareForOverview();
   prepared_for_overview_ = true;
+  if (Shell::Get()
+          ->tablet_mode_controller()
+          ->IsTabletModeWindowManagerEnabled()) {
+    ScreenRotationAnimator::GetForRootWindow(root_window_)->AddObserver(this);
+  }
 }
 
 void OverviewGrid::PositionWindows(
@@ -897,6 +905,21 @@
   }
 }
 
+void OverviewGrid::OnScreenCopiedBeforeRotation() {
+  for (auto& window : window_list()) {
+    window->set_disable_mask(true);
+    window->UpdateMaskAndShadow();
+  }
+}
+
+void OverviewGrid::OnScreenRotationAnimationFinished(
+    ScreenRotationAnimator* animator,
+    bool canceled) {
+  for (auto& window : window_list())
+    window->set_disable_mask(false);
+  Shell::Get()->overview_controller()->DelayedUpdateMaskAndShadow();
+}
+
 void OverviewGrid::OnStartingAnimationComplete() {
   if (!shield_widget_) {
     InitShieldWidget(/*animate=*/true);
diff --git a/ash/wm/overview/overview_grid.h b/ash/wm/overview/overview_grid.h
index b2cbe74..047ae51 100644
--- a/ash/wm/overview/overview_grid.h
+++ b/ash/wm/overview/overview_grid.h
@@ -11,6 +11,7 @@
 #include <set>
 #include <vector>
 
+#include "ash/rotator/screen_rotation_animator_observer.h"
 #include "ash/wm/overview/overview_session.h"
 #include "ash/wm/window_state_observer.h"
 #include "base/macros.h"
@@ -50,7 +51,8 @@
 // The selector is switched to the next window grid (if available) or wrapped if
 // it reaches the end of its movement sequence.
 class ASH_EXPORT OverviewGrid : public aura::WindowObserver,
-                                public wm::WindowStateObserver {
+                                public wm::WindowStateObserver,
+                                public ScreenRotationAnimatorObserver {
  public:
   OverviewGrid(aura::Window* root_window,
                const std::vector<aura::Window*>& window_list,
@@ -148,6 +150,11 @@
   void OnPostWindowStateTypeChange(wm::WindowState* window_state,
                                    mojom::WindowStateType old_type) override;
 
+  // ScreenRotationAnimatorObserver:
+  void OnScreenCopiedBeforeRotation() override;
+  void OnScreenRotationAnimationFinished(ScreenRotationAnimator* animator,
+                                         bool canceled) override;
+
   // Called when overview starting animation completes.
   void OnStartingAnimationComplete();
 
diff --git a/ash/wm/overview/overview_item.cc b/ash/wm/overview/overview_item.cc
index 0bc90b9..82a3d5b 100644
--- a/ash/wm/overview/overview_item.cc
+++ b/ash/wm/overview/overview_item.cc
@@ -625,7 +625,8 @@
   // 6) this overview item is in animation.
   bool should_show = true;
   OverviewController* overview_controller = Shell::Get()->overview_controller();
-  if (!overview_controller || !overview_controller->IsSelecting() ||
+  if (disable_mask_ || !overview_controller ||
+      !overview_controller->IsSelecting() ||
       overview_grid_->window_list().size() > 10 ||
       overview_controller->IsInStartAnimation() || is_being_dragged_ ||
       overview_grid_->IsDropTargetWindow(GetWindow()) ||
diff --git a/ash/wm/overview/overview_item.h b/ash/wm/overview/overview_item.h
index 40f2140..fb0330d 100644
--- a/ash/wm/overview/overview_item.h
+++ b/ash/wm/overview/overview_item.h
@@ -242,6 +242,8 @@
   bool animating_to_close() const { return animating_to_close_; }
   void set_animating_to_close(bool val) { animating_to_close_ = val; }
 
+  void set_disable_mask(bool disable) { disable_mask_ = disable; }
+
   float GetCloseButtonVisibilityForTesting() const;
   float GetTitlebarOpacityForTesting() const;
   gfx::Rect GetShadowBoundsForTesting();
@@ -340,6 +342,9 @@
   // True if this overview item is currently being dragged around.
   bool is_being_dragged_ = false;
 
+  // True to always disable mask regardless of the state.
+  bool disable_mask_ = false;
+
   // The shadow around the overview window. Shadows the original window, not
   // |item_widget_|. Done here instead of on the original window because of the
   // rounded edges mask applied on entering overview window.
diff --git a/ash/wm/overview/overview_session.cc b/ash/wm/overview/overview_session.cc
index a162ca8..7a88fbd1 100644
--- a/ash/wm/overview/overview_session.cc
+++ b/ash/wm/overview/overview_session.cc
@@ -574,11 +574,11 @@
 
 void OverviewSession::OnStartingAnimationComplete(bool canceled) {
   if (!canceled) {
-    UpdateMaskAndShadow();
     if (overview_focus_widget_)
       overview_focus_widget_->Show();
     for (auto& grid : grid_list_)
       grid->OnStartingAnimationComplete();
+    Shell::Get()->overview_controller()->DelayedUpdateMaskAndShadow();
   }
 }
 
diff --git a/ash/wm/overview/overview_session_unittest.cc b/ash/wm/overview/overview_session_unittest.cc
index fe87b0ea..e2d87a2 100644
--- a/ash/wm/overview/overview_session_unittest.cc
+++ b/ash/wm/overview/overview_session_unittest.cc
@@ -2238,6 +2238,11 @@
   EXPECT_FALSE(HasMaskForItem(item2));
   window1->layer()->GetAnimator()->StopAnimating();
   window2->layer()->GetAnimator()->StopAnimating();
+
+  // Mask is set asynchronously.
+  EXPECT_FALSE(HasMaskForItem(item1));
+  EXPECT_FALSE(HasMaskForItem(item2));
+  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(HasMaskForItem(item1));
   EXPECT_TRUE(HasMaskForItem(item2));
 
@@ -2262,6 +2267,9 @@
       ->layer()
       ->GetAnimator()
       ->StopAnimating();
+  EXPECT_FALSE(HasMaskForItem(item1));
+  EXPECT_FALSE(HasMaskForItem(item2));
+  base::RunLoop().RunUntilIdle();
   EXPECT_TRUE(HasMaskForItem(item1));
   EXPECT_TRUE(HasMaskForItem(item2));
 
diff --git a/ash/wm/tablet_mode/tablet_mode_controller.cc b/ash/wm/tablet_mode/tablet_mode_controller.cc
index 8273fc1..6e37ccc 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller.cc
@@ -294,6 +294,9 @@
 }
 
 void TabletModeController::OnDisplayConfigurationChanged() {
+  if (!AllowUiModeChange())
+    return;
+
   if (!HasActiveInternalDisplay()) {
     AttemptLeaveTabletMode();
   } else if (tablet_mode_switch_is_on_ && !IsTabletModeWindowManagerEnabled()) {
diff --git a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
index e0fa102..1231a2c 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
@@ -1080,6 +1080,29 @@
   EXPECT_TRUE(AreEventsBlocked());
 }
 
+TEST_F(TabletModeControllerForceTabletModeTest, DockInForcedTabletMode) {
+  UpdateDisplay("800x600, 800x600");
+  const int64_t internal_display_id =
+      display::test::DisplayManagerTestApi(display_manager())
+          .SetFirstDisplayAsInternalDisplay();
+
+  // Deactivate internal display to simulate Docked Mode.
+  std::vector<display::ManagedDisplayInfo> all_displays;
+  all_displays.push_back(display_manager()->GetDisplayInfo(
+      display_manager()->GetDisplayAt(0).id()));
+  std::vector<display::ManagedDisplayInfo> secondary_only;
+  display::ManagedDisplayInfo secondary_display =
+      display_manager()->GetDisplayInfo(
+          display_manager()->GetDisplayAt(1).id());
+  all_displays.push_back(secondary_display);
+  secondary_only.push_back(secondary_display);
+  display_manager()->OnNativeDisplaysChanged(secondary_only);
+  ASSERT_FALSE(display_manager()->IsActiveDisplayId(internal_display_id));
+
+  // Still expect tablet mode.
+  EXPECT_TRUE(IsTabletModeStarted());
+}
+
 class TabletModeControllerForceClamshellModeTest
     : public TabletModeControllerTest {
  public:
diff --git a/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc b/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
index 74fc9c3..4a3c8ff4 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
@@ -7,6 +7,8 @@
 #include "ash/root_window_controller.h"
 #include "ash/shelf/shelf_layout_manager.h"
 #include "ash/shell.h"
+#include "ash/system/overview/overview_button_tray.h"
+#include "ash/system/status_area_widget.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/overview/overview_grid.h"
 #include "ash/wm/overview/overview_item.h"
@@ -108,6 +110,12 @@
   // might open overview in the dragged window side of the screen.
   split_view_controller_->OnWindowDragStarted(dragged_window_);
   if (ShouldOpenOverviewWhenDragStarts() && !controller->IsSelecting()) {
+    OverviewButtonTray* overview_button_tray =
+        RootWindowController::ForWindow(dragged_window_)
+            ->GetStatusAreaWidget()
+            ->overview_button_tray();
+    DCHECK(overview_button_tray);
+    overview_button_tray->SnapRippleToActivated();
     controller->ToggleOverview(
         OverviewSession::EnterExitOverviewType::kWindowDragged);
   }
diff --git a/ash/wm/top_level_window_factory.cc b/ash/wm/top_level_window_factory.cc
index 06f5d8c..e1941ac 100644
--- a/ash/wm/top_level_window_factory.cc
+++ b/ash/wm/top_level_window_factory.cc
@@ -196,7 +196,7 @@
         NonClientFrameController::Get(window);
     window->SetProperty(ws::kCanFocus, can_focus);
     if (non_client_frame_controller)
-      non_client_frame_controller->set_can_activate(can_focus);
+      non_client_frame_controller->SetCanActivate(can_focus);
     // No need to persist this value.
     properties->erase(focusable_iter);
   }
diff --git a/ash/wm/window_state.cc b/ash/wm/window_state.cc
index a4698a12..cae029b 100644
--- a/ash/wm/window_state.cc
+++ b/ash/wm/window_state.cc
@@ -492,7 +492,7 @@
   if (IsPip()) {
     views::Widget::GetWidgetForNativeWindow(window())
         ->widget_delegate()
-        ->set_can_activate(false);
+        ->SetCanActivate(false);
   }
 }
 
@@ -687,7 +687,7 @@
     // widget may not exit in some unit tests.
     // TODO(oshima): Fix unit tests and add DCHECK.
     if (widget) {
-      widget->widget_delegate()->set_can_activate(false);
+      widget->widget_delegate()->SetCanActivate(false);
       if (widget->IsActive())
         widget->Deactivate();
       Shell::Get()->focus_cycler()->AddWidget(widget);
@@ -696,7 +696,7 @@
         window(), WINDOW_VISIBILITY_ANIMATION_TYPE_FADE_IN_SLIDE_OUT);
   } else if (was_pip) {
     if (widget) {
-      widget->widget_delegate()->set_can_activate(true);
+      widget->widget_delegate()->SetCanActivate(true);
       Shell::Get()->focus_cycler()->RemoveWidget(widget);
     }
     ::wm::SetWindowVisibilityAnimationType(
diff --git a/base/fuchsia/filtered_service_directory.cc b/base/fuchsia/filtered_service_directory.cc
index 732bc76..0fc3e22 100644
--- a/base/fuchsia/filtered_service_directory.cc
+++ b/base/fuchsia/filtered_service_directory.cc
@@ -26,7 +26,7 @@
 }
 
 void FilteredServiceDirectory::AddService(const char* service_name) {
-  outgoing_directory_->AddService(
+  outgoing_directory_->AddServiceUnsafe(
       service_name,
       base::BindRepeating(&FilteredServiceDirectory::HandleRequest,
                           base::Unretained(this), service_name));
diff --git a/base/fuchsia/filtered_service_directory_unittest.cc b/base/fuchsia/filtered_service_directory_unittest.cc
index 53e4e3f..6d5b27c 100644
--- a/base/fuchsia/filtered_service_directory_unittest.cc
+++ b/base/fuchsia/filtered_service_directory_unittest.cc
@@ -19,7 +19,7 @@
     filtered_service_directory_ = std::make_unique<FilteredServiceDirectory>(
         public_service_directory_client_.get());
     filtered_client_ = std::make_unique<ServiceDirectoryClient>(
-        filtered_service_directory_->ConnectClient().TakeChannel());
+        filtered_service_directory_->ConnectClient());
   }
 
  protected:
diff --git a/base/fuchsia/service_directory_test_base.h b/base/fuchsia/service_directory_test_base.h
index cb55036..bef8286 100644
--- a/base/fuchsia/service_directory_test_base.h
+++ b/base/fuchsia/service_directory_test_base.h
@@ -8,8 +8,8 @@
 #include <lib/zx/channel.h>
 #include <memory>
 
-#include "base/fuchsia/component_context.h"
 #include "base/fuchsia/scoped_service_binding.h"
+#include "base/fuchsia/service_directory_client.h"
 #include "base/fuchsia/test_interface_impl.h"
 #include "base/fuchsia/testfidl/cpp/fidl.h"
 #include "base/message_loop/message_loop.h"
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 91d076b..e7d4044 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-c0913f45105efa9685a2367400babb72ff6c13cd
\ No newline at end of file
+0b6ae643b2e5476f6db9f114a79d372fada8b17d
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 5f39dd2..88c251a 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-f8d3014a84247254f3310e093798d4f902afe1f0
\ No newline at end of file
+bb7c5bd418e7cc9f638d2b6d835742bbce30487b
\ No newline at end of file
diff --git a/cc/trees/property_tree_builder.cc b/cc/trees/property_tree_builder.cc
index 25bafa2..3b0833daf 100644
--- a/cc/trees/property_tree_builder.cc
+++ b/cc/trees/property_tree_builder.cc
@@ -820,7 +820,12 @@
   }
 
   // If the layer uses a CSS filter.
-  if (!Filters(layer).IsEmpty() || !BackdropFilters(layer).IsEmpty()) {
+  if (!Filters(layer).IsEmpty()) {
+    return true;
+  }
+
+  // If the layer uses a CSS backdrop-filter.
+  if (!BackdropFilters(layer).IsEmpty()) {
     return true;
   }
 
diff --git a/chrome/VERSION b/chrome/VERSION
index bbe843e..373ea95 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=74
 MINOR=0
-BUILD=3696
+BUILD=3697
 PATCH=0
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/RequestGenerator.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/RequestGenerator.java
index d7739ff..9f6b9a8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/RequestGenerator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/RequestGenerator.java
@@ -13,9 +13,14 @@
 import org.xmlpull.v1.XmlSerializer;
 
 import org.chromium.base.BuildInfo;
+import org.chromium.base.Log;
+import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.browser.identity.SettingsSecureBasedIdentificationGenerator;
 import org.chromium.chrome.browser.identity.UniqueIdentificationGeneratorFactory;
+import org.chromium.chrome.browser.init.ProcessInitializationHandler;
+import org.chromium.components.signin.AccountManagerFacade;
+import org.chromium.components.signin.ChromeSigninController;
 import org.chromium.ui.base.DeviceFormFactor;
 
 import java.io.IOException;
@@ -98,6 +103,12 @@
             serializer.attribute(null, "lang", getLanguage());
             serializer.attribute(null, "installage", String.valueOf(installAge));
             serializer.attribute(null, "ap", getAdditionalParameters());
+            // <code>_numaccounts</code> is actually number of profiles, which is always one for
+            // Chrome Android.
+            serializer.attribute(null, "_numaccounts", "1");
+            serializer.attribute(null, "_numgoogleaccountsondevice",
+                    String.valueOf(getNumGoogleAccountsOnDevice()));
+            serializer.attribute(null, "_numsignedin", String.valueOf(getNumSignedIn()));
             serializer.attribute(
                     null, "_dl_mgr_disabled", String.valueOf(getDownloadManagerState()));
 
@@ -177,6 +188,43 @@
     }
 
     /**
+     * Returns the number of accounts on the device, bucketed into:
+     * 0 accounts, 1 account, or 2+ accounts.
+     *
+     * @return Number of accounts on the device, bucketed as above.
+     */
+    @VisibleForTesting
+    public int getNumGoogleAccountsOnDevice() {
+        // RequestGenerator may be invoked from JobService or AlarmManager (through OmahaService),
+        // so have to make sure AccountManagerFacade instance is initialized.
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> ProcessInitializationHandler.getInstance().initializePreNative());
+        int numAccounts = 0;
+        try {
+            numAccounts = AccountManagerFacade.get().getGoogleAccounts().size();
+        } catch (Exception e) {
+            Log.e(TAG, "Can't get number of accounts.", e);
+        }
+        switch (numAccounts) {
+            case 0:
+                return 0;
+            case 1:
+                return 1;
+            default:
+                return 2;
+        }
+    }
+
+    /**
+     * Determine number of accounts signed in.
+     */
+    @VisibleForTesting
+    public int getNumSignedIn() {
+        // We only have a single account.
+        return ChromeSigninController.get().isSignedIn() ? 1 : 0;
+    }
+
+    /**
      * Returns DownloadManager system service enabled state as
      * -1 - manager state unknown
      *  0 - manager enabled
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/OmahaBaseTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/OmahaBaseTest.java
index 73336182..b6d445ff1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/OmahaBaseTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/OmahaBaseTest.java
@@ -22,6 +22,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.omaha.MockRequestGenerator;
 import org.chromium.chrome.test.omaha.MockRequestGenerator.DeviceType;
+import org.chromium.chrome.test.omaha.MockRequestGenerator.SignedInStatus;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -87,8 +88,8 @@
 
         @Override
         protected RequestGenerator createRequestGenerator(Context context) {
-            mMockGenerator = new MockRequestGenerator(
-                    context, mIsOnTablet ? DeviceType.TABLET : DeviceType.HANDSET);
+            mMockGenerator = new MockRequestGenerator(context,
+                    mIsOnTablet ? DeviceType.TABLET : DeviceType.HANDSET, SignedInStatus.FALSE);
             return mMockGenerator;
         }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/RequestGeneratorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/RequestGeneratorTest.java
index 08aa4cd..1f8b308 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/RequestGeneratorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omaha/RequestGeneratorTest.java
@@ -22,6 +22,7 @@
 import org.chromium.chrome.test.omaha.AttributeFinder;
 import org.chromium.chrome.test.omaha.MockRequestGenerator;
 import org.chromium.chrome.test.omaha.MockRequestGenerator.DeviceType;
+import org.chromium.chrome.test.omaha.MockRequestGenerator.SignedInStatus;
 import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.test.util.AccountHolder;
 import org.chromium.components.signin.test.util.FakeAccountManagerDelegate;
@@ -76,7 +77,7 @@
         UniqueIdentificationGeneratorFactory.clearGeneratorMapForTest();
 
         // Creating a RequestGenerator should register the identification generator.
-        new MockRequestGenerator(context, DeviceType.HANDSET);
+        new MockRequestGenerator(context, DeviceType.HANDSET, SignedInStatus.FALSE);
 
         // Verify the identification generator exists and is of the correct type.
         UniqueIdentificationGenerator instance = UniqueIdentificationGeneratorFactory.getInstance(
@@ -88,49 +89,88 @@
     @SmallTest
     @Feature({"Omaha"})
     public void testHandsetXMLCreationWithInstall() {
-        createAndCheckXML(DeviceType.HANDSET, true);
+        createAndCheckXML(DeviceType.HANDSET, SignedInStatus.FALSE, true);
     }
 
     @Test
     @SmallTest
     @Feature({"Omaha"})
     public void testHandsetXMLCreationWithoutInstall() {
-        createAndCheckXML(DeviceType.HANDSET, false);
+        createAndCheckXML(DeviceType.HANDSET, SignedInStatus.FALSE, false);
     }
 
     @Test
     @SmallTest
     @Feature({"Omaha"})
     public void testTabletXMLCreationWithInstall() {
-        createAndCheckXML(DeviceType.TABLET, true);
+        createAndCheckXML(DeviceType.TABLET, SignedInStatus.FALSE, true);
     }
 
     @Test
     @SmallTest
     @Feature({"Omaha"})
     public void testTabletXMLCreationWithoutInstall() {
-        createAndCheckXML(DeviceType.TABLET, false);
+        createAndCheckXML(DeviceType.TABLET, SignedInStatus.FALSE, false);
     }
 
     @Test
     @SmallTest
     @Feature({"Omaha"})
     public void testIsSignedIn() {
-        createAndCheckXML(DeviceType.HANDSET, false);
+        createAndCheckXML(DeviceType.HANDSET, SignedInStatus.TRUE, false);
     }
 
     @Test
     @SmallTest
     @Feature({"Omaha"})
     public void testIsNotSignedIn() {
-        createAndCheckXML(DeviceType.HANDSET, false);
+        createAndCheckXML(DeviceType.HANDSET, SignedInStatus.FALSE, false);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Omaha"})
+    public void testNoGoogleAccountsRetrieved() {
+        RequestGenerator generator =
+                createAndCheckXML(DeviceType.HANDSET, SignedInStatus.TRUE, false);
+        Assert.assertEquals(0, generator.getNumGoogleAccountsOnDevice());
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Omaha"})
+    public void testOneGoogleAccountRetrieved() {
+        RequestGenerator generator = createAndCheckXML(DeviceType.HANDSET, SignedInStatus.TRUE,
+                false, new Account("clanktester@this.com", "com.google"));
+        Assert.assertEquals(1, generator.getNumGoogleAccountsOnDevice());
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Omaha"})
+    public void testTwoGoogleAccountsRetrieved() {
+        RequestGenerator generator = createAndCheckXML(DeviceType.HANDSET, SignedInStatus.TRUE,
+                false, new Account("clanktester@gmail.com", "com.google"),
+                new Account("googleguy@elsewhere.com", "com.google"));
+        Assert.assertEquals(2, generator.getNumGoogleAccountsOnDevice());
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Omaha"})
+    public void testThreeGoogleAccountsExist() {
+        RequestGenerator generator = createAndCheckXML(DeviceType.HANDSET, SignedInStatus.TRUE,
+                false, new Account("clanktester@gmail.com", "com.google"),
+                new Account("googleguy@elsewhere.com", "com.google"),
+                new Account("ImInATest@gmail.com", "com.google"));
+        Assert.assertEquals(2, generator.getNumGoogleAccountsOnDevice());
     }
 
     /**
      * Checks that the XML is being created properly.
      */
-    private RequestGenerator createAndCheckXML(
-            DeviceType deviceType, boolean sendInstallEvent, Account... accounts) {
+    private RequestGenerator createAndCheckXML(DeviceType deviceType, SignedInStatus signInStatus,
+            boolean sendInstallEvent, Account... accounts) {
         Context targetContext = InstrumentationRegistry.getTargetContext();
         AdvancedMockContext context = new AdvancedMockContext(targetContext);
 
@@ -146,7 +186,9 @@
         String version = "1.2.3.4";
         long installAge = 42;
 
-        MockRequestGenerator generator = new MockRequestGenerator(context, deviceType);
+        MockRequestGenerator generator =
+                new MockRequestGenerator(context, deviceType, signInStatus);
+
         String xml = null;
         try {
             RequestData data = new RequestData(sendInstallEvent, 0, requestId, INSTALL_SOURCE);
@@ -184,6 +226,12 @@
 
         checkForAttributeAndValue(xml, "request", "userid", "{" + generator.getDeviceID() + "}");
 
+        checkForAttributeAndValue(xml, "app", "_numaccounts", "1");
+        checkForAttributeAndValue(xml, "app", "_numgoogleaccountsondevice",
+                String.valueOf(generator.getNumGoogleAccountsOnDevice()));
+        checkForAttributeAndValue(
+                xml, "app", "_numsignedin", String.valueOf(generator.getNumSignedIn()));
+
         return generator;
     }
 
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 113d3835..86301b0 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -765,8 +765,6 @@
     "memory_details_win.cc",
     "metrics/antivirus_metrics_provider_win.cc",
     "metrics/antivirus_metrics_provider_win.h",
-    "metrics/bluetooth_available_utility.cc",
-    "metrics/bluetooth_available_utility.h",
     "metrics/browser_window_histogram_helper.cc",
     "metrics/browser_window_histogram_helper.h",
     "metrics/chrome_browser_main_extra_parts_metrics.cc",
diff --git a/chrome/browser/apps/app_service/app_icon_factory.cc b/chrome/browser/apps/app_service/app_icon_factory.cc
index 7c8a5d3..3b4769a6 100644
--- a/chrome/browser/apps/app_service/app_icon_factory.cc
+++ b/chrome/browser/apps/app_service/app_icon_factory.cc
@@ -148,7 +148,7 @@
                            content::BrowserContext* context,
                            const std::string& extension_id,
                            apps::mojom::Publisher::LoadIconCallback callback) {
-  int size_hint_in_px = ConvertDipToPx(size_hint_in_dip);
+  int size_hint_in_px = apps_util::ConvertDipToPx(size_hint_in_dip);
 
   const extensions::Extension* extension =
       extensions::ExtensionSystem::Get(context)
diff --git a/chrome/browser/apps/app_service/app_icon_source.cc b/chrome/browser/apps/app_service/app_icon_source.cc
index 6eeab21..655ae95 100644
--- a/chrome/browser/apps/app_service/app_icon_source.cc
+++ b/chrome/browser/apps/app_service/app_icon_source.cc
@@ -26,7 +26,7 @@
 void LoadDefaultImage(const content::URLDataSource::GotDataCallback& callback) {
   base::StringPiece contents =
       ui::ResourceBundle::GetSharedInstance().GetRawDataResourceForScale(
-          IDR_APP_DEFAULT_ICON, ui::SCALE_FACTOR_100P);
+          IDR_APP_DEFAULT_ICON, apps_util::GetPrimaryDisplayUIScaleFactor());
 
   base::RefCountedBytes* image_bytes = new base::RefCountedBytes();
   image_bytes->data().assign(contents.data(),
@@ -88,7 +88,7 @@
     LoadDefaultImage(callback);
     return;
   }
-  int size_in_dip = ConvertPxToDip(size);
+  int size_in_dip = apps_util::ConvertPxToDip(size);
 
   apps::AppServiceProxy* app_service_proxy =
       apps::AppServiceProxy::Get(profile_);
diff --git a/chrome/browser/apps/app_service/arc_apps.cc b/chrome/browser/apps/app_service/arc_apps.cc
index 96418ad..62d2642 100644
--- a/chrome/browser/apps/app_service/arc_apps.cc
+++ b/chrome/browser/apps/app_service/arc_apps.cc
@@ -331,11 +331,10 @@
   // TODO(crbug.com/826982): process the app_id argument like the private
   // GetAppFromAppOrGroupId function and the ArcAppIcon::mapped_app_id_ field
   // in arc_app_icon.cc?
-  //
-  // TODO(crbug.com/826982): don't hard-code SCALE_FACTOR_100P.
   return prefs_->GetIconPath(
-      app_id, ArcAppIconDescriptor(size_hint_in_dip,
-                                   ui::ScaleFactor::SCALE_FACTOR_100P));
+      app_id,
+      ArcAppIconDescriptor(size_hint_in_dip,
+                           apps_util::GetPrimaryDisplayUIScaleFactor()));
 }
 
 void ArcApps::LoadIconFromVM(const std::string icon_key_s_key,
@@ -347,9 +346,9 @@
   if (app_info) {
     base::OnceCallback<void(apps::ArcApps::AppConnectionHolder*)> pending =
         base::BindOnce(&LoadIcon0, icon_compression,
-                       ConvertDipToPx(size_hint_in_dip), app_info->package_name,
-                       app_info->activity, app_info->icon_resource_id,
-                       std::move(callback));
+                       apps_util::ConvertDipToPx(size_hint_in_dip),
+                       app_info->package_name, app_info->activity,
+                       app_info->icon_resource_id, std::move(callback));
 
     AppConnectionHolder* app_connection_holder =
         prefs_->app_connection_holder();
@@ -369,7 +368,7 @@
                                 int32_t size_hint_in_dip,
                                 LoadIconCallback callback) {
   // Use overloaded Chrome icon for Play Store that is adapted to Chrome style.
-  int size_hint_in_px = ConvertDipToPx(size_hint_in_dip);
+  int size_hint_in_px = apps_util::ConvertDipToPx(size_hint_in_dip);
   int resource_id = (size_hint_in_px <= 32) ? IDR_ARC_SUPPORT_ICON_32
                                             : IDR_ARC_SUPPORT_ICON_192;
   LoadIconFromResource(icon_compression, size_hint_in_dip, resource_id,
@@ -400,6 +399,8 @@
                                   ? apps::mojom::OptionalBool::kTrue
                                   : apps::mojom::OptionalBool::kFalse;
 
+  app->is_platform_app = apps::mojom::OptionalBool::kFalse;
+
   auto show = app_info.show_in_launcher ? apps::mojom::OptionalBool::kTrue
                                         : apps::mojom::OptionalBool::kFalse;
   app->show_in_launcher = show;
diff --git a/chrome/browser/apps/app_service/built_in_chromeos_apps.cc b/chrome/browser/apps/app_service/built_in_chromeos_apps.cc
index 9ab489e0..43dc0bb 100644
--- a/chrome/browser/apps/app_service/built_in_chromeos_apps.cc
+++ b/chrome/browser/apps/app_service/built_in_chromeos_apps.cc
@@ -42,6 +42,7 @@
   app->install_time = base::Time();
 
   app->installed_internally = apps::mojom::OptionalBool::kTrue;
+  app->is_platform_app = apps::mojom::OptionalBool::kFalse;
   app->show_in_launcher = internal_app.show_in_launcher
                               ? apps::mojom::OptionalBool::kTrue
                               : apps::mojom::OptionalBool::kFalse;
diff --git a/chrome/browser/apps/app_service/crostini_apps.cc b/chrome/browser/apps/app_service/crostini_apps.cc
index 3710ded..4e51b4a3c 100644
--- a/chrome/browser/apps/app_service/crostini_apps.cc
+++ b/chrome/browser/apps/app_service/crostini_apps.cc
@@ -8,6 +8,7 @@
 
 #include "chrome/browser/apps/app_service/app_icon_factory.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/dip_px_util.h"
 #include "chrome/browser/apps/app_service/launch_util.h"
 #include "chrome/browser/chromeos/crostini/crostini_registry_service_factory.h"
 #include "chrome/browser/chromeos/crostini/crostini_util.h"
@@ -76,8 +77,7 @@
     }
 
     if (icon_key->icon_type == apps::mojom::IconType::kCrostini) {
-      // TODO(crbug.com/826982): don't hard-code SCALE_FACTOR_100P.
-      auto scale_factor = ui::ScaleFactor::SCALE_FACTOR_100P;
+      auto scale_factor = apps_util::GetPrimaryDisplayUIScaleFactor();
 
       // Try loading the icon from an on-disk cache. If that fails, fall back
       // to LoadIconFromVM.
@@ -180,6 +180,7 @@
   app->install_time = registration.InstallTime();
 
   app->installed_internally = apps::mojom::OptionalBool::kFalse;
+  app->is_platform_app = apps::mojom::OptionalBool::kFalse;
 
   // TODO(crbug.com/826982): if Crostini isn't enabled, don't show the Terminal
   // item until it becomes enabled.
diff --git a/chrome/browser/apps/app_service/dip_px_util.cc b/chrome/browser/apps/app_service/dip_px_util.cc
index e0511902..cbc7adfa 100644
--- a/chrome/browser/apps/app_service/dip_px_util.cc
+++ b/chrome/browser/apps/app_service/dip_px_util.cc
@@ -7,9 +7,15 @@
 #include <cmath>
 
 #include "base/numerics/safe_conversions.h"
+#include "ui/base/layout.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
 
+// TODO(crbug.com/826982): plumb through enough information to use one of
+// Screen::GetDisplayNearest{Window/View/Point}. That way in multi-monitor
+// setups where one screen is hidpi and the other one isn't, we don't always do
+// the wrong thing.
+
 namespace {
 
 float GetPrimaryDisplayScaleFactor() {
@@ -22,7 +28,7 @@
 
 }  // namespace
 
-namespace apps {
+namespace apps_util {
 
 int ConvertDipToPx(int dip) {
   return base::saturated_cast<int>(
@@ -34,4 +40,8 @@
       std::floor(static_cast<float>(px) / GetPrimaryDisplayScaleFactor()));
 }
 
-}  // namespace apps
+ui::ScaleFactor GetPrimaryDisplayUIScaleFactor() {
+  return ui::GetSupportedScaleFactor(GetPrimaryDisplayScaleFactor());
+}
+
+}  // namespace apps_util
diff --git a/chrome/browser/apps/app_service/dip_px_util.h b/chrome/browser/apps/app_service/dip_px_util.h
index 4b88ebf8..81d24a4 100644
--- a/chrome/browser/apps/app_service/dip_px_util.h
+++ b/chrome/browser/apps/app_service/dip_px_util.h
@@ -8,11 +8,14 @@
 // Utility functions for converting between DIP (device independent pixels) and
 // PX (physical pixels).
 
-namespace apps {
+#include "ui/base/resource/scale_factor.h"
+
+namespace apps_util {
 
 int ConvertDipToPx(int dip);
 int ConvertPxToDip(int px);
+ui::ScaleFactor GetPrimaryDisplayUIScaleFactor();
 
-}  // namespace apps
+}  // namespace apps_util
 
 #endif  // CHROME_BROWSER_APPS_APP_SERVICE_DIP_PX_UTIL_H_
diff --git a/chrome/browser/apps/app_service/extension_apps.cc b/chrome/browser/apps/app_service/extension_apps.cc
index 853b149..444b681 100644
--- a/chrome/browser/apps/app_service/extension_apps.cc
+++ b/chrome/browser/apps/app_service/extension_apps.cc
@@ -43,11 +43,6 @@
 // TODO(crbug.com/826982): do we also need to watch prefs, the same as
 // ExtensionAppModelBuilder?
 
-// TODO(crbug.com/826982): support the is_platform_app bit. We might not need
-// to plumb this all the way through the Mojo methods, as AFAICT it's only used
-// for populating the context menu, which is done on the app publisher side
-// (i.e. in this C++ file) and not at all on the app subscriber side.
-
 namespace {
 
 // Only supporting important permissions for now.
@@ -464,6 +459,10 @@
                                   ? apps::mojom::OptionalBool::kTrue
                                   : apps::mojom::OptionalBool::kFalse;
 
+  app->is_platform_app = extension->is_platform_app()
+                             ? apps::mojom::OptionalBool::kTrue
+                             : apps::mojom::OptionalBool::kFalse;
+
   auto show = app_list::ShouldShowInLauncher(extension, profile_)
                   ? apps::mojom::OptionalBool::kTrue
                   : apps::mojom::OptionalBool::kFalse;
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 6197331..4d69563 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -274,7 +274,6 @@
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/browser/resource_context.h"
-#include "content/public/browser/site_instance.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/tts_controller.h"
 #include "content/public/browser/tts_platform.h"
@@ -519,7 +518,6 @@
 #include "extensions/browser/guest_view/web_view/web_view_permission_helper.h"
 #include "extensions/browser/guest_view/web_view/web_view_renderer_state.h"
 #include "extensions/browser/process_manager.h"
-#include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_set.h"
 #include "extensions/common/manifest_handlers/background_info.h"
@@ -4911,14 +4909,6 @@
   if (!base::FeatureList::IsEnabled(features::kWebUsb))
     return;
 
-#if BUILDFLAG(ENABLE_EXTENSIONS)
-  // WebUSB is not supported in Apps/Extensions. https://crbug.com/770896
-  if (render_frame_host->GetSiteInstance()->GetSiteURL().SchemeIs(
-          extensions::kExtensionScheme)) {
-    return;
-  }
-#endif
-
   WebContents* web_contents =
       WebContents::FromRenderFrameHost(render_frame_host);
   if (!web_contents) {
diff --git a/chrome/browser/chromeos/accessibility/chromevox_panel.cc b/chrome/browser/chromeos/accessibility/chromevox_panel.cc
index b2474a0..11cb323 100644
--- a/chrome/browser/chromeos/accessibility/chromevox_panel.cc
+++ b/chrome/browser/chromeos/accessibility/chromevox_panel.cc
@@ -70,12 +70,12 @@
 
 void ChromeVoxPanel::ExitFullscreen() {
   GetWidget()->Deactivate();
-  GetWidget()->widget_delegate()->set_can_activate(false);
+  GetWidget()->widget_delegate()->SetCanActivate(false);
   SetAccessibilityPanelFullscreen(false);
 }
 
 void ChromeVoxPanel::Focus() {
-  GetWidget()->widget_delegate()->set_can_activate(true);
+  GetWidget()->widget_delegate()->SetCanActivate(true);
   GetWidget()->Activate();
   GetContentsView()->RequestFocus();
 }
diff --git a/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge_unittest.cc b/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge_unittest.cc
index e40a88a..b042e9b 100644
--- a/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge_unittest.cc
+++ b/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge_unittest.cc
@@ -418,7 +418,7 @@
 
   // Prepare widget to hold it.
   views::Widget* widget = CreateTestWidget();
-  widget->widget_delegate()->set_can_activate(false);
+  widget->widget_delegate()->SetCanActivate(false);
   widget->Deactivate();
   widget->SetContentsView(notification_view.get());
   widget->Show();
diff --git a/chrome/browser/chromeos/file_manager/video_player_browsertest.cc b/chrome/browser/chromeos/file_manager/video_player_browsertest.cc
index 53f0719..8922e2f 100644
--- a/chrome/browser/chromeos/file_manager/video_player_browsertest.cc
+++ b/chrome/browser/chromeos/file_manager/video_player_browsertest.cc
@@ -70,11 +70,6 @@
   StartTest();
 }
 
-IN_PROC_BROWSER_TEST_F(VideoPlayerBrowserTest, ClickControlButtons) {
-  set_test_case_name("clickControlButtons");
-  StartTest();
-}
-
 // Flaky. Suspect due to a race when loading Chromecast integration.
 // See https://crbug.com/926035.
 IN_PROC_BROWSER_TEST_F(VideoPlayerBrowserTest, DISABLED_NativeMediaKey) {
diff --git a/chrome/browser/chromeos/smb_client/smb_file_system.cc b/chrome/browser/chromeos/smb_client/smb_file_system.cc
index 93fc68ed..023dbbf6 100644
--- a/chrome/browser/chromeos/smb_client/smb_file_system.cc
+++ b/chrome/browser/chromeos/smb_client/smb_file_system.cc
@@ -124,10 +124,12 @@
 SmbFileSystem::SmbFileSystem(
     const file_system_provider::ProvidedFileSystemInfo& file_system_info,
     UnmountCallback unmount_callback,
-    RequestCredentialsCallback request_creds_callback)
+    RequestCredentialsCallback request_creds_callback,
+    RequestUpdatedSharePathCallback request_path_callback)
     : file_system_info_(file_system_info),
       unmount_callback_(std::move(unmount_callback)),
       request_creds_callback_(std::move(request_creds_callback)),
+      request_path_callback_(std::move(request_path_callback)),
       task_queue_(kTaskQueueCapacity) {}
 
 SmbFileSystem::~SmbFileSystem() {}
@@ -575,6 +577,11 @@
   request_creds_callback_.Run(GetMountPath(), GetMountId(), std::move(reply));
 }
 
+void SmbFileSystem::RequestUpdatedSharePath(
+    SmbService::StartReadDirIfSuccessfulCallback reply) {
+  request_path_callback_.Run(GetMountPath(), GetMountId(), std::move(reply));
+}
+
 void SmbFileSystem::HandleRequestReadDirectoryCallback(
     storage::AsyncFileUtil::ReadDirectoryCallback callback,
     const base::ElapsedTimer& metrics_timer,
diff --git a/chrome/browser/chromeos/smb_client/smb_file_system.h b/chrome/browser/chromeos/smb_client/smb_file_system.h
index 08915ae..dc7e9be 100644
--- a/chrome/browser/chromeos/smb_client/smb_file_system.h
+++ b/chrome/browser/chromeos/smb_client/smb_file_system.h
@@ -61,7 +61,8 @@
   SmbFileSystem(
       const file_system_provider::ProvidedFileSystemInfo& file_system_info,
       UnmountCallback unmount_callback,
-      RequestCredentialsCallback request_creds_callback);
+      RequestCredentialsCallback request_creds_callback,
+      RequestUpdatedSharePathCallback request_path_callback);
   ~SmbFileSystem() override;
 
   // ProvidedFileSystemInterface overrides.
@@ -238,6 +239,11 @@
   // updated, |reply| is executed.
   void RequestUpdatedCredentials(base::OnceClosure reply);
 
+  // Requests updated share path for the mount. Once the share path have been,
+  // updated, |reply| is executed.
+  void RequestUpdatedSharePath(
+      SmbService::StartReadDirIfSuccessfulCallback reply);
+
   void HandleRequestUnmountCallback(
       storage::AsyncFileUtil::StatusCallback callback,
       smbprovider::ErrorType error);
@@ -349,6 +355,7 @@
 
   UnmountCallback unmount_callback_;
   RequestCredentialsCallback request_creds_callback_;
+  RequestUpdatedSharePathCallback request_path_callback_;
   std::unique_ptr<TempFileManager> temp_file_manager_;
   mutable SmbTaskQueue task_queue_;
 
diff --git a/chrome/browser/chromeos/smb_client/smb_provider.cc b/chrome/browser/chromeos/smb_client/smb_provider.cc
index 1a4e26e..4d2cabd 100644
--- a/chrome/browser/chromeos/smb_client/smb_provider.cc
+++ b/chrome/browser/chromeos/smb_client/smb_provider.cc
@@ -47,7 +47,8 @@
     const ProvidedFileSystemInfo& file_system_info) {
   DCHECK(profile);
   return std::make_unique<SmbFileSystem>(file_system_info, unmount_callback_,
-                                         request_creds_callback_);
+                                         request_creds_callback_,
+                                         request_path_callback_);
 }
 
 const Capabilities& SmbProvider::GetCapabilities() const {
diff --git a/chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings_unittest.cc b/chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings_unittest.cc
index cfb7d296..24f3864 100644
--- a/chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings_unittest.cc
+++ b/chrome/browser/data_reduction_proxy/data_reduction_proxy_chrome_settings_unittest.cc
@@ -387,7 +387,7 @@
   scoped_refptr<net::HttpResponseHeaders> headers =
       new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
           raw_headers.c_str(), raw_headers.size()));
-  handle.set_response_headers(headers.get());
+  handle.set_response_headers(headers);
   auto data = drp_chrome_settings_->CreateDataFromNavigationHandle(
       &handle, headers.get());
 
@@ -406,7 +406,7 @@
   scoped_refptr<net::HttpResponseHeaders> headers =
       new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
           raw_headers.c_str(), raw_headers.size()));
-  handle.set_response_headers(headers.get());
+  handle.set_response_headers(headers);
   auto data = drp_chrome_settings_->CreateDataFromNavigationHandle(
       &handle, headers.get());
 
@@ -421,7 +421,7 @@
   scoped_refptr<net::HttpResponseHeaders> headers =
       new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
           raw_headers.c_str(), raw_headers.size()));
-  handle.set_response_headers(headers.get());
+  handle.set_response_headers(headers);
   handle.set_was_response_cached(true);
   auto data = drp_chrome_settings_->CreateDataFromNavigationHandle(
       &handle, headers.get());
@@ -435,7 +435,7 @@
   scoped_refptr<net::HttpResponseHeaders> headers =
       new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
           raw_headers.c_str(), raw_headers.size()));
-  handle.set_response_headers(headers.get());
+  handle.set_response_headers(headers);
   handle.set_was_response_cached(true);
   auto data = drp_chrome_settings_->CreateDataFromNavigationHandle(
       &handle, headers.get());
@@ -452,7 +452,7 @@
   scoped_refptr<net::HttpResponseHeaders> headers =
       new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
           raw_headers.c_str(), raw_headers.size()));
-  handle.set_response_headers(headers.get());
+  handle.set_response_headers(headers);
   handle.set_was_response_cached(true);
   auto data = drp_chrome_settings_->CreateDataFromNavigationHandle(
       &handle, headers.get());
@@ -468,7 +468,7 @@
   scoped_refptr<net::HttpResponseHeaders> headers =
       new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
           raw_headers.c_str(), raw_headers.size()));
-  handle.set_response_headers(headers.get());
+  handle.set_response_headers(headers);
   auto data = drp_chrome_settings_->CreateDataFromNavigationHandle(
       &handle, headers.get());
 
@@ -485,7 +485,7 @@
   scoped_refptr<net::HttpResponseHeaders> headers =
       new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
           raw_headers.c_str(), raw_headers.size()));
-  handle.set_response_headers(headers.get());
+  handle.set_response_headers(headers);
   auto data = drp_chrome_settings_->CreateDataFromNavigationHandle(
       &handle, headers.get());
 
@@ -502,7 +502,7 @@
   scoped_refptr<net::HttpResponseHeaders> headers =
       new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
           raw_headers.c_str(), raw_headers.size()));
-  handle.set_response_headers(headers.get());
+  handle.set_response_headers(headers);
   auto data = drp_chrome_settings_->CreateDataFromNavigationHandle(
       &handle, headers.get());
 
diff --git a/chrome/browser/extensions/api/declarative_net_request/rule_indexing_unittest.cc b/chrome/browser/extensions/api/declarative_net_request/rule_indexing_unittest.cc
index ae9133b..7bddaac 100644
--- a/chrome/browser/extensions/api/declarative_net_request/rule_indexing_unittest.cc
+++ b/chrome/browser/extensions/api/declarative_net_request/rule_indexing_unittest.cc
@@ -176,8 +176,9 @@
       std::vector<std::string>({"image", "stylesheet"});
   rule.condition->excluded_resource_types = std::vector<std::string>({"image"});
   AddRule(rule);
-  LoadAndExpectError(ParseInfo(ParseResult::ERROR_RESOURCE_TYPE_DUPLICATED, 0u)
-                         .GetErrorDescription(kJSONRulesFilename));
+  LoadAndExpectError(
+      ParseInfo(ParseResult::ERROR_RESOURCE_TYPE_DUPLICATED, *rule.id)
+          .GetErrorDescription(kJSONRulesFilename));
 }
 
 TEST_P(RuleIndexingTest, EmptyRedirectRulePriority) {
@@ -186,7 +187,7 @@
   rule.action->redirect_url = std::string("https://google.com");
   AddRule(rule);
   LoadAndExpectError(
-      ParseInfo(ParseResult::ERROR_EMPTY_REDIRECT_RULE_PRIORITY, 0u)
+      ParseInfo(ParseResult::ERROR_EMPTY_REDIRECT_RULE_PRIORITY, *rule.id)
           .GetErrorDescription(kJSONRulesFilename));
 }
 
@@ -200,7 +201,7 @@
   rule.priority = kMinValidPriority;
   AddRule(rule);
 
-  LoadAndExpectError(ParseInfo(ParseResult::ERROR_EMPTY_REDIRECT_URL, 1u)
+  LoadAndExpectError(ParseInfo(ParseResult::ERROR_EMPTY_REDIRECT_URL, *rule.id)
                          .GetErrorDescription(kJSONRulesFilename));
 }
 
@@ -208,7 +209,7 @@
   TestRule rule = CreateGenericRule();
   rule.id = kMinValidID - 1;
   AddRule(rule);
-  LoadAndExpectError(ParseInfo(ParseResult::ERROR_INVALID_RULE_ID, 0u)
+  LoadAndExpectError(ParseInfo(ParseResult::ERROR_INVALID_RULE_ID, *rule.id)
                          .GetErrorDescription(kJSONRulesFilename));
 }
 
@@ -219,7 +220,7 @@
   rule.priority = kMinValidPriority - 1;
   AddRule(rule);
   LoadAndExpectError(
-      ParseInfo(ParseResult::ERROR_INVALID_REDIRECT_RULE_PRIORITY, 0u)
+      ParseInfo(ParseResult::ERROR_INVALID_REDIRECT_RULE_PRIORITY, *rule.id)
           .GetErrorDescription(kJSONRulesFilename));
 }
 
@@ -231,7 +232,7 @@
        "other"});
   AddRule(rule);
   LoadAndExpectError(
-      ParseInfo(ParseResult::ERROR_NO_APPLICABLE_RESOURCE_TYPES, 0u)
+      ParseInfo(ParseResult::ERROR_NO_APPLICABLE_RESOURCE_TYPES, *rule.id)
           .GetErrorDescription(kJSONRulesFilename));
 }
 
@@ -239,7 +240,7 @@
   TestRule rule = CreateGenericRule();
   rule.condition->domains = std::vector<std::string>();
   AddRule(rule);
-  LoadAndExpectError(ParseInfo(ParseResult::ERROR_EMPTY_DOMAINS_LIST, 0u)
+  LoadAndExpectError(ParseInfo(ParseResult::ERROR_EMPTY_DOMAINS_LIST, *rule.id)
                          .GetErrorDescription(kJSONRulesFilename));
 }
 
@@ -247,15 +248,16 @@
   TestRule rule = CreateGenericRule();
   rule.condition->resource_types = std::vector<std::string>();
   AddRule(rule);
-  LoadAndExpectError(ParseInfo(ParseResult::ERROR_EMPTY_RESOURCE_TYPES_LIST, 0u)
-                         .GetErrorDescription(kJSONRulesFilename));
+  LoadAndExpectError(
+      ParseInfo(ParseResult::ERROR_EMPTY_RESOURCE_TYPES_LIST, *rule.id)
+          .GetErrorDescription(kJSONRulesFilename));
 }
 
 TEST_P(RuleIndexingTest, EmptyURLFilter) {
   TestRule rule = CreateGenericRule();
   rule.condition->url_filter = std::string();
   AddRule(rule);
-  LoadAndExpectError(ParseInfo(ParseResult::ERROR_EMPTY_URL_FILTER, 0u)
+  LoadAndExpectError(ParseInfo(ParseResult::ERROR_EMPTY_URL_FILTER, *rule.id)
                          .GetErrorDescription(kJSONRulesFilename));
 }
 
@@ -265,8 +267,9 @@
   rule.action->redirect_url = std::string("google");
   rule.priority = kMinValidPriority;
   AddRule(rule);
-  LoadAndExpectError(ParseInfo(ParseResult::ERROR_INVALID_REDIRECT_URL, 0u)
-                         .GetErrorDescription(kJSONRulesFilename));
+  LoadAndExpectError(
+      ParseInfo(ParseResult::ERROR_INVALID_REDIRECT_URL, *rule.id)
+          .GetErrorDescription(kJSONRulesFilename));
 }
 
 TEST_P(RuleIndexingTest, ListNotPassed) {
@@ -279,7 +282,7 @@
   TestRule rule = CreateGenericRule();
   AddRule(rule);
   AddRule(rule);
-  LoadAndExpectError(ParseInfo(ParseResult::ERROR_DUPLICATE_IDS, 1u)
+  LoadAndExpectError(ParseInfo(ParseResult::ERROR_DUPLICATE_IDS, *rule.id)
                          .GetErrorDescription(kJSONRulesFilename));
 }
 
@@ -321,7 +324,7 @@
     // |kMaxUnparsedRulesWarnings| rules, which couldn't be parsed.
     for (size_t i = 0; i < kMaxUnparsedRulesWarnings; i++) {
       warning.message = ErrorUtils::FormatErrorMessage(
-          kRuleNotParsedWarning, std::to_string(i),
+          kRuleNotParsedWarning, base::StringPrintf("id %zu", i + 1),
           "'RuleActionType': expected \"block\" or \"redirect\" or \"allow\", "
           "got \"invalid_action_type\"");
       EXPECT_EQ(expected_warnings[i], warning);
@@ -374,14 +377,14 @@
 
     expected_warnings.emplace_back(
         ErrorUtils::FormatErrorMessage(
-            kRuleNotParsedWarning, "1",
+            kRuleNotParsedWarning, "id 2",
             "'RuleActionType': expected \"block\" or \"redirect\" or \"allow\","
             " got \"invalid action\""),
         manifest_keys::kDeclarativeNetRequestKey,
         manifest_keys::kDeclarativeRuleResourcesKey);
     expected_warnings.emplace_back(
         ErrorUtils::FormatErrorMessage(
-            kRuleNotParsedWarning, "3",
+            kRuleNotParsedWarning, "id 4",
             "'DomainType': expected \"firstParty\" or \"thirdParty\", got "
             "\"invalid_domain_type\""),
         manifest_keys::kDeclarativeNetRequestKey,
@@ -412,7 +415,7 @@
         "action" : {"type" : "block" }
       },
       {
-        "id" : "4",
+        "id" : "6",
         "condition" : {"urlFilter" : "google"},
         "action" : {"type" : "block" }
       }
@@ -431,17 +434,17 @@
 
     expected_warnings.emplace_back(
         ErrorUtils::FormatErrorMessage(
-            kRuleNotParsedWarning, "0",
+            kRuleNotParsedWarning, "id 1",
             "'condition': expected dictionary, got list"),
         manifest_keys::kDeclarativeNetRequestKey,
         manifest_keys::kDeclarativeRuleResourcesKey);
     expected_warnings.emplace_back(
-        ErrorUtils::FormatErrorMessage(kRuleNotParsedWarning, "2",
+        ErrorUtils::FormatErrorMessage(kRuleNotParsedWarning, "id 3",
                                        "found unexpected key 'invalidKey'"),
         manifest_keys::kDeclarativeNetRequestKey,
         manifest_keys::kDeclarativeRuleResourcesKey);
     expected_warnings.emplace_back(
-        ErrorUtils::FormatErrorMessage(kRuleNotParsedWarning, "3",
+        ErrorUtils::FormatErrorMessage(kRuleNotParsedWarning, "index 4",
                                        "'id': expected id, got string"),
         manifest_keys::kDeclarativeNetRequestKey,
         manifest_keys::kDeclarativeRuleResourcesKey);
diff --git a/chrome/browser/mac/bluetooth_utility.h b/chrome/browser/mac/bluetooth_utility.h
index 8f4f559..df54d35e 100644
--- a/chrome/browser/mac/bluetooth_utility.h
+++ b/chrome/browser/mac/bluetooth_utility.h
@@ -17,7 +17,6 @@
   // On OSX 10.6, if the Link Manager Protocol version supports Low Energy,
   // there is no further indication of whether Low Energy is supported.
   BLUETOOTH_AVAILABLE_LE_UNKNOWN = 4,
-  BLUETOOTH_NOT_SUPPORTED = 5,
   BLUETOOTH_AVAILABILITY_COUNT,
 };
 
diff --git a/chrome/browser/metrics/bluetooth_available_utility.cc b/chrome/browser/metrics/bluetooth_available_utility.cc
deleted file mode 100644
index beecea5..0000000
--- a/chrome/browser/metrics/bluetooth_available_utility.cc
+++ /dev/null
@@ -1,77 +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/metrics/bluetooth_available_utility.h"
-
-#include "base/bind.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/task/post_task.h"
-#include "build/build_config.h"
-#include "chrome/browser/mac/bluetooth_utility.h"
-#include "content/public/browser/browser_task_traits.h"
-#include "content/public/browser/browser_thread.h"
-#include "device/bluetooth/bluetooth_adapter.h"
-#include "device/bluetooth/bluetooth_adapter_factory.h"
-
-#if defined(OS_LINUX)
-#include "device/bluetooth/dbus/bluez_dbus_manager.h"
-#endif  // defined(OS_LINUX)
-
-namespace bluetooth_utility {
-
-void ReportAvailability(BluetoothAvailability availability) {
-  UMA_HISTOGRAM_ENUMERATION("Bluetooth.Availability", availability,
-                            BLUETOOTH_AVAILABILITY_COUNT);
-}
-
-void OnGetAdapter(scoped_refptr<device::BluetoothAdapter> adapter) {
-  if (!adapter->IsPresent()) {
-    ReportAvailability(BLUETOOTH_NOT_AVAILABLE);
-    return;
-  }
-
-  if (!device::BluetoothAdapterFactory::Get().IsLowEnergySupported()) {
-    ReportAvailability(BLUETOOTH_AVAILABLE_WITHOUT_LE);
-    return;
-  }
-
-  ReportAvailability(BLUETOOTH_AVAILABLE_WITH_LE);
-}
-
-void ReportBluetoothAvailability() {
-#if defined(OS_MACOSX)
-  // TODO(kenrb): This is separate from other platforms because we get a
-  // little bit of extra information from the Mac-specific code. It might not
-  // be worth having the extra code path, and we should consider whether to
-  // combine them (https://crbug.com/907279).
-  bluetooth_utility::BluetoothAvailability availability =
-      bluetooth_utility::GetBluetoothAvailability();
-  UMA_HISTOGRAM_ENUMERATION("Bluetooth.Availability", availability,
-                            bluetooth_utility::BLUETOOTH_AVAILABILITY_COUNT);
-  return;
-#endif  // defined(OS_MACOSX)
-
-  // GetAdapter must be called on the UI thread, because it creates a
-  // WeakPtr, which is checked from that thread on future calls.
-  if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
-    base::PostTaskWithTraits(FROM_HERE, {content::BrowserThread::UI},
-                             base::BindOnce(&ReportBluetoothAvailability));
-    return;
-  }
-
-#if defined(OS_LINUX)
-  // This is for tests that have not initialized bluez or dbus thread manager.
-  // Outside of tests these are initialized earlier during browser startup.
-  if (!bluez::BluezDBusManager::IsInitialized())
-    return;
-#endif  // defined(OS_LINUX)
-
-  if (!device::BluetoothAdapterFactory::Get().IsBluetoothSupported())
-    ReportAvailability(BLUETOOTH_NOT_SUPPORTED);
-
-  device::BluetoothAdapterFactory::Get().GetAdapter(
-      base::BindOnce(&OnGetAdapter));
-}
-
-}  // namespace bluetooth_utility
diff --git a/chrome/browser/metrics/bluetooth_available_utility.h b/chrome/browser/metrics/bluetooth_available_utility.h
deleted file mode 100644
index dfa79b4..0000000
--- a/chrome/browser/metrics/bluetooth_available_utility.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_METRICS_BLUETOOTH_AVAILABLE_UTILITY_H_
-#define CHROME_BROWSER_METRICS_BLUETOOTH_AVAILABLE_UTILITY_H_
-
-namespace bluetooth_utility {
-
-// Reports the bluetooth availability of the system's hardware.
-// This currently only works on ChromeOS and Windows. For Bluetooth
-// availability on OS X, see chrome/browser/mac/bluetooth_utility.h.
-void ReportBluetoothAvailability();
-
-}  // namespace bluetooth_utility
-
-#endif  // CHROME_BROWSER_METRICS_BLUETOOTH_AVAILABLE_UTILITY_H_
diff --git a/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc b/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc
index 72257ad..85b818a 100644
--- a/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc
+++ b/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc
@@ -21,7 +21,7 @@
 #include "chrome/browser/about_flags.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_browser_main.h"
-#include "chrome/browser/metrics/bluetooth_available_utility.h"
+#include "chrome/browser/mac/bluetooth_utility.h"
 #include "chrome/browser/shell_integration.h"
 #include "chrome/browser/vr/service/xr_runtime_manager.h"
 #include "components/flags_ui/pref_service_flags_storage.h"
@@ -201,7 +201,13 @@
                         base::TimeTicks::IsHighResolution());
 #endif  // defined(OS_WIN)
 
-  bluetooth_utility::ReportBluetoothAvailability();
+#if defined(OS_MACOSX)
+  bluetooth_utility::BluetoothAvailability availability =
+      bluetooth_utility::GetBluetoothAvailability();
+  UMA_HISTOGRAM_ENUMERATION("OSX.BluetoothAvailability",
+                            availability,
+                            bluetooth_utility::BLUETOOTH_AVAILABILITY_COUNT);
+#endif  // defined(OS_MACOSX)
 
   // Record whether Chrome is the default browser or not.
   shell_integration::DefaultWebClientState default_state =
diff --git a/chrome/browser/pdf/pdf_extension_test.cc b/chrome/browser/pdf/pdf_extension_test.cc
index 05ce090..23ecd3b 100644
--- a/chrome/browser/pdf/pdf_extension_test.cc
+++ b/chrome/browser/pdf/pdf_extension_test.cc
@@ -487,20 +487,6 @@
         content::BrowserContext::GetDownloadManager(browser_context);
     download_awaiter_ = std::make_unique<DownloadAwaiter>();
     download_manager->AddObserver(download_awaiter_.get());
-
-    // TODO(tommycli): PDFIFrameNavigationThrottle currently doesn't wait for
-    // the plugin list to be loaded, and this causes some unpredictable (but not
-    // catastrophic) behavior on startup. Remove this after we fix that.
-    base::RunLoop run_loop;
-    content::PluginService::GetInstance()->GetPlugins(base::BindOnce(
-        &PDFPluginDisabledTest::PluginsLoadedCallback, run_loop.QuitClosure()));
-    run_loop.Run();
-  }
-
-  static void PluginsLoadedCallback(
-      base::OnceClosure callback,
-      const std::vector<content::WebPluginInfo>& plugins) {
-    std::move(callback).Run();
   }
 
   void TearDownOnMainThread() override {
diff --git a/chrome/browser/plugins/pdf_iframe_navigation_throttle.cc b/chrome/browser/plugins/pdf_iframe_navigation_throttle.cc
index f555d21..572e7226 100644
--- a/chrome/browser/plugins/pdf_iframe_navigation_throttle.cc
+++ b/chrome/browser/plugins/pdf_iframe_navigation_throttle.cc
@@ -9,7 +9,6 @@
 #include "base/feature_list.h"
 #include "base/memory/weak_ptr.h"
 #include "base/task/post_task.h"
-#include "chrome/common/chrome_content_client.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/pdf_util.h"
 #include "content/public/browser/browser_context.h"
@@ -23,7 +22,6 @@
 #include "content/public/browser/web_contents_user_data.h"
 #include "net/base/escape.h"
 #include "net/http/http_response_headers.h"
-#include "ppapi/buildflags/buildflags.h"
 
 #if BUILDFLAG(ENABLE_PLUGINS)
 #include "chrome/browser/plugins/chrome_plugin_service_filter.h"
@@ -58,6 +56,26 @@
 
 WEB_CONTENTS_USER_DATA_KEY_IMPL(PdfWebContentsLifetimeHelper)
 
+#if BUILDFLAG(ENABLE_PLUGINS)
+// Returns true if the PDF plugin for |navigation_handle| is enabled. Optionally
+// also sets |is_stale| to true if the plugin list needs a reload.
+bool IsPDFPluginEnabled(content::NavigationHandle* navigation_handle,
+                        bool* is_stale) {
+  content::WebContents* web_contents = navigation_handle->GetWebContents();
+  int process_id = web_contents->GetMainFrame()->GetProcess()->GetID();
+  int routing_id = web_contents->GetMainFrame()->GetRoutingID();
+  content::ResourceContext* resource_context =
+      web_contents->GetBrowserContext()->GetResourceContext();
+
+  content::WebPluginInfo plugin_info;
+  return content::PluginService::GetInstance()->GetPluginInfo(
+      process_id, routing_id, resource_context, navigation_handle->GetURL(),
+      web_contents->GetMainFrame()->GetLastCommittedOrigin(), kPDFMimeType,
+      false /* allow_wildcard */, is_stale, &plugin_info,
+      nullptr /* actual_mime_type */);
+}
+#endif
+
 }  // namespace
 
 PDFIFrameNavigationThrottle::PDFIFrameNavigationThrottle(
@@ -77,28 +95,6 @@
   if (handle->IsInMainFrame())
     return nullptr;
 
-#if BUILDFLAG(ENABLE_PLUGINS)
-  content::WebPluginInfo pdf_plugin_info;
-  static const base::FilePath pdf_plugin_path(
-      ChromeContentClient::kPDFPluginPath);
-  content::PluginService::GetInstance()->GetPluginInfoByPath(pdf_plugin_path,
-                                                             &pdf_plugin_info);
-
-  ChromePluginServiceFilter* filter = ChromePluginServiceFilter::GetInstance();
-  int process_id =
-      handle->GetWebContents()->GetMainFrame()->GetProcess()->GetID();
-  int routing_id = handle->GetWebContents()->GetMainFrame()->GetRoutingID();
-  content::ResourceContext* resource_context =
-      handle->GetWebContents()->GetBrowserContext()->GetResourceContext();
-  if (filter->IsPluginAvailable(process_id, routing_id, resource_context,
-                                handle->GetURL(), url::Origin(),
-                                &pdf_plugin_info)) {
-    return nullptr;
-  }
-#endif
-
-  // If ENABLE_PLUGINS is false, the PDF plugin is not available, so we should
-  // always intercept PDF iframe navigations.
   return std::make_unique<PDFIFrameNavigationThrottle>(handle);
 }
 
@@ -126,6 +122,41 @@
   if (!base::FeatureList::IsEnabled(features::kClickToOpenPDFPlaceholder))
     return content::NavigationThrottle::PROCEED;
 
+#if BUILDFLAG(ENABLE_PLUGINS)
+  bool is_stale = false;
+  bool pdf_plugin_enabled = IsPDFPluginEnabled(navigation_handle(), &is_stale);
+
+  if (is_stale) {
+    // On browser start, the plugin list may not be ready yet.
+    content::PluginService::GetInstance()->GetPlugins(
+        base::BindOnce(&PDFIFrameNavigationThrottle::OnPluginsLoaded,
+                       weak_factory_.GetWeakPtr()));
+    return content::NavigationThrottle::DEFER;
+  }
+
+  // If the plugin was found, proceed on the navigation. Otherwise fall through
+  // to the placeholder case.
+  if (pdf_plugin_enabled)
+    return content::NavigationThrottle::PROCEED;
+#endif
+
+  LoadPlaceholderHTML();
+  return content::NavigationThrottle::CANCEL_AND_IGNORE;
+}
+
+#if BUILDFLAG(ENABLE_PLUGINS)
+void PDFIFrameNavigationThrottle::OnPluginsLoaded(
+    const std::vector<content::WebPluginInfo>& plugins) {
+  if (IsPDFPluginEnabled(navigation_handle(), nullptr /* is_stale */)) {
+    Resume();
+  } else {
+    LoadPlaceholderHTML();
+    CancelDeferredNavigation(content::NavigationThrottle::CANCEL_AND_IGNORE);
+  }
+}
+#endif
+
+void PDFIFrameNavigationThrottle::LoadPlaceholderHTML() {
   // Prepare the params to navigate to the placeholder.
   std::string html = GetPDFPlaceholderHTML(navigation_handle()->GetURL());
   GURL data_url("data:text/html," + net::EscapePath(html));
@@ -148,6 +179,4 @@
       FROM_HERE, {content::BrowserThread::UI},
       base::BindOnce(&PdfWebContentsLifetimeHelper::NavigateIFrameToPlaceholder,
                      helper->GetWeakPtr(), params));
-
-  return content::NavigationThrottle::CANCEL_AND_IGNORE;
 }
diff --git a/chrome/browser/plugins/pdf_iframe_navigation_throttle.h b/chrome/browser/plugins/pdf_iframe_navigation_throttle.h
index 34e5398..39bf392 100644
--- a/chrome/browser/plugins/pdf_iframe_navigation_throttle.h
+++ b/chrome/browser/plugins/pdf_iframe_navigation_throttle.h
@@ -6,12 +6,16 @@
 #define CHROME_BROWSER_PLUGINS_PDF_IFRAME_NAVIGATION_THROTTLE_H_
 
 #include <memory>
+#include <vector>
 
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "content/public/browser/navigation_throttle.h"
+#include "ppapi/buildflags/buildflags.h"
 
 namespace content {
 class NavigationHandle;
+struct WebPluginInfo;
 }  // namespace content
 
 class PDFIFrameNavigationThrottle : public content::NavigationThrottle {
@@ -25,6 +29,17 @@
   // content::NavigationThrottle:
   ThrottleCheckResult WillProcessResponse() override;
   const char* GetNameForLogging() override;
+
+ private:
+#if BUILDFLAG(ENABLE_PLUGINS)
+  // Callback to check on the PDF plugin status after loading the plugin list.
+  void OnPluginsLoaded(const std::vector<content::WebPluginInfo>& plugins);
+#endif
+
+  // Loads the placeholder HTML into the IFRAME.
+  void LoadPlaceholderHTML();
+
+  base::WeakPtrFactory<PDFIFrameNavigationThrottle> weak_factory_{this};
 };
 
 #endif  // CHROME_BROWSER_PLUGINS_PDF_IFRAME_NAVIGATION_THROTTLE_H_
diff --git a/chrome/browser/plugins/pdf_iframe_navigation_throttle_unittest.cc b/chrome/browser/plugins/pdf_iframe_navigation_throttle_unittest.cc
index 0d8f7367..67e5415 100644
--- a/chrome/browser/plugins/pdf_iframe_navigation_throttle_unittest.cc
+++ b/chrome/browser/plugins/pdf_iframe_navigation_throttle_unittest.cc
@@ -6,8 +6,12 @@
 
 #include "base/bind.h"
 #include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/plugins/chrome_plugin_service_filter.h"
+#include "chrome/common/chrome_content_client.h"
 #include "chrome/common/chrome_features.h"
+#include "chrome/common/pdf_util.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_profile.h"
 #include "content/public/test/mock_navigation_handle.h"
@@ -21,16 +25,8 @@
 
 namespace {
 
-const char kHeader[] = "HTTP/1.1 200 OK\r\n";
 const char kExampleURL[] = "http://example.com";
 
-#if BUILDFLAG(ENABLE_PLUGINS)
-void PluginsLoadedCallback(base::OnceClosure callback,
-                           const std::vector<content::WebPluginInfo>& plugins) {
-  std::move(callback).Run();
-}
-#endif
-
 }  // namespace
 
 class PDFIFrameNavigationThrottleTest : public ChromeRenderViewHostTestHarness {
@@ -45,10 +41,15 @@
 #endif
   }
 
-  std::string GetHeaderWithMimeType(const std::string& mime_type) {
-    return "HTTP/1.1 200 OK\r\n"
-           "content-type: " +
-           mime_type + "\r\n";
+  scoped_refptr<net::HttpResponseHeaders> GetHeaderWithMimeType(
+      const std::string& mime_type) {
+    std::string raw_response_headers =
+        "HTTP/1.1 200 OK\r\n"
+        "content-type: " +
+        mime_type + "\r\n";
+    return base::MakeRefCounted<net::HttpResponseHeaders>(
+        net::HttpUtil::AssembleRawHeaders(raw_response_headers.c_str(),
+                                          raw_response_headers.size()));
   }
 
   content::RenderFrameHost* subframe() { return subframe_; }
@@ -58,13 +59,21 @@
     ChromeRenderViewHostTestHarness::SetUp();
 
 #if BUILDFLAG(ENABLE_PLUGINS)
-    content::PluginService::GetInstance()->Init();
+    content::PluginService* plugin_service =
+        content::PluginService::GetInstance();
+    plugin_service->Init();
+    plugin_service->SetFilter(ChromePluginServiceFilter::GetInstance());
 
-    // Load plugins.
-    base::RunLoop run_loop;
-    content::PluginService::GetInstance()->GetPlugins(
-        base::BindOnce(&PluginsLoadedCallback, run_loop.QuitClosure()));
-    run_loop.Run();
+    // Register a fake PDF Viewer plugin into our plugin service.
+    content::WebPluginInfo info;
+    info.name =
+        base::ASCIIToUTF16(ChromeContentClient::kPDFExtensionPluginName);
+    info.mime_types.push_back(content::WebPluginMimeType(
+        kPDFMimeType, "pdf", "Fake PDF description"));
+    plugin_service->RegisterInternalPlugin(info, true);
+
+    // Set the plugin list as dirty, like when the browser first starts.
+    plugin_service->RefreshPlugins();
 #endif
 
     content::RenderFrameHostTester::For(main_rfh())
@@ -84,62 +93,56 @@
   SetAlwaysOpenPdfExternallyForTests(true);
 
   // Never create throttle for main frames.
-  std::string raw_response_headers =
-      net::HttpUtil::AssembleRawHeaders(kHeader, strlen(kHeader));
-  scoped_refptr<net::HttpResponseHeaders> headers =
-      new net::HttpResponseHeaders(raw_response_headers);
   content::MockNavigationHandle handle(GURL(kExampleURL), main_rfh());
-  handle.set_response_headers(headers.get());
-  std::unique_ptr<content::NavigationThrottle> throttle =
-      PDFIFrameNavigationThrottle::MaybeCreateThrottleFor(&handle);
-  ASSERT_EQ(nullptr, throttle);
+  handle.set_response_headers(GetHeaderWithMimeType(""));
+  ASSERT_EQ(nullptr,
+            PDFIFrameNavigationThrottle::MaybeCreateThrottleFor(&handle));
 
   // Create a throttle for subframes.
   handle.set_render_frame_host(subframe());
-  throttle = PDFIFrameNavigationThrottle::MaybeCreateThrottleFor(&handle);
-  ASSERT_NE(nullptr, throttle);
+  ASSERT_NE(nullptr,
+            PDFIFrameNavigationThrottle::MaybeCreateThrottleFor(&handle));
 }
 
 TEST_F(PDFIFrameNavigationThrottleTest, InterceptPDFOnly) {
   // Setup
   SetAlwaysOpenPdfExternallyForTests(true);
 
-  std::string raw_response_headers = GetHeaderWithMimeType("application/pdf");
-  raw_response_headers = net::HttpUtil::AssembleRawHeaders(
-      raw_response_headers.c_str(), raw_response_headers.size());
-  scoped_refptr<net::HttpResponseHeaders> headers =
-      new net::HttpResponseHeaders(raw_response_headers);
+  // Load plugins to keep this test synchronous.
+#if BUILDFLAG(ENABLE_PLUGINS)
+  base::RunLoop run_loop;
+  content::PluginService::GetInstance()->GetPlugins(base::BindRepeating(
+      [](base::RunLoop* run_loop,
+         const std::vector<content::WebPluginInfo>& plugins) {
+        run_loop->Quit();
+      },
+      base::Unretained(&run_loop)));
+  run_loop.Run();
+#endif
+
   content::MockNavigationHandle handle(GURL(kExampleURL), subframe());
-  handle.set_response_headers(headers.get());
+  handle.set_response_headers(GetHeaderWithMimeType("application/pdf"));
 
   // Verify that we CANCEL for PDF mime type.
   std::unique_ptr<content::NavigationThrottle> throttle =
       PDFIFrameNavigationThrottle::MaybeCreateThrottleFor(&handle);
-
   ASSERT_NE(nullptr, throttle);
   ASSERT_EQ(content::NavigationThrottle::CANCEL_AND_IGNORE,
             throttle->WillProcessResponse().action());
 
   // Verify that we PROCEED for other mime types.
   // Blank mime type
-  raw_response_headers =
-      net::HttpUtil::AssembleRawHeaders(kHeader, strlen(kHeader));
-  headers = new net::HttpResponseHeaders(raw_response_headers);
-  handle.set_response_headers(headers.get());
+  handle.set_response_headers(GetHeaderWithMimeType(""));
   ASSERT_EQ(content::NavigationThrottle::PROCEED,
             throttle->WillProcessResponse().action());
 
   // HTML
-  raw_response_headers = GetHeaderWithMimeType("text/html");
-  headers = new net::HttpResponseHeaders(raw_response_headers);
-  handle.set_response_headers(headers.get());
+  handle.set_response_headers(GetHeaderWithMimeType("text/html"));
   ASSERT_EQ(content::NavigationThrottle::PROCEED,
             throttle->WillProcessResponse().action());
 
   // PNG
-  raw_response_headers = GetHeaderWithMimeType("image/png");
-  headers = new net::HttpResponseHeaders(raw_response_headers);
-  handle.set_response_headers(headers.get());
+  handle.set_response_headers(GetHeaderWithMimeType("image/png"));
   ASSERT_EQ(content::NavigationThrottle::PROCEED,
             throttle->WillProcessResponse().action());
 }
@@ -167,26 +170,54 @@
 }
 
 #if BUILDFLAG(ENABLE_PLUGINS)
-TEST_F(PDFIFrameNavigationThrottleTest, CancelOnlyIfPDFViewerIsDisabled) {
-  // Setup
-  std::string raw_response_headers = GetHeaderWithMimeType("application/pdf");
-  raw_response_headers = net::HttpUtil::AssembleRawHeaders(
-      raw_response_headers.c_str(), raw_response_headers.size());
-  scoped_refptr<net::HttpResponseHeaders> headers =
-      new net::HttpResponseHeaders(raw_response_headers);
+TEST_F(PDFIFrameNavigationThrottleTest, ProceedIfPDFViewerIsEnabled) {
   content::MockNavigationHandle handle(GURL(kExampleURL), subframe());
-  handle.set_response_headers(headers.get());
+  handle.set_response_headers(GetHeaderWithMimeType("application/pdf"));
 
-  // Test PDF Viewer enabled.
   SetAlwaysOpenPdfExternallyForTests(false);
+
+  // First time should asynchronously Resume the navigation.
   std::unique_ptr<content::NavigationThrottle> throttle =
       PDFIFrameNavigationThrottle::MaybeCreateThrottleFor(&handle);
-  ASSERT_EQ(nullptr, throttle);
+  ASSERT_NE(nullptr, throttle);
+  ASSERT_EQ(content::NavigationThrottle::DEFER,
+            throttle->WillProcessResponse().action());
+  base::RunLoop run_loop;
+  throttle->set_resume_callback_for_testing(run_loop.QuitClosure());
+  run_loop.Run();
 
-  // Test PDF Viewer disabled.
-  SetAlwaysOpenPdfExternallyForTests(true);
+  // Subsequent times should synchronously PROCEED the navigation.
   throttle = PDFIFrameNavigationThrottle::MaybeCreateThrottleFor(&handle);
+  ASSERT_NE(nullptr, throttle);
+  ASSERT_EQ(content::NavigationThrottle::PROCEED,
+            throttle->WillProcessResponse().action());
+}
 
+TEST_F(PDFIFrameNavigationThrottleTest, CancelIfPDFViewerIsDisabled) {
+  content::MockNavigationHandle handle(GURL(kExampleURL), subframe());
+  handle.set_response_headers(GetHeaderWithMimeType("application/pdf"));
+
+  SetAlwaysOpenPdfExternallyForTests(true);
+
+  // First time should asynchronously Cancel the navigation.
+  std::unique_ptr<content::NavigationThrottle> throttle =
+      PDFIFrameNavigationThrottle::MaybeCreateThrottleFor(&handle);
+  ASSERT_NE(nullptr, throttle);
+  ASSERT_EQ(content::NavigationThrottle::DEFER,
+            throttle->WillProcessResponse().action());
+  base::RunLoop run_loop;
+  throttle->set_cancel_deferred_navigation_callback_for_testing(
+      base::BindRepeating(
+          [](base::RunLoop* run_loop,
+             content::NavigationThrottle::ThrottleCheckResult result) {
+            ASSERT_EQ(content::NavigationThrottle::CANCEL_AND_IGNORE, result);
+            run_loop->Quit();
+          },
+          base::Unretained(&run_loop)));
+  run_loop.Run();
+
+  // Subsequent times should synchronously CANCEL the navigation.
+  throttle = PDFIFrameNavigationThrottle::MaybeCreateThrottleFor(&handle);
   ASSERT_NE(nullptr, throttle);
   ASSERT_EQ(content::NavigationThrottle::CANCEL_AND_IGNORE,
             throttle->WillProcessResponse().action());
diff --git a/chrome/browser/printing/cloud_print/privet_http_unittest.cc b/chrome/browser/printing/cloud_print/privet_http_unittest.cc
index 4d5dcb0..0d99892 100644
--- a/chrome/browser/printing/cloud_print/privet_http_unittest.cc
+++ b/chrome/browser/printing/cloud_print/privet_http_unittest.cc
@@ -752,6 +752,8 @@
   EXPECT_TRUE(SuccessfulResponse(kSubmitDocURL, kSampleLocalPrintResponse));
   EXPECT_EQ("foobar", GetUploadData(kSubmitDocURL));
 
+  EXPECT_EQ(printing::DuplexMode::SIMPLEX,
+            pwg_converter_->bitmap_settings().duplex_mode);
   EXPECT_EQ(printing::TRANSFORM_NORMAL,
             pwg_converter_->bitmap_settings().odd_page_transform);
   EXPECT_FALSE(pwg_converter_->bitmap_settings().rotate_all_pages);
@@ -788,6 +790,8 @@
       SuccessfulResponse(kSubmitDocWithJobIDURL, kSampleLocalPrintResponse));
   EXPECT_EQ("foobar", GetUploadData(kSubmitDocWithJobIDURL));
 
+  EXPECT_EQ(printing::DuplexMode::SHORT_EDGE,
+            pwg_converter_->bitmap_settings().duplex_mode);
   EXPECT_EQ(printing::TRANSFORM_ROTATE_180,
             pwg_converter_->bitmap_settings().odd_page_transform);
   EXPECT_FALSE(pwg_converter_->bitmap_settings().rotate_all_pages);
diff --git a/chrome/browser/printing/pwg_raster_converter.cc b/chrome/browser/printing/pwg_raster_converter.cc
index db841c9..3e86443 100644
--- a/chrome/browser/printing/pwg_raster_converter.cc
+++ b/chrome/browser/printing/pwg_raster_converter.cc
@@ -245,17 +245,20 @@
       raster_capability.value().document_sheet_back;
 
   PwgRasterSettings result;
-  result.odd_page_transform = TRANSFORM_NORMAL;
   switch (duplex_value) {
     case cloud_devices::printer::NO_DUPLEX:
+      result.duplex_mode = DuplexMode::SIMPLEX;
+      result.odd_page_transform = TRANSFORM_NORMAL;
       break;
     case cloud_devices::printer::LONG_EDGE:
+      result.duplex_mode = DuplexMode::LONG_EDGE;
       if (document_sheet_back == cloud_devices::printer::ROTATED)
         result.odd_page_transform = TRANSFORM_ROTATE_180;
       else if (document_sheet_back == cloud_devices::printer::FLIPPED)
         result.odd_page_transform = TRANSFORM_FLIP_VERTICAL;
       break;
     case cloud_devices::printer::SHORT_EDGE:
+      result.duplex_mode = DuplexMode::SHORT_EDGE;
       if (document_sheet_back == cloud_devices::printer::MANUAL_TUMBLE)
         result.odd_page_transform = TRANSFORM_ROTATE_180;
       else if (document_sheet_back == cloud_devices::printer::FLIPPED)
diff --git a/chrome/browser/printing/pwg_raster_converter_browsertest.cc b/chrome/browser/printing/pwg_raster_converter_browsertest.cc
index afb6f4c..c1e192c 100644
--- a/chrome/browser/printing/pwg_raster_converter_browsertest.cc
+++ b/chrome/browser/printing/pwg_raster_converter_browsertest.cc
@@ -28,10 +28,14 @@
 constexpr char kPdfToPwgRasterColorTestFile[] = "pdf_to_pwg_raster_test_32.pwg";
 constexpr char kPdfToPwgRasterMonoTestFile[] =
     "pdf_to_pwg_raster_mono_test_32.pwg";
+constexpr char kPdfToPwgRasterLongEdgeTestFile[] =
+    "pdf_to_pwg_raster_long_edge_test_32.pwg";
 #else
 constexpr char kPdfToPwgRasterColorTestFile[] = "pdf_to_pwg_raster_test.pwg";
 constexpr char kPdfToPwgRasterMonoTestFile[] =
     "pdf_to_pwg_raster_mono_test.pwg";
+constexpr char kPdfToPwgRasterLongEdgeTestFile[] =
+    "pdf_to_pwg_raster_long_edge_test.pwg";
 #endif
 
 void ResultCallbackImpl(bool* called,
@@ -123,6 +127,7 @@
                                  /*use_color=*/true,
                                  PdfRenderSettings::Mode::NORMAL);
   PwgRasterSettings pwg_settings;
+  pwg_settings.duplex_mode = DuplexMode::SIMPLEX;
   pwg_settings.odd_page_transform = PwgRasterTransformType::TRANSFORM_NORMAL;
   pwg_settings.rotate_all_pages = false;
   pwg_settings.reverse_page_order = false;
@@ -150,6 +155,7 @@
                                  /*use_color=*/false,
                                  PdfRenderSettings::Mode::NORMAL);
   PwgRasterSettings pwg_settings;
+  pwg_settings.duplex_mode = DuplexMode::SIMPLEX;
   pwg_settings.odd_page_transform = PwgRasterTransformType::TRANSFORM_NORMAL;
   pwg_settings.rotate_all_pages = false;
   pwg_settings.reverse_page_order = false;
@@ -164,4 +170,32 @@
   ComparePwgOutput(expected_pwg_file, std::move(pwg_region));
 }
 
+IN_PROC_BROWSER_TEST_F(PdfToPwgRasterBrowserTest, TestSuccessLongDuplex) {
+  base::ScopedAllowBlockingForTesting allow_blocking;
+
+  base::FilePath test_data_dir;
+  scoped_refptr<base::RefCountedString> pdf_data;
+  GetPdfData("pdf_to_pwg_raster_test.pdf", &test_data_dir, &pdf_data);
+
+  PdfRenderSettings pdf_settings(gfx::Rect(0, 0, 500, 500), gfx::Point(0, 0),
+                                 /*dpi=*/gfx::Size(1000, 1000),
+                                 /*autorotate=*/false,
+                                 /*use_color=*/false,
+                                 PdfRenderSettings::Mode::NORMAL);
+  PwgRasterSettings pwg_settings;
+  pwg_settings.duplex_mode = DuplexMode::LONG_EDGE;
+  pwg_settings.odd_page_transform = PwgRasterTransformType::TRANSFORM_NORMAL;
+  pwg_settings.rotate_all_pages = false;
+  pwg_settings.reverse_page_order = false;
+  pwg_settings.use_color = false;
+
+  base::ReadOnlySharedMemoryRegion pwg_region;
+  Convert(pdf_data.get(), pdf_settings, pwg_settings,
+          /*expect_success=*/true, &pwg_region);
+
+  base::FilePath expected_pwg_file =
+      test_data_dir.AppendASCII(kPdfToPwgRasterLongEdgeTestFile);
+  ComparePwgOutput(expected_pwg_file, std::move(pwg_region));
+}
+
 }  // namespace printing
diff --git a/chrome/browser/resources/app_management/actions.js b/chrome/browser/resources/app_management/actions.js
index 0427f84d..a195768e 100644
--- a/chrome/browser/resources/app_management/actions.js
+++ b/chrome/browser/resources/app_management/actions.js
@@ -43,7 +43,7 @@
    * @param {string=} id
    */
   function changePage(pageType, id) {
-    if (pageType == PageType.DETAIL && !id) {
+    if (pageType === PageType.DETAIL && !id) {
       console.warn(
           'Tried to load app detail page without providing an app id.');
     }
diff --git a/chrome/browser/resources/app_management/main_view.js b/chrome/browser/resources/app_management/main_view.js
index c1b75baf..5d1059d1 100644
--- a/chrome/browser/resources/app_management/main_view.js
+++ b/chrome/browser/resources/app_management/main_view.js
@@ -141,7 +141,7 @@
             .map(function(p) {
               // Make the titles of app collapsible but make the number in the
               // "X other app(s)" part non-collapsible.
-              p.collapsible = !!p.arg && p.arg != '$' + placeholder;
+              p.collapsible = !!p.arg && p.arg !== '$' + placeholder;
               return p;
             });
     return pieces;
@@ -158,7 +158,7 @@
     const textContainer = this.$['notifications-sublabel'];
     textContainer.textContent = '';
     for (const p of pieces) {
-      if (!p.value || p.value.length == 0) {
+      if (!p.value || p.value.length === 0) {
         return;
       }
 
diff --git a/chrome/browser/resources/app_management/reducers.js b/chrome/browser/resources/app_management/reducers.js
index 2263e85..337bdca2 100644
--- a/chrome/browser/resources/app_management/reducers.js
+++ b/chrome/browser/resources/app_management/reducers.js
@@ -76,12 +76,12 @@
    * @return {Page}
    */
   CurrentPageState.changePage = function(apps, action) {
-    if (action.pageType == PageType.DETAIL && apps[action.id]) {
+    if (action.pageType === PageType.DETAIL && apps[action.id]) {
       return {
         pageType: PageType.DETAIL,
         selectedAppId: action.id,
       };
-    } else if (action.pageType == PageType.NOTIFICATIONS) {
+    } else if (action.pageType === PageType.NOTIFICATIONS) {
       return {
         pageType: PageType.NOTIFICATIONS,
         selectedAppId: null,
@@ -100,8 +100,8 @@
    * @return {Page}
    */
   CurrentPageState.removeApp = function(currentPage, action) {
-    if (currentPage.pageType == PageType.DETAIL &&
-        currentPage.selectedAppId == action.id) {
+    if (currentPage.pageType === PageType.DETAIL &&
+        currentPage.selectedAppId === action.id) {
       return {
         pageType: PageType.MAIN,
         selectedAppId: null,
diff --git a/chrome/browser/resources/app_management/router.html b/chrome/browser/resources/app_management/router.html
index 0e404f2..bbe2548 100644
--- a/chrome/browser/resources/app_management/router.html
+++ b/chrome/browser/resources/app_management/router.html
@@ -8,7 +8,7 @@
 
 <dom-module id="app-management-router">
   <template>
-    <iron-location query="{{urlQuery_}}" path="{{path_}}" dwell-time="-1">
+    <iron-location id="iron-location" query="{{urlQuery_}}" path="{{path_}}">
     </iron-location>
     <iron-query-params params-string="{{query_}}"
         params-object="{{queryParams_}}"></iron-query-params>
diff --git a/chrome/browser/resources/app_management/router.js b/chrome/browser/resources/app_management/router.js
index abfc9dc4..0b5dae0 100644
--- a/chrome/browser/resources/app_management/router.js
+++ b/chrome/browser/resources/app_management/router.js
@@ -77,7 +77,12 @@
 
   /** @private */
   publishUrl_: function() {
+    // Disable pushing urls into the history stack, so that we only push one
+    // state.
+    this.$['iron-location'].dwellTime = Infinity;
     this.publishQueryParams_();
+    // Re-enable pushing urls into the history stack.
+    this.$['iron-location'].dwellTime = 0;
     this.publishPath_();
   },
 
diff --git a/chrome/browser/resources/chromeos/wallpaper_manager/OWNERS b/chrome/browser/resources/chromeos/wallpaper_manager/OWNERS
index 13c45fc..35e595f6 100644
--- a/chrome/browser/resources/chromeos/wallpaper_manager/OWNERS
+++ b/chrome/browser/resources/chromeos/wallpaper_manager/OWNERS
@@ -1,3 +1,4 @@
+maybelle@chromium.org
 wzang@chromium.org
 xdai@chromium.org
 
diff --git a/chrome/browser/resources/md_extensions/detail_view.js b/chrome/browser/resources/md_extensions/detail_view.js
index c2ef73f..1ff977b 100644
--- a/chrome/browser/resources/md_extensions/detail_view.js
+++ b/chrome/browser/resources/md_extensions/detail_view.js
@@ -48,6 +48,14 @@
     },
 
     /**
+     * Focuses the extensions options button. This should be used after the
+     * dialog closes.
+     */
+    focusOptionsButton: function() {
+      this.$$('#extensions-options').focus();
+    },
+
+    /**
      * Focuses the back button when page is loaded.
      * @private
      */
diff --git a/chrome/browser/resources/md_extensions/manager.js b/chrome/browser/resources/md_extensions/manager.js
index 2125fc2..4eba159 100644
--- a/chrome/browser/resources/md_extensions/manager.js
+++ b/chrome/browser/resources/md_extensions/manager.js
@@ -458,7 +458,6 @@
 
       const optionsDialog = this.$$('#options-dialog');
       if (optionsDialog && optionsDialog.open) {
-        optionsDialog.close();
         this.showOptionsDialog_ = false;
       }
 
@@ -535,6 +534,7 @@
     /** @private */
     onOptionsDialogClose_: function() {
       this.showOptionsDialog_ = false;
+      this.$$('extensions-detail-view').focusOptionsButton();
     },
 
     /** @private */
diff --git a/chrome/browser/resources/print_preview/cloud_print_interface.js b/chrome/browser/resources/print_preview/cloud_print_interface.js
index 319b8e7..7fb4b8d 100644
--- a/chrome/browser/resources/print_preview/cloud_print_interface.js
+++ b/chrome/browser/resources/print_preview/cloud_print_interface.js
@@ -23,6 +23,16 @@
 
 /**
  * @typedef {{
+ *   status: number,
+ *   errorCode: number,
+ *   message: string,
+ *   origin: !print_preview.DestinationOrigin,
+ * }}
+ */
+cloudprint.CloudPrintInterfaceErrorEventDetail;
+
+/**
+ * @typedef {{
  *   user: string,
  *   origin: !print_preview.DestinationOrigin,
  *   printers: (!Array<!print_preview.Destination>|undefined),
diff --git a/chrome/browser/resources/print_preview/cloud_print_interface_js.js b/chrome/browser/resources/print_preview/cloud_print_interface_js.js
index 4bed3cf..8ede13af 100644
--- a/chrome/browser/resources/print_preview/cloud_print_interface_js.js
+++ b/chrome/browser/resources/print_preview/cloud_print_interface_js.js
@@ -288,10 +288,7 @@
      * request.
      * @param {!cloudprint.CloudPrintRequest} request Request that has been
      *     completed.
-     * @return {!{ status: number,
-     *             errorCode: number,
-     *             message: string,
-     *             origin: !print_preview.DestinationOrigin }} Information
+     * @return {!cloudprint.CloudPrintInterfaceErrorEventDetail} Information
      *     about the error.
      * @private
      */
diff --git a/chrome/browser/resources/print_preview/data/destination_store.js b/chrome/browser/resources/print_preview/data/destination_store.js
index 3922b57..b71dfaf 100644
--- a/chrome/browser/resources/print_preview/data/destination_store.js
+++ b/chrome/browser/resources/print_preview/data/destination_store.js
@@ -1255,13 +1255,12 @@
     /**
      * Called when the /search call completes, either successfully or not.
      * In case of success, stores fetched destinations.
-     * @param {!CustomEvent} event Contains the request result.
+     * @param {!CustomEvent<!cloudprint.CloudPrintInterfaceSearchDoneDetail>}
+     *      event Contains the request result.
      * @private
      */
     onCloudPrintSearchDone_(event) {
-      const payload =
-          /** @type {!cloudprint.CloudPrintInterfaceSearchDoneDetail} */ (
-              event.detail);
+      const payload = event.detail;
       if (payload.printers && payload.printers.length > 0) {
         this.insertDestinations_(payload.printers);
         if (this.selectFirstDestination_) {
@@ -1344,8 +1343,9 @@
 
     /**
      * Called when printer sharing invitation was processed successfully.
-     * @param {!CustomEvent} event Contains detailed information about the
-     *     invite and newly accepted destination (if known).
+     * @param {!CustomEvent<!cloudprint.CloudPrintInterfaceProcessInviteDetail>}
+     *     event Contains detailed information about the invite and newly
+     *     accepted destination (if known).
      * @private
      */
     onCloudPrintProcessInviteDone_(event) {
diff --git a/chrome/browser/resources/print_preview/data/invitation_store.js b/chrome/browser/resources/print_preview/data/invitation_store.js
index 014ceeae..eeb06ea 100644
--- a/chrome/browser/resources/print_preview/data/invitation_store.js
+++ b/chrome/browser/resources/print_preview/data/invitation_store.js
@@ -149,16 +149,14 @@
 
     /**
      * Called when printer sharing invitations are fetched.
-     * @param {!CustomEvent} event Contains the list of invitations.
+     * @param {!CustomEvent<!cloudprint.CloudPrintInterfaceInvitesDoneDetail>}
+     *     event Contains the list of invitations.
      * @private
      */
     onCloudPrintInvitesDone_(event) {
-      const invitesDoneDetail =
-          /** @type {!cloudprint.CloudPrintInterfaceInvitesDoneDetail} */ (
-              event.detail);
-      this.loadStatus_[invitesDoneDetail.user] =
+      this.loadStatus_[event.detail.user] =
           print_preview.InvitationStoreLoadStatus.DONE;
-      this.invitations_[invitesDoneDetail.user] = invitesDoneDetail.invitations;
+      this.invitations_[event.detail.user] = event.detail.invitations;
 
       this.dispatchEvent(
           new CustomEvent(InvitationStore.EventType.INVITATION_SEARCH_DONE));
@@ -166,25 +164,23 @@
 
     /**
      * Called when printer sharing invitations fetch has failed.
-     * @param {!CustomEvent} event
+     * @param {!CustomEvent<string>} event Contains the user for whom invite
+     *     fetch failed.
      * @private
      */
     onCloudPrintInvitesFailed_(event) {
-      this.loadStatus_[/** @type {string} */ (event.detail)] =
+      this.loadStatus_[event.detail] =
           print_preview.InvitationStoreLoadStatus.FAILED;
     }
 
     /**
      * Called when printer sharing invitation was processed successfully.
-     * @param {!CustomEvent} event Contains detailed information about the
-     *     invite.
+     * @param {!CustomEvent<!cloudprint.CloudPrintInterfaceProcessInviteDetail>}
+     *     event Contains detailed information about the invite.
      * @private
      */
     onCloudPrintProcessInviteDone_(event) {
-      this.invitationProcessed_(
-          /** @type {!cloudprint.CloudPrintInterfaceProcessInviteDetail} */ (
-              event.detail)
-              .invitation);
+      this.invitationProcessed_(event.detail.invitation);
       this.dispatchEvent(
           new CustomEvent(InvitationStore.EventType.INVITATION_PROCESSED));
     }
diff --git a/chrome/browser/resources/print_preview/new/app.js b/chrome/browser/resources/print_preview/new/app.js
index 030cdd3..eeda43d 100644
--- a/chrome/browser/resources/print_preview/new/app.js
+++ b/chrome/browser/resources/print_preview/new/app.js
@@ -613,7 +613,8 @@
   /**
    * Updates the cloud print status to NOT_SIGNED_IN if there is an
    * authentication error.
-   * @param {!CustomEvent<{status: number}>} event Contains the error status
+   * @param {!CustomEvent<!cloudprint.CloudPrintInterfaceErrorEventDetail>}
+   *     event Contains the error status
    * @private
    */
   checkCloudPrintStatus_: function(event) {
@@ -631,7 +632,8 @@
   /**
    * Called when there was an error communicating with Google Cloud print.
    * Displays an error message in the print header.
-   * @param {!CustomEvent} event Contains the error message.
+   * @param {!CustomEvent<!cloudprint.CloudPrintInterfaceErrorEventDetail>}
+   *     event Contains the error message.
    * @private
    */
   onCloudPrintError_: function(event) {
diff --git a/chrome/browser/resources/print_preview/new/margin_control.js b/chrome/browser/resources/print_preview/new/margin_control.js
index 3ba711a..3d900e94 100644
--- a/chrome/browser/resources/print_preview/new/margin_control.js
+++ b/chrome/browser/resources/print_preview/new/margin_control.js
@@ -140,7 +140,7 @@
   },
 
   /**
-   * @param {!CustomEvent} e Contains the new value of the input.
+   * @param {!CustomEvent<string>} e Contains the new value of the input.
    * @private
    */
   onInputChange_: function(e) {
diff --git a/chrome/browser/resources/print_preview/new/margin_control_container.js b/chrome/browser/resources/print_preview/new/margin_control_container.js
index ac4a7123..7cb7a10b 100644
--- a/chrome/browser/resources/print_preview/new/margin_control_container.js
+++ b/chrome/browser/resources/print_preview/new/margin_control_container.js
@@ -336,8 +336,7 @@
   },
 
   /**
-   * @param {!CustomEvent} e Contains information about what control fired the
-   *     event.
+   * @param {!Event} e Contains information about what control fired the event.
    * @private
    */
   onTextFocus_: function(e) {
@@ -454,8 +453,9 @@
   },
 
   /**
-   * @param {!CustomEvent} e Event fired when a control's text field is blurred.
-   *     Contains information about whether the control is in an invalid state.
+   * @param {!CustomEvent<boolean>} e Event fired when a control's text field
+   *     is blurred. Contains information about whether the control is in an
+   *     invalid state.
    * @private
    */
   onTextBlur_: function(e) {
diff --git a/chrome/browser/resources/print_preview/new/print_preview_search_box.js b/chrome/browser/resources/print_preview/new/print_preview_search_box.js
index 6e986a33..2d2885c 100644
--- a/chrome/browser/resources/print_preview/new/print_preview_search_box.js
+++ b/chrome/browser/resources/print_preview/new/print_preview_search_box.js
@@ -39,12 +39,14 @@
   },
 
   /**
-   * @param {!CustomEvent} e Event containing the new search.
+   * @param {!CustomEvent<string>} e Event containing the new search.
    * @private
    */
   onSearchChanged_: function(e) {
-    let safeQuery = e.detail.trim().replace(SANITIZE_REGEX, '\\$&');
-    safeQuery = safeQuery.length > 0 ? new RegExp(`(${safeQuery})`, 'i') : null;
+    const safeQueryString = e.detail.trim().replace(SANITIZE_REGEX, '\\$&');
+    const safeQuery = safeQueryString.length > 0 ?
+        new RegExp(`(${safeQueryString})`, 'i') :
+        null;
     if (this.timeout_) {
       clearTimeout(this.timeout_);
     }
diff --git a/chrome/browser/resources/welcome/welcome.css b/chrome/browser/resources/welcome/welcome.css
index d859a2e..2d08e69 100644
--- a/chrome/browser/resources/welcome/welcome.css
+++ b/chrome/browser/resources/welcome/welcome.css
@@ -15,6 +15,10 @@
   padding: 8px;
 }
 
+[dark] body {
+  color: var(--cr-primary-text-color);
+}
+
 .watermark {
   -webkit-mask-image: url(chrome://resources/images/google_logo.svg);
   -webkit-mask-repeat: no-repeat;
@@ -27,6 +31,10 @@
   width: 74px;
 }
 
+[dark] .watermark {
+  background: var(--cr-secondary-text-color);
+}
+
 @media(max-height: 608px) {
   .watermark {
     display: none;
diff --git a/chrome/browser/resources/welcome/welcome.html b/chrome/browser/resources/welcome/welcome.html
index a1e54f0..4aa0ce86 100644
--- a/chrome/browser/resources/welcome/welcome.html
+++ b/chrome/browser/resources/welcome/welcome.html
@@ -1,5 +1,5 @@
 <!doctype html>
-<html dir="$i18n{textdirection}" lang="$i18n{language}">
+<html dir="$i18n{textdirection}" lang="$i18n{language}" $i18n{dark}>
 <head>
   <meta charset="utf-8">
   <title>$i18n{headerText}</title>
@@ -7,14 +7,21 @@
   <link rel="import" href="chrome://resources/html/polymer.html">
 
   <link rel="import" href="chrome://resources/cr_elements/paper_button_style_css.html">
+  <link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
   <link rel="import" href="chrome://resources/html/cr.html">
   <link rel="import" href="chrome://resources/html/util.html">
   <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
-  <link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html">
 
+  <link rel="stylesheet" href="chrome://resources/css/md_colors.css">
   <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
   <link rel="stylesheet" href="chrome://welcome/welcome.css">
 
+  <style>
+    html[dark] {
+      background-color: var(--md-background-color);
+    }
+  </style>
+
   <dom-module id="welcome-app">
     <template>
       <style include="paper-button-style">
@@ -128,13 +135,17 @@
 
         .subheading {
           animation: fadeInAndSlideUp 600ms 1.9s cubic-bezier(.4, .2, 0, 1) both;
-          color: #5f6368;
+          color: rgb(95, 99, 104);
           font-size: 1em;
           font-weight: 500;
           margin-top: .25em;
           text-align: center;
         }
 
+        :host-context([dark]) .subheading {
+          color: var(--cr-secondary-text-color);
+        }
+
         .logo {
           animation: fadeIn 600ms both, bounce 1s 600ms linear both;
           height: 96px;
@@ -167,7 +178,6 @@
         .signin {
           animation: fadeInAndSlideUp 600ms 2s cubic-bezier(.4, .2, 0, 1) both;
           margin-top: 3em;
-          text-align: left;
         }
 
         .signin-description {
@@ -227,5 +237,6 @@
 <body>
   <welcome-app></welcome-app>
   <div class="watermark"></div>
+  <link rel="import" href="chrome://resources/html/dark_mode.html">
 </body>
 </html>
diff --git a/chrome/browser/themes/browser_theme_pack.cc b/chrome/browser/themes/browser_theme_pack.cc
index a6893ef5..14efc2ca 100644
--- a/chrome/browser/themes/browser_theme_pack.cc
+++ b/chrome/browser/themes/browser_theme_pack.cc
@@ -634,6 +634,10 @@
 
   pack->CropImages(&pack->images_);
 
+  // Set toolbar related elements' colors (e.g. status bubble, info bar,
+  // download shelf, detached bookmark bar) to toolbar color.
+  pack->SetToolbarRelatedColors();
+
   // Create toolbar image, and generate toolbar color from image where relevant.
   // This must be done after reading colors from JSON (so they can be used for
   // compositing the image).
@@ -1272,6 +1276,19 @@
   }
 }
 
+void BrowserThemePack::SetToolbarRelatedColors() {
+  // Propagate the user-specified Toolbar Color to similar elements (for
+  // backwards-compatibility with themes written before this toolbar processing
+  // was introduced).
+  SkColor toolbar_color;
+  if (GetColor(TP::COLOR_TOOLBAR, &toolbar_color)) {
+    SetColor(TP::COLOR_DETACHED_BOOKMARK_BAR_BACKGROUND, toolbar_color);
+    SetColor(TP::COLOR_INFOBAR, toolbar_color);
+    SetColor(TP::COLOR_DOWNLOAD_SHELF, toolbar_color);
+    SetColor(TP::COLOR_STATUS_BUBBLE, toolbar_color);
+  }
+}
+
 void BrowserThemePack::CreateToolbarImageAndColors(ImageCache* images) {
   ImageCache temp_output;
 
@@ -1285,15 +1302,7 @@
 
   constexpr int kToolbarColorId = TP::COLOR_TOOLBAR;
   SkColor toolbar_color;
-  // Propagate the user-specified Toolbar Color to similar elements (for
-  // backwards-compatibility with themes written before this toolbar processing
-  // was introduced).
-  if (GetColor(kToolbarColorId, &toolbar_color)) {
-    SetColor(TP::COLOR_DETACHED_BOOKMARK_BAR_BACKGROUND, toolbar_color);
-    SetColor(TP::COLOR_INFOBAR, toolbar_color);
-    SetColor(TP::COLOR_DOWNLOAD_SHELF, toolbar_color);
-    SetColor(TP::COLOR_STATUS_BUBBLE, toolbar_color);
-  } else {
+  if (!GetColor(kToolbarColorId, &toolbar_color)) {
     toolbar_color = TP::GetDefaultColor(kToolbarColorId, false);
   }
 
diff --git a/chrome/browser/themes/browser_theme_pack.h b/chrome/browser/themes/browser_theme_pack.h
index d6d97fb9..3131d6c 100644
--- a/chrome/browser/themes/browser_theme_pack.h
+++ b/chrome/browser/themes/browser_theme_pack.h
@@ -167,6 +167,10 @@
   // can be of any size. Source and destination is |images|.
   void CropImages(ImageCache* images) const;
 
+  // Set toolbar related elements' colors (e.g. status bubble, info bar,
+  // download shelf, detached bookmark bar) to toolbar color.
+  void SetToolbarRelatedColors();
+
   // Creates a composited toolbar image. Source and destination is |images|.
   // Also sets toolbar color corresponding to this image.
   void CreateToolbarImageAndColors(ImageCache* images);
diff --git a/chrome/browser/themes/browser_theme_pack_unittest.cc b/chrome/browser/themes/browser_theme_pack_unittest.cc
index 7ef4a00..d0ac3620 100644
--- a/chrome/browser/themes/browser_theme_pack_unittest.cc
+++ b/chrome/browser/themes/browser_theme_pack_unittest.cc
@@ -1014,6 +1014,30 @@
   EXPECT_EQ(infobar_color, status_bubble_color);
 }
 
+// Ensure that a specified 'toolbar' color is propagated to other 'bar' and
+// 'shelf' colors (before a new color is computed from the toolbar image).
+TEST_F(BrowserThemePackTest, TestToolbarColorPropagationNoImage) {
+  scoped_refptr<BrowserThemePack> pack;
+  BuildTestExtensionTheme("theme_test_toolbar_color_no_image", &pack);
+
+  SkColor infobar_color;
+  SkColor bookmark_bar_color;
+  SkColor download_shelf_color;
+  SkColor status_bubble_color;
+
+  EXPECT_TRUE(pack->GetColor(TP::COLOR_INFOBAR, &infobar_color));
+  EXPECT_TRUE(pack->GetColor(TP::COLOR_DETACHED_BOOKMARK_BAR_BACKGROUND,
+                             &bookmark_bar_color));
+  EXPECT_TRUE(pack->GetColor(TP::COLOR_DOWNLOAD_SHELF, &download_shelf_color));
+  EXPECT_TRUE(pack->GetColor(TP::COLOR_STATUS_BUBBLE, &status_bubble_color));
+
+  constexpr SkColor kExpectedColor = SkColorSetRGB(0, 255, 0);
+  EXPECT_EQ(infobar_color, kExpectedColor);
+  EXPECT_EQ(infobar_color, bookmark_bar_color);
+  EXPECT_EQ(infobar_color, download_shelf_color);
+  EXPECT_EQ(infobar_color, status_bubble_color);
+}
+
 // Ensure that, given an explicit toolbar color and a toolbar image, the output
 // color in COLOR_TOOLBAR reflects the color of the image (not the explicit
 // color).
diff --git a/chrome/browser/ui/app_list/app_service_app_item.cc b/chrome/browser/ui/app_list/app_service_app_item.cc
index 6390570..a9d9e32 100644
--- a/chrome/browser/ui/app_list/app_service_app_item.cc
+++ b/chrome/browser/ui/app_list/app_service_app_item.cc
@@ -52,7 +52,8 @@
     const app_list::AppListSyncableService::SyncItem* sync_item,
     const apps::AppUpdate& app_update)
     : ChromeAppListItem(profile, app_update.AppId()),
-      app_type_(app_update.AppType()) {
+      app_type_(app_update.AppType()),
+      is_platform_app_(false) {
   OnAppUpdate(app_update, true);
   if (sync_item && sync_item->item_ordinal.IsValid()) {
     UpdateFromSync(sync_item);
@@ -86,6 +87,11 @@
                                      weak_ptr_factory_.GetWeakPtr()));
     }
   }
+
+  if (in_constructor || app_update.IsPlatformAppChanged()) {
+    is_platform_app_ =
+        app_update.IsPlatformApp() == apps::mojom::OptionalBool::kTrue;
+  }
 }
 
 void AppServiceAppItem::Activate(int event_flags) {
@@ -97,12 +103,8 @@
 }
 
 void AppServiceAppItem::GetContextMenuModel(GetMenuModelCallback callback) {
-  // TODO(crbug.com/826982): don't hard-code false. The App Service should
-  // probably provide this.
-  const bool is_platform_app = false;
-
   context_menu_ = MakeAppContextMenu(app_type_, this, profile(), id(),
-                                     GetController(), is_platform_app);
+                                     GetController(), is_platform_app_);
   context_menu_->GetMenuModel(std::move(callback));
 }
 
diff --git a/chrome/browser/ui/app_list/app_service_app_item.h b/chrome/browser/ui/app_list/app_service_app_item.h
index 15e8bfa..47be350 100644
--- a/chrome/browser/ui/app_list/app_service_app_item.h
+++ b/chrome/browser/ui/app_list/app_service_app_item.h
@@ -51,6 +51,7 @@
   void OnLoadIcon(apps::mojom::IconValuePtr icon_value);
 
   apps::mojom::AppType app_type_;
+  bool is_platform_app_;
 
   std::unique_ptr<app_list::AppContextMenu> context_menu_;
 
diff --git a/chrome/browser/ui/app_list/search/app_service_app_result.cc b/chrome/browser/ui/app_list/search/app_service_app_result.cc
index 967072d8..4239f0f 100644
--- a/chrome/browser/ui/app_list/search/app_service_app_result.cc
+++ b/chrome/browser/ui/app_list/search/app_service_app_result.cc
@@ -21,6 +21,7 @@
                                          bool is_recommendation)
     : AppResult(profile, app_id, controller, is_recommendation),
       app_type_(apps::mojom::AppType::kUnknown),
+      is_platform_app_(false),
       show_in_launcher_(false),
       weak_ptr_factory_(this) {
   apps::AppServiceProxy* proxy = apps::AppServiceProxy::Get(profile);
@@ -28,6 +29,8 @@
   if (proxy) {
     proxy->Cache().ForOneApp(app_id, [this](const apps::AppUpdate& update) {
       app_type_ = update.AppType();
+      is_platform_app_ =
+          update.IsPlatformApp() == apps::mojom::OptionalBool::kTrue;
       show_in_launcher_ =
           update.ShowInLauncher() == apps::mojom::OptionalBool::kTrue;
     });
@@ -80,10 +83,6 @@
 }
 
 void AppServiceAppResult::GetContextMenuModel(GetMenuModelCallback callback) {
-  // TODO(crbug.com/826982): don't hard-code false. The App Service should
-  // probably provide this.
-  const bool is_platform_app = false;
-
   // TODO(crbug.com/826982): drop the (app_type_ == etc), and check
   // show_in_launcher_ for all app types?
   if ((app_type_ == apps::mojom::AppType::kBuiltIn) && !show_in_launcher_) {
@@ -92,7 +91,7 @@
   }
 
   context_menu_ = AppServiceAppItem::MakeAppContextMenu(
-      app_type_, this, profile(), app_id(), controller(), is_platform_app);
+      app_type_, this, profile(), app_id(), controller(), is_platform_app_);
   context_menu_->GetMenuModel(std::move(callback));
 }
 
diff --git a/chrome/browser/ui/app_list/search/app_service_app_result.h b/chrome/browser/ui/app_list/search/app_service_app_result.h
index 2a7562d..2949e3c 100644
--- a/chrome/browser/ui/app_list/search/app_service_app_result.h
+++ b/chrome/browser/ui/app_list/search/app_service_app_result.h
@@ -35,6 +35,7 @@
   void OnLoadIcon(bool chip, apps::mojom::IconValuePtr icon_value);
 
   apps::mojom::AppType app_type_;
+  bool is_platform_app_;
   bool show_in_launcher_;
 
   std::unique_ptr<AppContextMenu> context_menu_;
diff --git a/chrome/browser/ui/app_list/search/arc/arc_app_reinstall_app_result.cc b/chrome/browser/ui/app_list/search/arc/arc_app_reinstall_app_result.cc
index f9e831a..f2cd178 100644
--- a/chrome/browser/ui/app_list/search/arc/arc_app_reinstall_app_result.cc
+++ b/chrome/browser/ui/app_list/search/arc/arc_app_reinstall_app_result.cc
@@ -25,16 +25,14 @@
 
 ArcAppReinstallAppResult::ArcAppReinstallAppResult(
     const arc::mojom::AppReinstallCandidatePtr& mojom_data,
-    const gfx::ImageSkia& skia_icon,
-    bool is_recommendation) {
+
+    const gfx::ImageSkia& skia_icon) {
   ash::mojom::SearchResultMetadataPtr metadata = {base::in_place};
   set_id(kPlayStoreAppUrlPrefix + mojom_data->package_name);
-  SetResultType(ash::SearchResultType::kPlayStoreApp);
+  SetResultType(ash::SearchResultType::kPlayStoreReinstallApp);
   SetTitle(base::UTF8ToUTF16(mojom_data->title));
   SetDetails(base::UTF8ToUTF16(metadata->id));
-  SetDisplayType(is_recommendation
-                     ? ash::SearchResultDisplayType::kRecommendation
-                     : ash::SearchResultDisplayType::kTile);
+  SetDisplayType(ash::SearchResultDisplayType::kRecommendation);
   set_relevance(kAppReinstallRelevance);
 
   SetIcon(skia_icon);
diff --git a/chrome/browser/ui/app_list/search/arc/arc_app_reinstall_app_result.h b/chrome/browser/ui/app_list/search/arc/arc_app_reinstall_app_result.h
index 9309662..40c58867 100644
--- a/chrome/browser/ui/app_list/search/arc/arc_app_reinstall_app_result.h
+++ b/chrome/browser/ui/app_list/search/arc/arc_app_reinstall_app_result.h
@@ -19,8 +19,7 @@
  public:
   ArcAppReinstallAppResult(
       const arc::mojom::AppReinstallCandidatePtr& mojom_data,
-      const gfx::ImageSkia& skia_icon,
-      bool is_recommendation);
+      const gfx::ImageSkia& skia_icon);
   ~ArcAppReinstallAppResult() override;
 
   // ArcAppResult:
diff --git a/chrome/browser/ui/app_list/search/arc/arc_app_reinstall_search_provider.cc b/chrome/browser/ui/app_list/search/arc/arc_app_reinstall_search_provider.cc
index bd5ea9e..1184967 100644
--- a/chrome/browser/ui/app_list/search/arc/arc_app_reinstall_search_provider.cc
+++ b/chrome/browser/ui/app_list/search/arc/arc_app_reinstall_search_provider.cc
@@ -182,7 +182,7 @@
     } else if (icon_it != icon_urls_.end()) {
       // Icon is loaded, add it to the results.
       new_results.emplace_back(std::make_unique<ArcAppReinstallAppResult>(
-          loaded_value_[i], icon_it->second, /*is_recommendation=*/true));
+          loaded_value_[i], icon_it->second));
     }
   }
 
diff --git a/chrome/browser/ui/views/autofill/save_card_bubble_views_browsertest.cc b/chrome/browser/ui/views/autofill/save_card_bubble_views_browsertest.cc
index c737177..d4bf3b2 100644
--- a/chrome/browser/ui/views/autofill/save_card_bubble_views_browsertest.cc
+++ b/chrome/browser/ui/views/autofill/save_card_bubble_views_browsertest.cc
@@ -13,6 +13,7 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/metrics/user_action_tester.h"
 #include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/sync/profile_sync_service_factory.h"
 #include "chrome/browser/sync/test/integration/profile_sync_service_harness.h"
@@ -94,9 +95,11 @@
     "link: "
     "{0}.\",\"template_parameter\":[{\"display_text\":\"Link\",\"url\":\"https:"
     "//www.example.com/\"}]}]},\"context_token\":\"dummy_context_token\"}";
-const char kResponseGetUploadDetailsFailure[] =
+const char kResponsePaymentsFailure[] =
     "{\"error\":{\"code\":\"FAILED_PRECONDITION\",\"user_error_message\":\"An "
     "unexpected error has occurred. Please try again later.\"}}";
+const char kURLUploadCardRequest[] =
+    "https://payments.google.com/payments/apis/chromepaymentsservice/savecard";
 
 const double kFakeGeolocationLatitude = 1.23;
 const double kFakeGeolocationLongitude = 4.56;
@@ -509,7 +512,7 @@
 
   void SetUploadDetailsRpcPaymentsDeclines() {
     test_url_loader_factory()->AddResponse(kURLGetUploadDetailsRequest,
-                                           kResponseGetUploadDetailsFailure);
+                                           kResponsePaymentsFailure);
   }
 
   void SetUploadDetailsRpcServerError() {
@@ -518,6 +521,11 @@
                                            net::HTTP_INTERNAL_SERVER_ERROR);
   }
 
+  void SetUploadCardRpcPaymentsFails() {
+    test_url_loader_factory()->AddResponse(kURLUploadCardRequest,
+                                           kResponsePaymentsFailure);
+  }
+
   void ClickOnView(views::View* view) {
     DCHECK(view);
     ui::MouseEvent pressed(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
@@ -590,19 +598,19 @@
     return specified_view;
   }
 
-  void ClickOnCancelButton() {
+  void ClickOnCancelButton(bool strike_expected = false) {
     SaveCardBubbleViews* save_card_bubble_views = GetSaveCardBubbleViews();
     DCHECK(save_card_bubble_views);
-    if (base::FeatureList::IsEnabled(
-            features::kAutofillSaveCreditCardUsesStrikeSystem) ||
-        base::FeatureList::IsEnabled(
-            features::kAutofillSaveCreditCardUsesStrikeSystemV2)) {
+    if (strike_expected &&
+        (base::FeatureList::IsEnabled(
+             features::kAutofillSaveCreditCardUsesStrikeSystem) ||
+         base::FeatureList::IsEnabled(
+             features::kAutofillSaveCreditCardUsesStrikeSystemV2))) {
       ResetEventWaiterForSequence(
           {DialogEvent::STRIKE_CHANGE_COMPLETE, DialogEvent::BUBBLE_CLOSED});
     } else {
       ResetEventWaiterForSequence({DialogEvent::BUBBLE_CLOSED});
     }
-
     ClickOnDialogViewWithIdAndWait(DialogViewId::CANCEL_BUTTON);
     DCHECK(!GetSaveCardBubbleViews());
   }
@@ -720,8 +728,12 @@
 IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
                        Local_ClickingNoThanksClosesBubble) {
   // Enable the updated UI.
-  scoped_feature_list_.InitAndEnableFeature(
-      features::kAutofillSaveCardImprovedUserConsent);
+  scoped_feature_list_.InitWithFeatures(
+      // Enabled
+      {features::kAutofillSaveCardImprovedUserConsent},
+      // Disabled
+      {features::kAutofillSaveCreditCardUsesStrikeSystem,
+       features::kAutofillSaveCreditCardUsesStrikeSystemV2});
 
   // Submitting the form and having Payments decline offering to save should
   // show the local save bubble.
@@ -1254,7 +1266,8 @@
       {features::kAutofillSaveCardImprovedUserConsent,
        features::kAutofillUpstream},
       // Disabled
-      {});
+      {features::kAutofillSaveCreditCardUsesStrikeSystem,
+       features::kAutofillSaveCreditCardUsesStrikeSystemV2});
 
   // Start sync.
   harness_->SetupSync();
@@ -2275,4 +2288,424 @@
             year_input()->GetTextForRow(year_input()->selected_index()));
 }
 
+// TODO(crbug.com/884817): Investigate combining local vs. upload tests using a
+//                         boolean to branch local vs. upload logic.
+
+// Tests StrikeDatabase interaction with the local save bubble. Ensures that no
+// strikes are added if the feature flag is disabled.
+IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
+                       StrikeDatabase_Local_StrikeNotAddedIfExperimentFlagOff) {
+  // Disable the the SaveCreditCardUsesStrikeSystem experiments.
+  scoped_feature_list_.InitWithFeatures(
+      // Enabled
+      {features::kAutofillSaveCardImprovedUserConsent},
+      // Disabled
+      {features::kAutofillSaveCreditCardUsesStrikeSystem,
+       features::kAutofillSaveCreditCardUsesStrikeSystemV2});
+
+  // Submitting the form and having Payments decline offering to save should
+  // show the local save bubble.
+  // (Must wait for response from Payments before accessing the controller.)
+  ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
+  NavigateTo(kCreditCardUploadForm);
+  FillAndSubmitForm();
+  WaitForObservedEvent();
+  EXPECT_TRUE(
+      FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_LOCAL)->visible());
+
+  base::HistogramTester histogram_tester;
+  ClickOnCancelButton();
+
+  // Ensure that no strike was added because the feature is disabled.
+  histogram_tester.ExpectTotalCount(
+      "Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave", 0);
+}
+
+// Tests StrikeDatabase interaction with the upload save bubble. Ensures that no
+// strikes are added if the feature flag is disabled.
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTest,
+    StrikeDatabase_Upload_StrikeNotAddedIfExperimentFlagOff) {
+  // Disable the the SaveCreditCardUsesStrikeSystem experiments.
+  scoped_feature_list_.InitWithFeatures(
+      // Enabled
+      {features::kAutofillUpstream,
+       features::kAutofillSaveCardImprovedUserConsent},
+      // Disabled
+      {features::kAutofillSaveCreditCardUsesStrikeSystem,
+       features::kAutofillSaveCreditCardUsesStrikeSystemV2});
+
+  // Start sync.
+  harness_->SetupSync();
+
+  // Set up the Payments RPC.
+  SetUploadDetailsRpcPaymentsAccepts();
+
+  // Submitting the form should show the upload save bubble and legal footer.
+  // (Must wait for response from Payments before accessing the controller.)
+  ResetEventWaiterForSequence(
+      {DialogEvent::REQUESTED_UPLOAD_SAVE,
+       DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
+  NavigateTo(kCreditCardUploadForm);
+  FillAndSubmitForm();
+  WaitForObservedEvent();
+  EXPECT_TRUE(
+      FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)->visible());
+  EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
+
+  base::HistogramTester histogram_tester;
+
+  ClickOnCancelButton();
+
+  // Ensure that no strike was added because the feature is disabled.
+  histogram_tester.ExpectTotalCount(
+      "Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave", 0);
+}
+
+// Tests StrikeDatabase interaction with the local save bubble. Ensures that a
+// strike is added if the bubble is ignored.
+IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
+                       StrikeDatabase_Local_AddStrikeIfBubbleIgnored) {
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillSaveCreditCardUsesStrikeSystemV2);
+
+  TestAutofillClock test_clock;
+  test_clock.SetNow(base::Time::Now());
+
+  // Set up the Payments RPC.
+  SetUploadDetailsRpcPaymentsDeclines();
+
+  // Submitting the form and having Payments decline offering to save should
+  // show the local save bubble.
+  // (Must wait for response from Payments before accessing the controller.)
+  ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
+  NavigateTo(kCreditCardUploadForm);
+  FillAndSubmitForm();
+  WaitForObservedEvent();
+  EXPECT_TRUE(
+      FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_LOCAL)->visible());
+
+  // Clicking the [X] close button should dismiss the bubble.
+  ClickOnCloseButton();
+
+  // Add an event observer to the controller to detect strike changes.
+  AddEventObserverToController();
+
+  base::HistogramTester histogram_tester;
+
+  // Wait long enough to avoid bubble stickiness, then navigate away from the
+  // page.
+  test_clock.Advance(kCardBubbleSurviveNavigationTime);
+  ResetEventWaiterForSequence({DialogEvent::STRIKE_CHANGE_COMPLETE});
+  NavigateTo(kCreditCardUploadForm);
+  WaitForObservedEvent();
+
+  // Ensure that a strike was added due to the bubble being ignored.
+  histogram_tester.ExpectUniqueSample(
+      "Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave",
+      /*sample=*/1, /*count=*/1);
+}
+
+// Tests StrikeDatabase interaction with the upload save bubble. Ensures that a
+// strike is added if the bubble is ignored.
+IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
+                       StrikeDatabase_Upload_AddStrikeIfBubbleIgnored) {
+  scoped_feature_list_.InitWithFeatures(
+      // Enabled
+      {features::kAutofillUpstream,
+       features::kAutofillSaveCreditCardUsesStrikeSystemV2},
+      // Disabled
+      {});
+
+  TestAutofillClock test_clock;
+  test_clock.SetNow(base::Time::Now());
+
+  // Start sync.
+  harness_->SetupSync();
+
+  // Set up the Payments RPC.
+  SetUploadDetailsRpcPaymentsAccepts();
+
+  // Submitting the form should show the upload save bubble and legal footer.
+  // (Must wait for response from Payments before accessing the controller.)
+  ResetEventWaiterForSequence(
+      {DialogEvent::REQUESTED_UPLOAD_SAVE,
+       DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
+  NavigateTo(kCreditCardUploadForm);
+  FillAndSubmitForm();
+  WaitForObservedEvent();
+  EXPECT_TRUE(
+      FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)->visible());
+  EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
+
+  // Clicking the [X] close button should dismiss the bubble.
+  ClickOnCloseButton();
+
+  // Add an event observer to the controller to detect strike changes.
+  AddEventObserverToController();
+
+  base::HistogramTester histogram_tester;
+
+  // Wait long enough to avoid bubble stickiness, then navigate away from the
+  // page.
+  test_clock.Advance(kCardBubbleSurviveNavigationTime);
+  ResetEventWaiterForSequence({DialogEvent::STRIKE_CHANGE_COMPLETE});
+  NavigateTo(kCreditCardUploadForm);
+  WaitForObservedEvent();
+
+  // Ensure that a strike was added due to the bubble being ignored.
+  histogram_tester.ExpectUniqueSample(
+      "Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave",
+      /*sample=*/1, /*count=*/1);
+}
+
+// Tests the local save bubble. Ensures that clicking the [No thanks] button
+// successfully causes a strike to be added.
+IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
+                       StrikeDatabase_Local_AddStrikeIfBubbleDeclined) {
+  // Enable the updated UI.
+  scoped_feature_list_.InitWithFeatures(
+      // Enabled
+      {features::kAutofillSaveCardImprovedUserConsent,
+       features::kAutofillSaveCreditCardUsesStrikeSystemV2},
+      // Disabled
+      {});
+
+  // Submitting the form and having Payments decline offering to save should
+  // show the local save bubble.
+  // (Must wait for response from Payments before accessing the controller.)
+  ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
+  NavigateTo(kCreditCardUploadForm);
+  FillAndSubmitForm();
+  WaitForObservedEvent();
+  EXPECT_TRUE(
+      FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_LOCAL)->visible());
+
+  // Clicking [No thanks] should cancel and close it.
+  base::HistogramTester histogram_tester;
+  ClickOnCancelButton(/*strike_expected=*/true);
+
+  // Ensure that a strike was added.
+  histogram_tester.ExpectUniqueSample(
+      "Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave",
+      /*sample=*/(1), /*count=*/1);
+}
+
+// Tests the upload save bubble. Ensures that clicking the [No thanks] button
+// successfully causes a strike to be added.
+IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
+                       StrikeDatabase_Upload_AddStrikeIfBubbleDeclined) {
+  // Enable the updated UI.
+  scoped_feature_list_.InitWithFeatures(
+      // Enabled
+      {features::kAutofillSaveCardImprovedUserConsent,
+       features::kAutofillUpstream,
+       features::kAutofillSaveCreditCardUsesStrikeSystemV2},
+      // Disabled
+      {});
+
+  // Start sync.
+  harness_->SetupSync();
+
+  // Set up the Payments RPC.
+  SetUploadDetailsRpcPaymentsAccepts();
+
+  // Submitting the form should show the upload save bubble and legal footer.
+  // (Must wait for response from Payments before accessing the controller.)
+  ResetEventWaiterForSequence(
+      {DialogEvent::REQUESTED_UPLOAD_SAVE,
+       DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
+  NavigateTo(kCreditCardUploadForm);
+  FillAndSubmitForm();
+  WaitForObservedEvent();
+  EXPECT_TRUE(
+      FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)->visible());
+  EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
+
+  // Clicking [No thanks] should cancel and close it.
+  base::HistogramTester histogram_tester;
+  ClickOnCancelButton(/*strike_expected=*/true);
+
+  // Ensure that a strike was added.
+  histogram_tester.ExpectUniqueSample(
+      "Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave",
+      /*sample=*/(1), /*count=*/1);
+}
+
+// Tests overall StrikeDatabase interaction with the local save bubble. Runs an
+// example of declining the prompt three times and ensuring that the
+// offer-to-save bubble does not appear on the fourth try. Then, ensures that no
+// strikes are added if the card already has max strikes.
+IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
+                       StrikeDatabase_Local_FullFlowTest) {
+  scoped_feature_list_.InitWithFeatures(
+      // Enabled
+      {features::kAutofillSaveCardImprovedUserConsent,
+       features::kAutofillSaveCreditCardUsesStrikeSystemV2},
+      // Disabled
+      {});
+
+  bool controller_observer_set = false;
+
+  // Show and ignore the bubble kMaxStrikesToPreventPoppingUpOfferToSavePrompt
+  // times in order to accrue maximum strikes.
+  for (int i = 0; i < kMaxStrikesToPreventPoppingUpOfferToSavePrompt; i++) {
+    // Submitting the form and having Payments decline offering to save should
+    // show the local save bubble.
+    // (Must wait for response from Payments before accessing the controller.)
+    ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
+    NavigateTo(kCreditCardUploadForm);
+    FillAndSubmitForm();
+    WaitForObservedEvent();
+    EXPECT_TRUE(
+        FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_LOCAL)->visible());
+
+    if (!controller_observer_set) {
+      // Add an event observer to the controller.
+      AddEventObserverToController();
+      ReduceAnimationTime();
+      controller_observer_set = true;
+    }
+
+    base::HistogramTester histogram_tester;
+    ResetEventWaiterForSequence({DialogEvent::STRIKE_CHANGE_COMPLETE});
+    ClickOnCancelButton(/*strike_expected=*/true);
+    WaitForObservedEvent();
+
+    // Ensure that a strike was added due to the bubble being declined.
+    // The sample logged is the Nth strike added, or (i+1).
+    histogram_tester.ExpectUniqueSample(
+        "Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave",
+        /*sample=*/(i + 1), /*count=*/1);
+  }
+
+  base::HistogramTester histogram_tester;
+
+  // Submit the form a fourth time. Since the card now has maximum strikes (3),
+  // the icon should be shown but the bubble should not.
+  ResetEventWaiterForSequence({DialogEvent::OFFERED_LOCAL_SAVE});
+  NavigateTo(kCreditCardUploadForm);
+  FillAndSubmitForm();
+  WaitForObservedEvent();
+  EXPECT_TRUE(GetSaveCardIconView()->visible());
+  EXPECT_FALSE(GetSaveCardBubbleViews());
+
+  // Click the icon to show the bubble.
+  ResetEventWaiterForSequence({DialogEvent::BUBBLE_SHOWN});
+  ClickOnView(GetSaveCardIconView());
+  WaitForObservedEvent();
+  EXPECT_TRUE(
+      FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_LOCAL)->visible());
+
+  ClickOnCancelButton();
+
+  // Ensure that no strike was added because the card already had max strikes.
+  histogram_tester.ExpectTotalCount(
+      "Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave", 0);
+
+  // Verify that the correct histogram entry was logged.
+  histogram_tester.ExpectBucketCount(
+      "Autofill.StrikeDatabase.CreditCardSaveNotOfferedDueToMaxStrikes",
+      AutofillMetrics::SaveTypeMetric::LOCAL, 1);
+
+  // UMA should have recorded bubble rejection.
+  histogram_tester.ExpectUniqueSample(
+      "Autofill.SaveCreditCardPrompt.Local.FirstShow",
+      AutofillMetrics::SAVE_CARD_ICON_SHOWN_WITHOUT_PROMPT, 1);
+}
+
+// Tests overall StrikeDatabase interaction with the upload save bubble. Runs an
+// example of declining the prompt three times and ensuring that the
+// offer-to-save bubble does not appear on the fourth try. Then, ensures that no
+// strikes are added if the card already has max strikes.
+IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
+                       StrikeDatabase_Upload_FullFlowTest) {
+  scoped_feature_list_.InitWithFeatures(
+      // Enabled
+      {features::kAutofillSaveCardImprovedUserConsent,
+       features::kAutofillUpstream,
+       features::kAutofillSaveCreditCardUsesStrikeSystemV2},
+      // Disabled
+      {});
+
+  bool controller_observer_set = false;
+
+  // Start sync.
+  harness_->SetupSync();
+
+  // Set up the Payments RPC.
+  SetUploadDetailsRpcPaymentsAccepts();
+
+  // Show and ignore the bubble kMaxStrikesToPreventPoppingUpOfferToSavePrompt
+  // times in order to accrue maximum strikes.
+  for (int i = 0; i < kMaxStrikesToPreventPoppingUpOfferToSavePrompt; i++) {
+    // Submitting the form should show the upload save bubble and legal footer.
+    // (Must wait for response from Payments before accessing the controller.)
+    ResetEventWaiterForSequence(
+        {DialogEvent::REQUESTED_UPLOAD_SAVE,
+         DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
+    NavigateTo(kCreditCardUploadForm);
+    FillAndSubmitForm();
+    WaitForObservedEvent();
+    EXPECT_TRUE(FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)
+                    ->visible());
+    EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
+
+    if (!controller_observer_set) {
+      // Add an event observer to the controller.
+      AddEventObserverToController();
+      ReduceAnimationTime();
+      controller_observer_set = true;
+    }
+
+    base::HistogramTester histogram_tester;
+
+    ClickOnCancelButton(/*strike_expected=*/true);
+    WaitForObservedEvent();
+
+    // Ensure that a strike was added due to the bubble being declined.
+    // The sample logged is the Nth strike added, or (i+1).
+    histogram_tester.ExpectUniqueSample(
+        "Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave",
+        /*sample=*/(i + 1), /*count=*/1);
+  }
+
+  base::HistogramTester histogram_tester;
+
+  // Submit the form a fourth time. Since the card now has maximum strikes (3),
+  // the icon should be shown but the bubble should not.
+  ResetEventWaiterForSequence(
+      {DialogEvent::REQUESTED_UPLOAD_SAVE,
+       DialogEvent::RECEIVED_GET_UPLOAD_DETAILS_RESPONSE});
+  NavigateTo(kCreditCardUploadForm);
+  FillAndSubmitForm();
+  WaitForObservedEvent();
+  EXPECT_TRUE(GetSaveCardIconView()->visible());
+  EXPECT_FALSE(GetSaveCardBubbleViews());
+
+  // Click the icon to show the bubble.
+  ResetEventWaiterForSequence({DialogEvent::BUBBLE_SHOWN});
+  ClickOnView(GetSaveCardIconView());
+  WaitForObservedEvent();
+  EXPECT_TRUE(
+      FindViewInBubbleById(DialogViewId::MAIN_CONTENT_VIEW_UPLOAD)->visible());
+  EXPECT_TRUE(FindViewInBubbleById(DialogViewId::FOOTNOTE_VIEW)->visible());
+
+  ClickOnCancelButton();
+
+  // Ensure that no strike was added because the card already had max strikes.
+  histogram_tester.ExpectTotalCount(
+      "Autofill.StrikeDatabase.NthStrikeAdded.CreditCardSave", 0);
+
+  // Verify that the correct histogram entry was logged.
+  histogram_tester.ExpectBucketCount(
+      "Autofill.StrikeDatabase.CreditCardSaveNotOfferedDueToMaxStrikes",
+      AutofillMetrics::SaveTypeMetric::SERVER, 1);
+
+  // UMA should have recorded bubble rejection.
+  histogram_tester.ExpectUniqueSample(
+      "Autofill.SaveCreditCardPrompt.Upload.FirstShow",
+      AutofillMetrics::SAVE_CARD_ICON_SHOWN_WITHOUT_PROMPT, 1);
+}
+
 }  // namespace autofill
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 e300b26..f86ec57 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
@@ -92,7 +92,7 @@
   AddChildView(label);
 
   if (activation_action == ActivationAction::DO_NOT_ACTIVATE) {
-    set_can_activate(activation_action == ActivationAction::ACTIVATE);
+    SetCanActivate(activation_action == ActivationAction::ACTIVATE);
     set_shadow(views::BubbleBorder::BIG_SHADOW);
   }
 
diff --git a/chrome/browser/ui/views/frame/browser_frame.cc b/chrome/browser/ui/views/frame/browser_frame.cc
index cc34073..80819733 100644
--- a/chrome/browser/ui/views/frame/browser_frame.cc
+++ b/chrome/browser/ui/views/frame/browser_frame.cc
@@ -164,6 +164,20 @@
   browser_frame_view_->OnBrowserViewInitViewsComplete();
 }
 
+bool BrowserFrame::ShouldUseTheme() const {
+  // Main browser windows are always themed.
+  if (browser_view_->IsBrowserTypeNormal())
+    return true;
+
+  // The system GTK theme should always be respected if the user has opted to
+  // use it.
+  if (IsUsingGtkTheme(browser_view_->browser()->profile()))
+    return true;
+
+  // Other window types (popups, hosted apps) on non-GTK use the default theme.
+  return false;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // BrowserFrame, views::Widget overrides:
 
@@ -264,20 +278,6 @@
   menu_runner_.reset();
 }
 
-bool BrowserFrame::ShouldUseTheme() const {
-  // Main browser windows are always themed.
-  if (browser_view_->IsBrowserTypeNormal())
-    return true;
-
-  // The system GTK theme should always be respected if the user has opted to
-  // use it.
-  if (IsUsingGtkTheme(browser_view_->browser()->profile()))
-    return true;
-
-  // Other window types (popups, hosted apps) on non-GTK use the default theme.
-  return false;
-}
-
 void BrowserFrame::OnTouchUiChanged() {
   client_view()->InvalidateLayout();
   non_client_view()->InvalidateLayout();
diff --git a/chrome/browser/ui/views/frame/browser_frame.h b/chrome/browser/ui/views/frame/browser_frame.h
index 9a2644b..0f36993 100644
--- a/chrome/browser/ui/views/frame/browser_frame.h
+++ b/chrome/browser/ui/views/frame/browser_frame.h
@@ -100,6 +100,9 @@
   // Called when BrowserView creates all it's child views.
   void OnBrowserViewInitViewsComplete();
 
+  // Returns whether this window should be themed with the user's theme or not.
+  bool ShouldUseTheme() const;
+
   // views::Widget:
   views::internal::RootView* CreateRootView() override;
   views::NonClientFrameView* CreateNonClientFrameView() override;
@@ -131,9 +134,6 @@
   // Callback for MenuRunner.
   void OnMenuClosed();
 
-  // Returns whether this window should be themed with the user's theme or not.
-  bool ShouldUseTheme() const;
-
   NativeBrowserFrame* native_browser_frame_;
 
   // A weak reference to the root view associated with the window. We save a
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view.cc
index 943a1f0..e6ffbfe 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view.cc
@@ -149,16 +149,7 @@
                    : ThemeProperties::COLOR_FRAME_INACTIVE;
   }
 
-  // For hosted app windows, if "painting as themed" (which is only true when on
-  // Linux and using the system theme), prefer the system theme color over the
-  // hosted app theme color. The title bar will be painted in the system theme
-  // color (regardless of what we do here), so by returning the system title bar
-  // background color here, we ensure that:
-  // a) The side and bottom borders are painted in the same color as the title
-  // bar background, and
-  // b) The title text is painted in a color that contrasts with the title bar
-  // background.
-  if (ShouldPaintAsThemed())
+  if (frame_->ShouldUseTheme())
     return GetThemeProviderForProfile()->GetColor(color_id);
 
   extensions::HostedAppBrowserController* hosted_app_controller =
@@ -255,10 +246,6 @@
          browser_view_->tabstrip()->SingleTabMode();
 }
 
-bool BrowserNonClientFrameView::ShouldPaintAsThemed() const {
-  return browser_view_->IsBrowserTypeNormal();
-}
-
 SkColor BrowserNonClientFrameView::GetCaptionColor(
     ActiveState active_state) const {
   return color_utils::GetColorWithMaxContrast(GetFrameColor(active_state));
@@ -276,8 +263,8 @@
   const int frame_image_id = ShouldPaintAsActive(active_state)
                                  ? IDR_THEME_FRAME
                                  : IDR_THEME_FRAME_INACTIVE;
-  return ShouldPaintAsThemed() && (tp->HasCustomImage(frame_image_id) ||
-                                   tp->HasCustomImage(IDR_THEME_FRAME))
+  return frame_->ShouldUseTheme() && (tp->HasCustomImage(frame_image_id) ||
+                                      tp->HasCustomImage(IDR_THEME_FRAME))
              ? *tp->GetImageSkiaNamed(frame_image_id)
              : gfx::ImageSkia();
 }
@@ -425,7 +412,7 @@
   // During shutdown, there may no longer be a widget, and thus no theme
   // provider.
   const auto* theme_provider = GetThemeProvider();
-  return ShouldPaintAsThemed() && theme_provider
+  return frame_->ShouldUseTheme() && theme_provider
              ? theme_provider->GetColor(color_id)
              : ThemeProperties::GetDefaultColor(color_id,
                                                 browser_view_->IsIncognito());
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view.h b/chrome/browser/ui/views/frame/browser_non_client_frame_view.h
index 5a21806d..c0d4fe7 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view.h
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view.h
@@ -140,13 +140,6 @@
   }
 
  protected:
-  // Whether the frame should be painted with theming.
-  // By default, tabbed browser windows are themed but popup and app windows are
-  // not.
-  // TODO(https://crbug.com/927381): Dedupe this with
-  // BrowserFrame::ShouldIgnoreTheme().
-  virtual bool ShouldPaintAsThemed() const;
-
   // Returns the color to use for text, caption buttons, and other title bar
   // elements.
   virtual SkColor GetCaptionColor(ActiveState active_state = kUseCurrent) const;
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc b/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
index 89b3422..1fa6165 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
@@ -10,8 +10,6 @@
 #include "build/build_config.h"
 #include "build/buildflag.h"
 #include "chrome/browser/themes/theme_properties.h"
-#include "chrome/browser/themes/theme_service.h"
-#include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/browser/ui/extensions/hosted_app_browser_controller.h"
 #include "chrome/browser/ui/layout_constants.h"
 #include "chrome/browser/ui/views/frame/browser_frame.h"
@@ -124,10 +122,8 @@
   layout_->set_delegate(this);
   SetLayoutManager(std::unique_ptr<views::LayoutManager>(layout_));
 
-  // This must be initialised before the call to GetFrameColor().
-  platform_observer_.reset(OpaqueBrowserFrameViewPlatformSpecific::Create(
-      this, layout_,
-      ThemeServiceFactory::GetForProfile(browser_view->browser()->profile())));
+  platform_observer_.reset(
+      OpaqueBrowserFrameViewPlatformSpecific::Create(this, layout_));
 }
 
 OpaqueBrowserFrameView::~OpaqueBrowserFrameView() {}
@@ -544,13 +540,6 @@
     PaintClientEdge(canvas);
 }
 
-// BrowserNonClientFrameView:
-bool OpaqueBrowserFrameView::ShouldPaintAsThemed() const {
-  // Theme app and popup windows if |platform_observer_| wants it.
-  return browser_view()->IsBrowserTypeNormal() ||
-         platform_observer_->IsUsingSystemTheme();
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 // OpaqueBrowserFrameView, private:
 
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view.h b/chrome/browser/ui/views/frame/opaque_browser_frame_view.h
index cc4a25ae..999b1b9 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view.h
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view.h
@@ -120,9 +120,6 @@
   // views::View:
   void OnPaint(gfx::Canvas* canvas) override;
 
-  // BrowserNonClientFrameView:
-  bool ShouldPaintAsThemed() const override;
-
   OpaqueBrowserFrameViewLayout* layout() { return layout_; }
 
  private:
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view_linux.cc b/chrome/browser/ui/views/frame/opaque_browser_frame_view_linux.cc
index 05b10c0..f34965e 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view_linux.cc
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view_linux.cc
@@ -4,7 +4,6 @@
 
 #include "chrome/browser/ui/views/frame/opaque_browser_frame_view_linux.h"
 
-#include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/ui/views/frame/opaque_browser_frame_view.h"
 #include "chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h"
 #include "ui/views/linux_ui/linux_ui.h"
@@ -14,11 +13,8 @@
 
 OpaqueBrowserFrameViewLinux::OpaqueBrowserFrameViewLinux(
     OpaqueBrowserFrameView* view,
-    OpaqueBrowserFrameViewLayout* layout,
-    ThemeService* theme_service)
-    : view_(view),
-      layout_(layout),
-      theme_service_(theme_service) {
+    OpaqueBrowserFrameViewLayout* layout)
+    : view_(view), layout_(layout) {
   views::LinuxUI* ui = views::LinuxUI::instance();
   if (ui)
     ui->AddWindowButtonOrderObserver(this);
@@ -30,10 +26,6 @@
     ui->RemoveWindowButtonOrderObserver(this);
 }
 
-bool OpaqueBrowserFrameViewLinux::IsUsingSystemTheme() {
-  return theme_service_->UsingSystemTheme();
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 // OpaqueBrowserFrameViewLinux,
 //     views::WindowButtonOrderObserver implementation:
@@ -63,7 +55,6 @@
 OpaqueBrowserFrameViewPlatformSpecific*
 OpaqueBrowserFrameViewPlatformSpecific::Create(
     OpaqueBrowserFrameView* view,
-    OpaqueBrowserFrameViewLayout* layout,
-    ThemeService* theme_service) {
-  return new OpaqueBrowserFrameViewLinux(view, layout, theme_service);
+    OpaqueBrowserFrameViewLayout* layout) {
+  return new OpaqueBrowserFrameViewLinux(view, layout);
 }
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view_linux.h b/chrome/browser/ui/views/frame/opaque_browser_frame_view_linux.h
index 172e0bd..b0759dbd 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view_linux.h
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view_linux.h
@@ -17,13 +17,9 @@
       public views::WindowButtonOrderObserver {
  public:
   OpaqueBrowserFrameViewLinux(OpaqueBrowserFrameView* view,
-                              OpaqueBrowserFrameViewLayout* layout,
-                              ThemeService* theme_service);
+                              OpaqueBrowserFrameViewLayout* layout);
   ~OpaqueBrowserFrameViewLinux() override;
 
-  // Overridden from OpaqueBrowserFrameViewPlatformSpecific:
-  bool IsUsingSystemTheme() override;
-
   // Overridden from views::WindowButtonOrderObserver:
   void OnWindowButtonOrderingChange(
       const std::vector<views::FrameButton>& leading_buttons,
@@ -32,7 +28,6 @@
  private:
   OpaqueBrowserFrameView* view_;
   OpaqueBrowserFrameViewLayout* layout_;
-  ThemeService* theme_service_;
 
   DISALLOW_COPY_AND_ASSIGN(OpaqueBrowserFrameViewLinux);
 };
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view_platform_specific.cc b/chrome/browser/ui/views/frame/opaque_browser_frame_view_platform_specific.cc
index c3e5f26..029e3ca 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view_platform_specific.cc
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view_platform_specific.cc
@@ -6,18 +6,13 @@
 
 #include "build/build_config.h"
 
-bool OpaqueBrowserFrameViewPlatformSpecific::IsUsingSystemTheme() {
-  return false;
-}
-
 #if !defined(OS_LINUX)
 
 // static
 OpaqueBrowserFrameViewPlatformSpecific*
 OpaqueBrowserFrameViewPlatformSpecific::Create(
     OpaqueBrowserFrameView* view,
-    OpaqueBrowserFrameViewLayout* layout,
-    ThemeService* theme_service) {
+    OpaqueBrowserFrameViewLayout* layout) {
   return new OpaqueBrowserFrameViewPlatformSpecific();
 }
 
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view_platform_specific.h b/chrome/browser/ui/views/frame/opaque_browser_frame_view_platform_specific.h
index 6916b52..b457233 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view_platform_specific.h
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view_platform_specific.h
@@ -7,26 +7,16 @@
 
 class OpaqueBrowserFrameView;
 class OpaqueBrowserFrameViewLayout;
-class ThemeService;
 
 // Handles platform specific configuration concepts.
 class OpaqueBrowserFrameViewPlatformSpecific {
  public:
   virtual ~OpaqueBrowserFrameViewPlatformSpecific() {}
 
-  // Returns whether we're using native system like rendering for theme
-  // elements.
-  //
-  // Why not just ask ThemeService::UsingSystemTheme()? Because on Windows, the
-  // default theme is UsingSystemTheme(). Therefore, the default implementation
-  // always returns false and we specifically override this on Linux.
-  virtual bool IsUsingSystemTheme();
-
   // Builds an observer for |view| and |layout|.
   static OpaqueBrowserFrameViewPlatformSpecific* Create(
       OpaqueBrowserFrameView* view,
-      OpaqueBrowserFrameViewLayout* layout,
-      ThemeService* theme_service);
+      OpaqueBrowserFrameViewLayout* layout);
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_FRAME_OPAQUE_BROWSER_FRAME_VIEW_PLATFORM_SPECIFIC_H_
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.cc b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.cc
index 5e5f8199..d87ce750 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.cc
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.cc
@@ -21,7 +21,7 @@
   // Set so that when hovering over a tab in a inactive window that window will
   // not become active. Setting this to false creates the need to explicitly
   // hide the hovercard on press, touch, and keyboard events.
-  set_can_activate(false);
+  SetCanActivate(false);
 
   title_label_ =
       new views::Label(base::string16(), CONTEXT_TAB_HOVER_CARD_TITLE,
diff --git a/chrome/browser/ui/webui/welcome/welcome_ui.cc b/chrome/browser/ui/webui/welcome/welcome_ui.cc
index 8c3dece..bed8524 100644
--- a/chrome/browser/ui/webui/welcome/welcome_ui.cc
+++ b/chrome/browser/ui/webui/welcome/welcome_ui.cc
@@ -10,6 +10,7 @@
 #include "base/metrics/histogram_macros.h"
 #include "build/build_config.h"
 #include "chrome/browser/signin/account_consistency_mode_manager.h"
+#include "chrome/browser/ui/webui/dark_mode_handler.h"
 #include "chrome/browser/ui/webui/localized_string.h"
 #include "chrome/browser/ui/webui/welcome/nux/bookmark_handler.h"
 #include "chrome/browser/ui/webui/welcome/nux/constants.h"
@@ -119,6 +120,8 @@
   content::WebUIDataSource* html_source =
       content::WebUIDataSource::Create(url.host());
 
+  DarkModeHandler::Initialize(web_ui, html_source);
+
   bool is_dice =
       AccountConsistencyModeManager::IsDiceEnabledForProfile(profile);
 
diff --git a/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc b/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc
index a18d582..7f05e32 100644
--- a/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc
+++ b/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "base/strings/stringprintf.h"
 #include "content/public/common/child_process_host.h"
+#include "extensions/common/api/messaging/messaging_endpoint.h"
 #include "extensions/common/extension_builder.h"
 #include "extensions/common/extension_messages.h"
 #include "extensions/common/value_builder.h"
@@ -184,7 +185,8 @@
       DictionaryBuilder().Set("tabId", tab_id).Build().get());
   ExtensionMsg_ExternalConnectionInfo external_connection_info;
   external_connection_info.target_id = extension()->id();
-  external_connection_info.source_id = extension()->id();
+  external_connection_info.source_endpoint =
+      MessagingEndpoint::ForExtension(extension()->id());
   external_connection_info.source_url = source_url;
   external_connection_info.guest_process_id =
       content::ChildProcessHost::kInvalidUniqueID;
diff --git a/chrome/renderer/resources/extensions/chromeos_ime_service_bindings.js b/chrome/renderer/resources/extensions/chromeos_ime_service_bindings.js
index 9357856..5862078 100644
--- a/chrome/renderer/resources/extensions/chromeos_ime_service_bindings.js
+++ b/chrome/renderer/resources/extensions/chromeos_ime_service_bindings.js
@@ -64,6 +64,7 @@
   /**
    * Get a cached bound InterfacePtr for this InputChannel impl.
    * Create one the ptr if it's not bound yet.
+   *
    * @return {!chromeos.ime.mojom.InputChannelPtr}.
    */
   getChannelPtr() {
@@ -73,6 +74,7 @@
   /**
    * Set a handler for processing text message. The handler must return a
    * nonnull string, otherwise it will lead to disconnection.
+   *
    * @param {function(string):string} handler.
    */
   onTextMessage(handler) {
@@ -83,6 +85,7 @@
   /**
    * Set a handler for processing protobuf message. The handler must return a
    * nonnull Uint8Array, otherwise it will lead to disconnection.
+   *
    * @param {function(!Uint8Array):!Uint8Array} handler.
    */
   onProtobufMessage(handler) {
@@ -122,6 +125,7 @@
 
   /**
    * Set the error handler when the channel Mojo pipe is disconnected.
+   *
    * @param {function():void} handler.
    */
   setConnectionErrorHandler(handler) {
@@ -178,6 +182,7 @@
 
   /**
    * Set the error handler when the IME Mojo service is disconnected.
+   *
    * @param {function():void} callback.
    */
   setConnectionErrorHandler(callback) {
@@ -198,7 +203,33 @@
   }
 
   /**
+   * Set a handler for the bound client delegate to process messages in text
+   * format. This should be called after an IME engine is activated.
+   *
+   * @param {!function(string):string} callback Callback on text message.
+   */
+  setDelegateTextHandler(callback) {
+    if (this.clientChannel_ && this.clientChannel_.ptr.isBound()) {
+      this.clientChannel_.onTextMessage(callback);
+    }
+  }
+
+  /**
+   * Set a handler for the bound client delegate to process messages in protobuf
+   * format. This should be called after an IME engine is activated.
+   *
+   * @param {!function(!Uint8Array):!Uint8Array} callback Callback on protobuf
+   *     message.
+   */
+  setDelegateProtobufHandler(callback) {
+    if (this.clientChannel_ && this.clientChannel_.ptr.isBound()) {
+      this.clientChannel_.onProtobufMessage(callback);
+    }
+  }
+
+  /**
    * Activates an input method based on its specification.
+   *
    * @param {string} imeSpec The specification of an IME (e.g. the engine ID).
    * @param {!Uint8Array} extra The extra data (e.g. initial tasks to run).
    * @param {function(boolean):void} onConnection The callback function to
@@ -244,6 +275,7 @@
       this.activeEngine_.ptr.reset();
     }
     this.activeEngine_ = null;
+    // TODO(crbug.com/837156): Release client channel?
   }
 }
 
diff --git a/chrome/services/app_service/public/cpp/app_update.cc b/chrome/services/app_service/public/cpp/app_update.cc
index 1ad15c6a..7ee4e64 100644
--- a/chrome/services/app_service/public/cpp/app_update.cc
+++ b/chrome/services/app_service/public/cpp/app_update.cc
@@ -63,6 +63,9 @@
   if (delta->installed_internally != apps::mojom::OptionalBool::kUnknown) {
     state->installed_internally = delta->installed_internally;
   }
+  if (delta->is_platform_app != apps::mojom::OptionalBool::kUnknown) {
+    state->is_platform_app = delta->is_platform_app;
+  }
   if (delta->show_in_launcher != apps::mojom::OptionalBool::kUnknown) {
     state->show_in_launcher = delta->show_in_launcher;
   }
@@ -222,6 +225,23 @@
           (delta_->installed_internally != state_->installed_internally));
 }
 
+apps::mojom::OptionalBool AppUpdate::IsPlatformApp() const {
+  if (delta_ &&
+      (delta_->is_platform_app != apps::mojom::OptionalBool::kUnknown)) {
+    return delta_->is_platform_app;
+  }
+  if (state_) {
+    return state_->is_platform_app;
+  }
+  return apps::mojom::OptionalBool::kUnknown;
+}
+
+bool AppUpdate::IsPlatformAppChanged() const {
+  return delta_ &&
+         (delta_->is_platform_app != apps::mojom::OptionalBool::kUnknown) &&
+         (!state_ || (delta_->is_platform_app != state_->is_platform_app));
+}
+
 apps::mojom::OptionalBool AppUpdate::ShowInLauncher() const {
   if (delta_ &&
       (delta_->show_in_launcher != apps::mojom::OptionalBool::kUnknown)) {
diff --git a/chrome/services/app_service/public/cpp/app_update.h b/chrome/services/app_service/public/cpp/app_update.h
index 4b94698d..aedcea5 100644
--- a/chrome/services/app_service/public/cpp/app_update.h
+++ b/chrome/services/app_service/public/cpp/app_update.h
@@ -82,6 +82,9 @@
   apps::mojom::OptionalBool InstalledInternally() const;
   bool InstalledInternallyChanged() const;
 
+  apps::mojom::OptionalBool IsPlatformApp() const;
+  bool IsPlatformAppChanged() const;
+
   apps::mojom::OptionalBool ShowInLauncher() const;
   bool ShowInLauncherChanged() const;
 
diff --git a/chrome/services/app_service/public/cpp/app_update_unittest.cc b/chrome/services/app_service/public/cpp/app_update_unittest.cc
index ca4a56c..1ba673e 100644
--- a/chrome/services/app_service/public/cpp/app_update_unittest.cc
+++ b/chrome/services/app_service/public/cpp/app_update_unittest.cc
@@ -38,6 +38,9 @@
   apps::mojom::OptionalBool expect_installed_internally_;
   bool expect_installed_internally_changed_;
 
+  apps::mojom::OptionalBool expect_is_platform_app_;
+  bool expect_is_platform_app_changed_;
+
   apps::mojom::OptionalBool expect_show_in_launcher_;
   bool expect_show_in_launcher_changed_;
 
@@ -65,6 +68,7 @@
     expect_install_time_changed_ = false;
     expect_permissions_changed_ = false;
     expect_installed_internally_changed_ = false;
+    expect_is_platform_app_changed_ = false;
     expect_show_in_launcher_changed_ = false;
     expect_show_in_search_changed_ = false;
   }
@@ -95,6 +99,9 @@
     EXPECT_EQ(expect_installed_internally_changed_,
               u.InstalledInternallyChanged());
 
+    EXPECT_EQ(expect_is_platform_app_, u.IsPlatformApp());
+    EXPECT_EQ(expect_is_platform_app_changed_, u.IsPlatformAppChanged());
+
     EXPECT_EQ(expect_show_in_launcher_, u.ShowInLauncher());
     EXPECT_EQ(expect_show_in_launcher_changed_, u.ShowInLauncherChanged());
 
@@ -117,6 +124,7 @@
     expect_install_time_ = base::Time();
     expect_permissions_.clear();
     expect_installed_internally_ = apps::mojom::OptionalBool::kUnknown;
+    expect_is_platform_app_ = apps::mojom::OptionalBool::kUnknown;
     expect_show_in_launcher_ = apps::mojom::OptionalBool::kUnknown;
     expect_show_in_search_ = apps::mojom::OptionalBool::kUnknown;
     ExpectNoChange();
@@ -278,6 +286,28 @@
       CheckExpects(u);
     }
 
+    // IsPlatformApp tests.
+
+    if (state) {
+      state->is_platform_app = apps::mojom::OptionalBool::kFalse;
+      expect_is_platform_app_ = apps::mojom::OptionalBool::kFalse;
+      expect_is_platform_app_changed_ = false;
+      CheckExpects(u);
+    }
+
+    if (delta) {
+      delta->is_platform_app = apps::mojom::OptionalBool::kTrue;
+      expect_is_platform_app_ = apps::mojom::OptionalBool::kTrue;
+      expect_is_platform_app_changed_ = true;
+      CheckExpects(u);
+    }
+
+    if (state) {
+      apps::AppUpdate::Merge(state, delta);
+      ExpectNoChange();
+      CheckExpects(u);
+    }
+
     // ShowInSearch tests.
 
     if (state) {
diff --git a/chrome/services/app_service/public/mojom/types.mojom b/chrome/services/app_service/public/mojom/types.mojom
index bf3b492..8ba3cce 100644
--- a/chrome/services/app_service/public/mojom/types.mojom
+++ b/chrome/services/app_service/public/mojom/types.mojom
@@ -31,6 +31,10 @@
   // Whether the app was installed by sync, policy or as a default app.
   OptionalBool installed_internally;
 
+  // Whether the app is an extensions::Extensions where is_platform_app()
+  // returns true.
+  OptionalBool is_platform_app;
+
   // TODO(nigeltao): be more principled, instead of ad hoc show_in_xxx and
   // show_in_yyy fields?
   OptionalBool show_in_launcher;
diff --git a/chrome/services/printing/pdf_to_pwg_raster_converter.cc b/chrome/services/printing/pdf_to_pwg_raster_converter.cc
index 9291473..59b7200 100644
--- a/chrome/services/printing/pdf_to_pwg_raster_converter.cc
+++ b/chrome/services/printing/pdf_to_pwg_raster_converter.cc
@@ -68,6 +68,22 @@
                                   ? pwg_encoder::PwgHeaderInfo::SRGB
                                   : pwg_encoder::PwgHeaderInfo::SGRAY;
 
+    switch (bitmap_settings.duplex_mode) {
+      case DuplexMode::UNKNOWN_DUPLEX_MODE:
+        NOTREACHED();
+        break;
+      case DuplexMode::SIMPLEX:
+        // Already defaults to false/false.
+        break;
+      case DuplexMode::LONG_EDGE:
+        header_info.duplex = true;
+        break;
+      case DuplexMode::SHORT_EDGE:
+        header_info.duplex = true;
+        header_info.tumble = true;
+        break;
+    }
+
     // Transform odd pages.
     if (page_number % 2) {
       switch (bitmap_settings.odd_page_transform) {
diff --git a/chrome/services/printing/public/mojom/pdf_to_pwg_raster_converter.mojom b/chrome/services/printing/public/mojom/pdf_to_pwg_raster_converter.mojom
index d5e1511b2..285099a 100644
--- a/chrome/services/printing/public/mojom/pdf_to_pwg_raster_converter.mojom
+++ b/chrome/services/printing/public/mojom/pdf_to_pwg_raster_converter.mojom
@@ -15,6 +15,14 @@
     TRANSFORM_FLIP_VERTICAL
   };
 
+  enum DuplexMode {
+    SIMPLEX,
+    LONG_EDGE,
+    SHORT_EDGE,
+  };
+
+  DuplexMode duplex_mode;
+
   // How to transform odd-numbered pages.
   TransformType odd_page_transform;
 
diff --git a/chrome/services/printing/public/mojom/pdf_to_pwg_raster_converter_mojom_traits.cc b/chrome/services/printing/public/mojom/pdf_to_pwg_raster_converter_mojom_traits.cc
index df9ece4..c66dcc3 100644
--- a/chrome/services/printing/public/mojom/pdf_to_pwg_raster_converter_mojom_traits.cc
+++ b/chrome/services/printing/public/mojom/pdf_to_pwg_raster_converter_mojom_traits.cc
@@ -14,7 +14,8 @@
   out->rotate_all_pages = data.rotate_all_pages();
   out->reverse_page_order = data.reverse_page_order();
   out->use_color = data.use_color();
-  return data.ReadOddPageTransform(&out->odd_page_transform);
+  return data.ReadOddPageTransform(&out->odd_page_transform) &&
+         data.ReadDuplexMode(&out->duplex_mode);
 }
 
 }  // namespace mojo
diff --git a/chrome/services/printing/public/mojom/pdf_to_pwg_raster_converter_mojom_traits.h b/chrome/services/printing/public/mojom/pdf_to_pwg_raster_converter_mojom_traits.h
index 7c83a273..5eb87a8 100644
--- a/chrome/services/printing/public/mojom/pdf_to_pwg_raster_converter_mojom_traits.h
+++ b/chrome/services/printing/public/mojom/pdf_to_pwg_raster_converter_mojom_traits.h
@@ -60,6 +60,43 @@
 };
 
 template <>
+struct EnumTraits<printing::mojom::PwgRasterSettings::DuplexMode,
+                  printing::DuplexMode> {
+  static printing::mojom::PwgRasterSettings::DuplexMode ToMojom(
+      printing::DuplexMode duplex_mode) {
+    switch (duplex_mode) {
+      case printing::DuplexMode::UNKNOWN_DUPLEX_MODE:
+        break;
+      case printing::DuplexMode::SIMPLEX:
+        return printing::mojom::PwgRasterSettings::DuplexMode::SIMPLEX;
+      case printing::DuplexMode::LONG_EDGE:
+        return printing::mojom::PwgRasterSettings::DuplexMode::LONG_EDGE;
+      case printing::DuplexMode::SHORT_EDGE:
+        return printing::mojom::PwgRasterSettings::DuplexMode::SHORT_EDGE;
+    }
+    NOTREACHED() << "Unknown duplex mode " << static_cast<int>(duplex_mode);
+    return printing::mojom::PwgRasterSettings::DuplexMode::SIMPLEX;
+  }
+
+  static bool FromMojom(printing::mojom::PwgRasterSettings::DuplexMode input,
+                        printing::DuplexMode* output) {
+    switch (input) {
+      case printing::mojom::PwgRasterSettings::DuplexMode::SIMPLEX:
+        *output = printing::DuplexMode::SIMPLEX;
+        return true;
+      case printing::mojom::PwgRasterSettings::DuplexMode::LONG_EDGE:
+        *output = printing::DuplexMode::LONG_EDGE;
+        return true;
+      case printing::mojom::PwgRasterSettings::DuplexMode::SHORT_EDGE:
+        *output = printing::DuplexMode::SHORT_EDGE;
+        return true;
+    }
+    NOTREACHED() << "Unknown duplex mode " << static_cast<int>(input);
+    return false;
+  }
+};
+
+template <>
 class StructTraits<printing::mojom::PwgRasterSettingsDataView,
                    printing::PwgRasterSettings> {
  public:
@@ -76,6 +113,10 @@
       const printing::PwgRasterSettings& settings) {
     return settings.odd_page_transform;
   }
+  static printing::DuplexMode duplex_mode(
+      const printing::PwgRasterSettings& settings) {
+    return settings.duplex_mode;
+  }
 
   static bool Read(printing::mojom::PwgRasterSettingsDataView data,
                    printing::PwgRasterSettings* out_settings);
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 5c01854..484506e 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -5219,6 +5219,13 @@
       # target should be deleted and this line removed. See the
       # chrome_extensions_interactive_uitests target for more.
       deps += [ "//extensions:chrome_extensions_interactive_uitests" ]
+
+      if (include_js_tests) {
+        sources += [
+          "../browser/ui/webui/extensions/extension_settings_browsertest.cc",
+          "../browser/ui/webui/extensions/extension_settings_browsertest.h",
+        ]
+      }
     }
 
     if (!enable_native_notifications) {
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/omaha/MockRequestGenerator.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/omaha/MockRequestGenerator.java
index d9e4493..75dc803 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/omaha/MockRequestGenerator.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/omaha/MockRequestGenerator.java
@@ -14,6 +14,8 @@
         HANDSET, TABLET
     }
 
+    public enum SignedInStatus { TRUE, FALSE }
+
     public static final String UUID_PHONE = "uuid_phone";
     public static final String UUID_TABLET = "uuid_tablet";
     public static final String SERVER_URL = "http://totallylegitserver.com";
@@ -27,9 +29,13 @@
 
     private final boolean mIsOnTablet;
 
-    public MockRequestGenerator(Context context, DeviceType deviceType) {
+    private final boolean mIsSignedIn;
+
+    public MockRequestGenerator(
+            Context context, DeviceType deviceType, SignedInStatus signInStatus) {
         super(context);
         mIsOnTablet = deviceType == DeviceType.TABLET;
+        mIsSignedIn = signInStatus == SignedInStatus.TRUE;
     }
 
     @Override
@@ -73,6 +79,11 @@
     }
 
     @Override
+    public int getNumSignedIn() {
+        return mIsSignedIn ? 1 : 0;
+    }
+
+    @Override
     public String getAdditionalParameters() {
         return ADDITIONAL_PARAMETERS;
     }
diff --git a/chrome/test/chromedriver/server/server.py b/chrome/test/chromedriver/server/server.py
index 164fc76..24b1da5f 100644
--- a/chrome/test/chromedriver/server/server.py
+++ b/chrome/test/chromedriver/server/server.py
@@ -43,8 +43,9 @@
       chromedriver_args.extend(['--devtools-replay=%s' % devtools_replay_path])
 
     self._process = subprocess.Popen(chromedriver_args)
-    self._url = 'http://127.0.0.1:%d' % port
+    self._host = '127.0.0.1'
     self._port = port
+    self._url = 'http://%s:%d' % (self._host, port)
     if self._process is None:
       raise RuntimeError('ChromeDriver server cannot be started')
 
@@ -68,6 +69,9 @@
   def GetUrl(self):
     return self._url
 
+  def GetHost(self):
+    return self._host
+
   def GetPort(self):
     return self._port
 
diff --git a/chrome/test/chromedriver/test/run_webdriver_tests.py b/chrome/test/chromedriver/test/run_webdriver_tests.py
new file mode 100644
index 0000000..4fcfcdb
--- /dev/null
+++ b/chrome/test/chromedriver/test/run_webdriver_tests.py
@@ -0,0 +1,187 @@
+# 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.
+
+"""WPT WebDriver tests runner."""
+
+import pytest
+import os
+import argparse
+import sys
+import json
+import time
+
+_TEST_DIR = os.path.abspath(os.path.dirname(__file__))
+_CHROMEDRIVER_DIR = os.path.join(_TEST_DIR, os.pardir)
+SRC_DIR = os.path.join(_CHROMEDRIVER_DIR, os.pardir, os.pardir, os.pardir)
+_CLIENT_DIR = os.path.join(_CHROMEDRIVER_DIR, "client")
+_SERVER_DIR = os.path.join(_CHROMEDRIVER_DIR, "server")
+
+sys.path.insert(1, _CHROMEDRIVER_DIR)
+import util
+
+sys.path.insert(1, _SERVER_DIR)
+import server
+
+WD_CLIENT_PATH = ('third_party/blink/tools/blinkpy/'
+                  'third_party/wpt/wpt/tools/webdriver')
+WEBDRIVER_CLIENT_ABS_PATH = os.path.join(SRC_DIR, WD_CLIENT_PATH)
+
+
+class WebDriverTestResult(object):
+  def __init__(self, test_name, test_status, messsage=None):
+    self.test_name = test_name
+    self.test_status = test_status
+    self.message = messsage
+
+class SubtestResultRecorder(object):
+  def __init__(self):
+    self.result = []
+
+  def pytest_runtest_logreport(self, report):
+    if report.passed and report.when == "call":
+      self.record_pass(report)
+    elif report.failed:
+      if report.when != "call":
+        self.record_error(report)
+      else:
+        self.record_fail(report)
+    elif report.skipped:
+      self.record_skip(report)
+
+  def record_pass(self, report):
+    self.record(report.nodeid, "PASS")
+
+  def record_fail(self, report):
+    message = report.longrepr.reprcrash.message
+    self.record(report.nodeid, "FAIL", message=message)
+
+  def record_error(self, report):
+    # error in setup/teardown
+    if report.when != "call":
+      message = "error in %s" % report.when
+    self.record(report.nodeid, "FAIL", message)
+
+  def record_skip(self, report):
+    self.record(report.nodeid, "FAIL",
+                "In-test skip decorators are disallowed.")
+
+  def record(self, test, status, message=None):
+    self.result.append(WebDriverTestResult(
+        test, status, message))
+
+def set_up_config(chromedriver_server):
+  # These envs are used to create a WebDriver session in the fixture.py file.
+  os.environ["WD_HOST"] = chromedriver_server.GetHost()
+  os.environ["WD_PORT"] = str(chromedriver_server.GetPort())
+  os.environ["WD_CAPABILITIES"] = json.dumps({
+      "goog:chromeOptions": {
+          "w3c": True,
+          "prefs": {
+              "profile": {
+                  "default_content_setting_values": {
+                      "popups": 1
+                  }
+              }
+          },
+      }
+  })
+  os.environ["WD_SERVER_CONFIG"] = json.dumps({
+    "browser_host": "web-platform.test",
+    "ports": {"http": [8000, 8001]}})
+
+def run_test(path):
+  subtests = SubtestResultRecorder()
+  pytest.main(["--show-capture", "no", path], plugins=[subtests])
+  return subtests.result
+
+if __name__ == '__main__':
+  parser = argparse.ArgumentParser()
+  parser.description = __doc__
+  parser.add_argument(
+      '--chromedriver',
+      required=True,
+      help='Path to chromedriver binary')
+  parser.add_argument(
+      '--log-path',
+      help='Output verbose server logs to this file')
+  parser.add_argument(
+      '--chrome', help='Path to chrome binary')
+  parser.add_argument(
+      '--isolated-script-test-output',
+      help='JSON output file used by swarming')
+  parser.add_argument(
+      '--isolated-script-test-perf-output',
+      help='JSON perf output file used by swarming, ignored')
+  parser.add_argument(
+      '--test-path',
+      help='Path to the WPT WebDriver tests')
+
+  options = parser.parse_args()
+
+  options.chromedriver = util.GetAbsolutePathOfUserPath(options.chromedriver)
+  if (not os.path.exists(options.chromedriver) and
+      util.GetPlatformName() == 'win' and
+      not options.chromedriver.lower().endswith('.exe')):
+    options.chromedriver = options.chromedriver + '.exe'
+
+  if not os.path.exists(options.chromedriver):
+    parser.error('Path given by --chromedriver is invalid.\n' +
+                 'Please run "%s --help" for help' % __file__)
+
+  chromedriver_server = server.Server(options.chromedriver, options.log_path)
+
+  if not chromedriver_server.IsRunning():
+    print 'ChromeDriver is not running.'
+    sys.exit(1)
+
+  set_up_config(chromedriver_server)
+
+  test_path = options.test_path
+  start_time = time.time()
+  test_results = []
+  sys.path.insert(1, WEBDRIVER_CLIENT_ABS_PATH)
+  if os.path.isfile(test_path):
+    test_results = run_test(test_path)
+  elif os.path.isdir(test_path):
+    for root, dirnames, filenames in os.walk(test_path):
+      for filename in filenames:
+        if '__init__' in filename:
+          continue
+        test_file = os.path.join(root, filename)
+        test_results += run_test(test_file)
+  else:
+    print '%s is not a file nor directory.' % test_path
+    sys.exit(1)
+  sys.path.remove(WEBDRIVER_CLIENT_ABS_PATH)
+
+  chromedriver_server.Kill()
+
+  if options.isolated_script_test_output:
+    output = {
+      'interrupted': False,
+      'num_failures_by_type': { },
+      'path_delimiter': '.',
+      'seconds_since_epoch': start_time,
+      'tests': { },
+      'version': 3,
+    }
+
+    success_count = 0
+    for test_result in test_results:
+      output['tests'][test_result.test_name] = {
+        'expected': 'PASS',
+        'actual': test_result.test_status,
+        'message': test_result.message
+      }
+
+      if test_result.test_status == 'PASS':
+        success_count += 1
+
+    output['num_failures_by_type']['PASS'] = success_count
+    output['num_failures_by_type']['FAIL'] = len(test_results) - success_count
+
+    with open(options.isolated_script_test_output, 'w') as fp:
+      json.dump(output, fp)
+
+  sys.exit(0)
diff --git a/chrome/test/data/extensions/api_test/webrequest/test_extra_headers.js b/chrome/test/data/extensions/api_test/webrequest/test_extra_headers.js
index dc0f2666..04d1eea 100644
--- a/chrome/test/data/extensions/api_test/webrequest/test_extra_headers.js
+++ b/chrome/test/data/extensions/api_test/webrequest/test_extra_headers.js
@@ -184,29 +184,4 @@
       }));
     });
   },
-
-  function testRedirectToUrlWithExtraHeadersListener() {
-    // Set a cookie so the cookie request header is set.
-    navigateAndWait(getSetCookieUrl('foo', 'bar'), function() {
-      var finalURL = getServerURL('echoheader?Cookie');
-      var url = getServerURL('server-redirect?' + finalURL);
-      var listener = callbackPass(function(details) {
-        removeHeader(details.requestHeaders, 'cookie');
-        return {requestHeaders: details.requestHeaders};
-      });
-      chrome.webRequest.onBeforeSendHeaders.addListener(listener,
-          {urls: [finalURL]}, ['requestHeaders', 'blocking', 'extraHeaders']);
-
-      navigateAndWait(url, function(tab) {
-        chrome.test.assertEq(finalURL, tab.url);
-        chrome.webRequest.onBeforeSendHeaders.removeListener(listener);
-        chrome.tabs.executeScript(tab.id, {
-          code: 'document.body.innerText'
-        }, callbackPass(function(results) {
-          chrome.test.assertTrue(results[0].indexOf('bar') == -1,
-              'Header not removed.');
-        }));
-      });
-    });
-  },
 ]);
diff --git a/chrome/test/data/extensions/api_test/webrequest/test_redirects.js b/chrome/test/data/extensions/api_test/webrequest/test_redirects.js
index 6d593a9..d4c19779 100644
--- a/chrome/test/data/extensions/api_test/webrequest/test_redirects.js
+++ b/chrome/test/data/extensions/api_test/webrequest/test_redirects.js
@@ -27,253 +27,203 @@
   });
 }
 
-chrome.test.getConfig(function(config) {
-  var onHeadersReceivedExtraInfoSpec = ['blocking'];
-  if (config.customArg === 'useExtraHeaders')
-    onHeadersReceivedExtraInfoSpec.push('extraHeaders');
+runTests([
+  function redirectToDataUrlOnHeadersReceived() {
+    var url = getServerURL('echo');
+    var listener = function(details) {
+      return {redirectUrl: dataURL};
+    };
+    chrome.webRequest.onHeadersReceived.addListener(listener,
+        {urls: [url]}, ['blocking']);
 
-  runTests([
-    function redirectToDataUrlOnHeadersReceived() {
-      var url = getServerURL('echo');
-      var listener = function(details) {
-        return {redirectUrl: dataURL};
-      };
-      chrome.webRequest.onHeadersReceived.addListener(listener,
-          {urls: [url]}, onHeadersReceivedExtraInfoSpec);
+    assertRedirectSucceeds(url, dataURL, function() {
+      chrome.webRequest.onHeadersReceived.removeListener(listener);
+    });
+  },
 
-      assertRedirectSucceeds(url, dataURL, function() {
-        chrome.webRequest.onHeadersReceived.removeListener(listener);
-      });
-    },
+  function redirectToAboutUrlOnHeadersReceived() {
+    var url = getServerURL('echo');
+    var listener = function(details) {
+      return {redirectUrl: aboutURL};
+    };
+    chrome.webRequest.onHeadersReceived.addListener(listener,
+        {urls: [url]}, ['blocking']);
 
-    function redirectToAboutUrlOnHeadersReceived() {
-      var url = getServerURL('echo');
-      var listener = function(details) {
-        return {redirectUrl: aboutURL};
-      };
-      chrome.webRequest.onHeadersReceived.addListener(listener,
-          {urls: [url]}, onHeadersReceivedExtraInfoSpec);
+    assertRedirectSucceeds(url, aboutURL, function() {
+      chrome.webRequest.onHeadersReceived.removeListener(listener);
+    });
+  },
 
-      assertRedirectSucceeds(url, aboutURL, function() {
-        chrome.webRequest.onHeadersReceived.removeListener(listener);
-      });
-    },
+  function redirectToNonWebAccessibleUrlOnHeadersReceived() {
+    var url = getServerURL('echo');
+    var listener = function(details) {
+      return {redirectUrl: getURLNonWebAccessible()};
+    };
+    chrome.webRequest.onHeadersReceived.addListener(listener,
+        {urls: [url]}, ['blocking']);
 
-    function redirectToNonWebAccessibleUrlOnHeadersReceived() {
-      var url = getServerURL('echo');
-      var listener = function(details) {
-        return {redirectUrl: getURLNonWebAccessible()};
-      };
-      chrome.webRequest.onHeadersReceived.addListener(listener,
-          {urls: [url]}, onHeadersReceivedExtraInfoSpec);
+    assertRedirectSucceeds(url, getURLNonWebAccessible(), function() {
+      chrome.webRequest.onHeadersReceived.removeListener(listener);
+    });
+  },
 
-      assertRedirectSucceeds(url, getURLNonWebAccessible(), function() {
-        chrome.webRequest.onHeadersReceived.removeListener(listener);
-      });
-    },
+  function redirectToServerRedirectOnHeadersReceived() {
+    var url = getServerURL('echo');
+    var redirectURL = getServerURL('server-redirect?' + getURLWebAccessible());
+    var listener = function(details) {
+      return {redirectUrl: redirectURL};
+    };
+    chrome.webRequest.onHeadersReceived.addListener(listener,
+        {urls: [url]}, ['blocking']);
 
-    function redirectToServerRedirectOnHeadersReceived() {
-      var url = getServerURL('echo');
-      var redirectURL = getServerURL('server-redirect?' +
-          getURLWebAccessible());
-      var listener = function(details) {
-        return {redirectUrl: redirectURL};
-      };
-      chrome.webRequest.onHeadersReceived.addListener(listener,
-          {urls: [url]}, onHeadersReceivedExtraInfoSpec);
+    assertRedirectSucceeds(url, getURLWebAccessible(), function() {
+      chrome.webRequest.onHeadersReceived.removeListener(listener);
+    });
+  },
 
-      assertRedirectSucceeds(url, getURLWebAccessible(), function() {
-        chrome.webRequest.onHeadersReceived.removeListener(listener);
-      });
-    },
+  function serverRedirectThenExtensionRedirectOnHeadersReceived() {
+    var url_1 = getServerURL('echo');
+    var url_2 = getURLWebAccessible();
+    var serverRedirect = getServerURL('server-redirect?' + url_1);
+    var listener = function(details) {
+      return {redirectUrl: url_2};
+    };
+    chrome.webRequest.onHeadersReceived.addListener(
+      listener,
+      { urls: [url_1] },
+      ["blocking"]
+    );
 
-    function serverRedirectThenExtensionRedirectOnHeadersReceived() {
-      var url_1 = getServerURL('echo');
-      var url_2 = getURLWebAccessible();
-      var serverRedirect = getServerURL('server-redirect?' + url_1);
-      var listener = function(details) {
-        return {redirectUrl: url_2};
-      };
-      chrome.webRequest.onHeadersReceived.addListener(
-        listener,
-        { urls: [url_1] },
-        ["blocking"]
-      );
+    assertRedirectSucceeds(serverRedirect, url_2, function() {
+      chrome.webRequest.onHeadersReceived.removeListener(listener);
+    });
+  },
 
-      assertRedirectSucceeds(serverRedirect, url_2, function() {
-        chrome.webRequest.onHeadersReceived.removeListener(listener);
-      });
-    },
+  function redirectToUnallowedServerRedirectOnHeadersReceived() {
+    var url = getServerURL('echo');
+    var redirectURL = getServerURL('server-redirect?' +
+        getURLNonWebAccessible());
+    var listener = function(details) {
+      return {redirectUrl: redirectURL};
+    };
+    chrome.webRequest.onHeadersReceived.addListener(listener,
+        {urls: [url]}, ['blocking']);
 
-    function redirectToUnallowedServerRedirectOnHeadersReceived() {
-      var url = getServerURL('echo');
-      var redirectURL = getServerURL('server-redirect?' +
-          getURLNonWebAccessible());
-      var listener = function(details) {
-        return {redirectUrl: redirectURL};
-      };
-      chrome.webRequest.onHeadersReceived.addListener(listener,
-          {urls: [url]}, onHeadersReceivedExtraInfoSpec);
+    // The page should be redirected to redirectURL, but not to the non web
+    // accessible URL.
+    assertRedirectSucceeds(url, redirectURL, function() {
+      chrome.webRequest.onHeadersReceived.removeListener(listener);
+    });
+  },
 
-      // The page should be redirected to redirectURL, but not to the non web
-      // accessible URL.
-      assertRedirectSucceeds(url, redirectURL, function() {
-        chrome.webRequest.onHeadersReceived.removeListener(listener);
-      });
-    },
+  function redirectToDataUrlOnBeforeRequest() {
+    var url = getServerURL('echo');
+    var listener = function(details) {
+      return {redirectUrl: dataURL};
+    };
+    chrome.webRequest.onBeforeRequest.addListener(listener,
+        {urls: [url]}, ['blocking']);
 
-    function redirectToDataUrlOnBeforeRequest() {
-      var url = getServerURL('echo');
-      var listener = function(details) {
-        return {redirectUrl: dataURL};
-      };
-      chrome.webRequest.onBeforeRequest.addListener(listener,
-          {urls: [url]}, ['blocking']);
+    assertRedirectSucceeds(url, dataURL, function() {
+      chrome.webRequest.onBeforeRequest.removeListener(listener);
+    });
+  },
 
-      assertRedirectSucceeds(url, dataURL, function() {
-        chrome.webRequest.onBeforeRequest.removeListener(listener);
-      });
-    },
+  function redirectToAboutUrlOnBeforeRequest() {
+    var url = getServerURL('echo');
+    var listener = function(details) {
+      return {redirectUrl: aboutURL};
+    };
+    chrome.webRequest.onBeforeRequest.addListener(listener,
+        {urls: [url]}, ['blocking']);
 
-    function redirectToAboutUrlOnBeforeRequest() {
-      var url = getServerURL('echo');
-      var listener = function(details) {
-        return {redirectUrl: aboutURL};
-      };
-      chrome.webRequest.onBeforeRequest.addListener(listener,
-          {urls: [url]}, ['blocking']);
+    assertRedirectSucceeds(url, aboutURL, function() {
+      chrome.webRequest.onBeforeRequest.removeListener(listener);
+    });
+  },
 
-      assertRedirectSucceeds(url, aboutURL, function() {
-        chrome.webRequest.onBeforeRequest.removeListener(listener);
-      });
-    },
+  function redirectToNonWebAccessibleUrlOnBeforeRequest() {
+    var url = getServerURL('echo');
+    var listener = function(details) {
+      return {redirectUrl: getURLNonWebAccessible()};
+    };
+    chrome.webRequest.onBeforeRequest.addListener(listener,
+        {urls: [url]}, ['blocking']);
 
-    function redirectToNonWebAccessibleUrlOnBeforeRequest() {
-      var url = getServerURL('echo');
-      var listener = function(details) {
-        return {redirectUrl: getURLNonWebAccessible()};
-      };
-      chrome.webRequest.onBeforeRequest.addListener(listener,
-          {urls: [url]}, ['blocking']);
+    assertRedirectSucceeds(url, getURLNonWebAccessible(), function() {
+      chrome.webRequest.onBeforeRequest.removeListener(listener);
+    });
+  },
 
-      assertRedirectSucceeds(url, getURLNonWebAccessible(), function() {
-        chrome.webRequest.onBeforeRequest.removeListener(listener);
-      });
-    },
+  function redirectToServerRedirectOnBeforeRequest() {
+    var url = getServerURL('echo');
+    var redirectURL = getServerURL('server-redirect?' + getURLWebAccessible());
+    var listener = function(details) {
+      return {redirectUrl: redirectURL};
+    };
+    chrome.webRequest.onBeforeRequest.addListener(listener,
+        {urls: [url]}, ['blocking']);
 
-    function redirectToServerRedirectOnBeforeRequest() {
-      var url = getServerURL('echo');
-      var redirectURL = getServerURL('server-redirect?' +
-          getURLWebAccessible());
-      var listener = function(details) {
-        return {redirectUrl: redirectURL};
-      };
-      chrome.webRequest.onBeforeRequest.addListener(listener,
-          {urls: [url]}, ['blocking']);
+    assertRedirectSucceeds(url, getURLWebAccessible(), function() {
+      chrome.webRequest.onBeforeRequest.removeListener(listener);
+    });
+  },
 
-      assertRedirectSucceeds(url, getURLWebAccessible(), function() {
-        chrome.webRequest.onBeforeRequest.removeListener(listener);
-      });
-    },
+  // A server redirect immediately followed by an extension redirect.
+  // Regression test for:
+  // - https://crbug.com/882661
+  // - https://crbug.com/880741
+  function serverRedirectThenExtensionRedirectOnBeforeRequest() {
+    var url_1 = getServerURL('echo');
+    var url_2 = getURLWebAccessible();
+    var serverRedirect = getServerURL('server-redirect?' + url_1);
+    var listener = function(details) {
+      return {redirectUrl: url_2};
+    };
+    chrome.webRequest.onBeforeRequest.addListener(
+      listener,
+      { urls: [url_1] },
+      ["blocking"]
+    );
 
-    // A server redirect immediately followed by an extension redirect.
-    // Regression test for:
-    // - https://crbug.com/882661
-    // - https://crbug.com/880741
-    function serverRedirectThenExtensionRedirectOnBeforeRequest() {
-      var url_1 = getServerURL('echo');
-      var url_2 = getURLWebAccessible();
-      var serverRedirect = getServerURL('server-redirect?' + url_1);
-      var listener = function(details) {
-        return {redirectUrl: url_2};
-      };
-      chrome.webRequest.onBeforeRequest.addListener(
-        listener,
-        { urls: [url_1] },
-        ["blocking"]
-      );
+    assertRedirectSucceeds(serverRedirect, url_2, function() {
+      chrome.webRequest.onBeforeRequest.removeListener(listener);
+    });
+  },
 
-      assertRedirectSucceeds(serverRedirect, url_2, function() {
-        chrome.webRequest.onBeforeRequest.removeListener(listener);
-      });
-    },
+  function redirectToUnallowedServerRedirectOnBeforeRequest() {
+    var url = getServerURL('echo');
+    var redirectURL = getServerURL('server-redirect?' +
+        getURLNonWebAccessible());
+    var listener = function(details) {
+      return {redirectUrl: redirectURL};
+    };
+    chrome.webRequest.onBeforeRequest.addListener(listener,
+        {urls: [url]}, ['blocking']);
 
-    function redirectToUnallowedServerRedirectOnBeforeRequest() {
-      var url = getServerURL('echo');
-      var redirectURL = getServerURL('server-redirect?' +
-          getURLNonWebAccessible());
-      var listener = function(details) {
-        return {redirectUrl: redirectURL};
-      };
-      chrome.webRequest.onBeforeRequest.addListener(listener,
-          {urls: [url]}, ['blocking']);
+    // The page should be redirected to redirectURL, but not to the non web
+    // accessible URL.
+    assertRedirectSucceeds(url, redirectURL, function() {
+      chrome.webRequest.onBeforeRequest.removeListener(listener);
+    });
+  },
 
-      // The page should be redirected to redirectURL, but not to the non web
-      // accessible URL.
-      assertRedirectSucceeds(url, redirectURL, function() {
-        chrome.webRequest.onBeforeRequest.removeListener(listener);
-      });
-    },
+  function redirectToAboutUrlWithServerRedirect() {
+    assertRedirectFails(getServerURL('server-redirect?' + aboutURL));
+  },
 
-    function redirectToAboutUrlWithServerRedirect() {
-      assertRedirectFails(getServerURL('server-redirect?' + aboutURL));
-    },
+  function redirectToDataUrlWithServerRedirect() {
+    assertRedirectFails(getServerURL('server-redirect?' + dataURL));
+  },
 
-    function redirectToDataUrlWithServerRedirect() {
-      assertRedirectFails(getServerURL('server-redirect?' + dataURL));
-    },
+  function redirectToNonWebAccessibleUrlWithServerRedirect() {
+    assertRedirectFails(
+        getServerURL('server-redirect?' + getURLNonWebAccessible()));
+  },
 
-    function redirectToNonWebAccessibleUrlWithServerRedirect() {
-      assertRedirectFails(
-          getServerURL('server-redirect?' + getURLNonWebAccessible()));
-    },
-
-    function redirectToWebAccessibleUrlWithServerRedirect() {
-      assertRedirectSucceeds(
-          getServerURL('server-redirect?' + getURLWebAccessible()),
-          getURLWebAccessible());
-    },
-
-    function beforeRequestRedirectAfterServerRedirect() {
-      var finalURL = getServerURL('echo?foo');
-      var intermediateURL = getServerURL('echo?bar');
-      var redirectURL = getServerURL('server-redirect?' + intermediateURL);
-
-      var onBeforeSendHeadersListener = function(details) {
-        chrome.test.assertFalse(details.url == intermediateURL,
-            'intermediateURL should be redirected before the request starts.');
-      };
-      // Make sure all URLs use the extraHeaders path to expose
-      // http://crbug.com/918761.
-      chrome.webRequest.onBeforeSendHeaders.addListener(
-          onBeforeSendHeadersListener,
-          {urls: ['<all_urls>']}, ['blocking', 'extraHeaders']);
-
-      var onBeforeRequestListener = function(details) {
-        return {redirectUrl: finalURL};
-      };
-      chrome.webRequest.onBeforeRequest.addListener(onBeforeRequestListener,
-          {urls: [intermediateURL]}, ['blocking']);
-
-      assertRedirectSucceeds(redirectURL, finalURL, function() {
-        chrome.webRequest.onBeforeRequest.removeListener(
-            onBeforeRequestListener);
-        chrome.webRequest.onBeforeSendHeaders.removeListener(
-            onBeforeSendHeadersListener);
-      });
-    },
-
-    function serverRedirectChain() {
-      var url = getServerURL('echo');
-      var redirectURL = getServerURL('server-redirect?' +
-          getServerURL('server-redirect?' + url));
-      var listener = function(details) {};
-      chrome.webRequest.onHeadersReceived.addListener(listener,
-          {urls: ['<all_urls>']}, onHeadersReceivedExtraInfoSpec);
-
-      assertRedirectSucceeds(redirectURL, url, function() {
-        chrome.webRequest.onHeadersReceived.removeListener(listener);
-      });
-    },
-  ]);
-});
+  function redirectToWebAccessibleUrlWithServerRedirect() {
+    assertRedirectSucceeds(
+        getServerURL('server-redirect?' + getURLWebAccessible()),
+        getURLWebAccessible());
+  },
+]);
diff --git a/chrome/test/data/extensions/options_page_in_view/manifest.json b/chrome/test/data/extensions/options_page_in_view/manifest.json
index 1331309..3f36abb 100644
--- a/chrome/test/data/extensions/options_page_in_view/manifest.json
+++ b/chrome/test/data/extensions/options_page_in_view/manifest.json
@@ -1,6 +1,7 @@
 {
   "name": "Extension With Options Page",
   "description": "An extension with an options page and an extra page",
+  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm3v3jAleGUmPIlw9zsz5xfNE0uP41ZguA2nz4sM6Dy477Ve8rLMwqdsDjxDDnHWdIxNsGkXQbP+Yo8jhH/tEc1lo780b5T3Sjj+ULKsW/lunxQV35Q4x5UNs0vryQvVZZCn4euYSn66XtRGIe4+3nq55cw8HgF2Ex3r7jJ3jRzQg0LV9j1JyoibkjwAqxvdYdnHtMvVfJPTo8NoTrFFZbq1qBQTZCvZKIFJX/iwY/ib3AnH+DCnc1jj2mBuoOoNYHuRwYt/7uCmst8UNSr47deNlv4neCh5IIWZjz6AROFRUGFrIcP6s3f1FiOQoGGAD5x7adL2zAsScNqyCm2TczQIDAQAB",
   "options_ui": { "page": "options.html", "open_in_tab": false },
   "version": "0.1.1",
   "manifest_version": 2
diff --git a/chrome/test/data/extensions/theme_test_toolbar_color_no_image/manifest.json b/chrome/test/data/extensions/theme_test_toolbar_color_no_image/manifest.json
new file mode 100644
index 0000000..501223e
--- /dev/null
+++ b/chrome/test/data/extensions/theme_test_toolbar_color_no_image/manifest.json
@@ -0,0 +1,12 @@
+{
+  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuGMyBRIRAOiUgubfmS+4RHiNi/u/okGcU17xoKTXnziTiwmo7lq4V7unr3Vn4wWSG5bIG+yhQhQAG1TvfsXimBIUrrVKX4b9IADZIHR5akWgb1K4EV111PW/D8RDOwYgTPHPx5eA49TxB0Yw36DNC9LE0tPZOGgDR9hCFgaN1bcszlmJBcBmx39iGCeKPIjKB4bp5hXe33T5IUdoyuW4JHf4JDqC6unwQ1Y9nwzCRUbFis7YevbGQCgWP3I+Hp+bOo30mC2/Gq2UZfIczR4vIOrarxBV8IO4Ue4gzaDl6dGkGKnkZ3JdZKbLeRke1DmaKfjVSsTUp++X2nfMaPZphQIDAQAB",
+  "manifest_version": 2,
+  "name": "Toolbar Color with  No Image Test",
+  "theme": {
+    "colors": {
+      "frame": [ 255, 0, 255 ],
+      "toolbar": [ 0, 255, 0 ]
+      }
+  },
+  "version": "0.0.1"
+}
diff --git a/chrome/test/data/printing/pdf_to_pwg_raster_long_edge_test.pwg b/chrome/test/data/printing/pdf_to_pwg_raster_long_edge_test.pwg
new file mode 100644
index 0000000..ee083dc
--- /dev/null
+++ b/chrome/test/data/printing/pdf_to_pwg_raster_long_edge_test.pwg
Binary files differ
diff --git a/chrome/test/data/printing/pdf_to_pwg_raster_long_edge_test_32.pwg b/chrome/test/data/printing/pdf_to_pwg_raster_long_edge_test_32.pwg
new file mode 100644
index 0000000..9c56b86
--- /dev/null
+++ b/chrome/test/data/printing/pdf_to_pwg_raster_long_edge_test_32.pwg
Binary files differ
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index a32d494..e13e316c 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -12,6 +12,7 @@
     "bookmarks/bookmarks_focus_test.js",
     "cr_elements/cr_elements_focus_test.js",
     "cr_focus_row_behavior_interactive_test.js",
+    "extensions/cr_extensions_interactive_ui_tests.js",
     "history/history_focus_test.js",
     "print_preview/print_preview_interactive_ui_tests.js",
     "settings/cr_settings_interactive_ui_tests.js",
diff --git a/chrome/test/data/webui/extensions/cr_extensions_interactive_ui_tests.js b/chrome/test/data/webui/extensions/cr_extensions_interactive_ui_tests.js
new file mode 100644
index 0000000..2d56707
--- /dev/null
+++ b/chrome/test/data/webui/extensions/cr_extensions_interactive_ui_tests.js
@@ -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.
+
+/** @fileoverview Runs the Polymer Extensions interactive UI tests. */
+
+/** @const {string} Path to source root. */
+const ROOT_PATH = '../../../../../';
+
+// Polymer BrowserTest fixture.
+GEN_INCLUDE(
+    [ROOT_PATH + 'chrome/test/data/webui/polymer_interactive_ui_test.js']);
+GEN('#include "chrome/browser/ui/webui/extensions/' +
+    'extension_settings_browsertest.h"');
+
+/**
+ * Test fixture for interactive Polymer Extensions elements.
+ * @constructor
+ * @extends {PolymerInteractiveUITest}
+ */
+const CrExtensionsInteractiveUITest = class extends PolymerInteractiveUITest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://extensions/';
+  }
+
+  /** @override */
+  get extraLibraries() {
+    return PolymerTest.getLibraries(ROOT_PATH).concat([
+      '../settings/test_util.js',
+    ]);
+  }
+};
+
+
+/** Test fixture for Sync Page. */
+CrExtensionsOptionsPageTest = class extends CrExtensionsInteractiveUITest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://extensions/?id=ibbpngabdmdpednkhonkkobdeccpkiff';
+  }
+
+  /** @override */
+  get extraLibraries() {
+    return super.extraLibraries.concat([
+      'extension_options_dialog_test.js',
+    ]);
+  }
+
+  /** @override */
+  testGenPreamble() {
+    GEN('  InstallExtensionWithInPageOptions();');
+  }
+
+  /** @override */
+  get typedefCppFixture() {
+    return 'ExtensionSettingsUIBrowserTest';
+  }
+};
+
+TEST_F('CrExtensionsOptionsPageTest', 'All', function() {
+  mocha.run();
+});
diff --git a/chrome/test/data/webui/extensions/extension_options_dialog_test.js b/chrome/test/data/webui/extensions/extension_options_dialog_test.js
new file mode 100644
index 0000000..dc4ff32
--- /dev/null
+++ b/chrome/test/data/webui/extensions/extension_options_dialog_test.js
@@ -0,0 +1,46 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Suite of tests for extension options dialog.
+ * These are run as part of interactive_ui_tests.
+ */
+suite('ExtensionOptionsDialogTest', function() {
+  /** @type {extensions.Manager} */
+  let manager;
+
+  setup(function() {
+    manager = document.querySelector('extensions-manager');
+  });
+
+  function getOptionsDialog() {
+    const dialog = manager.$$('#options-dialog');
+    assertTrue(!!dialog);
+    return dialog;
+  }
+
+  test('show options dialog', function() {
+    const extensionDetailView = manager.$$('extensions-detail-view');
+    assertTrue(!!extensionDetailView);
+
+    const optionsButton = extensionDetailView.$$('#extensions-options');
+
+    // Click the options button.
+    optionsButton.click();
+
+    // Wait for dialog to open.
+    return test_util.eventToPromise('cr-dialog-open', manager)
+        .then(() => {
+          const dialog = getOptionsDialog();
+          let waitForClose = test_util.eventToPromise('close', dialog);
+          // Close dialog and wait.
+          dialog.$.dialog.cancel();
+          return waitForClose;
+        })
+        .then(() => {
+          // Validate that this button is focused after dialog closes.
+          assertEquals(optionsButton.$$('button'), getDeepActiveElement());
+        });
+  });
+});
diff --git a/chrome/test/data/webui/settings/cr_settings_browsertest.js b/chrome/test/data/webui/settings/cr_settings_browsertest.js
index a31f82d..e8b64862 100644
--- a/chrome/test/data/webui/settings/cr_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/cr_settings_browsertest.js
@@ -1345,7 +1345,8 @@
   ]),
 };
 
-TEST_F('CrSettingsSiteListTest', 'SiteList', function() {
+// TODO(crbug.com/929455): flaky, fix.
+TEST_F('CrSettingsSiteListTest', 'DISABLED_SiteList', function() {
   mocha.grep('SiteList').run();
 });
 
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index dfaa0fb..d328928 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -71,7 +71,7 @@
 
 // Enable or disable native controls in video player on Chrome OS.
 const base::Feature kVideoPlayerNativeControls{
-    "VideoPlayerNativeControls", base::FEATURE_DISABLED_BY_DEFAULT};
+    "VideoPlayerNativeControls", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Use the messages.google.com domain as part of the "Messages" feature under
 // "Connected Devices" settings.
diff --git a/components/crash/content/app/BUILD.gn b/components/crash/content/app/BUILD.gn
index 30ba4f9..10481c60 100644
--- a/components/crash/content/app/BUILD.gn
+++ b/components/crash/content/app/BUILD.gn
@@ -198,6 +198,7 @@
     ]
 
     deps = [
+      "//components/gwp_asan/crash_handler",
       "//third_party/crashpad/crashpad/handler:handler",
     ]
 
diff --git a/components/crash/content/app/chrome_crashpad_handler.cc b/components/crash/content/app/chrome_crashpad_handler.cc
index ac121e2..24a8388 100644
--- a/components/crash/content/app/chrome_crashpad_handler.cc
+++ b/components/crash/content/app/chrome_crashpad_handler.cc
@@ -2,8 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <memory>
+
+#include "components/gwp_asan/crash_handler/crash_handler.h"
 #include "third_party/crashpad/crashpad/handler/handler_main.h"
+#include "third_party/crashpad/crashpad/handler/user_stream_data_source.h"
 
 int main(int argc, char* argv[]) {
-  return crashpad::HandlerMain(argc, argv, nullptr);
+  crashpad::UserStreamDataSources user_stream_data_sources;
+  user_stream_data_sources.push_back(
+      std::make_unique<gwp_asan::UserStreamDataSource>());
+
+  return crashpad::HandlerMain(argc, argv, &user_stream_data_sources);
 }
diff --git a/components/exo/shell_surface_base.cc b/components/exo/shell_surface_base.cc
index fc34a85f..7d6582ca 100644
--- a/components/exo/shell_surface_base.cc
+++ b/components/exo/shell_surface_base.cc
@@ -1108,7 +1108,7 @@
     // Prevent window from being activated when hit test region is empty.
     bool activatable = activatable_ && HasHitTestRegion();
     if (activatable != CanActivate()) {
-      set_can_activate(activatable);
+      SetCanActivate(activatable);
       // Activate or deactivate window if activation state changed.
       if (activatable) {
         // Automatically activate only if the window is modal.
diff --git a/components/exo/text_input.cc b/components/exo/text_input.cc
index f2534c4c..02fb7dcd 100644
--- a/components/exo/text_input.cc
+++ b/components/exo/text_input.cc
@@ -4,6 +4,9 @@
 
 #include "components/exo/text_input.h"
 
+#include <algorithm>
+
+#include "base/strings/utf_string_conversions.h"
 #include "components/exo/surface.h"
 #include "components/exo/wm_helper.h"
 #include "third_party/icu/source/common/unicode/uchar.h"
@@ -25,6 +28,14 @@
 
 }  // namespace
 
+size_t OffsetFromUTF8Offset(const base::StringPiece& text, uint32_t offset) {
+  return base::UTF8ToUTF16(text.substr(0, offset)).size();
+}
+
+size_t OffsetFromUTF16Offset(const base::StringPiece16& text, uint32_t offset) {
+  return base::UTF16ToUTF8(text.substr(0, offset)).size();
+}
+
 TextInput::TextInput(std::unique_ptr<Delegate> delegate)
     : delegate_(std::move(delegate)) {}
 
@@ -69,8 +80,14 @@
 }
 
 void TextInput::SetSurroundingText(const base::string16& text,
-                                   uint32_t cursor_pos) {
-  NOTIMPLEMENTED();
+                                   uint32_t cursor_pos,
+                                   uint32_t anchor) {
+  surrounding_text_ = text;
+  cursor_pos_ = gfx::Range(cursor_pos);
+  if (anchor < cursor_pos)
+    cursor_pos_->set_start(anchor);
+  else
+    cursor_pos_->set_end(anchor);
 }
 
 void TextInput::SetTypeModeFlags(ui::TextInputType type,
@@ -163,37 +180,87 @@
 }
 
 bool TextInput::GetTextRange(gfx::Range* range) const {
-  NOTIMPLEMENTED_LOG_ONCE();
-  return false;
+  if (!cursor_pos_)
+    return false;
+  range->set_start(0);
+  if (composition_.text.empty()) {
+    range->set_end(surrounding_text_.size());
+  } else {
+    range->set_end(surrounding_text_.size() - cursor_pos_->length() +
+                   composition_.text.size());
+  }
+  return true;
 }
 
 bool TextInput::GetCompositionTextRange(gfx::Range* range) const {
-  NOTIMPLEMENTED_LOG_ONCE();
-  return false;
+  if (!cursor_pos_ || composition_.text.empty())
+    return false;
+
+  range->set_start(cursor_pos_->start());
+  range->set_end(cursor_pos_->start() + composition_.text.size());
+  return true;
 }
 
 bool TextInput::GetEditableSelectionRange(gfx::Range* range) const {
-  NOTIMPLEMENTED_LOG_ONCE();
-  return false;
+  if (!cursor_pos_)
+    return false;
+  range->set_start(cursor_pos_->start());
+  range->set_end(cursor_pos_->end());
+  return true;
 }
 
 bool TextInput::SetEditableSelectionRange(const gfx::Range& range) {
-  NOTIMPLEMENTED_LOG_ONCE();
-  return false;
+  if (surrounding_text_.size() < range.GetMax())
+    return false;
+  delegate_->SetCursor(
+      gfx::Range(OffsetFromUTF16Offset(surrounding_text_, range.start()),
+                 OffsetFromUTF16Offset(surrounding_text_, range.end())));
+  return true;
 }
 
 bool TextInput::DeleteRange(const gfx::Range& range) {
-  // TODO(mukai): call delegate_->DeleteSurroundingText(range) once it's
-  // supported.
-  NOTIMPLEMENTED_LOG_ONCE();
-  return false;
+  if (surrounding_text_.size() < range.GetMax())
+    return false;
+  delegate_->DeleteSurroundingText(
+      gfx::Range(OffsetFromUTF16Offset(surrounding_text_, range.start()),
+                 OffsetFromUTF16Offset(surrounding_text_, range.end())));
+  return true;
 }
 
 bool TextInput::GetTextFromRange(const gfx::Range& range,
                                  base::string16* text) const {
-  // TODO(mukai): support of surrounding text.
-  NOTIMPLEMENTED_LOG_ONCE();
-  return false;
+  gfx::Range text_range;
+  if (!GetTextRange(&text_range) || !text_range.Contains(range))
+    return false;
+  if (composition_.text.empty() || range.GetMax() <= cursor_pos_->GetMin()) {
+    text->assign(surrounding_text_, range.GetMin(), range.length());
+    return true;
+  }
+  size_t composition_end = cursor_pos_->GetMin() + composition_.text.size();
+  if (range.GetMin() >= composition_end) {
+    size_t start =
+        range.GetMin() - composition_.text.size() + cursor_pos_->length();
+    text->assign(surrounding_text_, start, range.length());
+    return true;
+  }
+
+  size_t start_in_composition = 0;
+  if (range.GetMin() <= cursor_pos_->GetMin()) {
+    text->assign(surrounding_text_, range.GetMin(),
+                 cursor_pos_->GetMin() - range.GetMin());
+  } else {
+    start_in_composition = range.GetMin() - cursor_pos_->GetMin();
+  }
+  if (range.GetMax() <= composition_end) {
+    text->append(composition_.text, start_in_composition,
+                 range.GetMax() - cursor_pos_->GetMin() - start_in_composition);
+  } else {
+    text->append(composition_.text, start_in_composition,
+                 composition_.text.size() - start_in_composition);
+    text->append(surrounding_text_, cursor_pos_->GetMax(),
+                 range.GetMax() - composition_end);
+  }
+  return true;
 }
 
 void TextInput::OnInputMethodChanged() {
@@ -214,7 +281,17 @@
   return true;
 }
 
-void TextInput::ExtendSelectionAndDelete(size_t before, size_t after) {}
+void TextInput::ExtendSelectionAndDelete(size_t before, size_t after) {
+  if (!cursor_pos_)
+    return;
+  uint32_t start =
+      (cursor_pos_->GetMin() < before) ? 0 : (cursor_pos_->GetMin() - before);
+  uint32_t end =
+      std::min(cursor_pos_->GetMax() + after, surrounding_text_.size());
+  delegate_->DeleteSurroundingText(
+      gfx::Range(OffsetFromUTF16Offset(surrounding_text_, start),
+                 OffsetFromUTF16Offset(surrounding_text_, end)));
+}
 
 void TextInput::EnsureCaretNotInRect(const gfx::Rect& rect) {}
 
diff --git a/components/exo/text_input.h b/components/exo/text_input.h
index 4a47440..b00873a 100644
--- a/components/exo/text_input.h
+++ b/components/exo/text_input.h
@@ -23,6 +23,9 @@
 namespace exo {
 class Surface;
 
+size_t OffsetFromUTF8Offset(const base::StringPiece& text, uint32_t offset);
+size_t OffsetFromUTF16Offset(const base::StringPiece16& text, uint32_t offset);
+
 // This class bridges the ChromeOS input method and a text-input context.
 class TextInput : public ui::TextInputClient,
                   public keyboard::KeyboardControllerObserver {
@@ -47,10 +50,11 @@
     // Commit |text| to the current text input session.
     virtual void Commit(const base::string16& text) = 0;
 
-    // Set the cursor position.
+    // Set the cursor position. The range should be in bytes offset.
     virtual void SetCursor(const gfx::Range& selection) = 0;
 
-    // Delete the surrounding text of the current text input.
+    // Delete the surrounding text of the current text input. The range should
+    // be in the bytes offset.
     virtual void DeleteSurroundingText(const gfx::Range& range) = 0;
 
     // Sends a key event.
@@ -83,7 +87,9 @@
   void Resync();
 
   // Sets the surrounding text in the app.
-  void SetSurroundingText(const base::string16& text, uint32_t cursor_pos);
+  void SetSurroundingText(const base::string16& text,
+                          uint32_t cursor_pos,
+                          uint32_t anchor);
 
   // Sets the text input type, mode, flags, and |should_do_learning|.
   void SetTypeModeFlags(ui::TextInputType type,
@@ -148,6 +154,8 @@
   int flags_ = ui::TEXT_INPUT_FLAG_NONE;
   bool should_do_learning_ = true;
   ui::CompositionText composition_;
+  base::string16 surrounding_text_;
+  base::Optional<gfx::Range> cursor_pos_;
   base::i18n::TextDirection direction_ = base::i18n::UNKNOWN_DIRECTION;
 
   DISALLOW_COPY_AND_ASSIGN(TextInput);
diff --git a/components/exo/text_input_unittest.cc b/components/exo/text_input_unittest.cc
index 2aa5e1a6..3dc0ed5 100644
--- a/components/exo/text_input_unittest.cc
+++ b/components/exo/text_input_unittest.cc
@@ -4,6 +4,8 @@
 
 #include "components/exo/text_input.h"
 
+#include <string>
+
 #include "base/strings/utf_string_conversions.h"
 #include "components/exo/buffer.h"
 #include "components/exo/shell_surface.h"
@@ -117,6 +119,16 @@
     return surface_->window()->GetHost()->GetInputMethod();
   }
 
+  void SetCompositionText(const std::string& utf8) {
+    ui::CompositionText t;
+    t.text = base::UTF8ToUTF16(utf8);
+    t.selection = gfx::Range(1u);
+    t.ime_text_spans.push_back(
+        ui::ImeTextSpan(0, t.text.size(), ui::ImeTextSpan::Thickness::kThick));
+    EXPECT_CALL(*delegate(), SetCompositionText(t)).Times(1);
+    text_input()->SetCompositionText(t);
+  }
+
  private:
   std::unique_ptr<TextInput> text_input_;
 
@@ -224,31 +236,17 @@
 }
 
 TEST_F(TextInputTest, CompositionText) {
-  ui::CompositionText t;
-  t.text = base::ASCIIToUTF16("composition");
-  t.selection = gfx::Range(1u);
-  t.ime_text_spans.push_back(
-      ui::ImeTextSpan(0, t.text.size(), ui::ImeTextSpan::Thickness::kThick));
+  SetCompositionText("composition");
 
   ui::CompositionText empty;
-  EXPECT_CALL(*delegate(), SetCompositionText(t)).Times(1);
   EXPECT_CALL(*delegate(), SetCompositionText(empty)).Times(1);
-
-  text_input()->SetCompositionText(t);
   text_input()->ClearCompositionText();
 }
 
 TEST_F(TextInputTest, CommitCompositionText) {
-  ui::CompositionText t;
-  t.text = base::ASCIIToUTF16("composition");
-  t.selection = gfx::Range(1u);
-  t.ime_text_spans.push_back(
-      ui::ImeTextSpan(0, t.text.size(), ui::ImeTextSpan::Thickness::kThick));
+  SetCompositionText("composition");
 
-  EXPECT_CALL(*delegate(), SetCompositionText(t)).Times(1);
-  EXPECT_CALL(*delegate(), Commit(t.text)).Times(1);
-
-  text_input()->SetCompositionText(t);
+  EXPECT_CALL(*delegate(), Commit(base::UTF8ToUTF16("composition"))).Times(1);
   text_input()->ConfirmCompositionText();
 }
 
@@ -275,5 +273,66 @@
   text_input()->InsertChar(ev);
 }
 
+TEST_F(TextInputTest, SurroundingText) {
+  gfx::Range range;
+  EXPECT_FALSE(text_input()->GetTextRange(&range));
+  EXPECT_FALSE(text_input()->GetCompositionTextRange(&range));
+  EXPECT_FALSE(text_input()->GetEditableSelectionRange(&range));
+  base::string16 got_text;
+  EXPECT_FALSE(text_input()->GetTextFromRange(gfx::Range(0, 1), &got_text));
+
+  base::string16 text = base::UTF8ToUTF16("surrounding\xE3\x80\x80text");
+  text_input()->SetSurroundingText(text, 11, 12);
+
+  EXPECT_TRUE(text_input()->GetTextRange(&range));
+  EXPECT_EQ(gfx::Range(0, text.size()).ToString(), range.ToString());
+
+  EXPECT_FALSE(text_input()->GetCompositionTextRange(&range));
+  EXPECT_TRUE(text_input()->GetEditableSelectionRange(&range));
+  EXPECT_EQ(gfx::Range(11, 12).ToString(), range.ToString());
+  EXPECT_TRUE(text_input()->GetTextFromRange(gfx::Range(11, 12), &got_text));
+  EXPECT_EQ(text.substr(11, 1), got_text);
+
+  // DeleteSurroundingText receives the range in UTF8 -- so (11, 14) range is
+  // expected.
+  EXPECT_CALL(*delegate(), DeleteSurroundingText(gfx::Range(11, 14))).Times(1);
+  text_input()->ExtendSelectionAndDelete(0, 0);
+
+  size_t composition_size = std::string("composition").size();
+  SetCompositionText("composition");
+  EXPECT_TRUE(text_input()->GetCompositionTextRange(&range));
+  EXPECT_EQ(gfx::Range(11, 11 + composition_size).ToString(), range.ToString());
+  EXPECT_TRUE(text_input()->GetTextRange(&range));
+  EXPECT_EQ(gfx::Range(0, text.size() - 1 + composition_size).ToString(),
+            range.ToString());
+  EXPECT_TRUE(text_input()->GetEditableSelectionRange(&range));
+  EXPECT_EQ(gfx::Range(11, 12).ToString(), range.ToString());
+}
+
+TEST_F(TextInputTest, GetTextRange) {
+  base::string16 text = base::UTF8ToUTF16("surrounding text");
+  text_input()->SetSurroundingText(text, 11, 12);
+
+  SetCompositionText("composition");
+
+  const struct {
+    gfx::Range range;
+    std::string expected;
+  } kTestCases[] = {
+      {gfx::Range(0, 3), "sur"},
+      {gfx::Range(10, 13), "gco"},
+      {gfx::Range(10, 23), "gcompositiont"},
+      {gfx::Range(12, 15), "omp"},
+      {gfx::Range(12, 23), "ompositiont"},
+      {gfx::Range(22, 25), "tex"},
+  };
+  for (auto& c : kTestCases) {
+    base::string16 result;
+    EXPECT_TRUE(text_input()->GetTextFromRange(c.range, &result))
+        << c.range.ToString();
+    EXPECT_EQ(base::UTF8ToUTF16(c.expected), result) << c.range.ToString();
+  }
+}
+
 }  // anonymous namespace
 }  // namespace exo
diff --git a/components/exo/wayland/zwp_text_input_manager.cc b/components/exo/wayland/zwp_text_input_manager.cc
index 15ebfff..73ae15f 100644
--- a/components/exo/wayland/zwp_text_input_manager.cc
+++ b/components/exo/wayland/zwp_text_input_manager.cc
@@ -25,14 +25,6 @@
 ////////////////////////////////////////////////////////////////////////////////
 // text_input_v1 interface:
 
-size_t OffsetFromUTF8Offset(const base::StringPiece& text, uint32_t offset) {
-  return base::UTF8ToUTF16(text.substr(0, offset)).size();
-}
-
-size_t OffsetFromUTF16Offset(const base::StringPiece16& text, uint32_t offset) {
-  return base::UTF16ToUTF8(text.substr(0, offset)).size();
-}
-
 class WaylandTextInputDelegate : public TextInput::Delegate {
  public:
   WaylandTextInputDelegate(wl_resource* text_input) : text_input_(text_input) {}
@@ -107,15 +99,13 @@
   }
 
   void SetCursor(const gfx::Range& selection) override {
-    // TODO(mukai): compute the utf8 offset for |selection| and call
-    // zwp_text_input_v1_send_cursor_position.
-    NOTIMPLEMENTED();
+    zwp_text_input_v1_send_cursor_position(text_input_, selection.end(),
+                                           selection.start());
   }
 
   void DeleteSurroundingText(const gfx::Range& range) override {
-    // TODO(mukai): compute the utf8 offset for |range| and call
-    // zwp_text_input_send_delete_surrounding_text.
-    NOTIMPLEMENTED();
+    zwp_text_input_v1_send_delete_surrounding_text(text_input_, range.start(),
+                                                   range.length());
   }
 
   void SendKey(const ui::KeyEvent& event) override {
@@ -216,7 +206,8 @@
                                      uint32_t anchor) {
   TextInput* text_input = GetUserDataAs<TextInput>(resource);
   text_input->SetSurroundingText(base::UTF8ToUTF16(text),
-                                 OffsetFromUTF8Offset(text, cursor));
+                                 OffsetFromUTF8Offset(text, cursor),
+                                 OffsetFromUTF8Offset(text, anchor));
 }
 
 void text_input_set_content_type(wl_client* client,
diff --git a/components/feed/core/feed_content_database.cc b/components/feed/core/feed_content_database.cc
index 7e06660..a9b760a 100644
--- a/components/feed/core/feed_content_database.cc
+++ b/components/feed/core/feed_content_database.cc
@@ -36,6 +36,12 @@
 const size_t kDatabaseWriteBufferSizeBytes = 64 * 1024;                 // 64KB
 const size_t kDatabaseWriteBufferSizeBytesForLowEndDevice = 32 * 1024;  // 32KB
 
+leveldb::ReadOptions CreateReadOptions() {
+  leveldb::ReadOptions opts;
+  opts.fill_cache = false;
+  return opts;
+}
+
 bool DatabaseKeyFilter(const std::unordered_set<std::string>& key_set,
                        const std::string& key) {
   return key_set.find(key) != key_set.end();
@@ -97,6 +103,7 @@
 
   storage_database_->LoadEntriesWithFilter(
       base::BindRepeating(&DatabaseKeyFilter, std::move(key_set)),
+      CreateReadOptions(), /* target_prefix */ "",
       base::BindOnce(&FeedContentDatabase::OnLoadEntriesForLoadContent,
                      weak_ptr_factory_.GetWeakPtr(), base::TimeTicks::Now(),
                      std::move(callback)));
@@ -108,6 +115,7 @@
 
   storage_database_->LoadEntriesWithFilter(
       base::BindRepeating(&DatabasePrefixFilter, std::move(prefix)),
+      CreateReadOptions(), /* target_prefix */ "",
       base::BindOnce(&FeedContentDatabase::OnLoadEntriesForLoadContent,
                      weak_ptr_factory_.GetWeakPtr(), base::TimeTicks::Now(),
                      std::move(callback)));
diff --git a/components/feed/core/feed_networking_host.cc b/components/feed/core/feed_networking_host.cc
index 6bc4c4a..bf723ff6 100644
--- a/components/feed/core/feed_networking_host.cc
+++ b/components/feed/core/feed_networking_host.cc
@@ -85,7 +85,13 @@
   network::SharedURLLoaderFactory* loader_factory_;
   const std::string api_key_;
   const base::TickClock* tick_clock_;
-  base::TimeTicks start_ticks_;
+
+  // Set when the NetworkFetch is constructed, before token and article fetch.
+  const base::TimeTicks entire_send_start_ticks_;
+
+  // Should be set right before the article fetch, and after the token fetch if
+  // there is one.
+  base::TimeTicks loader_only_start_ticks_;
 
   DISALLOW_COPY_AND_ASSIGN(NetworkFetch);
 };
@@ -104,7 +110,7 @@
       loader_factory_(loader_factory),
       api_key_(api_key),
       tick_clock_(tick_clock),
-      start_ticks_(tick_clock_->NowTicks()) {}
+      entire_send_start_ticks_(tick_clock_->NowTicks()) {}
 
 void NetworkFetch::Start(FeedNetworkingHost::ResponseCallback done_callback) {
   done_callback_ = std::move(done_callback);
@@ -138,6 +144,7 @@
 }
 
 void NetworkFetch::StartLoader() {
+  loader_only_start_ticks_ = tick_clock_->NowTicks();
   simple_loader_ = MakeLoader();
   simple_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
       loader_factory_, base::BindOnce(&NetworkFetch::OnSimpleLoaderComplete,
@@ -250,9 +257,16 @@
     response_body.assign(begin, end);
   }
 
-  base::TimeDelta duration = tick_clock_->NowTicks() - start_ticks_;
+  base::TimeDelta entire_send_duration =
+      tick_clock_->NowTicks() - entire_send_start_ticks_;
   UMA_HISTOGRAM_MEDIUM_TIMES("ContentSuggestions.Feed.Network.Duration",
-                             duration);
+                             entire_send_duration);
+
+  base::TimeDelta loader_only_duration =
+      tick_clock_->NowTicks() - loader_only_start_ticks_;
+  // This histogram purposefully matches name and bucket size used in
+  // RemoteSuggestionsFetcherImpl.
+  UMA_HISTOGRAM_TIMES("NewTabPage.Snippets.FetchTime", loader_only_duration);
 
   base::UmaHistogramSparse("ContentSuggestions.Feed.Network.RequestStatusCode",
                            status_code);
diff --git a/components/image_fetcher/core/cache/image_metadata_store_leveldb.cc b/components/image_fetcher/core/cache/image_metadata_store_leveldb.cc
index db0d293..d9f438f 100644
--- a/components/image_fetcher/core/cache/image_metadata_store_leveldb.cc
+++ b/components/image_fetcher/core/cache/image_metadata_store_leveldb.cc
@@ -23,6 +23,12 @@
 
 namespace {
 
+leveldb::ReadOptions CreateReadOptions() {
+  leveldb::ReadOptions opts;
+  opts.fill_cache = false;
+  return opts;
+}
+
 int64_t ToDatabaseTime(base::Time time) {
   return time.since_origin().InMicroseconds();
 }
@@ -143,7 +149,8 @@
   }
 
   database_->LoadEntriesWithFilter(
-      base::BindRepeating(&KeyMatcherFilter, key),
+      base::BindRepeating(&KeyMatcherFilter, key), CreateReadOptions(),
+      /* target_prefix */ "",
       base::BindOnce(&ImageMetadataStoreLevelDB::UpdateImageMetadataImpl,
                      weak_ptr_factory_.GetWeakPtr()));
 }
diff --git a/components/viz/service/display_embedder/gl_output_surface_buffer_queue_android.cc b/components/viz/service/display_embedder/gl_output_surface_buffer_queue_android.cc
index 9e99733..b0b6206 100644
--- a/components/viz/service/display_embedder/gl_output_surface_buffer_queue_android.cc
+++ b/components/viz/service/display_embedder/gl_output_surface_buffer_queue_android.cc
@@ -61,16 +61,6 @@
 GLOutputSurfaceBufferQueueAndroid::~GLOutputSurfaceBufferQueueAndroid() =
     default;
 
-void GLOutputSurfaceBufferQueueAndroid::HandlePartialSwap(
-    const gfx::Rect& sub_buffer_rect,
-    uint32_t flags,
-    gpu::ContextSupport::SwapCompletedCallback swap_callback,
-    gpu::ContextSupport::PresentationCallback presentation_callback) {
-  DCHECK(sub_buffer_rect.IsEmpty());
-  context_provider_->ContextSupport()->CommitOverlayPlanes(
-      flags, std::move(swap_callback), std::move(presentation_callback));
-}
-
 OverlayCandidateValidator*
 GLOutputSurfaceBufferQueueAndroid::GetOverlayCandidateValidator() const {
   return overlay_candidate_validator_.get();
diff --git a/components/viz/service/display_embedder/gl_output_surface_buffer_queue_android.h b/components/viz/service/display_embedder/gl_output_surface_buffer_queue_android.h
index 822a78d..134006f 100644
--- a/components/viz/service/display_embedder/gl_output_surface_buffer_queue_android.h
+++ b/components/viz/service/display_embedder/gl_output_surface_buffer_queue_android.h
@@ -21,11 +21,6 @@
   ~GLOutputSurfaceBufferQueueAndroid() override;
 
   // GLOutputSurfaceBufferQueue implementation:
-  void HandlePartialSwap(
-      const gfx::Rect& sub_buffer_rect,
-      uint32_t flags,
-      gpu::ContextSupport::SwapCompletedCallback swap_callback,
-      gpu::ContextSupport::PresentationCallback presentation_callback) override;
   OverlayCandidateValidator* GetOverlayCandidateValidator() const override;
 
  private:
diff --git a/content/browser/accessibility/browser_accessibility_com_win.cc b/content/browser/accessibility/browser_accessibility_com_win.cc
index 1183e151..d1d073cf 100644
--- a/content/browser/accessibility/browser_accessibility_com_win.cc
+++ b/content/browser/accessibility/browser_accessibility_com_win.cc
@@ -1799,16 +1799,6 @@
         FireNativeEvent(IA2_EVENT_TEXT_INSERTED);
       }
     }
-
-    // Changing a static text node can affect the IA2 hypertext of its parent
-    // and, if the node is in a simple text control, the hypertext of the text
-    // control itself.
-    BrowserAccessibilityComWin* parent =
-        ToBrowserAccessibilityComWin(owner()->PlatformGetParent());
-    if (parent && (parent->owner()->HasState(ax::mojom::State::kEditable) ||
-                   owner()->IsTextOnlyObject())) {
-      parent->owner()->UpdatePlatformAttributes();
-    }
   }
 
   old_win_attributes_.reset(nullptr);
diff --git a/content/browser/accessibility/browser_accessibility_manager.cc b/content/browser/accessibility/browser_accessibility_manager.cc
index 5162eaacb..0116955 100644
--- a/content/browser/accessibility/browser_accessibility_manager.cc
+++ b/content/browser/accessibility/browser_accessibility_manager.cc
@@ -390,7 +390,7 @@
     // Fire the native event.
     BrowserAccessibility* event_target = GetFromID(event.id);
     if (!event_target || !event_target->CanFireEvents())
-      return;
+      continue;
 
     if (event.event_type == ax::mojom::Event::kHover)
       GetRootManager()->CacheHitTestResult(event_target);
diff --git a/content/browser/accessibility/browser_accessibility_manager_win.cc b/content/browser/accessibility/browser_accessibility_manager_win.cc
index 3b6ddfe..75d206d 100644
--- a/content/browser/accessibility/browser_accessibility_manager_win.cc
+++ b/content/browser/accessibility/browser_accessibility_manager_win.cc
@@ -15,6 +15,7 @@
 #include "content/browser/accessibility/browser_accessibility_win.h"
 #include "content/browser/renderer_host/legacy_render_widget_host_win.h"
 #include "content/common/accessibility_messages.h"
+#include "ui/accessibility/ax_role_properties.h"
 #include "ui/base/win/atl_module.h"
 
 namespace content {
@@ -253,29 +254,63 @@
 
   // Do a sequence of Windows-specific updates on each node. Each one is
   // done in a single pass that must complete before the next step starts.
-  // The first step moves win_attributes_ to old_win_attributes_ and then
-  // recomputes all of win_attributes_ other than IAccessibleText.
+  // The nodes that need to be updated are all of the nodes that were changed,
+  // plus some parents.
+  std::map<BrowserAccessibilityComWin*, bool /* is_subtree_created */>
+      objs_to_update;
   for (const auto& change : changes) {
     const ui::AXNode* changed_node = change.node;
     DCHECK(changed_node);
+
+    bool is_subtree_created = change.type == AXTreeObserver::SUBTREE_CREATED;
     BrowserAccessibility* obj = GetFromAXNode(changed_node);
     if (obj && obj->IsNative()) {
-      ToBrowserAccessibilityWin(obj)
-          ->GetCOM()
-          ->UpdateStep1ComputeWinAttributes();
+      objs_to_update[ToBrowserAccessibilityWin(obj)->GetCOM()] =
+          is_subtree_created;
+    }
+
+    // When a node is a text node or line break, update its parent, because
+    // its text is part of its hypertext.
+    const ui::AXNode* parent = changed_node->parent();
+    if (!parent)
+      continue;
+    if (ui::IsTextOrLineBreak(changed_node->data().role)) {
+      BrowserAccessibility* parent_obj = GetFromAXNode(parent);
+      if (parent_obj && parent_obj->IsNative()) {
+        BrowserAccessibilityComWin* parent_com_obj =
+            ToBrowserAccessibilityWin(parent_obj)->GetCOM();
+        if (objs_to_update.find(parent_com_obj) == objs_to_update.end())
+          objs_to_update[parent_com_obj] = false;
+      }
+    }
+
+    // When a node is editable, update the editable root too.
+    if (!changed_node->data().HasState(ax::mojom::State::kEditable))
+      continue;
+    const ui::AXNode* editable_root = changed_node;
+    while (editable_root->parent() && editable_root->parent()->data().HasState(
+                                          ax::mojom::State::kEditable)) {
+      editable_root = editable_root->parent();
+    }
+    BrowserAccessibility* editable_root_obj = GetFromAXNode(editable_root);
+    if (editable_root_obj && editable_root_obj->IsNative()) {
+      BrowserAccessibilityComWin* editable_root_com_obj =
+          ToBrowserAccessibilityWin(editable_root_obj)->GetCOM();
+      if (objs_to_update.find(editable_root_com_obj) == objs_to_update.end())
+        objs_to_update[editable_root_com_obj] = false;
     }
   }
 
+  // The first step moves win_attributes_ to old_win_attributes_ and then
+  // recomputes all of win_attributes_ other than IAccessibleText.
+  for (auto& key_value : objs_to_update)
+    key_value.first->UpdateStep1ComputeWinAttributes();
+
   // The next step updates the hypertext of each node, which is a
   // concatenation of all of its child text nodes, so it can't run until
   // the text of all of the nodes was computed in the previous step.
-  for (const auto& change : changes) {
-    const ui::AXNode* changed_node = change.node;
-    DCHECK(changed_node);
-    BrowserAccessibility* obj = GetFromAXNode(changed_node);
-    if (obj && obj->IsNative())
-      ToBrowserAccessibilityWin(obj)->GetCOM()->UpdateStep2ComputeHypertext();
-  }
+  for (auto& key_value : objs_to_update)
+    key_value.first->UpdateStep2ComputeHypertext();
 
   // The third step fires events on nodes based on what's changed - like
   // if the name, value, or description changed, or if the hypertext had
@@ -285,14 +320,10 @@
   // client may walk the tree when it receives any of these events.
   // At the end, it deletes old_win_attributes_ since they're not needed
   // anymore.
-  for (const auto& change : changes) {
-    const ui::AXNode* changed_node = change.node;
-    DCHECK(changed_node);
-    BrowserAccessibility* obj = GetFromAXNode(changed_node);
-    if (obj && obj->IsNative()) {
-      ToBrowserAccessibilityWin(obj)->GetCOM()->UpdateStep3FireEvents(
-          change.type == AXTreeObserver::SUBTREE_CREATED);
-    }
+  for (auto& key_value : objs_to_update) {
+    BrowserAccessibilityComWin* obj = key_value.first;
+    bool is_subtree_created = key_value.second;
+    obj->UpdateStep3FireEvents(is_subtree_created);
   }
 }
 
diff --git a/content/browser/appcache/appcache_update_job.cc b/content/browser/appcache/appcache_update_job.cc
index 2e76caa..3eaa0a94 100644
--- a/content/browser/appcache/appcache_update_job.cc
+++ b/content/browser/appcache/appcache_update_job.cc
@@ -729,12 +729,18 @@
       const char kFormatString[] = "Manifest re-fetch failed (%d) %s";
       std::string message = FormatUrlErrorMessage(
           kFormatString, manifest_url_, fetcher->result(), response_code);
+      ResultType result = fetcher->result();
+      if (result == UPDATE_OK) {
+        // URLFetcher considers any 2xx response a success, however in this
+        // particular case we want to treat any non 200 responses as failures.
+        result = SERVER_ERROR;
+      }
       HandleCacheFailure(
           blink::mojom::AppCacheErrorDetails(
               message,
               blink::mojom::AppCacheErrorReason::APPCACHE_MANIFEST_ERROR,
               GURL(), response_code, false /*is_cross_origin*/),
-          fetcher->result(), GURL());
+          result, GURL());
     }
   }
 }
diff --git a/content/browser/find_request_manager.cc b/content/browser/find_request_manager.cc
index 12e9039..5917984 100644
--- a/content/browser/find_request_manager.cc
+++ b/content/browser/find_request_manager.cc
@@ -668,12 +668,22 @@
                                               bool matches_only,
                                               bool wrap) const {
   DCHECK(from_rfh);
+  // If |from_rfh| is being detached, it might already be removed from
+  // its parent's list of children, meaning we can't traverse it correctly.
+  if (!static_cast<RenderFrameHostImpl*>(from_rfh)->is_active())
+    return nullptr;
   FrameTreeNode* node =
       static_cast<RenderFrameHostImpl*>(from_rfh)->frame_tree_node();
-
+  FrameTreeNode* last_node = node;
   while ((node = TraverseNode(node, forward, wrap)) != nullptr) {
-    if (!CheckFrame(node->current_frame_host()))
+    if (!CheckFrame(node->current_frame_host())) {
+      // If we're in the same frame as before, we might got into an infinite
+      // loop.
+      if (last_node == node)
+        break;
+      last_node = node;
       continue;
+    }
     RenderFrameHost* current_rfh = node->current_frame_host();
     if (!matches_only ||
         find_in_page_clients_.find(current_rfh)->second->number_of_matches() ||
diff --git a/content/browser/find_request_manager_browsertest.cc b/content/browser/find_request_manager_browsertest.cc
index 26a1fb9..b8f22c30 100644
--- a/content/browser/find_request_manager_browsertest.cc
+++ b/content/browser/find_request_manager_browsertest.cc
@@ -594,6 +594,32 @@
   }
 }
 
+IN_PROC_BROWSER_TEST_F(FindRequestManagerTest, DetachFrameWithMatch) {
+  // Detaching an iframe with matches when the main document doesn't
+  // have matches should work and just remove the matches from the
+  // removed frame.
+  LoadAndWait("/find_in_page_two_frames.html");
+  auto options = blink::mojom::FindOptions::New();
+  options->run_synchronously_for_testing = true;
+
+  Find("result", options.Clone());
+  delegate()->WaitForFinalReply();
+  FindResults results = delegate()->GetFindResults();
+  EXPECT_EQ(last_request_id(), results.request_id);
+  EXPECT_EQ(6, results.number_of_matches);
+  EXPECT_EQ(1, results.active_match_ordinal);
+  EXPECT_TRUE(ExecuteScript(shell(),
+                            "document.body.removeChild("
+                            "document.querySelectorAll('iframe')[0])"));
+
+  Find("result", options.Clone());
+  delegate()->WaitForFinalReply();
+  results = delegate()->GetFindResults();
+  EXPECT_EQ(last_request_id(), results.request_id);
+  EXPECT_EQ(3, results.number_of_matches);
+  EXPECT_EQ(1, results.active_match_ordinal);
+}
+
 IN_PROC_BROWSER_TEST_F(FindRequestManagerTest, MAYBE(FindInPage_Issue644448)) {
   TestNavigationObserver navigation_observer(contents());
   NavigateToURL(shell(), GURL("about:blank"));
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index ba9cdb72..6837e87 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -3024,6 +3024,7 @@
 void RenderFrameHostImpl::VisibilityChanged(
     blink::mojom::FrameVisibility visibility) {
   visibility_ = visibility;
+  UpdateFrameFrozenState();
 }
 
 void RenderFrameHostImpl::OnDidBlockFramebust(const GURL& url) {
@@ -3384,6 +3385,8 @@
   // of this RenderFrameHost is being tracked.
   if (is_active())
     frame_tree_node_->DidStopLoading();
+
+  UpdateFrameFrozenState();
 }
 
 void RenderFrameHostImpl::OnDidChangeLoadProgress(double load_progress) {
@@ -6575,4 +6578,27 @@
       base::Unretained(this), navigation_request);
 }
 
+void RenderFrameHostImpl::UpdateFrameFrozenState() {
+  if (!base::FeatureList::IsEnabled(features::kFreezeFramesOnVisibility))
+    return;
+
+  // If the document is in the loading state keep it still loading.
+  if (is_loading_)
+    return;
+
+  // TODO(dtapuska): Adjust these based on feature policies when
+  // they are available.
+  // Feature policies don't support parameterized values yet.
+  // crbug.com/924568, crbug.com/907125
+  if (visibility_ == blink::mojom::FrameVisibility::kNotRendered) {
+    frame_->SetLifecycleState(blink::mojom::FrameLifecycleState::kFrozen);
+  } else if (visibility_ ==
+             blink::mojom::FrameVisibility::kRenderedOutOfViewport) {
+    frame_->SetLifecycleState(
+        blink::mojom::FrameLifecycleState::kFrozenAutoResumeMedia);
+  } else {
+    frame_->SetLifecycleState(blink::mojom::FrameLifecycleState::kRunning);
+  }
+}
+
 }  // namespace content
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index 9d1335e..535cff9 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -1496,6 +1496,10 @@
   // - Speculative RenderFrameHost.
   void ResetNavigationsForPendingDeletion();
 
+  // Update the frozen state of the frame applying current inputs (visibility,
+  // loaded state) to determine the new state.
+  void UpdateFrameFrozenState();
+
   // For now, RenderFrameHosts indirectly keep RenderViewHosts alive via a
   // refcount that calls Shutdown when it reaches zero.  This allows each
   // RenderFrameHostManager to just care about RenderFrameHosts, while ensuring
diff --git a/content/browser/gpu/browser_gpu_channel_host_factory.cc b/content/browser/gpu/browser_gpu_channel_host_factory.cc
index ecec626..af9310c 100644
--- a/content/browser/gpu/browser_gpu_channel_host_factory.cc
+++ b/content/browser/gpu/browser_gpu_channel_host_factory.cc
@@ -129,7 +129,8 @@
 void BrowserGpuChannelHostFactory::EstablishRequest::RestartTimeout() {
   BrowserGpuChannelHostFactory* factory =
       BrowserGpuChannelHostFactory::instance();
-  factory->RestartTimeout();
+  if (factory)
+    factory->RestartTimeout();
 }
 
 void BrowserGpuChannelHostFactory::EstablishRequest::EstablishOnIO() {
diff --git a/content/browser/plugin_list.cc b/content/browser/plugin_list.cc
index a1e2bd9..29061b8 100644
--- a/content/browser/plugin_list.cc
+++ b/content/browser/plugin_list.cc
@@ -37,12 +37,10 @@
   if (mime_type.empty())
     return false;
 
-  for (size_t i = 0; i < plugin.mime_types.size(); ++i) {
-    const WebPluginMimeType& mime_info = plugin.mime_types[i];
+  for (const WebPluginMimeType& mime_info : plugin.mime_types) {
     if (net::MatchesMimeType(mime_info.mime_type, mime_type)) {
-      if (!allow_wildcard && mime_info.mime_type == "*")
-        continue;
-      return true;
+      if (allow_wildcard || mime_info.mime_type != "*")
+        return true;
     }
   }
   return false;
@@ -55,10 +53,9 @@
 bool SupportsExtension(const WebPluginInfo& plugin,
                        const std::string& extension,
                        std::string* actual_mime_type) {
-  for (size_t i = 0; i < plugin.mime_types.size(); ++i) {
-    const WebPluginMimeType& mime_type = plugin.mime_types[i];
-    for (size_t j = 0; j < mime_type.file_extensions.size(); ++j) {
-      if (mime_type.file_extensions[j] == extension) {
+  for (const WebPluginMimeType& mime_type : plugin.mime_types) {
+    for (const std::string& file_extension : mime_type.file_extensions) {
+      if (file_extension == extension) {
         *actual_mime_type = mime_type.mime_type;
         return true;
       }
@@ -111,10 +108,8 @@
     std::vector<WebPluginInfo>* internal_plugins) {
   base::AutoLock lock(lock_);
 
-  for (std::vector<WebPluginInfo>::iterator it = internal_plugins_.begin();
-       it != internal_plugins_.end(); ++it) {
-    internal_plugins->push_back(*it);
-  }
+  for (const auto& plugin : internal_plugins_)
+    internal_plugins->push_back(plugin);
 }
 
 bool PluginList::ReadPluginInfo(const base::FilePath& filename,
@@ -145,21 +140,20 @@
     return;
 
   std::vector<WebPluginInfo> new_plugins;
-  base::Closure will_load_callback;
+  base::OnceClosure will_load_callback;
   {
     base::AutoLock lock(lock_);
     will_load_callback = will_load_plugins_callback_;
   }
-  if (!will_load_callback.is_null())
+  if (will_load_callback)
     std::move(will_load_callback).Run();
 
   std::vector<base::FilePath> plugin_paths;
   GetPluginPathsToLoad(&plugin_paths);
 
-  for (std::vector<base::FilePath>::const_iterator it = plugin_paths.begin();
-       it != plugin_paths.end(); ++it) {
+  for (const base::FilePath& path : plugin_paths) {
     WebPluginInfo plugin_info;
-    LoadPluginIntoPluginList(*it, &new_plugins, &plugin_info);
+    LoadPluginIntoPluginList(path, &new_plugins, &plugin_info);
   }
 
   SetPlugins(new_plugins);
@@ -172,12 +166,11 @@
     return false;
 
   // TODO(piman): Do we still need this after NPAPI removal?
-  for (size_t i = 0; i < plugin_info->mime_types.size(); ++i) {
+  for (const content::WebPluginMimeType& mime_type : plugin_info->mime_types) {
     // TODO: don't load global handlers for now.
     // WebKit hands to the Plugin before it tries
     // to handle mimeTypes on its own.
-    const std::string& mime_type = plugin_info->mime_types[i].mime_type;
-    if (mime_type == "*")
+    if (mime_type.mime_type == "*")
       return false;
   }
   plugins->push_back(*plugin_info);
@@ -194,8 +187,7 @@
     extra_plugin_paths = extra_plugin_paths_;
   }
 
-  for (size_t i = 0; i < extra_plugin_paths.size(); ++i) {
-    const base::FilePath& path = extra_plugin_paths[i];
+  for (const base::FilePath& path : extra_plugin_paths) {
     if (base::ContainsValue(*plugin_paths, path))
       continue;
     plugin_paths->push_back(path);
@@ -213,7 +205,8 @@
   plugins_list_ = plugins;
 }
 
-void PluginList::set_will_load_plugins_callback(const base::Closure& callback) {
+void PluginList::set_will_load_plugins_callback(
+    const base::RepeatingClosure& callback) {
   base::AutoLock lock(lock_);
   will_load_plugins_callback_ = callback;
 }
@@ -253,11 +246,11 @@
   std::set<base::FilePath> visited_plugins;
 
   // Add in plugins by mime type.
-  for (size_t i = 0; i < plugins_list_.size(); ++i) {
-    if (SupportsType(plugins_list_[i], mime_type, allow_wildcard)) {
-      base::FilePath path = plugins_list_[i].path;
+  for (const WebPluginInfo& plugin : plugins_list_) {
+    if (SupportsType(plugin, mime_type, allow_wildcard)) {
+      const base::FilePath& path = plugin.path;
       if (visited_plugins.insert(path).second) {
-        info->push_back(plugins_list_[i]);
+        info->push_back(plugin);
         if (actual_mime_types)
           actual_mime_types->push_back(mime_type);
       }
@@ -272,18 +265,19 @@
   // as when the user doesn't have the Flash plugin enabled.
   std::string path = url.path();
   std::string::size_type last_dot = path.rfind('.');
-  if (last_dot != std::string::npos && mime_type.empty()) {
-    std::string extension =
-        base::ToLowerASCII(base::StringPiece(path).substr(last_dot + 1));
-    std::string actual_mime_type;
-    for (size_t i = 0; i < plugins_list_.size(); ++i) {
-      if (SupportsExtension(plugins_list_[i], extension, &actual_mime_type)) {
-        base::FilePath plugin_path = plugins_list_[i].path;
-        if (visited_plugins.insert(plugin_path).second) {
-          info->push_back(plugins_list_[i]);
-          if (actual_mime_types)
-            actual_mime_types->push_back(actual_mime_type);
-        }
+  if (last_dot == std::string::npos || !mime_type.empty())
+    return;
+
+  std::string extension =
+      base::ToLowerASCII(base::StringPiece(path).substr(last_dot + 1));
+  std::string actual_mime_type;
+  for (const WebPluginInfo& plugin : plugins_list_) {
+    if (SupportsExtension(plugin, extension, &actual_mime_type)) {
+      base::FilePath plugin_path = plugin.path;
+      if (visited_plugins.insert(plugin_path).second) {
+        info->push_back(plugin);
+        if (actual_mime_types)
+          actual_mime_types->push_back(actual_mime_type);
       }
     }
   }
@@ -298,6 +292,6 @@
     extra_plugin_paths_.erase(it);
 }
 
-PluginList::~PluginList() {}
+PluginList::~PluginList() = default;
 
 }  // namespace content
diff --git a/content/browser/plugin_list.h b/content/browser/plugin_list.h
index 9f4732f..d55fc2d 100644
--- a/content/browser/plugin_list.h
+++ b/content/browser/plugin_list.h
@@ -86,7 +86,7 @@
                           std::vector<WebPluginInfo>* info,
                           std::vector<std::string>* actual_mime_types);
 
-  void set_will_load_plugins_callback(const base::Closure& callback);
+  void set_will_load_plugins_callback(const base::RepeatingClosure& callback);
 
  private:
   enum LoadingState {
@@ -155,7 +155,7 @@
   std::vector<WebPluginInfo> plugins_list_ GUARDED_BY(lock_);
 
   // Callback that is invoked whenever the PluginList will reload the plugins.
-  base::Closure will_load_plugins_callback_ GUARDED_BY(lock_);
+  base::RepeatingClosure will_load_plugins_callback_ GUARDED_BY(lock_);
 
   // Need synchronization for the above members since this object can be
   // accessed on multiple threads.
diff --git a/content/browser/plugin_service_impl.cc b/content/browser/plugin_service_impl.cc
index a029fbb..74b629f5 100644
--- a/content/browser/plugin_service_impl.cc
+++ b/content/browser/plugin_service_impl.cc
@@ -99,8 +99,6 @@
 }
 
 PluginServiceImpl::PluginServiceImpl() : filter_(nullptr) {
-  plugin_list_sequence_checker_.DetachFromSequence();
-
   // Collect the total number of browser processes (which create
   // PluginServiceImpl objects, to be precise). The number is used to normalize
   // the number of processes which start at least one NPAPI/PPAPI Flash process.
@@ -119,8 +117,11 @@
   plugin_list_task_runner_ = base::CreateSequencedTaskRunnerWithTraits(
       {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
        base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
-  PluginList::Singleton()->set_will_load_plugins_callback(
-      base::Bind(&WillLoadPluginsCallback, &plugin_list_sequence_checker_));
+
+  // Setup the sequence checker right after setting up the task runner.
+  plugin_list_sequence_checker_.DetachFromSequence();
+  PluginList::Singleton()->set_will_load_plugins_callback(base::BindRepeating(
+      &WillLoadPluginsCallback, &plugin_list_sequence_checker_));
 
   RegisterPepperPlugins();
 }
diff --git a/content/browser/renderer_host/compositor_impl_android.cc b/content/browser/renderer_host/compositor_impl_android.cc
index 0d3a9e7..4833cb6 100644
--- a/content/browser/renderer_host/compositor_impl_android.cc
+++ b/content/browser/renderer_host/compositor_impl_android.cc
@@ -1194,6 +1194,7 @@
       display_client_->GetBoundPtr(task_runner).PassInterface();
 
   viz::RendererSettings renderer_settings;
+  renderer_settings.partial_swap_enabled = true;
   renderer_settings.allow_antialiasing = false;
   renderer_settings.highp_threshold_min = 2048;
   renderer_settings.requires_alpha_channel = requires_alpha_channel_;
diff --git a/content/browser/renderer_host/render_widget_host_input_event_router.cc b/content/browser/renderer_host/render_widget_host_input_event_router.cc
index 5fad7ad..0ab5c78 100644
--- a/content/browser/renderer_host/render_widget_host_input_event_router.cc
+++ b/content/browser/renderer_host/render_widget_host_input_event_router.cc
@@ -950,12 +950,11 @@
 void RenderWidgetHostInputEventRouter::ReportBubblingScrollToSameView(
     const blink::WebGestureEvent& event,
     const RenderWidgetHostViewBase* view) {
-#if 0
-  // For now, we've disabled the DumpWithoutCrashing as it's no longer
-  // providing useful information.
-  // TODO(828422): Determine useful crash keys and reenable the report.
+  static auto* device_key = base::debug::AllocateCrashKeyString(
+      "same-view-bubble-source-device", base::debug::CrashKeySize::Size32);
+  base::debug::ScopedCrashKeyString device_key_value(
+      device_key, std::to_string(event.SourceDevice()));
   base::debug::DumpWithoutCrashing();
-#endif
 }
 
 namespace {
diff --git a/content/browser/service_worker/embedded_worker_instance.cc b/content/browser/service_worker/embedded_worker_instance.cc
index e0ac361..876b7db 100644
--- a/content/browser/service_worker/embedded_worker_instance.cc
+++ b/content/browser/service_worker/embedded_worker_instance.cc
@@ -645,9 +645,6 @@
 
 EmbeddedWorkerInstance::~EmbeddedWorkerInstance() {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  DCHECK(status_ == EmbeddedWorkerStatus::STOPPING ||
-         status_ == EmbeddedWorkerStatus::STOPPED)
-      << static_cast<int>(status_);
   devtools_proxy_.reset();
   if (registry_->GetWorker(embedded_worker_id_))
     registry_->RemoveWorker(process_id(), embedded_worker_id_);
diff --git a/content/browser/service_worker/service_worker_installed_script_loader.cc b/content/browser/service_worker/service_worker_installed_script_loader.cc
index f7a4096..d25fe43 100644
--- a/content/browser/service_worker/service_worker_installed_script_loader.cc
+++ b/content/browser/service_worker/service_worker_installed_script_loader.cc
@@ -25,10 +25,27 @@
 ServiceWorkerInstalledScriptLoader::ServiceWorkerInstalledScriptLoader(
     uint32_t options,
     network::mojom::URLLoaderClientPtr client,
-    std::unique_ptr<ServiceWorkerResponseReader> response_reader)
+    std::unique_ptr<ServiceWorkerResponseReader> response_reader,
+    scoped_refptr<ServiceWorkerVersion>
+        version_for_main_script_http_response_info,
+    const GURL& request_url)
     : options_(options),
       client_(std::move(client)),
       request_start_(base::TimeTicks::Now()) {
+  // Normally, the main script info is set by ServiceWorkerNewScriptLoader for
+  // new service workers and ServiceWorkerInstalledScriptsSender for installed
+  // service workes. But some embedders might preinstall scripts to the
+  // ServiceWorkerScriptCacheMap while not setting the ServiceWorkerVersion
+  // status to INSTALLED, so we can come to here instead of using
+  // SeviceWorkerInstalledScriptsSender.
+  // In this case, the main script info would not yet have been set, so set it
+  // here.
+  if (request_url == version_for_main_script_http_response_info->script_url() &&
+      !version_for_main_script_http_response_info
+           ->GetMainScriptHttpResponseInfo()) {
+    version_for_main_script_http_response_info_ =
+        std::move(version_for_main_script_http_response_info);
+  }
   reader_ = std::make_unique<ServiceWorkerInstalledScriptReader>(
       std::move(response_reader), this);
   reader_->Start();
@@ -61,6 +78,12 @@
 void ServiceWorkerInstalledScriptLoader::OnHttpInfoRead(
     scoped_refptr<HttpResponseInfoIOBuffer> http_info) {
   net::HttpResponseInfo* info = http_info->http_info.get();
+  DCHECK(info);
+
+  if (version_for_main_script_http_response_info_) {
+    version_for_main_script_http_response_info_->SetMainScriptHttpResponseInfo(
+        *info);
+  }
 
   network::ResourceResponseHead head;
   head.request_start = request_start_;
diff --git a/content/browser/service_worker/service_worker_installed_script_loader.h b/content/browser/service_worker/service_worker_installed_script_loader.h
index c88bf5e..18733b0c 100644
--- a/content/browser/service_worker/service_worker_installed_script_loader.h
+++ b/content/browser/service_worker/service_worker_installed_script_loader.h
@@ -16,6 +16,8 @@
 
 namespace content {
 
+class ServiceWorkerVersion;
+
 // S13nServiceWorker: A URLLoader that loads an installed service worker script
 // for a service worker that doesn't have a
 // ServiceWorkerInstalledScriptsManager.
@@ -34,7 +36,10 @@
   ServiceWorkerInstalledScriptLoader(
       uint32_t options,
       network::mojom::URLLoaderClientPtr client,
-      std::unique_ptr<ServiceWorkerResponseReader> response_reader);
+      std::unique_ptr<ServiceWorkerResponseReader> response_reader,
+      scoped_refptr<ServiceWorkerVersion>
+          version_for_main_script_http_response_info,
+      const GURL& request_url);
   ~ServiceWorkerInstalledScriptLoader() override;
 
   // ServiceWorkerInstalledScriptReader::Client overrides:
@@ -67,6 +72,8 @@
 
   uint32_t options_ = network::mojom::kURLLoadOptionNone;
   network::mojom::URLLoaderClientPtr client_;
+  scoped_refptr<ServiceWorkerVersion>
+      version_for_main_script_http_response_info_;
   base::TimeTicks request_start_;
   std::unique_ptr<ServiceWorkerInstalledScriptReader> reader_;
 
diff --git a/content/browser/service_worker/service_worker_navigation_loader.cc b/content/browser/service_worker/service_worker_navigation_loader.cc
index 4011411..e76f057 100644
--- a/content/browser/service_worker/service_worker_navigation_loader.cc
+++ b/content/browser/service_worker/service_worker_navigation_loader.cc
@@ -420,6 +420,7 @@
   // browser. See https://crbug.com/392409 for details about this design.
   // TODO(horo): When we support mixed-content (HTTP) no-cors requests from a
   // ServiceWorker, we have to check the security level of the responses.
+  DCHECK(version->GetMainScriptHttpResponseInfo());
   response_head_.ssl_info = version->GetMainScriptHttpResponseInfo()->ssl_info;
 
   // Handle a redirect response. ComputeRedirectInfo returns non-null redirect
diff --git a/content/browser/service_worker/service_worker_script_loader_factory.cc b/content/browser/service_worker/service_worker_script_loader_factory.cc
index 9e6dd9b..b24330f9 100644
--- a/content/browser/service_worker/service_worker_script_loader_factory.cc
+++ b/content/browser/service_worker/service_worker_script_loader_factory.cc
@@ -8,6 +8,7 @@
 #include <string>
 #include <utility>
 
+#include "base/debug/crash_logging.h"
 #include "content/browser/service_worker/service_worker_cache_writer.h"
 #include "content/browser/service_worker/service_worker_context_core.h"
 #include "content/browser/service_worker/service_worker_installed_script_loader.h"
@@ -87,7 +88,8 @@
         context_->storage()->CreateResponseReader(resource_id);
     mojo::MakeStrongBinding(
         std::make_unique<ServiceWorkerInstalledScriptLoader>(
-            options, std::move(client), std::move(response_reader)),
+            options, std::move(client), std::move(response_reader), version,
+            resource_request.url),
         std::move(request));
     return;
   }
@@ -164,6 +166,10 @@
   // or importScripts() (RESOURCE_TYPE_SCRIPT).
   if (resource_request.resource_type != RESOURCE_TYPE_SERVICE_WORKER &&
       resource_request.resource_type != RESOURCE_TYPE_SCRIPT) {
+    static auto* key = base::debug::AllocateCrashKeyString(
+        "swslf_bad_type", base::debug::CrashKeySize::Size32);
+    base::debug::SetCrashKeyString(
+        key, base::NumberToString(resource_request.resource_type));
     mojo::ReportBadMessage("SWSLF_BAD_RESOURCE_TYPE");
     return false;
   }
@@ -238,7 +244,8 @@
   mojo::MakeStrongBinding(
       std::make_unique<ServiceWorkerInstalledScriptLoader>(
           options, std::move(client),
-          context_->storage()->CreateResponseReader(new_resource_id)),
+          context_->storage()->CreateResponseReader(new_resource_id), version,
+          resource_request.url),
       std::move(request));
 }
 }  // namespace content
diff --git a/content/browser/service_worker/service_worker_version.cc b/content/browser/service_worker/service_worker_version.cc
index f9841aa..6997223 100644
--- a/content/browser/service_worker/service_worker_version.cc
+++ b/content/browser/service_worker/service_worker_version.cc
@@ -256,6 +256,8 @@
 }
 
 ServiceWorkerVersion::~ServiceWorkerVersion() {
+  // TODO(falken): Investigate whether this can be removed. The destructor used
+  // to be more complicated and could result in various methods being called.
   in_dtor_ = true;
 
   // Record UMA if the worker was trying to start. One way we get here is if the
@@ -271,10 +273,6 @@
   if (context_)
     context_->RemoveLiveVersion(version_id_);
 
-  if (running_status() == EmbeddedWorkerStatus::STARTING ||
-      running_status() == EmbeddedWorkerStatus::RUNNING) {
-    embedded_worker_->Stop();
-  }
   embedded_worker_->RemoveObserver(this);
 }
 
diff --git a/content/browser/service_worker/service_worker_version_unittest.cc b/content/browser/service_worker/service_worker_version_unittest.cc
index a60edc92..dd147c0 100644
--- a/content/browser/service_worker/service_worker_version_unittest.cc
+++ b/content/browser/service_worker/service_worker_version_unittest.cc
@@ -688,21 +688,6 @@
   EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, version_->running_status());
 }
 
-TEST_F(ServiceWorkerVersionTest, StoppingBeforeDestruct) {
-  RunningStateListener listener;
-  version_->AddObserver(&listener);
-  StartWorker(version_.get(), ServiceWorkerMetrics::EventType::UNKNOWN);
-  EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, listener.last_status);
-
-  // Destruct |version_| by releasing all references, including the provider
-  // host's.
-  helper_->context()->RemoveProviderHost(
-      version_->provider_host()->process_id(),
-      version_->provider_host()->provider_id());
-  version_ = nullptr;
-  EXPECT_EQ(EmbeddedWorkerStatus::STOPPING, listener.last_status);
-}
-
 // Test that update isn't triggered for a non-stale worker.
 TEST_F(ServiceWorkerVersionTest, StaleUpdate_FreshWorker) {
   version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 63b892510..74c9f5a 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -3749,11 +3749,6 @@
   return interstitial_page_;
 }
 
-void WebContentsImpl::PausePageScheduledTasks(bool paused) {
-  SendPageMessage(
-      new PageMsg_PausePageScheduledTasks(MSG_ROUTING_NONE, paused));
-}
-
 bool WebContentsImpl::IsSavable() {
   // WebKit creates Document object when MIME type is application/xhtml+xml,
   // so we also support this MIME type.
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 9dfdc90..9e72788 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -460,7 +460,6 @@
   void ClearFocusedElement() override;
   bool IsShowingContextMenu() override;
   void SetShowingContextMenu(bool showing) override;
-  void PausePageScheduledTasks(bool paused) override;
   base::UnguessableToken GetAudioGroupId() override;
   bool CompletedFirstVisuallyNonEmptyPaint() override;
 
diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc
index 47db5cf5..7e7cee1 100644
--- a/content/browser/web_contents/web_contents_impl_browsertest.cc
+++ b/content/browser/web_contents/web_contents_impl_browsertest.cc
@@ -2842,7 +2842,7 @@
   SetBrowserClientForTesting(old_client);
 }
 
-IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, PausePageScheduledTasks) {
+IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, SetPageFrozen) {
   EXPECT_TRUE(embedded_test_server()->Start());
 
   GURL test_url = embedded_test_server()->GetURL("/pause_schedule_task.html");
@@ -2862,8 +2862,9 @@
       break;
   }
 
-  // Suspend blink schedule tasks.
-  shell()->web_contents()->PausePageScheduledTasks(true);
+  // Freeze the blink page.
+  shell()->web_contents()->WasHidden();
+  shell()->web_contents()->SetPageFrozen(true);
 
   // Make the javascript work.
   for (int i = 0; i < 10; i++) {
@@ -2883,8 +2884,9 @@
       &next_text_length));
   EXPECT_EQ(text_length, next_text_length);
 
-  // Resume the paused blink schedule tasks.
-  shell()->web_contents()->PausePageScheduledTasks(false);
+  // Wake the frozen page up.
+  shell()->web_contents()->WasHidden();
+  shell()->web_contents()->SetPageFrozen(false);
 
   // Wait for an amount of time in order to give the javascript time to
   // work again. If the javascript doesn't work again, the test will fail due to
diff --git a/content/common/frame.mojom b/content/common/frame.mojom
index 57f2cfc..f660470 100644
--- a/content/common/frame.mojom
+++ b/content/common/frame.mojom
@@ -58,6 +58,9 @@
   // activated.
   OnPortalActivated();
 
+  // Set the lifecycle state.
+  SetLifecycleState(blink.mojom.FrameLifecycleState state);
+
   // Samsung Galaxy Note-specific "smart clip" stylus text getter.
   // Extracts the data at the given rect.
   [EnableIf=is_android]
diff --git a/content/common/page_messages.h b/content/common/page_messages.h
index e80e230a..83d8a3c3 100644
--- a/content/common/page_messages.h
+++ b/content/common/page_messages.h
@@ -34,10 +34,6 @@
 
 IPC_MESSAGE_ROUTED1(PageMsg_AudioStateChanged, bool /* is_audio_playing */)
 
-// Pause and unpause active tasks regarding deferLoading, active javascripts,
-// timer, scheduled task through |blink::WebFrameScheduler|.
-IPC_MESSAGE_ROUTED1(PageMsg_PausePageScheduledTasks, bool /* paused */)
-
 // Sent to OOPIF renderers when the main frame's ScreenInfo changes.
 IPC_MESSAGE_ROUTED1(PageMsg_UpdateScreenInfo,
                     content::ScreenInfo /* screen_info */)
diff --git a/content/public/browser/gpu_utils.cc b/content/public/browser/gpu_utils.cc
index d16a9348..60e2ada 100644
--- a/content/public/browser/gpu_utils.cc
+++ b/content/public/browser/gpu_utils.cc
@@ -71,10 +71,6 @@
       base::CommandLine::ForCurrentProcess();
   gpu::GpuPreferences gpu_preferences =
       gpu::gles2::ParseGpuPreferences(command_line);
-  gpu_preferences.single_process =
-      command_line->HasSwitch(switches::kSingleProcess);
-  gpu_preferences.in_process_gpu =
-      command_line->HasSwitch(switches::kInProcessGPU);
   gpu_preferences.disable_accelerated_video_decode =
       command_line->HasSwitch(switches::kDisableAcceleratedVideoDecode);
   gpu_preferences.disable_accelerated_video_encode =
@@ -105,7 +101,8 @@
       command_line->HasSwitch(switches::kGpuStartupDialog);
   gpu_preferences.disable_gpu_watchdog =
       command_line->HasSwitch(switches::kDisableGpuWatchdog) ||
-      (gpu_preferences.single_process || gpu_preferences.in_process_gpu);
+      command_line->HasSwitch(switches::kSingleProcess) ||
+      command_line->HasSwitch(switches::kInProcessGPU);
   gpu_preferences.gpu_sandbox_start_early =
       command_line->HasSwitch(switches::kGpuSandboxStartEarly);
 
diff --git a/content/public/browser/navigation_throttle.cc b/content/public/browser/navigation_throttle.cc
index 14270f9..45ff97e 100644
--- a/content/public/browser/navigation_throttle.cc
+++ b/content/public/browser/navigation_throttle.cc
@@ -81,7 +81,7 @@
 }
 
 void NavigationThrottle::Resume() {
-  if (!resume_callback_.is_null()) {
+  if (resume_callback_) {
     resume_callback_.Run();
     return;
   }
@@ -90,6 +90,10 @@
 
 void NavigationThrottle::CancelDeferredNavigation(
     NavigationThrottle::ThrottleCheckResult result) {
+  if (cancel_deferred_navigation_callback_) {
+    cancel_deferred_navigation_callback_.Run(result);
+    return;
+  }
   static_cast<NavigationHandleImpl*>(navigation_handle_)
       ->CancelDeferredNavigation(this, result);
 }
diff --git a/content/public/browser/navigation_throttle.h b/content/public/browser/navigation_throttle.h
index a25f91a5..fc2610fa 100644
--- a/content/public/browser/navigation_throttle.h
+++ b/content/public/browser/navigation_throttle.h
@@ -180,6 +180,13 @@
     resume_callback_ = callback;
   }
 
+  // Overrides the default CancelDeferredNavigation method and replaces it by
+  // |callback|. This should only be used in tests.
+  void set_cancel_deferred_navigation_callback_for_testing(
+      const base::RepeatingCallback<void(ThrottleCheckResult)> callback) {
+    cancel_deferred_navigation_callback_ = callback;
+  }
+
  protected:
   // Resumes a navigation that was previously deferred by this
   // NavigationThrottle.
@@ -201,6 +208,8 @@
 
   // Used in tests.
   base::RepeatingClosure resume_callback_;
+  base::RepeatingCallback<void(ThrottleCheckResult)>
+      cancel_deferred_navigation_callback_;
 };
 
 #if defined(UNIT_TEST)
diff --git a/content/public/browser/render_widget_host_view.h b/content/public/browser/render_widget_host_view.h
index b2ee7df..d02f832 100644
--- a/content/public/browser/render_widget_host_view.h
+++ b/content/public/browser/render_widget_host_view.h
@@ -143,9 +143,7 @@
   virtual base::string16 GetSelectedText() = 0;
 
   // This only returns non-null on platforms that implement touch
-  // selection editing (TSE), currently Aura and (soon) Android.
-  // TODO(wjmaclean): update this comment when OOPIF TSE is implemented on
-  // Android. https://crbug.com/470662.
+  // selection editing (TSE), currently Aura and Android.
   virtual TouchSelectionControllerClientManager*
   GetTouchSelectionControllerClientManager() = 0;
 
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h
index 758415f7..87031ac 100644
--- a/content/public/browser/web_contents.h
+++ b/content/public/browser/web_contents.h
@@ -893,20 +893,6 @@
   // Tells the WebContents whether the context menu is showing.
   virtual void SetShowingContextMenu(bool showing) = 0;
 
-  // Pause and unpause scheduled tasks in the page of blink. This function will
-  // suspend page loadings and all background processing like active javascript,
-  // and timers through |blink::Page::SetPaused|. If you want to resume the
-  // paused state, you have to call this function with |false| argument again.
-  // The function with |false| should be called after calling it with |true|. If
-  // not, assertion will happen.
-  //
-  // WARNING: This only pauses the activities in the particular page in the
-  // renderer process, but may indirectly block or break other pages when they
-  // wait for the common backend (e.g. storage) in the browser process.
-  // TODO(gyuyoung): https://crbug.com/822564 - Make this feature safer and fix
-  // bugs.
-  virtual void PausePageScheduledTasks(bool paused) = 0;
-
 #if defined(OS_ANDROID)
   CONTENT_EXPORT static WebContents* FromJavaWebContents(
       const base::android::JavaRef<jobject>& jweb_contents_android);
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 51dc6571..9f93e3f 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -171,6 +171,10 @@
     "FramebustingNeedsSameOriginOrUserGesture",
     base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Enables freezing frame support based on feature policies.
+const base::Feature kFreezeFramesOnVisibility{
+    "FreezeFramesOnVisibility", base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kFreezePurgeMemoryBackgroundedOnly{
     "FreezePurgeMemoryBackgroundedOnly", base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 3fd33e0..dd36944 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -49,6 +49,7 @@
 CONTENT_EXPORT extern const base::Feature kFontSrcLocalMatching;
 CONTENT_EXPORT extern const base::Feature
     kFramebustingNeedsSameOriginOrUserGesture;
+CONTENT_EXPORT extern const base::Feature kFreezeFramesOnVisibility;
 CONTENT_EXPORT extern const base::Feature kFreezePurgeMemoryBackgroundedOnly;
 CONTENT_EXPORT extern const base::Feature kGamepadVibration;
 CONTENT_EXPORT extern const base::Feature kGuestViewCrossProcessFrames;
diff --git a/content/public/test/mock_navigation_handle.h b/content/public/test/mock_navigation_handle.h
index 56261b6..7a43701 100644
--- a/content/public/test/mock_navigation_handle.h
+++ b/content/public/test/mock_navigation_handle.h
@@ -5,6 +5,7 @@
 #ifndef CONTENT_PUBLIC_TEST_MOCK_NAVIGATION_HANDLE_H_
 #define CONTENT_PUBLIC_TEST_MOCK_NAVIGATION_HANDLE_H_
 
+#include "base/memory/ref_counted.h"
 #include "content/public/browser/global_request_id.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_frame_host.h"
@@ -72,7 +73,7 @@
   MOCK_METHOD1(RemoveRequestHeader, void(const std::string&));
   MOCK_METHOD2(SetRequestHeader, void(const std::string&, const std::string&));
   const net::HttpResponseHeaders* GetResponseHeaders() override {
-    return response_headers_;
+    return response_headers_.get();
   }
   MOCK_METHOD0(GetConnectionInfo, net::HttpResponseInfo::ConnectionInfo());
   const net::SSLInfo& GetSSLInfo() override { return ssl_info_; }
@@ -117,8 +118,9 @@
   void set_request_headers(const net::HttpRequestHeaders& request_headers) {
     request_headers_ = request_headers;
   }
-  void set_response_headers(net::HttpResponseHeaders* reponse_headers) {
-    response_headers_ = reponse_headers;
+  void set_response_headers(
+      scoped_refptr<net::HttpResponseHeaders> response_headers) {
+    response_headers_ = response_headers;
   }
   void set_ssl_info(const net::SSLInfo& ssl_info) { ssl_info_ = ssl_info; }
   void set_is_form_submission(bool is_form_submission) {
@@ -146,7 +148,7 @@
   bool has_committed_ = false;
   bool is_error_page_ = false;
   net::HttpRequestHeaders request_headers_;
-  net::HttpResponseHeaders* response_headers_ = nullptr;
+  scoped_refptr<net::HttpResponseHeaders> response_headers_;
   net::SSLInfo ssl_info_;
   bool is_form_submission_ = false;
   bool was_response_cached_ = false;
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index b2cec49..a7fbf37 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -819,7 +819,8 @@
     mojom::NavigationClient::CommitNavigationCallback
         per_navigation_mojo_interface_commit_callback,
     const network::ResourceResponseHead* head,
-    std::unique_ptr<NavigationClient> navigation_client) {
+    std::unique_ptr<NavigationClient> navigation_client,
+    int request_id) {
   std::unique_ptr<DocumentState> document_state(new DocumentState());
   InternalDocumentStateData* internal_data =
       InternalDocumentStateData::FromDocumentState(document_state.get());
@@ -848,7 +849,7 @@
       common_params.navigation_type ==
       FrameMsg_Navigate_Type::RELOAD_ORIGINAL_REQUEST_URL);
   internal_data->set_previews_state(common_params.previews_state);
-  internal_data->set_request_id(ResourceDispatcher::MakeRequestID());
+  internal_data->set_request_id(request_id);
   document_state->set_can_load_local_resources(
       commit_params.can_load_local_resources);
 
@@ -971,6 +972,60 @@
 
 }  // namespace
 
+// This class uses existing WebNavigationBodyLoader to read the whole response
+// body into in-memory buffer, and then creates another body loader with static
+// data so that we can parse mhtml archive synchronously. This is a workaround
+// for the fact that we need the whole archive to determine the document's mime
+// type and construct a right document instance.
+class RenderFrameImpl::MHTMLBodyLoaderClient
+    : public blink::WebNavigationBodyLoader::Client {
+ public:
+  // Once the body is read, fills |navigation_params| with the new body loader
+  // and calls |done_callbcak|.
+  MHTMLBodyLoaderClient(
+      std::unique_ptr<blink::WebNavigationParams> navigation_params,
+      base::OnceCallback<void(std::unique_ptr<blink::WebNavigationParams>)>
+          done_callback)
+      : navigation_params_(std::move(navigation_params)),
+        done_callback_(std::move(done_callback)) {
+    body_loader_ = std::move(navigation_params_->body_loader);
+    body_loader_->StartLoadingBody(this, false /* use_isolated_code_cache */);
+  }
+
+  ~MHTMLBodyLoaderClient() override {}
+
+  void BodyCodeCacheReceived(base::span<const uint8_t>) override {}
+
+  void BodyDataReceived(base::span<const char> data) override {
+    data_.Append(data.data(), data.size());
+  }
+
+  void BodyLoadingFinished(base::TimeTicks completion_time,
+                           int64_t total_encoded_data_length,
+                           int64_t total_encoded_body_length,
+                           int64_t total_decoded_body_length,
+                           bool should_report_corb_blocking,
+                           const base::Optional<WebURLError>& error) override {
+    if (!error.has_value()) {
+      WebNavigationParams::FillBodyLoader(navigation_params_.get(), data_);
+      // Clear |is_static_data| flag to avoid the special behavior it triggers,
+      // e.g. skipping content disposition check. We want this load to be
+      // regular, just like with an original body loader.
+      navigation_params_->is_static_data = false;
+    }
+    std::move(done_callback_).Run(std::move(navigation_params_));
+  }
+
+ private:
+  WebData data_;
+  std::unique_ptr<blink::WebNavigationParams> navigation_params_;
+  std::unique_ptr<blink::WebNavigationBodyLoader> body_loader_;
+  base::OnceCallback<void(std::unique_ptr<blink::WebNavigationParams>)>
+      done_callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(MHTMLBodyLoaderClient);
+};
+
 class RenderFrameImpl::FrameURLLoaderFactory
     : public blink::WebURLLoaderFactory {
  public:
@@ -2562,6 +2617,11 @@
   frame_->OnPortalActivated();
 }
 
+void RenderFrameImpl::SetLifecycleState(
+    blink::mojom::FrameLifecycleState state) {
+  frame_->SetLifecycleState(state);
+}
+
 void RenderFrameImpl::VisibilityChanged(
     blink::mojom::FrameVisibility visibility) {
   GetFrameHost()->VisibilityChanged(visibility);
@@ -2844,7 +2904,8 @@
         navigation_state->common_params(), navigation_state->commit_params(),
         base::TimeTicks(),  // Not used for failed navigation.
         mojom::FrameNavigationControl::CommitNavigationCallback(),
-        mojom::NavigationClient::CommitNavigationCallback(), nullptr, nullptr);
+        mojom::NavigationClient::CommitNavigationCallback(), nullptr, nullptr,
+        ResourceDispatcher::MakeRequestID());
     FillMiscNavigationParams(navigation_state->common_params(),
                              navigation_state->commit_params(),
                              navigation_params.get());
@@ -3273,18 +3334,12 @@
   DCHECK(!IsRendererDebugURL(common_params.url));
   DCHECK(
       !FrameMsg_Navigate_Type::IsSameDocument(common_params.navigation_type));
-  // If this was a renderer-initiated navigation (nav_entry_id == 0) from this
-  // frame, but it was aborted, then ignore it.
-  if (!browser_side_navigation_pending_ &&
-      !browser_side_navigation_pending_url_.is_empty() &&
-      browser_side_navigation_pending_url_ == commit_params.original_url &&
-      commit_params.nav_entry_id == 0) {
+  if (ShouldIgnoreCommitNavigation(commit_params)) {
     browser_side_navigation_pending_url_ = GURL();
-    if (IsPerNavigationMojoInterfaceEnabled()) {
+    if (IsPerNavigationMojoInterfaceEnabled())
       navigation_client_impl_.reset();
-    } else {
+    else
       std::move(callback).Run(blink::mojom::CommitResult::Aborted);
-    }
     return;
   }
 
@@ -3294,6 +3349,130 @@
          !base::FeatureList::IsEnabled(network::features::kNetworkService) ||
          subresource_loader_factories);
 
+  // We only save metrics of the main frame's main resource to the
+  // document state. In view source mode, we effectively let the user
+  // see the source of the server's error page instead of using custom
+  // one derived from the metrics saved to document state.
+  const network::ResourceResponseHead* response_head = nullptr;
+  if (!frame_->Parent() && !frame_->IsViewSourceModeEnabled())
+    response_head = &head;
+  int request_id = ResourceDispatcher::MakeRequestID();
+  std::unique_ptr<DocumentState> document_state = BuildDocumentStateFromParams(
+      common_params, commit_params, base::TimeTicks::Now(), std::move(callback),
+      std::move(per_navigation_mojo_interface_callback), response_head,
+      std::move(navigation_client_impl_), request_id);
+
+  // Check if the navigation being committed originated as a client redirect.
+  bool is_client_redirect =
+      !!(common_params.transition & ui::PAGE_TRANSITION_CLIENT_REDIRECT);
+  auto navigation_params =
+      std::make_unique<WebNavigationParams>(devtools_navigation_token);
+  navigation_params->is_client_redirect = is_client_redirect;
+  FillMiscNavigationParams(common_params, commit_params,
+                           navigation_params.get());
+
+  auto commit_with_params = base::BindOnce(
+      &RenderFrameImpl::CommitNavigationWithParams, weak_factory_.GetWeakPtr(),
+      common_params, commit_params, std::move(subresource_loader_factories),
+      std::move(subresource_overrides),
+      std::move(controller_service_worker_info),
+      std::move(prefetch_loader_factory), std::move(document_state));
+
+  // Perform a navigation to a data url if needed (for main frames).
+  // Note: the base URL might be invalid, so also check the data URL string.
+  bool should_load_data_url = !common_params.base_url_for_data_url.is_empty();
+#if defined(OS_ANDROID)
+  should_load_data_url |= !commit_params.data_url_as_string.empty();
+#endif
+  if (is_main_frame_ && should_load_data_url) {
+    std::string mime_type, charset, data;
+    GURL base_url;
+    DecodeDataURL(common_params, commit_params, &mime_type, &charset, &data,
+                  &base_url);
+    navigation_params->url = base_url;
+    WebNavigationParams::FillStaticResponse(navigation_params.get(),
+                                            WebString::FromUTF8(mime_type),
+                                            WebString::FromUTF8(charset), data);
+    // Needed so that history-url-only changes don't become reloads.
+    navigation_params->unreachable_url = common_params.history_url_for_data_url;
+    std::move(commit_with_params).Run(std::move(navigation_params));
+    return;
+  }
+
+  FillNavigationParamsRequest(common_params, commit_params,
+                              navigation_params.get());
+  if (!url_loader_client_endpoints &&
+      common_params.url.SchemeIs(url::kDataScheme)) {
+    // Normally, data urls will have |url_loader_client_endpoints| set.
+    // However, tests and interstitial pages pass data urls directly,
+    // without creating url loader.
+    std::string mime_type, charset, data;
+    if (!net::DataURL::Parse(common_params.url, &mime_type, &charset, &data)) {
+      CHECK(false) << "Invalid URL passed: "
+                   << common_params.url.possibly_invalid_spec();
+      return;
+    }
+    WebNavigationParams::FillStaticResponse(navigation_params.get(),
+                                            WebString::FromUTF8(mime_type),
+                                            WebString::FromUTF8(charset), data);
+  } else {
+    NavigationBodyLoader::FillNavigationParamsResponseAndBodyLoader(
+        common_params, commit_params, request_id, head,
+        std::move(url_loader_client_endpoints),
+        GetTaskRunner(blink::TaskType::kInternalLoading), GetRoutingID(),
+        !frame_->Parent(), navigation_params.get());
+  }
+
+  // The MHTML mime type should be same as the one we check in the browser
+  // process's download_utils::MustDownload.
+  bool is_mhtml_archive =
+      base::LowerCaseEqualsASCII(head.mime_type, "multipart/related") ||
+      base::LowerCaseEqualsASCII(head.mime_type, "message/rfc822");
+  if (is_mhtml_archive && navigation_params->body_loader) {
+    // Load full mhtml archive before committing navigation.
+    // We need this to retrieve the document mime type prior to committing.
+    mhtml_body_loader_client_ =
+        std::make_unique<RenderFrameImpl::MHTMLBodyLoaderClient>(
+            std::move(navigation_params), std::move(commit_with_params));
+    return;
+  }
+
+  // Common case - fill navigation params from provided information and commit.
+  std::move(commit_with_params).Run(std::move(navigation_params));
+}
+
+bool RenderFrameImpl::ShouldIgnoreCommitNavigation(
+    const CommitNavigationParams& commit_params) {
+  // We can ignore renderer-initiated navigations (nav_entry_id == 0) which
+  // have been canceled in the renderer, but browser was not aware yet at the
+  // moment of issuing a CommitNavigation call.
+  if (!browser_side_navigation_pending_ &&
+      !browser_side_navigation_pending_url_.is_empty() &&
+      browser_side_navigation_pending_url_ == commit_params.original_url &&
+      commit_params.nav_entry_id == 0) {
+    return true;
+  }
+  return false;
+}
+
+void RenderFrameImpl::CommitNavigationWithParams(
+    const CommonNavigationParams& common_params,
+    const CommitNavigationParams& commit_params,
+    std::unique_ptr<blink::URLLoaderFactoryBundleInfo>
+        subresource_loader_factories,
+    base::Optional<std::vector<mojom::TransferrableURLLoaderPtr>>
+        subresource_overrides,
+    blink::mojom::ControllerServiceWorkerInfoPtr controller_service_worker_info,
+    network::mojom::URLLoaderFactoryPtr prefetch_loader_factory,
+    std::unique_ptr<DocumentState> document_state,
+    std::unique_ptr<WebNavigationParams> navigation_params) {
+  if (ShouldIgnoreCommitNavigation(commit_params)) {
+    browser_side_navigation_pending_url_ = GURL();
+    if (IsPerNavigationMojoInterfaceEnabled())
+      navigation_client_impl_.reset();
+    return;
+  }
+
   // TODO(yoichio): This is temporary switch to have chrome WebUI
   // use the old web APIs.
   // After completion of the migration, we should remove this.
@@ -3315,18 +3494,6 @@
 
   PrepareFrameForCommit(common_params.url, commit_params);
 
-  // We only save metrics of the main frame's main resource to the
-  // document state. In view source mode, we effectively let the user
-  // see the source of the server's error page instead of using custom
-  // one derived from the metrics saved to document state.
-  const network::ResourceResponseHead* response_head = nullptr;
-  if (!frame_->Parent() && !frame_->IsViewSourceModeEnabled())
-    response_head = &head;
-  std::unique_ptr<DocumentState> document_state(BuildDocumentStateFromParams(
-      common_params, commit_params, base::TimeTicks::Now(), std::move(callback),
-      std::move(per_navigation_mojo_interface_callback), response_head,
-      std::move(navigation_client_impl_)));
-
   blink::WebFrameLoadType load_type = NavigationTypeToLoadType(
       common_params.navigation_type, common_params.should_replace_current_entry,
       commit_params.page_state.IsValid());
@@ -3354,66 +3521,11 @@
     return;
   }
 
-  // Check if the navigation being committed originated as a client redirect.
-  bool is_client_redirect =
-      !!(common_params.transition & ui::PAGE_TRANSITION_CLIENT_REDIRECT);
-
-  auto navigation_params =
-      std::make_unique<WebNavigationParams>(devtools_navigation_token);
   navigation_params->frame_load_type = load_type;
   navigation_params->history_item = item_for_history_navigation;
-  navigation_params->is_client_redirect = is_client_redirect;
   navigation_params->service_worker_network_provider =
       BuildServiceWorkerNetworkProviderForNavigation(
           &commit_params, std::move(controller_service_worker_info));
-  FillMiscNavigationParams(common_params, commit_params,
-                           navigation_params.get());
-
-  // Perform a navigation to a data url if needed (for main frames).
-  // Note: the base URL might be invalid, so also check the data URL string.
-  bool should_load_data_url = !common_params.base_url_for_data_url.is_empty();
-#if defined(OS_ANDROID)
-  should_load_data_url |= !commit_params.data_url_as_string.empty();
-#endif
-  if (is_main_frame_ && should_load_data_url) {
-    std::string mime_type, charset, data;
-    GURL base_url;
-    DecodeDataURL(common_params, commit_params, &mime_type, &charset, &data,
-                  &base_url);
-    navigation_params->url = base_url;
-    WebNavigationParams::FillStaticResponse(navigation_params.get(),
-                                            WebString::FromUTF8(mime_type),
-                                            WebString::FromUTF8(charset), data);
-    // Needed so that history-url-only changes don't become reloads.
-    navigation_params->unreachable_url = common_params.history_url_for_data_url;
-  } else {
-    InternalDocumentStateData* internal_data =
-        InternalDocumentStateData::FromDocumentState(document_state.get());
-    FillNavigationParamsRequest(common_params, commit_params,
-                                navigation_params.get());
-    if (!url_loader_client_endpoints &&
-        common_params.url.SchemeIs(url::kDataScheme)) {
-      // Normally, data urls will have |url_loader_client_endpoints| set.
-      // However, tests and interstitial pages pass data urls directly,
-      // without creating url loader.
-      std::string mime_type, charset, data;
-      if (!net::DataURL::Parse(common_params.url, &mime_type, &charset,
-                               &data)) {
-        CHECK(false) << "Invalid URL passed: "
-                     << common_params.url.possibly_invalid_spec();
-        return;
-      }
-      WebNavigationParams::FillStaticResponse(
-          navigation_params.get(), WebString::FromUTF8(mime_type),
-          WebString::FromUTF8(charset), data);
-    } else {
-      NavigationBodyLoader::FillNavigationParamsResponseAndBodyLoader(
-          common_params, commit_params, internal_data->request_id(), head,
-          std::move(url_loader_client_endpoints),
-          GetTaskRunner(blink::TaskType::kInternalLoading), GetRoutingID(),
-          !frame_->Parent(), navigation_params.get());
-    }
-  }
 
   frame_->CommitNavigation(std::move(navigation_params),
                            std::move(document_state));
@@ -3476,6 +3588,7 @@
   RenderFrameImpl::PrepareRenderViewForNavigation(common_params.url,
                                                   commit_params);
   sync_navigation_callback_.Cancel();
+  mhtml_body_loader_client_.reset();
 
   GetContentClient()->SetActiveURL(
       common_params.url, frame_->Top()->GetSecurityOrigin().ToString().Utf8());
@@ -3591,7 +3704,7 @@
   std::unique_ptr<DocumentState> document_state = BuildDocumentStateFromParams(
       common_params, commit_params, base::TimeTicks(), std::move(callback),
       std::move(per_navigation_mojo_interface_callback), nullptr,
-      std::move(navigation_client_impl_));
+      std::move(navigation_client_impl_), ResourceDispatcher::MakeRequestID());
 
   // The load of the error page can result in this frame being removed.
   // Use a WeakPtr as an easy way to detect whether this has occured. If so,
@@ -4844,6 +4957,7 @@
 void RenderFrameImpl::AbortClientNavigation() {
   browser_side_navigation_pending_ = false;
   sync_navigation_callback_.Cancel();
+  mhtml_body_loader_client_.reset();
   if (!IsPerNavigationMojoInterfaceEnabled())
     Send(new FrameHostMsg_AbortNavigation(routing_id_));
 }
@@ -5860,6 +5974,7 @@
   browser_side_navigation_pending_ = false;
   browser_side_navigation_pending_url_ = GURL();
   sync_navigation_callback_.Cancel();
+  mhtml_body_loader_client_.reset();
 
   GetContentClient()->SetActiveURL(
       url, frame_->Top()->GetSecurityOrigin().ToString().Utf8());
@@ -6185,6 +6300,7 @@
     }
 
     sync_navigation_callback_.Cancel();
+    mhtml_body_loader_client_.reset();
 
     // When an MHTML Archive is present, it should be used to serve iframe
     // content instead of doing a network request.
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index d26a27ca..75a649b 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -157,6 +157,7 @@
 
 class BlinkInterfaceRegistryImpl;
 class CompositorDependencies;
+class DocumentState;
 class ExternalPopupMenu;
 class FrameRequestBlocker;
 class ManifestManager;
@@ -547,6 +548,8 @@
   void ResumeBlockedRequests() override;
   void CancelBlockedRequests() override;
   void OnPortalActivated() override;
+  void SetLifecycleState(blink::mojom::FrameLifecycleState state) override;
+
 #if defined(OS_ANDROID)
   void ExtractSmartClipData(
       const gfx::Rect& rect,
@@ -1227,6 +1230,26 @@
   // document or an MHTML archive).
   void CommitSyncNavigation(std::unique_ptr<blink::WebNavigationInfo> info);
 
+  // Commit navigation with |navigation_params| prepared.
+  void CommitNavigationWithParams(
+      const CommonNavigationParams& common_params,
+      const CommitNavigationParams& commit_params,
+      std::unique_ptr<blink::URLLoaderFactoryBundleInfo>
+          subresource_loader_factories,
+      base::Optional<std::vector<mojom::TransferrableURLLoaderPtr>>
+          subresource_overrides,
+      blink::mojom::ControllerServiceWorkerInfoPtr
+          controller_service_worker_info,
+      network::mojom::URLLoaderFactoryPtr prefetch_loader_factory,
+      std::unique_ptr<DocumentState> document_state,
+      std::unique_ptr<blink::WebNavigationParams> navigation_params);
+
+  // We can ignore renderer-initiated navigations which have been canceled
+  // in the renderer, but browser was not aware yet at the moment of issuing
+  // a CommitNavigation call.
+  bool ShouldIgnoreCommitNavigation(
+      const CommitNavigationParams& commit_params);
+
   // Decodes a data url for navigation commit.
   void DecodeDataURL(const CommonNavigationParams& common_params,
                      const CommitNavigationParams& commit_params,
@@ -1703,6 +1726,9 @@
 
   base::CancelableOnceCallback<void()> sync_navigation_callback_;
 
+  class MHTMLBodyLoaderClient;
+  std::unique_ptr<MHTMLBodyLoaderClient> mhtml_body_loader_client_;
+
   base::WeakPtrFactory<RenderFrameImpl> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(RenderFrameImpl);
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index 73b2a3c..0623b9f 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -1196,10 +1196,6 @@
   webview()->AudioStateChanged(is_audio_playing);
 }
 
-void RenderViewImpl::OnPausePageScheduledTasks(bool paused) {
-  webview()->PausePageScheduledTasks(paused);
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 
 void RenderViewImpl::ShowCreatedPopupWidget(RenderWidget* popup_widget,
@@ -1268,8 +1264,6 @@
     IPC_MESSAGE_HANDLER(PageMsg_SetHistoryOffsetAndLength,
                         OnSetHistoryOffsetAndLength)
     IPC_MESSAGE_HANDLER(PageMsg_AudioStateChanged, OnAudioStateChanged)
-    IPC_MESSAGE_HANDLER(PageMsg_PausePageScheduledTasks,
-                        OnPausePageScheduledTasks)
     IPC_MESSAGE_HANDLER(PageMsg_UpdateScreenInfo, OnUpdateScreenInfo)
     IPC_MESSAGE_HANDLER(PageMsg_SetPageFrozen, SetPageFrozen)
 
diff --git a/content/renderer/render_view_impl.h b/content/renderer/render_view_impl.h
index a35989a..e454434c 100644
--- a/content/renderer/render_view_impl.h
+++ b/content/renderer/render_view_impl.h
@@ -462,7 +462,6 @@
   void OnUpdateWebPreferences(const WebPreferences& prefs);
   void OnSetPageScale(float page_scale_factor);
   void OnAudioStateChanged(bool is_audio_playing);
-  void OnPausePageScheduledTasks(bool paused);
   void OnSetBackgroundOpaque(bool opaque);
 
   // Page message handlers -----------------------------------------------------
diff --git a/content/renderer/service_worker/service_worker_network_provider_for_service_worker.cc b/content/renderer/service_worker/service_worker_network_provider_for_service_worker.cc
index 008f3ee..689f771 100644
--- a/content/renderer/service_worker/service_worker_network_provider_for_service_worker.cc
+++ b/content/renderer/service_worker/service_worker_network_provider_for_service_worker.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "base/debug/alias.h"
 #include "content/public/common/resource_type.h"
 #include "content/public/renderer/url_loader_throttle_provider.h"
 #include "content/renderer/loader/request_extra_data.h"
@@ -15,9 +16,25 @@
 #include "ipc/ipc_message.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 #include "third_party/blink/public/common/service_worker/service_worker_utils.h"
+#include "third_party/blink/public/platform/web_url.h"
+#include "third_party/blink/public/platform/web_url_request.h"
+#include "url/gurl.h"
 
 namespace content {
 
+namespace {
+
+// TODO(https://crbug.com/929042): Remove this after the linked bug is fixed.
+void CrashBecauseNotMainScriptRequest(const blink::WebURLRequest& request) {
+  GURL url(request.Url());
+  DEBUG_ALIAS_FOR_GURL(url_buf, url);
+  blink::mojom::RequestContextType context = request.GetRequestContext();
+  base::debug::Alias(&context);
+  CHECK(false);
+}
+
+}  // namespace
+
 ServiceWorkerNetworkProviderForServiceWorker::
     ServiceWorkerNetworkProviderForServiceWorker(
         int provider_id,
@@ -61,6 +78,11 @@
   // We only get here for the main script request from the shadow page.
   // importScripts() and other subresource fetches are handled on the worker
   // thread by ServiceWorkerFetchContextImpl.
+  if (request.GetRequestContext() !=
+      blink::mojom::RequestContextType::SERVICE_WORKER) {
+    CrashBecauseNotMainScriptRequest(request);
+    return nullptr;
+  }
   DCHECK_EQ(blink::mojom::RequestContextType::SERVICE_WORKER,
             request.GetRequestContext());
 
diff --git a/content/test/data/find_in_page_simple_frame.html b/content/test/data/find_in_page_simple_frame.html
new file mode 100644
index 0000000..fae4833
--- /dev/null
+++ b/content/test/data/find_in_page_simple_frame.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
+<body>
+  this frame contains a result.
+  and another result.
+  and another result.
+</body>
+</html>
diff --git a/content/test/data/find_in_page_two_frames.html b/content/test/data/find_in_page_two_frames.html
new file mode 100644
index 0000000..e702a174
--- /dev/null
+++ b/content/test/data/find_in_page_two_frames.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<body>
+Making two frames to reproduce crbug.com/889075.
+<iframe src="find_in_page_simple_frame.html"></iframe>
+<iframe src="find_in_page_simple_frame.html"></iframe>
+</body>
+</html>
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
index 0e9bcbd..074f245 100644
--- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -77,9 +77,6 @@
         'vector-scalar-arithmetic-inside-loop-complex.html',
         ['nvidia'], bug=772651)
 
-    # All platforms.
-    self.Fail('conformance2/glsl3/tricky-loop-conditions.html', bug=905001)
-
     # This test needs to be rewritten to measure its expected
     # performance; it's currently too flaky even on release bots.
     self.Skip('conformance/rendering/texture-switch-performance.html',
@@ -117,6 +114,8 @@
     self.Fail('conformance2/textures/misc/' +
         'generate-mipmap-with-large-base-level.html',
         ['win', 'no_passthrough'], bug=3033) # angle bug ID
+    self.Fail('conformance2/glsl3/tricky-loop-conditions.html',
+        ['win'], bug=1465) # anglebug.com/1465
 
     # Win / NVidia
     self.Flaky('deqp/functional/gles3/fbomultisample*',
@@ -673,6 +672,9 @@
     self.Flaky('conformance2/textures/canvas/tex-3d-r16f-red-half_float.html',
         ['mac', ('nvidia', 0xfe9)], bug=911772)
 
+    self.Fail('conformance2/glsl3/tricky-loop-conditions.html',
+        ['mac', ('nvidia', 0xfe9)], bug=929398)
+
     # When these fail on this configuration, they fail multiple times in a row.
     self.Fail('deqp/functional/gles3/shaderoperator/*',
         ['mac', 'nvidia'], bug=756537)
@@ -1324,14 +1326,6 @@
     # Basic failures that need to be investigated on multiple devices
     self.Fail('conformance2/glsl3/vector-dynamic-indexing-swizzled-lvalue.html',
         ['android'], bug=709351)
-    # Video uploads to some texture formats new in WebGL 2.0 are
-    # failing.
-    self.Fail('conformance2/textures/video/' +
-        'tex-2d-rg8ui-rg_integer-unsigned_byte.html',
-        ['android'], bug=906735)
-    self.Fail('conformance2/textures/video/' +
-        'tex-2d-rgb8ui-rgb_integer-unsigned_byte.html',
-        ['android'], bug=906735)
 
     # Qualcomm (Pixel 2) failures
     self.Fail('conformance/extensions/webgl-compressed-texture-astc.html',
@@ -1342,9 +1336,6 @@
         ['android', 'qualcomm'], bug=906742)
     self.Fail('conformance2/rendering/uniform-block-buffer-size.html',
         ['android', 'qualcomm'], bug=906743)
-    self.Fail('conformance2/textures/video/' +
-        'tex-2d-rgba8ui-rgba_integer-unsigned_byte.html',
-        ['android', 'qualcomm'], bug=906735)
     self.Fail('conformance2/textures/misc/tex-new-formats.html',
         ['android', 'qualcomm'], bug=906740)
     self.Fail('conformance2/textures/misc/copy-texture-image-luma-format.html',
diff --git a/extensions/browser/api/declarative_net_request/constants.cc b/extensions/browser/api/declarative_net_request/constants.cc
index e530344..084b92f 100644
--- a/extensions/browser/api/declarative_net_request/constants.cc
+++ b/extensions/browser/api/declarative_net_request/constants.cc
@@ -8,34 +8,34 @@
 namespace declarative_net_request {
 
 const char kErrorResourceTypeDuplicated[] =
-    "*: Rule at index * includes and excludes the same resource.";
+    "*: Rule with id * includes and excludes the same resource.";
 const char kErrorEmptyRedirectRuleKey[] =
-    "*: Rule at index * does not specify the value for * key. This is required "
+    "*: Rule with id * does not specify the value for * key. This is required "
     "for redirect rules.";
 const char kErrorInvalidRuleKey[] =
-    "*: Rule at index * has an invalid value for * key. This should be greater "
+    "*: Rule with id * has an invalid value for * key. This should be greater "
     "than or equal to *.";
 const char kErrorNoApplicableResourceTypes[] =
-    "*: Rule at index * is not applicable to any resource type.";
+    "*: Rule with id * is not applicable to any resource type.";
 const char kErrorEmptyList[] =
-    "*: Rule at index * cannot have an empty list as the value for * key.";
+    "*: Rule with id * cannot have an empty list as the value for * key.";
 const char kErrorEmptyUrlFilter[] =
-    "*: Rule at index * cannot have an empty value for * key.";
+    "*: Rule with id * cannot have an empty value for * key.";
 const char kErrorInvalidRedirectUrl[] =
-    "*: Rule at index * does not provide a valid URL for * key.";
+    "*: Rule with id * does not provide a valid URL for * key.";
 const char kErrorDuplicateIDs[] =
-    "*: Rule at index * does not have a unique ID.";
+    "*: Rule with id * does not have a unique ID.";
 // Don't surface the actual error to the user, since it's an implementation
 // detail.
 const char kErrorPersisting[] = "*: Rules file could not be parsed.";
 const char kErrorListNotPassed[] = "*: Rules file must contain a list.";
 const char kErrorNonAscii[] =
-    "*: Rule at index * cannot have non-ascii characters as part of \"*\" key.";
+    "*: Rule with id * cannot have non-ascii characters as part of \"*\" key.";
 
 const char kRuleCountExceeded[] =
     "Declarative Net Request: Rule count exceeded. Some rules were ignored.";
 const char kRuleNotParsedWarning[] =
-    "Declarative Net Request: Rule at index * couldn't be parsed. Parse error: "
+    "Declarative Net Request: Rule with * couldn't be parsed. Parse error: "
     "*.";
 const char kTooManyParseFailuresWarning[] =
     "Declarative Net Request: Too many rule parse failures; Reporting the "
diff --git a/extensions/browser/api/declarative_net_request/parse_info.cc b/extensions/browser/api/declarative_net_request/parse_info.cc
index 7f39d50..356b5d8 100644
--- a/extensions/browser/api/declarative_net_request/parse_info.cc
+++ b/extensions/browser/api/declarative_net_request/parse_info.cc
@@ -12,16 +12,16 @@
 namespace declarative_net_request {
 
 ParseInfo::ParseInfo(ParseResult result) : result_(result) {}
-ParseInfo::ParseInfo(ParseResult result, size_t rule_index)
-    : result_(result), rule_index_(rule_index) {}
+ParseInfo::ParseInfo(ParseResult result, int rule_id)
+    : result_(result), rule_id_(rule_id) {}
 ParseInfo::ParseInfo(const ParseInfo&) = default;
 ParseInfo& ParseInfo::operator=(const ParseInfo&) = default;
 
 std::string ParseInfo::GetErrorDescription(
     const base::StringPiece json_rules_filename) const {
   // Every error except ERROR_PERSISTING_RULESET and ERROR_LIST_NOT_PASSED
-  // requires |rule_index_|.
-  DCHECK_EQ(!rule_index_.has_value(),
+  // requires |rule_id_|.
+  DCHECK_EQ(!rule_id_.has_value(),
             result_ == ParseResult::ERROR_PERSISTING_RULESET ||
                 result_ == ParseResult::ERROR_LIST_NOT_PASSED);
 
@@ -33,58 +33,56 @@
     case ParseResult::ERROR_RESOURCE_TYPE_DUPLICATED:
       error = ErrorUtils::FormatErrorMessage(kErrorResourceTypeDuplicated,
                                              json_rules_filename,
-                                             std::to_string(*rule_index_));
+                                             std::to_string(*rule_id_));
       break;
     case ParseResult::ERROR_EMPTY_REDIRECT_RULE_PRIORITY:
       error = ErrorUtils::FormatErrorMessage(
           kErrorEmptyRedirectRuleKey, json_rules_filename,
-          std::to_string(*rule_index_), kPriorityKey);
+          std::to_string(*rule_id_), kPriorityKey);
       break;
     case ParseResult::ERROR_EMPTY_REDIRECT_URL:
       error = ErrorUtils::FormatErrorMessage(
           kErrorEmptyRedirectRuleKey, json_rules_filename,
-          std::to_string(*rule_index_), kRedirectUrlKey);
+          std::to_string(*rule_id_), kRedirectUrlKey);
       break;
     case ParseResult::ERROR_INVALID_RULE_ID:
       error = ErrorUtils::FormatErrorMessage(
-          kErrorInvalidRuleKey, json_rules_filename,
-          std::to_string(*rule_index_), kIDKey, std::to_string(kMinValidID));
+          kErrorInvalidRuleKey, json_rules_filename, std::to_string(*rule_id_),
+          kIDKey, std::to_string(kMinValidID));
       break;
     case ParseResult::ERROR_INVALID_REDIRECT_RULE_PRIORITY:
       error = ErrorUtils::FormatErrorMessage(
-          kErrorInvalidRuleKey, json_rules_filename,
-          std::to_string(*rule_index_), kPriorityKey,
-          std::to_string(kMinValidPriority));
+          kErrorInvalidRuleKey, json_rules_filename, std::to_string(*rule_id_),
+          kPriorityKey, std::to_string(kMinValidPriority));
       break;
     case ParseResult::ERROR_NO_APPLICABLE_RESOURCE_TYPES:
       error = ErrorUtils::FormatErrorMessage(kErrorNoApplicableResourceTypes,
                                              json_rules_filename,
-                                             std::to_string(*rule_index_));
+                                             std::to_string(*rule_id_));
       break;
     case ParseResult::ERROR_EMPTY_DOMAINS_LIST:
       error = ErrorUtils::FormatErrorMessage(
-          kErrorEmptyList, json_rules_filename, std::to_string(*rule_index_),
+          kErrorEmptyList, json_rules_filename, std::to_string(*rule_id_),
           kDomainsKey);
       break;
     case ParseResult::ERROR_EMPTY_RESOURCE_TYPES_LIST:
       error = ErrorUtils::FormatErrorMessage(
-          kErrorEmptyList, json_rules_filename, std::to_string(*rule_index_),
+          kErrorEmptyList, json_rules_filename, std::to_string(*rule_id_),
           kResourceTypesKey);
       break;
     case ParseResult::ERROR_EMPTY_URL_FILTER:
       error = ErrorUtils::FormatErrorMessage(
-          kErrorEmptyUrlFilter, json_rules_filename,
-          std::to_string(*rule_index_), kUrlFilterKey);
+          kErrorEmptyUrlFilter, json_rules_filename, std::to_string(*rule_id_),
+          kUrlFilterKey);
       break;
     case ParseResult::ERROR_INVALID_REDIRECT_URL:
       error = ErrorUtils::FormatErrorMessage(
           kErrorInvalidRedirectUrl, json_rules_filename,
-          std::to_string(*rule_index_), kRedirectUrlKey);
+          std::to_string(*rule_id_), kRedirectUrlKey);
       break;
     case ParseResult::ERROR_DUPLICATE_IDS:
-      error = ErrorUtils::FormatErrorMessage(kErrorDuplicateIDs,
-                                             json_rules_filename,
-                                             std::to_string(*rule_index_));
+      error = ErrorUtils::FormatErrorMessage(
+          kErrorDuplicateIDs, json_rules_filename, std::to_string(*rule_id_));
       break;
     case ParseResult::ERROR_PERSISTING_RULESET:
       error =
@@ -96,17 +94,17 @@
       break;
     case ParseResult::ERROR_NON_ASCII_URL_FILTER:
       error = ErrorUtils::FormatErrorMessage(
-          kErrorNonAscii, json_rules_filename, std::to_string(*rule_index_),
+          kErrorNonAscii, json_rules_filename, std::to_string(*rule_id_),
           kUrlFilterKey);
       break;
     case ParseResult::ERROR_NON_ASCII_DOMAIN:
       error = ErrorUtils::FormatErrorMessage(
-          kErrorNonAscii, json_rules_filename, std::to_string(*rule_index_),
+          kErrorNonAscii, json_rules_filename, std::to_string(*rule_id_),
           kDomainsKey);
       break;
     case ParseResult::ERROR_NON_ASCII_EXCLUDED_DOMAIN:
       error = ErrorUtils::FormatErrorMessage(
-          kErrorNonAscii, json_rules_filename, std::to_string(*rule_index_),
+          kErrorNonAscii, json_rules_filename, std::to_string(*rule_id_),
           kExcludedDomainsKey);
       break;
   }
diff --git a/extensions/browser/api/declarative_net_request/parse_info.h b/extensions/browser/api/declarative_net_request/parse_info.h
index aae037c..e59010d 100644
--- a/extensions/browser/api/declarative_net_request/parse_info.h
+++ b/extensions/browser/api/declarative_net_request/parse_info.h
@@ -15,12 +15,12 @@
 namespace extensions {
 namespace declarative_net_request {
 
-// Holds the ParseResult together with the index of the rule at which the error
+// Holds the ParseResult together with the id of the rule at which the error
 // occurred, if any.
 class ParseInfo {
  public:
   explicit ParseInfo(ParseResult result);
-  ParseInfo(ParseResult result, size_t rule_index);
+  ParseInfo(ParseResult result, int rule_id);
   ParseInfo(const ParseInfo&);
   ParseInfo& operator=(const ParseInfo&);
 
@@ -33,9 +33,9 @@
 
  private:
   ParseResult result_;
-  // When set, denotes the index of the rule with which the |result_| is
+  // When set, denotes the id of the rule with which the |result_| is
   // associated.
-  base::Optional<size_t> rule_index_;
+  base::Optional<int> rule_id_;
 };
 
 }  // namespace declarative_net_request
diff --git a/extensions/browser/api/declarative_net_request/utils.cc b/extensions/browser/api/declarative_net_request/utils.cc
index c90942a9..9b2fa30b 100644
--- a/extensions/browser/api/declarative_net_request/utils.cc
+++ b/extensions/browser/api/declarative_net_request/utils.cc
@@ -184,9 +184,20 @@
       if (!parsed_rule || !parse_error.empty()) {
         if (unparsed_warning_count < kMaxUnparsedRulesWarnings) {
           ++unparsed_warning_count;
+          std::string rule_location;
+
+          // If possible use the rule ID in the install warning.
+          if (auto* id_val = rules_list[i].FindKeyOfType(
+                  kIDKey, base::Value::Type::INTEGER)) {
+            rule_location = base::StringPrintf("id %d", id_val->GetInt());
+          } else {
+            // Use one-based indices.
+            rule_location = base::StringPrintf("index %zu", i + 1);
+          }
+
           warnings->push_back(
               CreateInstallWarning(ErrorUtils::FormatErrorMessage(
-                  kRuleNotParsedWarning, std::to_string(i),
+                  kRuleNotParsedWarning, rule_location,
                   base::UTF16ToUTF8(parse_error))));
         } else {
           unparsed_warnings_limit_exeeded = true;
@@ -194,15 +205,16 @@
         continue;
       }
 
-      bool inserted = id_set.insert(parsed_rule->id).second;
+      int rule_id = parsed_rule->id;
+      bool inserted = id_set.insert(rule_id).second;
       if (!inserted)
-        return ParseInfo(ParseResult::ERROR_DUPLICATE_IDS, i);
+        return ParseInfo(ParseResult::ERROR_DUPLICATE_IDS, rule_id);
 
       IndexedRule indexed_rule;
       ParseResult parse_result =
           IndexedRule::CreateIndexedRule(std::move(parsed_rule), &indexed_rule);
       if (parse_result != ParseResult::SUCCESS)
-        return ParseInfo(parse_result, i);
+        return ParseInfo(parse_result, rule_id);
 
       if (indexer.indexed_rules_count() >= kRuleCountLimit) {
         rule_count_exceeded = true;
diff --git a/extensions/browser/api/messaging/extension_message_port.cc b/extensions/browser/api/messaging/extension_message_port.cc
index cbb583d..bd8bd697 100644
--- a/extensions/browser/api/messaging/extension_message_port.cc
+++ b/extensions/browser/api/messaging/extension_message_port.cc
@@ -18,6 +18,7 @@
 #include "extensions/browser/process_manager.h"
 #include "extensions/browser/process_manager_observer.h"
 #include "extensions/common/api/messaging/message.h"
+#include "extensions/common/api/messaging/messaging_endpoint.h"
 #include "extensions/common/extension_messages.h"
 #include "extensions/common/manifest_handlers/background_info.h"
 
@@ -207,7 +208,7 @@
     int source_frame_id,
     int guest_process_id,
     int guest_render_frame_routing_id,
-    const std::string& source_extension_id,
+    const MessagingEndpoint& source_endpoint,
     const std::string& target_extension_id,
     const GURL& source_url) {
   ExtensionMsg_TabConnectionInfo source;
@@ -217,7 +218,7 @@
 
   ExtensionMsg_ExternalConnectionInfo info;
   info.target_id = target_extension_id;
-  info.source_id = source_extension_id;
+  info.source_endpoint = source_endpoint;
   info.source_url = source_url;
   info.guest_process_id = guest_process_id;
   info.guest_render_frame_routing_id = guest_render_frame_routing_id;
diff --git a/extensions/browser/api/messaging/extension_message_port.h b/extensions/browser/api/messaging/extension_message_port.h
index 606cfe28..cc7582fd 100644
--- a/extensions/browser/api/messaging/extension_message_port.h
+++ b/extensions/browser/api/messaging/extension_message_port.h
@@ -61,7 +61,7 @@
                          int source_frame_id,
                          int guest_process_id,
                          int guest_render_frame_routing_id,
-                         const std::string& source_extension_id,
+                         const MessagingEndpoint& source_endpoint,
                          const std::string& target_extension_id,
                          const GURL& source_url) override;
   void DispatchOnDisconnect(const std::string& error_message) override;
diff --git a/extensions/browser/api/messaging/message_port.cc b/extensions/browser/api/messaging/message_port.cc
index 115e8a9..20eea06 100644
--- a/extensions/browser/api/messaging/message_port.cc
+++ b/extensions/browser/api/messaging/message_port.cc
@@ -21,7 +21,7 @@
     int source_frame_id,
     int guest_process_id,
     int guest_render_frame_routing_id,
-    const std::string& source_extension_id,
+    const MessagingEndpoint& source_endpoint,
     const std::string& target_extension_id,
     const GURL& source_url) {}
 
diff --git a/extensions/browser/api/messaging/message_port.h b/extensions/browser/api/messaging/message_port.h
index 9c8f7a5..676a4b0 100644
--- a/extensions/browser/api/messaging/message_port.h
+++ b/extensions/browser/api/messaging/message_port.h
@@ -19,6 +19,7 @@
 
 namespace extensions {
 struct Message;
+struct MessagingEndpoint;
 struct PortId;
 
 // One side of the communication handled by extensions::MessageService.
@@ -58,7 +59,7 @@
       int source_frame_id,
       int guest_process_id,
       int guest_render_frame_routing_id,
-      const std::string& source_extension_id,
+      const MessagingEndpoint& source_endpoint,
       const std::string& target_extension_id,
       const GURL& source_url);
 
diff --git a/extensions/browser/api/messaging/message_service.cc b/extensions/browser/api/messaging/message_service.cc
index 8258afb..7d64865 100644
--- a/extensions/browser/api/messaging/message_service.cc
+++ b/extensions/browser/api/messaging/message_service.cc
@@ -39,6 +39,7 @@
 #include "extensions/browser/extensions_browser_client.h"
 #include "extensions/browser/guest_view/web_view/web_view_guest.h"
 #include "extensions/browser/process_manager.h"
+#include "extensions/common/api/messaging/messaging_endpoint.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/manifest_constants.h"
 #include "extensions/common/manifest_handlers/background_info.h"
@@ -80,7 +81,7 @@
   int source_frame_id;
   std::unique_ptr<MessagePort> receiver;
   PortId receiver_port_id;
-  std::string source_extension_id;
+  MessagingEndpoint source_endpoint;
   std::string target_extension_id;
   GURL source_url;
   std::string channel_name;
@@ -93,7 +94,7 @@
                     int source_frame_id,
                     MessagePort* receiver,
                     const PortId& receiver_port_id,
-                    const std::string& source_extension_id,
+                    const MessagingEndpoint& source_endpoint,
                     const std::string& target_extension_id,
                     const GURL& source_url,
                     const std::string& channel_name,
@@ -103,7 +104,7 @@
         source_frame_id(source_frame_id),
         receiver(receiver),
         receiver_port_id(receiver_port_id),
-        source_extension_id(source_extension_id),
+        source_endpoint(source_endpoint),
         target_extension_id(target_extension_id),
         source_url(source_url),
         channel_name(channel_name),
@@ -161,12 +162,16 @@
     int source_process_id,
     int source_routing_id,
     const PortId& source_port_id,
-    const std::string& source_extension_id,
+    const MessagingEndpoint& source_endpoint,
     const std::string& target_extension_id,
     const GURL& source_url,
     const std::string& channel_name) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(source_port_id.is_opener);
+  DCHECK(!target_extension_id.empty());
+  DCHECK_NE(source_endpoint.type, MessagingEndpoint::Type::kNativeApp);
+  DCHECK(source_endpoint.extension_id.has_value() ||
+         source_endpoint.type == MessagingEndpoint::Type::kTab);
 
   content::RenderFrameHost* source_render_frame_host =
       content::RenderFrameHost::FromID(source_process_id, source_routing_id);
@@ -188,7 +193,9 @@
 
   bool is_web_connection = false;
 
-  if (source_extension_id != target_extension_id) {
+  if ((source_endpoint.type == MessagingEndpoint::Type::kTab ||
+       source_endpoint.type == MessagingEndpoint::Type::kExtension) &&
+      source_endpoint.extension_id != target_extension_id) {
     // It's an external connection. Check the externally_connectable manifest
     // key if it's present. If it's not, we allow connection from any extension
     // but not webpages.
@@ -199,21 +206,20 @@
     bool is_externally_connectable = false;
 
     if (externally_connectable) {
-      if (source_extension_id.empty()) {
-        // No source extension ID so the source was a web page. Check that the
-        // URL matches.
+      if (source_endpoint.extension_id) {
+        // The source was an extension or a content script. Check that the
+        // extension ID matches.
+        is_externally_connectable =
+            externally_connectable->IdCanConnect(*source_endpoint.extension_id);
+      } else {
+        // Check that the web page URL matches.
         is_web_connection = true;
         is_externally_connectable =
             externally_connectable->matches.MatchesURL(source_url);
-      } else {
-        // Source extension ID so the source was an extension. Check that the
-        // extension matches.
-        is_externally_connectable =
-            externally_connectable->IdCanConnect(source_extension_id);
       }
     } else {
-      // Default behaviour. Any extension, no webpages.
-      is_externally_connectable = !source_extension_id.empty();
+      // Default behaviour. Any extension or content script, no webpages.
+      is_externally_connectable = source_endpoint.extension_id.has_value();
     }
 
     if (!is_externally_connectable) {
@@ -254,7 +260,7 @@
 
   std::unique_ptr<OpenChannelParams> params(new OpenChannelParams(
       source_process_id, source_routing_id, std::move(source_tab),
-      source_frame_id, nullptr, receiver_port_id, source_extension_id,
+      source_frame_id, nullptr, receiver_port_id, source_endpoint,
       target_extension_id, source_url, channel_name,
       include_guest_process_info));
 
@@ -441,7 +447,8 @@
                                                  // sense
                                                  // for opening to tabs.
       -1,  // If there is no tab, then there is no frame either.
-      receiver.release(), receiver_port_id, extension_id, extension_id,
+      receiver.release(), receiver_port_id,
+      MessagingEndpoint::ForExtension(extension_id), extension_id,
       GURL(),  // Source URL doesn't make sense for opening to tabs.
       channel_name,
       false));  // Connections to tabs aren't webview guests.
@@ -470,7 +477,10 @@
 
   std::unique_ptr<ExtensionMessagePort> opener(new ExtensionMessagePort(
       weak_factory_.GetWeakPtr(), params->receiver_port_id.GetOppositePortId(),
-      params->source_extension_id, source, false));
+      params->source_endpoint.extension_id
+          ? *params->source_endpoint.extension_id
+          : ExtensionId(),
+      source, false));
   if (!opener->IsValidPort())
     return;
   opener->OpenPort(params->source_process_id, params->source_routing_id);
@@ -504,8 +514,7 @@
   channel->receiver->DispatchOnConnect(
       params->channel_name, std::move(params->source_tab),
       params->source_frame_id, guest_process_id, guest_render_frame_routing_id,
-      params->source_extension_id, params->target_extension_id,
-      params->source_url);
+      params->source_endpoint, params->target_extension_id, params->source_url);
 
   // Report the event to the event router, if the target is an extension.
   //
@@ -523,7 +532,7 @@
   if (target_extension) {
     events::HistogramValue histogram_value = events::UNKNOWN;
     bool is_external =
-        params->source_extension_id != params->target_extension_id;
+        params->source_endpoint.extension_id != params->target_extension_id;
     if (params->channel_name == "chrome.runtime.onRequest") {
       histogram_value = is_external ? events::RUNTIME_ON_REQUEST_EXTERNAL
                                     : events::RUNTIME_ON_REQUEST;
diff --git a/extensions/browser/api/messaging/message_service.h b/extensions/browser/api/messaging/message_service.h
index f9f746d..90c421a8 100644
--- a/extensions/browser/api/messaging/message_service.h
+++ b/extensions/browser/api/messaging/message_service.h
@@ -33,6 +33,7 @@
 class Extension;
 class ExtensionHost;
 class MessagingDelegate;
+struct MessagingEndpoint;
 
 // This class manages message and event passing between renderer processes.
 // It maintains a list of processes that are listening to events and a set of
@@ -81,7 +82,7 @@
   void OpenChannelToExtension(int source_process_id,
                               int source_routing_id,
                               const PortId& source_port_id,
-                              const std::string& source_extension_id,
+                              const MessagingEndpoint& source_endpoint,
                               const std::string& target_extension_id,
                               const GURL& source_url,
                               const std::string& channel_name);
diff --git a/extensions/browser/api/web_request/web_request_api.cc b/extensions/browser/api/web_request/web_request_api.cc
index f8258eef..f9e7896 100644
--- a/extensions/browser/api/web_request/web_request_api.cc
+++ b/extensions/browser/api/web_request/web_request_api.cc
@@ -145,14 +145,6 @@
     keys::kOnHeadersReceivedEvent,
 };
 
-// List of all webRequest events that support extraHeaders in the extraInfoSpec.
-const char* const kWebRequestExtraHeadersEventNames[] = {
-    keys::kOnBeforeSendHeadersEvent, keys::kOnSendHeadersEvent,
-    keys::kOnHeadersReceivedEvent,   keys::kOnAuthRequiredEvent,
-    keys::kOnResponseStartedEvent,   keys::kOnBeforeRedirectEvent,
-    keys::kOnCompletedEvent,
-};
-
 // User data key for WebRequestAPI::ProxySet.
 const void* const kWebRequestProxySetUserDataKey =
     &kWebRequestProxySetUserDataKey;
@@ -997,8 +989,7 @@
   }
   request_time_tracker_->LogRequestStartTime(
       request->id, base::TimeTicks::Now(), has_listener,
-      HasExtraHeadersListenerForRequest(browser_context, extension_info_map,
-                                        request));
+      HasExtraHeadersListener(browser_context, extension_info_map, request));
 
   const bool is_incognito_context = IsIncognitoBrowserContext(browser_context);
 
@@ -1740,12 +1731,18 @@
   callbacks_for_page_load_.push_back(callback);
 }
 
-bool ExtensionWebRequestEventRouter::HasExtraHeadersListenerForRequest(
+bool ExtensionWebRequestEventRouter::HasExtraHeadersListener(
     void* browser_context,
     const extensions::InfoMap* extension_info_map,
     const WebRequestInfo* request) {
+  static const char* kEventNames[] = {
+      keys::kOnBeforeSendHeadersEvent, keys::kOnSendHeadersEvent,
+      keys::kOnHeadersReceivedEvent,   keys::kOnAuthRequiredEvent,
+      keys::kOnResponseStartedEvent,   keys::kOnBeforeRedirectEvent,
+      keys::kOnCompletedEvent,
+  };
   int extra_info_spec = 0;
-  for (const char* name : kWebRequestExtraHeadersEventNames) {
+  for (const char* name : kEventNames) {
     GetMatchingListeners(browser_context, extension_info_map, name, request,
                          &extra_info_spec);
     if (extra_info_spec & ExtraInfoSpec::EXTRA_HEADERS)
@@ -1754,30 +1751,6 @@
   return false;
 }
 
-bool ExtensionWebRequestEventRouter::HasAnyExtraHeadersListener(
-    void* browser_context) {
-  if (HasAnyExtraHeadersListenerImpl(browser_context))
-    return true;
-
-  void* cross_browser_context = GetCrossBrowserContext(browser_context);
-  if (cross_browser_context)
-    return HasAnyExtraHeadersListenerImpl(cross_browser_context);
-
-  return false;
-}
-
-bool ExtensionWebRequestEventRouter::HasAnyExtraHeadersListenerImpl(
-    void* browser_context) {
-  for (const char* name : kWebRequestExtraHeadersEventNames) {
-    const Listeners& listeners = listeners_[browser_context][name];
-    for (const auto& listener : listeners) {
-      if (listener->extra_info_spec & ExtraInfoSpec::EXTRA_HEADERS)
-        return true;
-    }
-  }
-  return false;
-}
-
 bool ExtensionWebRequestEventRouter::IsPageLoad(
     const WebRequestInfo& request) const {
   return request.type == content::RESOURCE_TYPE_MAIN_FRAME;
diff --git a/extensions/browser/api/web_request/web_request_api.h b/extensions/browser/api/web_request/web_request_api.h
index 1e525d5..26f6563 100644
--- a/extensions/browser/api/web_request/web_request_api.h
+++ b/extensions/browser/api/web_request/web_request_api.h
@@ -480,14 +480,9 @@
 
   // Whether there is a listener matching the request that has
   // ExtraInfoSpec::EXTRA_HEADERS set.
-  bool HasExtraHeadersListenerForRequest(
-      void* browser_context,
-      const extensions::InfoMap* extension_info_map,
-      const WebRequestInfo* request);
-
-  // Whether there are any listeners for this context that have
-  // ExtraInfoSpec::EXTRA_HEADERS set.
-  bool HasAnyExtraHeadersListener(void* browser_context);
+  bool HasExtraHeadersListener(void* browser_context,
+                               const extensions::InfoMap* extension_info_map,
+                               const WebRequestInfo* request);
 
  private:
   friend class WebRequestAPI;
@@ -704,9 +699,6 @@
   // Returns true if |request| was already signaled to some event handlers.
   bool WasSignaled(const WebRequestInfo& request) const;
 
-  // Helper for |HasAnyExtraHeadersListener()|.
-  bool HasAnyExtraHeadersListenerImpl(void* browser_context);
-
   // Get the number of listeners - for testing only.
   size_t GetListenerCountForTesting(void* browser_context,
                                     const std::string& event_name);
diff --git a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
index a8c522b..75ad640 100644
--- a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
+++ b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.cc
@@ -22,22 +22,6 @@
 
 namespace extensions {
 
-struct WebRequestProxyingURLLoaderFactory::InProgressRequest::
-    PendingBeforeSendHeadersData {
-  PendingBeforeSendHeadersData(const net::HttpRequestHeaders& headers_,
-                               OnBeforeSendHeadersCallback callback_)
-      : headers(headers_), callback(std::move(callback_)) {}
-  PendingBeforeSendHeadersData(PendingBeforeSendHeadersData&&) = default;
-
-  ~PendingBeforeSendHeadersData() {
-    if (callback)
-      std::move(callback).Run(net::ERR_ABORTED, base::nullopt);
-  }
-
-  net::HttpRequestHeaders headers;
-  OnBeforeSendHeadersCallback callback;
-};
-
 WebRequestProxyingURLLoaderFactory::InProgressRequest::InProgressRequest(
     WebRequestProxyingURLLoaderFactory* factory,
     uint64_t request_id,
@@ -58,9 +42,6 @@
       proxied_loader_binding_(this, std::move(loader_request)),
       target_client_(std::move(client)),
       proxied_client_binding_(this),
-      has_any_extra_headers_listeners_(
-          ExtensionWebRequestEventRouter::GetInstance()
-              ->HasAnyExtraHeadersListener(factory_->browser_context_)),
       weak_factory_(this) {
   // If there is a client error, clean up the request.
   target_client_.set_connection_error_handler(base::BindOnce(
@@ -88,7 +69,6 @@
 
 void WebRequestProxyingURLLoaderFactory::InProgressRequest::Restart() {
   request_completed_ = false;
-  network_request_started_ = false;
   // Derive a new WebRequestInfo value any time |Restart()| is called, because
   // the details in |request_| may have changed e.g. if we've been redirected.
   info_.emplace(
@@ -98,18 +78,17 @@
       routing_id_, factory_->resource_context_, request_,
       !(options_ & network::mojom::kURLLoadOptionSynchronous));
 
-  current_request_uses_header_client_ =
+  uses_header_client_ =
       factory_->header_client_binding_ && request_.url.SchemeIsHTTPOrHTTPS() &&
       network_service_request_id_ != 0 &&
-      ExtensionWebRequestEventRouter::GetInstance()
-          ->HasExtraHeadersListenerForRequest(
-              factory_->browser_context_, factory_->info_map_, &info_.value());
+      ExtensionWebRequestEventRouter::GetInstance()->HasExtraHeadersListener(
+          factory_->browser_context_, factory_->info_map_, &info_.value());
 
   // If the header client will be used, we start the request immediately, and
   // OnBeforeSendHeaders and OnSendHeaders will be handled there. Otherwise,
   // send these events before the request starts.
   base::RepeatingCallback<void(int)> continuation;
-  if (current_request_uses_header_client_) {
+  if (uses_header_client_) {
     continuation = base::BindRepeating(
         &InProgressRequest::ContinueToStartRequest, weak_factory_.GetWeakPtr());
   } else {
@@ -196,7 +175,7 @@
     const network::ResourceResponseHead& head) {
   current_response_ = head;
   on_receive_response_received_ = true;
-  if (current_request_uses_header_client_) {
+  if (uses_header_client_) {
     ContinueToResponseStarted(net::OK);
   } else {
     HandleResponseOrRedirectHeaders(
@@ -216,7 +195,7 @@
   }
 
   current_response_ = head;
-  if (current_request_uses_header_client_) {
+  if (uses_header_client_) {
     ContinueToBeforeRedirect(redirect_info, net::OK);
   } else {
     HandleResponseOrRedirectHeaders(
@@ -290,19 +269,7 @@
 void WebRequestProxyingURLLoaderFactory::InProgressRequest::OnBeforeSendHeaders(
     const net::HttpRequestHeaders& headers,
     OnBeforeSendHeadersCallback callback) {
-  if (!current_request_uses_header_client_) {
-    std::move(callback).Run(net::OK, base::nullopt);
-    return;
-  }
-
-  if (!network_request_started_) {
-    DCHECK(!pending_before_send_headers_data_);
-    pending_before_send_headers_data_ =
-        std::make_unique<PendingBeforeSendHeadersData>(headers,
-                                                       std::move(callback));
-    return;
-  }
-
+  DCHECK(uses_header_client_);
   request_.headers = headers;
   on_before_send_headers_callback_ = std::move(callback);
   ContinueToBeforeSendHeaders(net::OK);
@@ -311,11 +278,7 @@
 void WebRequestProxyingURLLoaderFactory::InProgressRequest::OnHeadersReceived(
     const std::string& headers,
     OnHeadersReceivedCallback callback) {
-  if (!current_request_uses_header_client_) {
-    std::move(callback).Run(net::OK, base::nullopt, GURL());
-    return;
-  }
-
+  DCHECK(uses_header_client_);
   on_headers_received_callback_ = std::move(callback);
   current_response_.headers =
       base::MakeRefCounted<net::HttpResponseHeaders>(headers);
@@ -381,7 +344,7 @@
     return;
   }
 
-  if (!current_request_uses_header_client_ && !redirect_url_.is_empty()) {
+  if (!uses_header_client_ && !redirect_url_.is_empty()) {
     HandleBeforeRequestRedirect();
     return;
   }
@@ -426,17 +389,12 @@
 
 void WebRequestProxyingURLLoaderFactory::InProgressRequest::
     ContinueToStartRequest(int error_code) {
-  // Move to a local to make sure this gets destroyed at the end of this
-  // function if it isn't used.
-  std::unique_ptr<PendingBeforeSendHeadersData> before_send_headers_data =
-      std::move(pending_before_send_headers_data_);
-
   if (error_code != net::OK) {
     OnRequestError(network::URLLoaderCompletionStatus(error_code));
     return;
   }
 
-  if (current_request_uses_header_client_ && !redirect_url_.is_empty()) {
+  if (uses_header_client_ && !redirect_url_.is_empty()) {
     HandleBeforeRequestRedirect();
     return;
   }
@@ -444,24 +402,18 @@
   if (proxied_client_binding_.is_bound())
     proxied_client_binding_.ResumeIncomingMethodCallProcessing();
 
-  network_request_started_ = true;
   if (!target_loader_.is_bound() && factory_->target_factory_.is_bound()) {
     // No extensions have cancelled us up to this point, so it's now OK to
     // initiate the real network request.
     network::mojom::URLLoaderClientPtr proxied_client;
     proxied_client_binding_.Bind(mojo::MakeRequest(&proxied_client));
     uint32_t options = options_;
-    // Even if this request does not use the header client, future redirects
-    // might, so we need to set the option on the loader.
-    if (has_any_extra_headers_listeners_)
+    if (uses_header_client_)
       options |= network::mojom::kURLLoadOptionUseHeaderClient;
     factory_->target_factory_->CreateLoaderAndStart(
         mojo::MakeRequest(&target_loader_), info_->routing_id,
         network_service_request_id_, options, request_,
         std::move(proxied_client), traffic_annotation_);
-  } else if (before_send_headers_data) {
-    OnBeforeSendHeaders(before_send_headers_data->headers,
-                        std::move(before_send_headers_data->callback));
   }
 
   // From here the lifecycle of this request is driven by subsequent events on
@@ -476,7 +428,7 @@
     return;
   }
 
-  if (current_request_uses_header_client_) {
+  if (uses_header_client_) {
     DCHECK(on_before_send_headers_callback_);
     std::move(on_before_send_headers_callback_)
         .Run(error_code, request_.headers);
@@ -494,7 +446,7 @@
         request_.headers);
   }
 
-  if (!current_request_uses_header_client_)
+  if (!uses_header_client_)
     ContinueToStartRequest(net::OK);
 }
 
@@ -590,7 +542,7 @@
     return;
   }
 
-  DCHECK(!current_request_uses_header_client_ || !override_headers_);
+  DCHECK(!uses_header_client_ || !override_headers_);
   if (override_headers_)
     current_response_.headers = override_headers_;
 
diff --git a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h
index 17cb6f6..1ca71ad 100644
--- a/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h
+++ b/extensions/browser/api/web_request/web_request_proxying_url_loader_factory.h
@@ -153,32 +153,15 @@
 
     bool request_completed_ = false;
 
-    // If |has_any_extra_headers_listeners_| is set to true, the request will be
-    // sent with the network::mojom::kURLLoadOptionUseHeaderClient option, and
-    // we expect events to come through the
-    // network::mojom::TrustedURLLoaderHeaderClient binding on the factory. This
-    // is only set to true if there is a listener that needs to view or modify
-    // headers set in the network process.
-    bool has_any_extra_headers_listeners_ = false;
-    bool current_request_uses_header_client_ = false;
+    // If |uses_header_client_| is set to true, the request will be sent with
+    // the network::mojom::kURLLoadOptionUseHeaderClient option, and we expect
+    // events to come through the network::mojom::TrustedURLLoaderHeaderClient
+    // binding on the factory. This is only set to true if there is a listener
+    // that needs to view or modify headers set in the network process.
+    bool uses_header_client_ = false;
     OnBeforeSendHeadersCallback on_before_send_headers_callback_;
     OnHeadersReceivedCallback on_headers_received_callback_;
 
-    // If extraHeaders listeners are present, we may receive the
-    // OnBeforeSendHeaders event for a redirect before OnBeforeRequest has been
-    // run on that URL. In that case we need to queue up this event until
-    // OnBeforeRequest has been completed and we know the request will not be
-    // canceled/redirected there.
-    struct PendingBeforeSendHeadersData;
-    std::unique_ptr<PendingBeforeSendHeadersData>
-        pending_before_send_headers_data_;
-
-    // Whether the request has gone to the network yet. If an
-    // OnBeforeSendHeaders event reaches us before the request has gone to the
-    // network, it should be saved for later in
-    // |pending_before_send_headers_data_|.
-    bool network_request_started_ = false;
-
     base::WeakPtrFactory<InProgressRequest> weak_factory_;
 
     DISALLOW_COPY_AND_ASSIGN(InProgressRequest);
diff --git a/extensions/browser/extension_message_filter.cc b/extensions/browser/extension_message_filter.cc
index 6d58a49..22f794c 100644
--- a/extensions/browser/extension_message_filter.cc
+++ b/extensions/browser/extension_message_filter.cc
@@ -19,6 +19,7 @@
 #include "extensions/browser/process_manager_factory.h"
 #include "extensions/browser/process_map.h"
 #include "extensions/common/api/messaging/message.h"
+#include "extensions/common/api/messaging/messaging_endpoint.h"
 #include "extensions/common/api/messaging/port_id.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_messages.h"
@@ -390,7 +391,7 @@
   if (browser_context_) {
     MessageService::Get(browser_context_)
         ->OpenChannelToExtension(render_process_id_, routing_id, port_id,
-                                 info.source_id, info.target_id,
+                                 info.source_endpoint, info.target_id,
                                  info.source_url, channel_name);
   }
 }
diff --git a/extensions/common/BUILD.gn b/extensions/common/BUILD.gn
index 9f0adbd..593b8f9c 100644
--- a/extensions/common/BUILD.gn
+++ b/extensions/common/BUILD.gn
@@ -87,6 +87,8 @@
       "api/declarative_net_request/utils.cc",
       "api/declarative_net_request/utils.h",
       "api/messaging/message.h",
+      "api/messaging/messaging_endpoint.cc",
+      "api/messaging/messaging_endpoint.h",
       "api/messaging/port_id.cc",
       "api/messaging/port_id.h",
       "api/printer_provider/usb_printer_manifest_data.cc",
diff --git a/extensions/common/api/messaging/messaging_endpoint.cc b/extensions/common/api/messaging/messaging_endpoint.cc
new file mode 100644
index 0000000..6ae7dcc9
--- /dev/null
+++ b/extensions/common/api/messaging/messaging_endpoint.cc
@@ -0,0 +1,53 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/common/api/messaging/messaging_endpoint.h"
+
+#include <utility>
+
+namespace extensions {
+
+// static
+MessagingEndpoint MessagingEndpoint::ForExtension(ExtensionId extension_id) {
+  MessagingEndpoint messaging_endpoint;
+  messaging_endpoint.type = MessagingEndpoint::Type::kExtension;
+  messaging_endpoint.extension_id = std::move(extension_id);
+  return messaging_endpoint;
+}
+
+// static
+MessagingEndpoint MessagingEndpoint::ForContentScript(
+    ExtensionId extension_id) {
+  MessagingEndpoint messaging_endpoint;
+  messaging_endpoint.type = MessagingEndpoint::Type::kTab;
+  messaging_endpoint.extension_id = std::move(extension_id);
+  return messaging_endpoint;
+}
+
+// static
+MessagingEndpoint MessagingEndpoint::ForWebPage() {
+  MessagingEndpoint messaging_endpoint;
+  messaging_endpoint.type = MessagingEndpoint::Type::kTab;
+  return messaging_endpoint;
+}
+
+// static
+MessagingEndpoint MessagingEndpoint::ForNativeApp() {
+  MessagingEndpoint messaging_endpoint;
+  messaging_endpoint.type = MessagingEndpoint::Type::kNativeApp;
+  return messaging_endpoint;
+}
+
+MessagingEndpoint::MessagingEndpoint() = default;
+
+MessagingEndpoint::MessagingEndpoint(const MessagingEndpoint&) = default;
+
+MessagingEndpoint::MessagingEndpoint(MessagingEndpoint&&) = default;
+
+MessagingEndpoint& MessagingEndpoint::operator=(const MessagingEndpoint&) =
+    default;
+
+MessagingEndpoint::~MessagingEndpoint() = default;
+
+}  // namespace extensions
diff --git a/extensions/common/api/messaging/messaging_endpoint.h b/extensions/common/api/messaging/messaging_endpoint.h
new file mode 100644
index 0000000..1a2bd67
--- /dev/null
+++ b/extensions/common/api/messaging/messaging_endpoint.h
@@ -0,0 +1,51 @@
+// 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 EXTENSIONS_COMMON_API_MESSAGING_MESSAGING_ENDPOINT_H_
+#define EXTENSIONS_COMMON_API_MESSAGING_MESSAGING_ENDPOINT_H_
+
+#include "base/optional.h"
+#include "extensions/common/extension_id.h"
+
+namespace extensions {
+
+struct MessagingEndpoint {
+  // Type of the messaging source or destination - i.e., the type of the
+  // component which talks to a messaging channel.
+  enum class Type {
+    // An extension.
+    kExtension = 0,
+    // A web page or a content script or a hosted app.
+    kTab = 1,
+    // A native application.
+    kNativeApp = 2,
+
+    // This item must be equal to the last actual enum item.
+    kLast = kNativeApp,
+  };
+
+  static MessagingEndpoint ForExtension(ExtensionId extension_id);
+  static MessagingEndpoint ForContentScript(ExtensionId extension_id);
+  static MessagingEndpoint ForWebPage();
+  static MessagingEndpoint ForNativeApp();
+
+  MessagingEndpoint();
+  MessagingEndpoint(const MessagingEndpoint&);
+  MessagingEndpoint(MessagingEndpoint&&);
+
+  MessagingEndpoint& operator=(const MessagingEndpoint&);
+
+  ~MessagingEndpoint();
+
+  Type type = Type::kExtension;
+
+  // Identifier of the extension (or the content script).  It is required for
+  // |type| of kExtension.  For |type| of kTab, it is set if the endpoint is a
+  // content script (otherwise, it's the web page).
+  base::Optional<ExtensionId> extension_id;
+};
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_COMMON_API_MESSAGING_MESSAGING_ENDPOINT_H_
diff --git a/extensions/common/extension_messages.h b/extensions/common/extension_messages.h
index 3efca1e..96649cc8 100644
--- a/extensions/common/extension_messages.h
+++ b/extensions/common/extension_messages.h
@@ -18,6 +18,7 @@
 #include "content/public/common/common_param_traits.h"
 #include "content/public/common/socket_permission_request.h"
 #include "extensions/common/api/messaging/message.h"
+#include "extensions/common/api/messaging/messaging_endpoint.h"
 #include "extensions/common/api/messaging/port_id.h"
 #include "extensions/common/common_param_traits.h"
 #include "extensions/common/constants.h"
@@ -51,6 +52,9 @@
 IPC_ENUM_TRAITS_MAX_VALUE(extensions::UserScript::RunLocation,
                           extensions::UserScript::RUN_LOCATION_LAST - 1)
 
+IPC_ENUM_TRAITS_MAX_VALUE(extensions::MessagingEndpoint::Type,
+                          extensions::MessagingEndpoint::Type::kLast)
+
 IPC_ENUM_TRAITS_MAX_VALUE(HostID::HostType, HostID::HOST_TYPE_LAST)
 
 // Parameters structure for ExtensionHostMsg_AddAPIActionToActivityLog and
@@ -211,15 +215,19 @@
   IPC_STRUCT_MEMBER(int, frame_id)
 IPC_STRUCT_END()
 
+IPC_STRUCT_TRAITS_BEGIN(extensions::MessagingEndpoint)
+  IPC_STRUCT_TRAITS_MEMBER(type)
+  IPC_STRUCT_TRAITS_MEMBER(extension_id)
+IPC_STRUCT_TRAITS_END()
+
 // Struct containing the data for external connections to extensions. Used to
 // handle the IPCs initiated by both connect() and onConnect().
 IPC_STRUCT_BEGIN(ExtensionMsg_ExternalConnectionInfo)
   // The ID of the extension that is the target of the request.
   IPC_STRUCT_MEMBER(std::string, target_id)
 
-  // The ID of the extension that initiated the request. May be empty if it
-  // wasn't initiated by an extension.
-  IPC_STRUCT_MEMBER(std::string, source_id)
+  // Specifies the type and the ID of the endpoint that initiated the request.
+  IPC_STRUCT_MEMBER(extensions::MessagingEndpoint, source_endpoint)
 
   // The URL of the frame that initiated the request.
   IPC_STRUCT_MEMBER(GURL, source_url)
diff --git a/extensions/renderer/ipc_message_sender.cc b/extensions/renderer/ipc_message_sender.cc
index 897fc721..a641db3 100644
--- a/extensions/renderer/ipc_message_sender.cc
+++ b/extensions/renderer/ipc_message_sender.cc
@@ -11,6 +11,7 @@
 #include "content/public/renderer/render_frame.h"
 #include "content/public/renderer/render_thread.h"
 #include "content/public/renderer/worker_thread.h"
+#include "extensions/common/api/messaging/messaging_endpoint.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension_messages.h"
 #include "extensions/common/features/feature.h"
@@ -158,8 +159,14 @@
     switch (target.type) {
       case MessageTarget::EXTENSION: {
         ExtensionMsg_ExternalConnectionInfo info;
-        if (extension && !extension->is_hosted_app())
-          info.source_id = extension->id();
+        if (extension && !extension->is_hosted_app()) {
+          info.source_endpoint =
+              script_context->context_type() == Feature::CONTENT_SCRIPT_CONTEXT
+                  ? MessagingEndpoint::ForContentScript(extension->id())
+                  : MessagingEndpoint::ForExtension(extension->id());
+        } else {
+          info.source_endpoint = MessagingEndpoint::ForWebPage();
+        }
         info.target_id = *target.extension_id;
         info.source_url = script_context->url();
 
diff --git a/extensions/renderer/js_renderer_messaging_service.cc b/extensions/renderer/js_renderer_messaging_service.cc
index 2c5ff504..8f75d16 100644
--- a/extensions/renderer/js_renderer_messaging_service.cc
+++ b/extensions/renderer/js_renderer_messaging_service.cc
@@ -82,11 +82,15 @@
   }
 
   v8::Local<v8::String> v8_channel_name;
-  v8::Local<v8::String> v8_source_id;
+  v8::Local<v8::String> v8_source_extension_id;
   v8::Local<v8::String> v8_target_extension_id;
   v8::Local<v8::String> v8_source_url_spec;
   if (!ToV8String(isolate, channel_name.c_str(), &v8_channel_name) ||
-      !ToV8String(isolate, info.source_id.c_str(), &v8_source_id) ||
+      !ToV8String(isolate,
+                  info.source_endpoint.extension_id
+                      ? *info.source_endpoint.extension_id
+                      : ExtensionId(),
+                  &v8_source_extension_id) ||
       !ToV8String(isolate, target_extension_id.c_str(),
                   &v8_target_extension_id) ||
       !ToV8String(isolate, source_url_spec.c_str(), &v8_source_url_spec)) {
@@ -108,7 +112,7 @@
       // guestRenderFrameRoutingId
       guest_render_frame_routing_id,
       // sourceExtensionId
-      v8_source_id,
+      v8_source_extension_id,
       // targetExtensionId
       v8_target_extension_id,
       // sourceUrl
diff --git a/extensions/renderer/messaging_bindings.cc b/extensions/renderer/messaging_bindings.cc
index d31c5ffa..1a38ac07 100644
--- a/extensions/renderer/messaging_bindings.cc
+++ b/extensions/renderer/messaging_bindings.cc
@@ -18,6 +18,7 @@
 #include "base/values.h"
 #include "content/public/renderer/render_frame.h"
 #include "extensions/common/api/messaging/message.h"
+#include "extensions/common/api/messaging/messaging_endpoint.h"
 #include "extensions/common/api/messaging/port_id.h"
 #include "extensions/common/extension_messages.h"
 #include "extensions/renderer/extension_frame_helper.h"
@@ -188,14 +189,22 @@
   ports_[js_id] = std::make_unique<ExtensionPort>(context(), port_id, js_id);
 
   ExtensionMsg_ExternalConnectionInfo info;
+
   // For messaging APIs, hosted apps should be considered a web page so hide
   // its extension ID.
   const Extension* extension = context()->extension();
-  if (extension && !extension->is_hosted_app())
-    info.source_id = extension->id();
+  if (extension && !extension->is_hosted_app()) {
+    info.source_endpoint =
+        context()->context_type() == Feature::CONTENT_SCRIPT_CONTEXT
+            ? MessagingEndpoint::ForContentScript(extension->id())
+            : MessagingEndpoint::ForExtension(extension->id());
+  } else {
+    info.source_endpoint = MessagingEndpoint::ForWebPage();
+  }
 
   v8::Isolate* isolate = args.GetIsolate();
   info.target_id = *v8::String::Utf8Value(isolate, args[0]);
+
   info.source_url = context()->url();
   std::string channel_name = *v8::String::Utf8Value(isolate, args[1]);
 
diff --git a/extensions/renderer/native_renderer_messaging_service.cc b/extensions/renderer/native_renderer_messaging_service.cc
index 44471d5..42dee4d5 100644
--- a/extensions/renderer/native_renderer_messaging_service.cc
+++ b/extensions/renderer/native_renderer_messaging_service.cc
@@ -12,6 +12,7 @@
 #include "content/public/renderer/render_frame.h"
 #include "content/public/renderer/v8_value_converter.h"
 #include "extensions/common/api/messaging/message.h"
+#include "extensions/common/api/messaging/messaging_endpoint.h"
 #include "extensions/common/api/messaging/port_id.h"
 #include "extensions/common/extension_messages.h"
 #include "extensions/common/manifest_handlers/externally_connectable.h"
@@ -185,14 +186,16 @@
     const ExtensionMsg_TabConnectionInfo* source,
     const ExtensionMsg_ExternalConnectionInfo& info,
     const std::string& event_name) {
+  DCHECK_NE(info.source_endpoint.type, MessagingEndpoint::Type::kNativeApp);
+
   v8::Isolate* isolate = script_context->isolate();
   v8::HandleScope handle_scope(isolate);
   v8::Local<v8::Context> v8_context = script_context->v8_context();
   v8::Context::Scope context_scope(v8_context);
 
   gin::DataObjectBuilder sender_builder(isolate);
-  if (!info.source_id.empty())
-    sender_builder.Set("id", info.source_id);
+  if (info.source_endpoint.extension_id)
+    sender_builder.Set("id", *info.source_endpoint.extension_id);
   if (!info.source_url.is_empty())
     sender_builder.Set("url", info.source_url.spec());
   if (source->frame_id >= 0)
@@ -242,8 +245,8 @@
         std::make_unique<base::Value>(base::Value::Type::LIST);
     auto& list = activity_logging_args->GetList();
     list.reserve(2u);
-    if (!info.source_id.empty())
-      list.emplace_back(info.source_id);
+    if (info.source_endpoint.extension_id)
+      list.emplace_back(*info.source_endpoint.extension_id);
     else
       list.emplace_back();
 
diff --git a/extensions/renderer/native_renderer_messaging_service_unittest.cc b/extensions/renderer/native_renderer_messaging_service_unittest.cc
index d3e64ca..9129486 100644
--- a/extensions/renderer/native_renderer_messaging_service_unittest.cc
+++ b/extensions/renderer/native_renderer_messaging_service_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/stl_util.h"
 #include "components/crx_file/id_util.h"
 #include "content/public/common/child_process_host.h"
+#include "extensions/common/api/messaging/messaging_endpoint.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_builder.h"
 #include "extensions/common/extension_messages.h"
@@ -112,7 +113,8 @@
       DictionaryBuilder().Set("tabId", tab_id).Build().get());
   ExtensionMsg_ExternalConnectionInfo external_connection_info;
   external_connection_info.target_id = extension()->id();
-  external_connection_info.source_id = extension()->id();
+  external_connection_info.source_endpoint =
+      MessagingEndpoint::ForExtension(extension()->id());
   external_connection_info.source_url = source_url;
   external_connection_info.guest_process_id =
       content::ChildProcessHost::kInvalidUniqueID;
@@ -406,7 +408,8 @@
       DictionaryBuilder().Set("tabId", tab_id).Build().get());
   ExtensionMsg_ExternalConnectionInfo external_connection_info;
   external_connection_info.target_id = extension()->id();
-  external_connection_info.source_id = extension()->id();
+  external_connection_info.source_endpoint =
+      MessagingEndpoint::ForExtension(extension()->id());
   external_connection_info.source_url = source_url;
   external_connection_info.guest_process_id =
       content::ChildProcessHost::kInvalidUniqueID;
@@ -474,7 +477,8 @@
 
     ExtensionMsg_ExternalConnectionInfo external_connection_info;
     external_connection_info.target_id = extension()->id();
-    external_connection_info.source_id = source_id;
+    external_connection_info.source_endpoint =
+        MessagingEndpoint::ForExtension(source_id);
     external_connection_info.source_url = source_url;
     external_connection_info.guest_process_id =
         content::ChildProcessHost::kInvalidUniqueID;
diff --git a/extensions/renderer/renderer_messaging_service.cc b/extensions/renderer/renderer_messaging_service.cc
index c68a13b..c0b5137 100644
--- a/extensions/renderer/renderer_messaging_service.cc
+++ b/extensions/renderer/renderer_messaging_service.cc
@@ -16,6 +16,7 @@
 #include "content/public/renderer/render_thread.h"
 #include "content/public/renderer/v8_value_converter.h"
 #include "extensions/common/api/messaging/message.h"
+#include "extensions/common/api/messaging/messaging_endpoint.h"
 #include "extensions/common/api/messaging/port_id.h"
 #include "extensions/common/extension_messages.h"
 #include "extensions/renderer/extension_bindings_system.h"
@@ -140,7 +141,7 @@
 
   // First, determine the event we'll use to connect.
   std::string target_extension_id = script_context->GetExtensionID();
-  bool is_external = info.source_id != target_extension_id;
+  bool is_external = info.source_endpoint.extension_id != target_extension_id;
   std::string event_name;
   if (channel_name == messaging_util::kSendRequestChannel) {
     event_name = is_external ? messaging_util::kOnRequestExternalEvent
diff --git a/extensions/renderer/resources/messaging.js b/extensions/renderer/resources/messaging.js
index f32ddadd..acdd8a4d 100644
--- a/extensions/renderer/resources/messaging.js
+++ b/extensions/renderer/resources/messaging.js
@@ -171,7 +171,7 @@
         'Cannot send a response more than once per chrome.' + eventName +
         ' listener per document';
     }
-    errorMsg += ' (message was sent by extension' + sourceExtensionId;
+    errorMsg += ' (message was sent by extension ' + sourceExtensionId;
     if (sourceExtensionId && sourceExtensionId !== targetExtensionId)
       errorMsg += ' for extension ' + targetExtensionId;
     if (sourceUrl)
diff --git a/fuchsia/http/http_service_unittest.cc b/fuchsia/http/http_service_unittest.cc
index 56c24994..fcea6be4 100644
--- a/fuchsia/http/http_service_unittest.cc
+++ b/fuchsia/http/http_service_unittest.cc
@@ -5,7 +5,6 @@
 #include <fuchsia/net/oldhttp/cpp/fidl.h>
 #include <lib/fidl/cpp/binding.h>
 
-#include "base/fuchsia/component_context.h"
 #include "base/fuchsia/scoped_service_binding.h"
 #include "base/fuchsia/service_directory.h"
 #include "base/run_loop.h"
diff --git a/fuchsia/runners/cast/cast_runner_integration_test.cc b/fuchsia/runners/cast/cast_runner_integration_test.cc
index bf57eb58..db21715 100644
--- a/fuchsia/runners/cast/cast_runner_integration_test.cc
+++ b/fuchsia/runners/cast/cast_runner_integration_test.cc
@@ -42,17 +42,14 @@
   CastRunnerIntegrationTest()
       : run_timeout_(TestTimeouts::action_timeout()),
         cast_channel_binding_(this) {
-    // Create a new test ServiceDirectory, and a test ComponentContext
-    // connected to it, for the test to use to drive the CastRunner.
-    zx::channel service_directory_request, service_directory_client;
-    zx_status_t status = zx::channel::create(0, &service_directory_client,
-                                             &service_directory_request);
-    ZX_CHECK(status == ZX_OK, status) << "zx_channel_create";
-
-    test_service_directory_ = std::make_unique<base::fuchsia::ServiceDirectory>(
-        std::move(service_directory_request));
-    test_component_context_ = std::make_unique<base::fuchsia::ComponentContext>(
-        std::move(service_directory_client));
+    // Create a new test ServiceDirectory, and ServiceDirectoryClient connected
+    // to it, for tests to use to drive the CastRunner.
+    fidl::InterfaceHandle<fuchsia::io::Directory> directory;
+    test_services_ = std::make_unique<base::fuchsia::ServiceDirectory>(
+        directory.NewRequest());
+    test_services_client_ =
+        std::make_unique<base::fuchsia::ServiceDirectoryClient>(
+            std::move(directory));
 
     // Create the AppConfigManager.
     app_config_binding_ = std::make_unique<
@@ -61,16 +58,15 @@
     chromium::cast::ApplicationConfigManagerPtr app_config_manager_interface;
     app_config_binding_->Bind(app_config_manager_interface.NewRequest());
 
-    // Create the CastRunner, published into |test_service_directory_|.
+    // Create the CastRunner, published into |test_services_|.
     cast_runner_ = std::make_unique<CastRunner>(
-        test_service_directory_.get(),
-        WebContentRunner::CreateDefaultWebContext(),
+        test_services_.get(), WebContentRunner::CreateDefaultWebContext(),
         std::move(app_config_manager_interface),
         cast_runner_run_loop_.QuitClosure());
 
     // Connect to the CastRunner's fuchsia.sys.Runner interface.
     cast_runner_ptr_ =
-        test_component_context_->ConnectToService<fuchsia::sys::Runner>();
+        test_services_client_->ConnectToService<fuchsia::sys::Runner>();
     cast_runner_ptr_.set_error_handler([this](zx_status_t status) {
       ZX_LOG(ERROR, status) << "CastRunner closed channel.";
       ADD_FAILURE();
@@ -131,8 +127,8 @@
   fidl::Binding<chromium::cast::CastChannel> cast_channel_binding_;
 
   // ServiceDirectory into which the CastRunner will publish itself.
-  std::unique_ptr<base::fuchsia::ServiceDirectory> test_service_directory_;
-  std::unique_ptr<base::fuchsia::ComponentContext> test_component_context_;
+  std::unique_ptr<base::fuchsia::ServiceDirectory> test_services_;
+  std::unique_ptr<base::fuchsia::ComponentContext> test_services_client_;
 
   std::unique_ptr<CastRunner> cast_runner_;
   fuchsia::sys::RunnerPtr cast_runner_ptr_;
@@ -151,7 +147,7 @@
 
   // Launch the test-app component.
   fuchsia::sys::ComponentControllerPtr component_controller_ptr;
-  base::fuchsia::ComponentContext component_services(StartCastComponent(
+  base::fuchsia::ServiceDirectoryClient services_client(StartCastComponent(
       base::StringPrintf("cast:%s", kBlankAppId), &cast_runner_ptr_,
       component_controller_ptr.NewRequest(), &cast_channel_binding_));
   component_controller_ptr.set_error_handler(&ComponentErrorHandler);
@@ -193,7 +189,7 @@
 TEST_F(CastRunnerIntegrationTest, IncorrectCastAppId) {
   // Launch the test-app component.
   fuchsia::sys::ComponentControllerPtr component_controller_ptr;
-  base::fuchsia::ComponentContext component_services(StartCastComponent(
+  base::fuchsia::ServiceDirectoryClient services_client(StartCastComponent(
       "cast:99999999", &cast_runner_ptr_, component_controller_ptr.NewRequest(),
       &cast_channel_binding_));
   component_controller_ptr.set_error_handler(&ComponentErrorHandler);
@@ -214,7 +210,7 @@
 
   // Launch the test-app component.
   fuchsia::sys::ComponentControllerPtr component_controller_ptr;
-  base::fuchsia::ComponentContext component_services(StartCastComponent(
+  base::fuchsia::ServiceDirectoryClient services_client(StartCastComponent(
       base::StringPrintf("cast:%s", kCastChannelAppId), &cast_runner_ptr_,
       component_controller_ptr.NewRequest(), &cast_channel_binding_));
   component_controller_ptr.set_error_handler(&ComponentErrorHandler);
@@ -274,7 +270,7 @@
 
   // Launch the test-app component.
   fuchsia::sys::ComponentControllerPtr component_controller_ptr;
-  base::fuchsia::ComponentContext component_services(StartCastComponent(
+  base::fuchsia::ServiceDirectoryClient services_client(StartCastComponent(
       base::StringPrintf("cast:%s", kCastChannelAppId), &cast_runner_ptr_,
       component_controller_ptr.NewRequest(), &cast_channel_binding_));
 
@@ -297,7 +293,7 @@
 
   // Launch the test-app component.
   fuchsia::sys::ComponentControllerPtr component_controller_ptr;
-  base::fuchsia::ComponentContext component_services(StartCastComponent(
+  base::fuchsia::ServiceDirectoryClient services_client(StartCastComponent(
       base::StringPrintf("cast:%s", kCastChannelAppId), &cast_runner_ptr_,
       component_controller_ptr.NewRequest(), &cast_channel_binding_));
 
diff --git a/fuchsia/runners/cast/main.cc b/fuchsia/runners/cast/main.cc
index e04adb878..1cefdf6 100644
--- a/fuchsia/runners/cast/main.cc
+++ b/fuchsia/runners/cast/main.cc
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/fuchsia/component_context.h"
 #include "base/fuchsia/service_directory.h"
+#include "base/fuchsia/service_directory_client.h"
 #include "base/message_loop/message_loop.h"
 #include "base/run_loop.h"
 #include "fuchsia/runners/cast/cast_runner.h"
@@ -15,7 +15,7 @@
   CastRunner runner(
       base::fuchsia::ServiceDirectory::GetDefault(),
       WebContentRunner::CreateDefaultWebContext(),
-      base::fuchsia::ComponentContext::GetDefault()
+      base::fuchsia::ServiceDirectoryClient::ForCurrentProcess()
           ->ConnectToService<chromium::cast::ApplicationConfigManager>(),
       run_loop.QuitClosure());
 
diff --git a/fuchsia/runners/cast/test_common.cc b/fuchsia/runners/cast/test_common.cc
index 3b8b0c0454..0e166d7 100644
--- a/fuchsia/runners/cast/test_common.cc
+++ b/fuchsia/runners/cast/test_common.cc
@@ -13,7 +13,7 @@
 #include "base/fuchsia/service_directory.h"
 #include "base/run_loop.h"
 
-zx::channel StartCastComponent(
+fidl::InterfaceHandle<fuchsia::io::Directory> StartCastComponent(
     const base::StringPiece& cast_url,
     fuchsia::sys::RunnerPtr* sys_runner,
     fidl::InterfaceRequest<fuchsia::sys::ComponentController>
@@ -22,40 +22,30 @@
   // Construct, bind, and populate a ServiceDirectory for publishing
   // the CastChannel service to the CastComponent.
   auto service_list = std::make_unique<fuchsia::sys::ServiceList>();
-  zx::channel cast_channel_dir_request, cast_channel_dir_client;
-  zx_status_t status = zx::channel::create(0, &cast_channel_dir_request,
-                                           &cast_channel_dir_client);
-  ZX_CHECK(status == ZX_OK, status) << "zx_channel_create";
+  fidl::InterfaceHandle<fuchsia::io::Directory> directory;
   base::fuchsia::ServiceDirectory cast_channel_directory(
-      std::move(cast_channel_dir_request));
+      directory.NewRequest());
   base::RunLoop service_connect_runloop;
-  cast_channel_directory.AddService(
-      chromium::cast::CastChannel::Name_,
-      base::BindRepeating(
-          [](base::RepeatingClosure on_connect_cb,
-             fidl::Binding<chromium::cast::CastChannel>* cast_channel_binding_,
-             zx::channel channel) {
-            cast_channel_binding_->Bind(std::move(channel));
-            on_connect_cb.Run();
-          },
-          base::Passed(service_connect_runloop.QuitClosure()),
-          base::Unretained(cast_channel_binding)));
+  cast_channel_directory.AddService(base::BindRepeating(
+      [](base::RepeatingClosure on_connect_cb,
+         fidl::Binding<chromium::cast::CastChannel>* cast_channel_binding_,
+         fidl::InterfaceRequest<chromium::cast::CastChannel> request) {
+        cast_channel_binding_->Bind(std::move(request));
+        on_connect_cb.Run();
+      },
+      base::Passed(service_connect_runloop.QuitClosure()),
+      base::Unretained(cast_channel_binding)));
   service_list->names.push_back(chromium::cast::CastChannel::Name_);
-  service_list->host_directory = std::move(cast_channel_dir_client);
+  service_list->host_directory = directory.TakeChannel();
 
-  fuchsia::sys::LaunchInfo launch_info;
-  launch_info.url = cast_url.as_string();
-  launch_info.additional_services = std::move(service_list);
-
-  // Create a channel to pass to the Runner, through which to expose the new
-  // component's ServiceDirectory.
-  zx::channel service_directory_client;
-  status = zx::channel::create(0, &service_directory_client,
-                               &launch_info.directory_request);
-  ZX_CHECK(status == ZX_OK, status) << "zx_channel_create";
-
+  // Configure the Runner, including a service directory channel to publish
+  // services to.
   fuchsia::sys::StartupInfo startup_info;
-  startup_info.launch_info = std::move(launch_info);
+  startup_info.launch_info.url = cast_url.as_string();
+  startup_info.launch_info.additional_services = std::move(service_list);
+  fidl::InterfaceHandle<fuchsia::io::Directory> component_services;
+  startup_info.launch_info.directory_request =
+      component_services.NewRequest().TakeChannel();
 
   // The FlatNamespace vectors must be non-null, but may be empty.
   startup_info.flat_namespace.paths.resize(0);
@@ -73,5 +63,5 @@
   // Prepare the service directory for clean teardown.
   cast_channel_directory.RemoveAllServices();
 
-  return service_directory_client;
+  return component_services;
 }
diff --git a/fuchsia/runners/cast/test_common.h b/fuchsia/runners/cast/test_common.h
index 98dbac32..ec1c20fd 100644
--- a/fuchsia/runners/cast/test_common.h
+++ b/fuchsia/runners/cast/test_common.h
@@ -11,12 +11,18 @@
 #include "base/strings/string_piece.h"
 #include "fuchsia/fidl/chromium/cast/cpp/fidl.h"
 
+namespace fuchsia {
+namespace io {
+class Directory;
+}
+}  // namespace fuchsia
+
 // Starts a Cast component from the runner |sys_runner| with the URL |cast_url|
 // and returns the outgoing service directory client channel.
 // The Cast component will connect to the CastChannel FIDL service bound at
 // |cast_channel_binding|.
 // Blocks until |cast_channel_binding| is bound.
-zx::channel StartCastComponent(
+fidl::InterfaceHandle<fuchsia::io::Directory> StartCastComponent(
     const base::StringPiece& cast_url,
     fuchsia::sys::RunnerPtr* sys_runner,
     fidl::InterfaceRequest<fuchsia::sys::ComponentController>
diff --git a/fuchsia/runners/common/web_component.cc b/fuchsia/runners/common/web_component.cc
index c86a450..b7cd32c 100644
--- a/fuchsia/runners/common/web_component.cc
+++ b/fuchsia/runners/common/web_component.cc
@@ -47,8 +47,9 @@
   if (startup_info.launch_info.additional_services &&
       startup_info.launch_info.additional_services->host_directory) {
     additional_services_ =
-        std::make_unique<base::fuchsia::ComponentContext>(std::move(
-            startup_info.launch_info.additional_services->host_directory));
+        std::make_unique<base::fuchsia::ServiceDirectoryClient>(
+            fidl::InterfaceHandle<fuchsia::io::Directory>(std::move(
+                startup_info.launch_info.additional_services->host_directory)));
     additional_service_names_ =
         std::move(startup_info.launch_info.additional_services->names);
   }
@@ -75,7 +76,8 @@
   // message-loop, to ensure that it is available before the ServiceDirectory
   // starts processing requests.
   service_directory_ = std::make_unique<base::fuchsia::ServiceDirectory>(
-      std::move(startup_info.launch_info.directory_request));
+      fidl::InterfaceRequest<fuchsia::io::Directory>(
+          std::move(startup_info.launch_info.directory_request)));
   view_provider_binding_ = std::make_unique<
       base::fuchsia::ScopedServiceBinding<fuchsia::ui::app::ViewProvider>>(
       service_directory_.get(), this);
diff --git a/fuchsia/runners/common/web_component.h b/fuchsia/runners/common/web_component.h
index 6d493835..09874bd 100644
--- a/fuchsia/runners/common/web_component.h
+++ b/fuchsia/runners/common/web_component.h
@@ -15,9 +15,9 @@
 #include <utility>
 #include <vector>
 
-#include "base/fuchsia/component_context.h"
 #include "base/fuchsia/scoped_service_binding.h"
 #include "base/fuchsia/service_directory.h"
+#include "base/fuchsia/service_directory_client.h"
 #include "base/logging.h"
 #include "fuchsia/fidl/chromium/web/cpp/fidl.h"
 #include "url/gurl.h"
@@ -73,14 +73,14 @@
   virtual void DestroyComponent(int termination_exit_code,
                                 fuchsia::sys::TerminationReason reason);
 
-  // Gets a directory of incoming services provided to the component, or returns
+  // Returns the directory of incoming services provided to the component, or
   // nullptr if none was provided.
-  base::fuchsia::ComponentContext* additional_services() const {
+  base::fuchsia::ServiceDirectoryClient* additional_services() const {
     return additional_services_.get();
   }
 
-  // Gets the names of services available in additional_services().
-  const std::vector<std::string> additional_service_names() {
+  // Returns the names of services available in additional_services().
+  const std::vector<std::string>& additional_service_names() const {
     return additional_service_names_;
   }
 
@@ -96,7 +96,7 @@
   fidl::Binding<fuchsia::sys::ComponentController> controller_binding_;
 
   // Incoming services provided at component creation.
-  std::unique_ptr<base::fuchsia::ComponentContext> additional_services_;
+  std::unique_ptr<base::fuchsia::ServiceDirectoryClient> additional_services_;
 
   // The names of services provided at component creation.
   std::vector<std::string> additional_service_names_;
diff --git a/fuchsia/runners/common/web_content_runner.cc b/fuchsia/runners/common/web_content_runner.cc
index 3eb9995..281bb07 100644
--- a/fuchsia/runners/common/web_content_runner.cc
+++ b/fuchsia/runners/common/web_content_runner.cc
@@ -10,11 +10,11 @@
 
 #include "base/bind.h"
 #include "base/files/file.h"
-#include "base/fuchsia/component_context.h"
 #include "base/fuchsia/file_utils.h"
 #include "base/fuchsia/fuchsia_logging.h"
 #include "base/fuchsia/scoped_service_binding.h"
 #include "base/fuchsia/service_directory.h"
+#include "base/fuchsia/service_directory_client.h"
 #include "base/logging.h"
 #include "fuchsia/runners/common/web_component.h"
 #include "url/gurl.h"
@@ -22,7 +22,7 @@
 // static
 chromium::web::ContextPtr WebContentRunner::CreateDefaultWebContext() {
   auto web_context_provider =
-      base::fuchsia::ComponentContext::GetDefault()
+      base::fuchsia::ServiceDirectoryClient::ForCurrentProcess()
           ->ConnectToService<chromium::web::ContextProvider>();
 
   chromium::web::CreateContextParams create_params;
diff --git a/fuchsia/runners/web/web_runner_smoke_test.cc b/fuchsia/runners/web/web_runner_smoke_test.cc
index de5f04b..e014b33 100644
--- a/fuchsia/runners/web/web_runner_smoke_test.cc
+++ b/fuchsia/runners/web/web_runner_smoke_test.cc
@@ -5,7 +5,7 @@
 #include <fuchsia/sys/cpp/fidl.h>
 
 #include "base/bind.h"
-#include "base/fuchsia/component_context.h"
+#include "base/fuchsia/service_directory_client.h"
 #include "base/test/test_timeouts.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
@@ -64,7 +64,7 @@
   fuchsia::sys::LaunchInfo launch_info;
   launch_info.url = test_server_.GetURL("/test.html").spec();
 
-  auto launcher = base::fuchsia::ComponentContext::GetDefault()
+  auto launcher = base::fuchsia::ServiceDirectoryClient::ForCurrentProcess()
                       ->ConnectToServiceSync<fuchsia::sys::Launcher>();
 
   fuchsia::sys::ComponentControllerSyncPtr controller;
diff --git a/gpu/config/gpu_preferences.h b/gpu/config/gpu_preferences.h
index 032d86a..b5d39972 100644
--- a/gpu/config/gpu_preferences.h
+++ b/gpu/config/gpu_preferences.h
@@ -56,12 +56,6 @@
   // ===================================
   // Settings from //content/public/common/content_switches.h
 
-  // Runs the renderer and plugins in the same process as the browser.
-  bool single_process = false;
-
-  // Run the GPU process as a thread in the browser process.
-  bool in_process_gpu = false;
-
   // Disables hardware acceleration of video decode, where available.
   bool disable_accelerated_video_decode = false;
 
diff --git a/gpu/config/gpu_preferences_unittest.cc b/gpu/config/gpu_preferences_unittest.cc
index d31ea56..14708613b 100644
--- a/gpu/config/gpu_preferences_unittest.cc
+++ b/gpu/config/gpu_preferences_unittest.cc
@@ -14,8 +14,6 @@
 namespace {
 
 void CheckGpuPreferencesEqual(GpuPreferences left, GpuPreferences right) {
-  EXPECT_EQ(left.single_process, right.single_process);
-  EXPECT_EQ(left.in_process_gpu, right.in_process_gpu);
   EXPECT_EQ(left.disable_accelerated_video_decode,
             right.disable_accelerated_video_decode);
   EXPECT_EQ(left.disable_accelerated_video_encode,
@@ -110,8 +108,6 @@
   prefs_mojom.name = value;                        \
   EXPECT_EQ(input_prefs.name, prefs_mojom.name);
 
-    GPU_PREFERENCES_FIELD(single_process, true)
-    GPU_PREFERENCES_FIELD(in_process_gpu, true)
     GPU_PREFERENCES_FIELD(disable_accelerated_video_decode, true)
     GPU_PREFERENCES_FIELD(disable_accelerated_video_encode, true)
     GPU_PREFERENCES_FIELD(gpu_startup_dialog, true)
diff --git a/gpu/ipc/common/gpu_preferences.mojom b/gpu/ipc/common/gpu_preferences.mojom
index 37bdcc4..92b32cfb 100644
--- a/gpu/ipc/common/gpu_preferences.mojom
+++ b/gpu/ipc/common/gpu_preferences.mojom
@@ -17,8 +17,6 @@
 
 // gpu::GpuPreferences
 struct GpuPreferences {
-  bool single_process;
-  bool in_process_gpu;
   bool disable_accelerated_video_decode;
   bool disable_accelerated_video_encode;
   bool gpu_startup_dialog;
diff --git a/gpu/ipc/common/gpu_preferences_struct_traits.h b/gpu/ipc/common/gpu_preferences_struct_traits.h
index 7bea28c..2487731f 100644
--- a/gpu/ipc/common/gpu_preferences_struct_traits.h
+++ b/gpu/ipc/common/gpu_preferences_struct_traits.h
@@ -56,8 +56,6 @@
 struct StructTraits<gpu::mojom::GpuPreferencesDataView, gpu::GpuPreferences> {
   static bool Read(gpu::mojom::GpuPreferencesDataView prefs,
                    gpu::GpuPreferences* out) {
-    out->single_process = prefs.single_process();
-    out->in_process_gpu = prefs.in_process_gpu();
     out->disable_accelerated_video_decode =
         prefs.disable_accelerated_video_decode();
     out->disable_accelerated_video_encode =
@@ -132,12 +130,6 @@
     return true;
   }
 
-  static bool single_process(const gpu::GpuPreferences& prefs) {
-    return prefs.single_process;
-  }
-  static bool in_process_gpu(const gpu::GpuPreferences& prefs) {
-    return prefs.in_process_gpu;
-  }
   static bool disable_accelerated_video_decode(
       const gpu::GpuPreferences& prefs) {
     return prefs.disable_accelerated_video_decode;
diff --git a/gpu/ipc/common/struct_traits_unittest.cc b/gpu/ipc/common/struct_traits_unittest.cc
index 95e18dc3..2c51f08d 100644
--- a/gpu/ipc/common/struct_traits_unittest.cc
+++ b/gpu/ipc/common/struct_traits_unittest.cc
@@ -418,8 +418,8 @@
 
 TEST_F(StructTraitsTest, GpuPreferences) {
   GpuPreferences prefs;
-  prefs.single_process = true;
-  prefs.in_process_gpu = true;
+  prefs.gpu_startup_dialog = true;
+  prefs.disable_gpu_watchdog = true;
 #if defined(OS_WIN)
   const GpuPreferences::VpxDecodeVendors vendor =
       GpuPreferences::VPX_VENDOR_AMD;
@@ -430,8 +430,8 @@
   mojom::TraitsTestServicePtr proxy = GetTraitsTestProxy();
   GpuPreferences echo;
   proxy->EchoGpuPreferences(prefs, &echo);
-  EXPECT_TRUE(echo.single_process);
-  EXPECT_TRUE(echo.in_process_gpu);
+  EXPECT_TRUE(echo.gpu_startup_dialog);
+  EXPECT_TRUE(echo.disable_gpu_watchdog);
   EXPECT_TRUE(echo.enable_gpu_driver_debug_logging);
 #if defined(OS_WIN)
   EXPECT_EQ(vendor, echo.enable_accelerated_vpx_decode);
diff --git a/ios/chrome/browser/ntp/BUILD.gn b/ios/chrome/browser/ntp/BUILD.gn
index 103ce3d6..769e0da 100644
--- a/ios/chrome/browser/ntp/BUILD.gn
+++ b/ios/chrome/browser/ntp/BUILD.gn
@@ -10,6 +10,7 @@
   ]
   configs += [ "//build/config/compiler:enable_arc" ]
   deps = [
+    ":features",
     "//base:base",
     "//components/strings:components_strings_grit",
     "//ios/chrome/browser",
@@ -19,6 +20,16 @@
   ]
 }
 
+source_set("features") {
+  sources = [
+    "features.cc",
+    "features.h",
+  ]
+  deps = [
+    "//base",
+  ]
+}
+
 source_set("unit_tests") {
   configs += [ "//build/config/compiler:enable_arc" ]
   testonly = true
diff --git a/ios/chrome/browser/ntp/features.cc b/ios/chrome/browser/ntp/features.cc
new file mode 100644
index 0000000..5095e51
--- /dev/null
+++ b/ios/chrome/browser/ntp/features.cc
@@ -0,0 +1,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.
+
+#include "ios/chrome/browser/ntp/features.h"
+
+const base::Feature kBlockNewTabPagePendingLoad{
+    "BlockNewTabPagePendingLoad", base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/ios/chrome/browser/ntp/features.h b/ios/chrome/browser/ntp/features.h
new file mode 100644
index 0000000..0fbb1d3
--- /dev/null
+++ b/ios/chrome/browser/ntp/features.h
@@ -0,0 +1,13 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_NTP_FEATURES_H_
+#define IOS_CHROME_BROWSER_NTP_FEATURES_H_
+
+#include "base/feature_list.h"
+
+// Feature flag to enable NTP UI pending loader blocker.
+extern const base::Feature kBlockNewTabPagePendingLoad;
+
+#endif  // IOS_CHROME_BROWSER_NTP_FEATURES_H_
diff --git a/ios/chrome/browser/ntp/new_tab_page_tab_helper.h b/ios/chrome/browser/ntp/new_tab_page_tab_helper.h
index e0d9a6d1..6493814 100644
--- a/ios/chrome/browser/ntp/new_tab_page_tab_helper.h
+++ b/ios/chrome/browser/ntp/new_tab_page_tab_helper.h
@@ -6,8 +6,10 @@
 #define IOS_CHROME_BROWSER_NTP_NEW_TAB_PAGE_TAB_HELPER_H_
 
 #import <UIKit/UIKit.h>
+#include <memory>
 
 #include "base/macros.h"
+#include "base/timer/timer.h"
 #include "ios/web/public/web_state/web_state_observer.h"
 #import "ios/web/public/web_state/web_state_user_data.h"
 
@@ -31,6 +33,13 @@
   // WebStateObserver callback.
   void Deactivate();
 
+  // Sometimes the underlying ios/web page used for the NTP (about://newtab)
+  // takes a long time to load.  Loading any page before the newtab is committed
+  // will leave ios/web in a bad state.  See: crbug.com/925304 for more context.
+  // Remove this when ios/web supports queueing multiple loads during this
+  // state.
+  bool IgnoreLoadRequests() const;
+
  private:
   NewTabPageTabHelper(web::WebState* web_state,
                       id<NewTabPageTabHelperDelegate> delegate);
@@ -52,6 +61,14 @@
   // Returns true if an |url| is either chrome://newtab or about://newtab.
   bool IsNTPURL(const GURL& url);
 
+  // Sets the |ignore_load_requests_| flag to YES and starts the ignore load
+  // timer.
+  void EnableIgnoreLoadRequests();
+
+  // Sets the |ignore_load_requests_| flag to NO and stops the ignore load
+  // timer.
+  void DisableIgnoreLoadRequests();
+
   // Used to present and dismiss the NTP.
   __weak id<NewTabPageTabHelperDelegate> delegate_ = nil;
 
@@ -61,6 +78,13 @@
   // |YES| if the current tab helper is active.
   BOOL active_;
 
+  // |YES| if the NTP's underlying ios/web page is still loading.
+  BOOL ignore_load_requests_ = NO;
+
+  // Ensure the ignore_load_requests_ flag is never set to NO for more than
+  // |kMaximumIgnoreLoadRequestsTime| seconds.
+  std::unique_ptr<base::OneShotTimer> ignore_load_requests_timer_ = nullptr;
+
   DISALLOW_COPY_AND_ASSIGN(NewTabPageTabHelper);
 };
 
diff --git a/ios/chrome/browser/ntp/new_tab_page_tab_helper.mm b/ios/chrome/browser/ntp/new_tab_page_tab_helper.mm
index 5411c84..2d4d6b3 100644
--- a/ios/chrome/browser/ntp/new_tab_page_tab_helper.mm
+++ b/ios/chrome/browser/ntp/new_tab_page_tab_helper.mm
@@ -12,6 +12,7 @@
 #include "base/strings/sys_string_conversions.h"
 #include "components/strings/grit/components_strings.h"
 #include "ios/chrome/browser/chrome_url_constants.h"
+#include "ios/chrome/browser/ntp/features.h"
 #include "ios/chrome/browser/ntp/new_tab_page_tab_helper_delegate.h"
 #include "ios/chrome/browser/ui/ui_feature_flags.h"
 #import "ios/web/public/navigation_item.h"
@@ -29,6 +30,10 @@
 // |url::kAboutScheme|, there's no host value, only a path.  Use this value for
 // matching the NTP.
 const char kAboutNewTabPath[] = "//newtab/";
+
+// Maximum number of seconds for |ignore_load_requests_| to be set to YES.
+static const size_t kMaximumIgnoreLoadRequestsTime = 10;
+
 }  // namespace
 
 // static
@@ -58,6 +63,12 @@
   if (active_) {
     UpdatePendingItem();
     [delegate_ newTabPageHelperDidChangeVisibility:this forWebState:web_state_];
+
+    // If about://newtab is currently loading but has not yet committed, block
+    // loads until it does commit.
+    if (!IsNTPURL(web_state->GetLastCommittedURL())) {
+      EnableIgnoreLoadRequests();
+    }
   }
 }
 
@@ -69,11 +80,39 @@
   SetActive(false);
 }
 
+bool NewTabPageTabHelper::IgnoreLoadRequests() const {
+  return ignore_load_requests_;
+}
+
+void NewTabPageTabHelper::EnableIgnoreLoadRequests() {
+  if (!base::FeatureList::IsEnabled(kBlockNewTabPagePendingLoad))
+    return;
+
+  ignore_load_requests_ = YES;
+
+  // |ignore_load_requests_timer_| is deleted when the tab helper is deleted, so
+  // it's safe to use Unretained here.
+  ignore_load_requests_timer_.reset(new base::OneShotTimer());
+  ignore_load_requests_timer_->Start(
+      FROM_HERE, base::TimeDelta::FromSeconds(kMaximumIgnoreLoadRequestsTime),
+      base::BindOnce(&NewTabPageTabHelper::DisableIgnoreLoadRequests,
+                     base::Unretained(this)));
+}
+
+void NewTabPageTabHelper::DisableIgnoreLoadRequests() {
+  if (ignore_load_requests_timer_) {
+    ignore_load_requests_timer_->Stop();
+    ignore_load_requests_timer_.reset();
+  }
+  ignore_load_requests_ = NO;
+}
+
 #pragma mark - WebStateObserver
 
 void NewTabPageTabHelper::WebStateDestroyed(web::WebState* web_state) {
   web_state->RemoveObserver(this);
   SetActive(false);
+  DisableIgnoreLoadRequests();
 }
 
 void NewTabPageTabHelper::DidStartNavigation(
@@ -93,6 +132,7 @@
     return;
   }
 
+  DisableIgnoreLoadRequests();
   SetActive(IsNTPURL(web_state->GetLastCommittedURL()));
 }
 
diff --git a/ios/chrome/browser/ui/content_suggestions/BUILD.gn b/ios/chrome/browser/ui/content_suggestions/BUILD.gn
index d54e3a8..c7b8227 100644
--- a/ios/chrome/browser/ui/content_suggestions/BUILD.gn
+++ b/ios/chrome/browser/ui/content_suggestions/BUILD.gn
@@ -40,6 +40,7 @@
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/favicon",
     "//ios/chrome/browser/metrics:metrics_internal",
+    "//ios/chrome/browser/ntp",
     "//ios/chrome/browser/ntp_snippets",
     "//ios/chrome/browser/ntp_tiles",
     "//ios/chrome/browser/reading_list",
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.mm b/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.mm
index a04fd368..31070b6 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.mm
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.mm
@@ -14,6 +14,7 @@
 #include "components/strings/grit/components_strings.h"
 #include "ios/chrome/browser/chrome_url_constants.h"
 #import "ios/chrome/browser/metrics/new_tab_page_uma.h"
+#import "ios/chrome/browser/ntp/new_tab_page_tab_helper.h"
 #import "ios/chrome/browser/search_engines/search_engine_observer_bridge.h"
 #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
 #import "ios/chrome/browser/ui/commands/application_commands.h"
@@ -201,6 +202,11 @@
 }
 
 - (void)openPageForItemAtIndexPath:(NSIndexPath*)indexPath {
+  NewTabPageTabHelper* NTPHelper =
+      NewTabPageTabHelper::FromWebState(self.webState);
+  if (NTPHelper && NTPHelper->IgnoreLoadRequests()) {
+    return;
+  }
   CollectionViewItem* item = [self.suggestionsViewController.collectionViewModel
       itemAtIndexPath:indexPath];
   ContentSuggestionsItem* suggestionItem =
@@ -256,6 +262,11 @@
     return;
   }
 
+  NewTabPageTabHelper* NTPHelper =
+      NewTabPageTabHelper::FromWebState(self.webState);
+  if (NTPHelper && NTPHelper->IgnoreLoadRequests())
+    return;
+
   ContentSuggestionsMostVisitedItem* mostVisitedItem =
       base::mac::ObjCCastStrict<ContentSuggestionsMostVisitedItem>(item);
 
@@ -346,6 +357,10 @@
 }
 
 - (void)handleLearnMoreTapped {
+  NewTabPageTabHelper* NTPHelper =
+      NewTabPageTabHelper::FromWebState(self.webState);
+  if (NTPHelper && NTPHelper->IgnoreLoadRequests())
+    return;
   GURL URL(kNTPHelpURL);
   ChromeLoadParams params(URL);
   [self.dispatcher loadURLWithParams:params];
diff --git a/media/audio/fuchsia/audio_output_stream_fuchsia.cc b/media/audio/fuchsia/audio_output_stream_fuchsia.cc
index bdb614f..6fc9f38 100644
--- a/media/audio/fuchsia/audio_output_stream_fuchsia.cc
+++ b/media/audio/fuchsia/audio_output_stream_fuchsia.cc
@@ -7,7 +7,7 @@
 #include <zircon/syscalls.h>
 
 #include "base/bind.h"
-#include "base/fuchsia/component_context.h"
+#include "base/fuchsia/service_directory_client.h"
 #include "media/audio/fuchsia/audio_manager_fuchsia.h"
 #include "media/base/audio_sample_types.h"
 #include "media/base/audio_timestamp_helper.h"
@@ -36,7 +36,7 @@
 
   // Connect |audio_renderer_| to the audio service.
   fuchsia::media::AudioPtr audio_server =
-      base::fuchsia::ComponentContext::GetDefault()
+      base::fuchsia::ServiceDirectoryClient::ForCurrentProcess()
           ->ConnectToService<fuchsia::media::Audio>();
   audio_server->CreateAudioRenderer(audio_renderer_.NewRequest());
   audio_renderer_.set_error_handler(
diff --git a/media/filters/fuchsia/fuchsia_video_decoder.cc b/media/filters/fuchsia/fuchsia_video_decoder.cc
index 3cf45416..84c3f481 100644
--- a/media/filters/fuchsia/fuchsia_video_decoder.cc
+++ b/media/filters/fuchsia/fuchsia_video_decoder.cc
@@ -10,8 +10,8 @@
 
 #include "base/bind.h"
 #include "base/callback_helpers.h"
-#include "base/fuchsia/component_context.h"
 #include "base/fuchsia/fuchsia_logging.h"
+#include "base/fuchsia/service_directory_client.h"
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/macros.h"
@@ -402,7 +402,7 @@
   codec_params.require_hw = !enable_sw_decoding_;
 
   auto codec_factory =
-      base::fuchsia::ComponentContext::GetDefault()
+      base::fuchsia::ServiceDirectoryClient::ForCurrentProcess()
           ->ConnectToService<fuchsia::mediacodec::CodecFactory>();
   codec_factory->CreateDecoder(std::move(codec_params), codec_.NewRequest());
 
diff --git a/media/gpu/v4l2/v4l2_image_processor.cc b/media/gpu/v4l2/v4l2_image_processor.cc
index bd013f9..a9b81c30 100644
--- a/media/gpu/v4l2/v4l2_image_processor.cc
+++ b/media/gpu/v4l2/v4l2_image_processor.cc
@@ -84,7 +84,7 @@
       error_cb_(error_cb) {}
 
 V4L2ImageProcessor::~V4L2ImageProcessor() {
-  DCHECK_CALLED_ON_VALID_THREAD(client_thread_checker_);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
 
   Destroy();
 
@@ -443,7 +443,7 @@
 
 bool V4L2ImageProcessor::Reset() {
   VLOGF(2);
-  DCHECK_CALLED_ON_VALID_THREAD(client_thread_checker_);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
   DCHECK(device_thread_.IsRunning());
 
   process_task_tracker_.TryCancelAll();
@@ -452,7 +452,7 @@
 
 void V4L2ImageProcessor::Destroy() {
   VLOGF(2);
-  DCHECK_CALLED_ON_VALID_THREAD(client_thread_checker_);
+  DCHECK_CALLED_ON_VALID_SEQUENCE(client_sequence_checker_);
 
   // If the device thread is running, destroy using posted task.
   if (device_thread_.IsRunning()) {
diff --git a/media/gpu/v4l2/v4l2_image_processor.h b/media/gpu/v4l2/v4l2_image_processor.h
index 99e266e..9d1b0306 100644
--- a/media/gpu/v4l2/v4l2_image_processor.h
+++ b/media/gpu/v4l2/v4l2_image_processor.h
@@ -18,9 +18,9 @@
 #include "base/files/scoped_file.h"
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/sequence_checker.h"
 #include "base/task/cancelable_task_tracker.h"
 #include "base/threading/thread.h"
-#include "base/threading/thread_checker.h"
 #include "media/base/video_frame.h"
 #include "media/base/video_frame_layout.h"
 #include "media/gpu/image_processor.h"
@@ -196,8 +196,8 @@
   // Error callback to the client.
   ErrorCB error_cb_;
 
-  // Checker for the thread that creates this V4L2ImageProcessor.
-  THREAD_CHECKER(client_thread_checker_);
+  // Checker for the sequence that creates this V4L2ImageProcessor.
+  SEQUENCE_CHECKER(client_sequence_checker_);
 
   DISALLOW_COPY_AND_ASSIGN(V4L2ImageProcessor);
 };
diff --git a/media/gpu/vaapi/vp8_encoder.cc b/media/gpu/vaapi/vp8_encoder.cc
index 53c196b..b591fd143 100644
--- a/media/gpu/vaapi/vp8_encoder.cc
+++ b/media/gpu/vaapi/vp8_encoder.cc
@@ -11,15 +11,15 @@
 
 namespace {
 // Keyframe period.
-const size_t kKFPeriod = 3000;
+constexpr size_t kKFPeriod = 3000;
 
 // Arbitrarily chosen bitrate window size for rate control, in ms.
-const int kCPBWindowSizeMs = 1500;
+constexpr int kCPBWindowSizeMs = 1500;
 
 // Based on WebRTC's defaults.
-const int kMinQP = 4;
-const int kMaxQP = 112;
-const int kDefaultQP = (3 * kMinQP + kMaxQP) / 4;
+constexpr int kMinQP = 4;
+constexpr int kMaxQP = 112;
+constexpr int kDefaultQP = (3 * kMinQP + kMaxQP) / 4;
 }  // namespace
 
 VP8Encoder::EncodeParams::EncodeParams()
diff --git a/media/gpu/vp8_reference_frame_vector.h b/media/gpu/vp8_reference_frame_vector.h
index 63c7159..cdb7c564 100644
--- a/media/gpu/vp8_reference_frame_vector.h
+++ b/media/gpu/vp8_reference_frame_vector.h
@@ -7,7 +7,7 @@
 
 #include <array>
 
-#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/sequence_checker.h"
 #include "media/filters/vp8_parser.h"
 
diff --git a/net/base/network_change_notifier_fuchsia.cc b/net/base/network_change_notifier_fuchsia.cc
index a8044df..bc108fe 100644
--- a/net/base/network_change_notifier_fuchsia.cc
+++ b/net/base/network_change_notifier_fuchsia.cc
@@ -10,8 +10,8 @@
 #include <vector>
 
 #include "base/bind.h"
-#include "base/fuchsia/component_context.h"
 #include "base/fuchsia/fuchsia_logging.h"
+#include "base/fuchsia/service_directory_client.h"
 #include "base/optional.h"
 #include "base/run_loop.h"
 #include "net/base/network_interfaces.h"
@@ -41,7 +41,7 @@
 NetworkChangeNotifierFuchsia::NetworkChangeNotifierFuchsia(
     uint32_t required_features)
     : NetworkChangeNotifierFuchsia(
-          base::fuchsia::ComponentContext::GetDefault()
+          base::fuchsia::ServiceDirectoryClient::ForCurrentProcess()
               ->ConnectToService<fuchsia::netstack::Netstack>(),
           required_features) {}
 
diff --git a/net/base/network_interfaces_fuchsia.cc b/net/base/network_interfaces_fuchsia.cc
index 30dcc5d..300966e 100644
--- a/net/base/network_interfaces_fuchsia.cc
+++ b/net/base/network_interfaces_fuchsia.cc
@@ -12,8 +12,8 @@
 #include <utility>
 
 #include "base/format_macros.h"
-#include "base/fuchsia/component_context.h"
 #include "base/fuchsia/fuchsia_logging.h"
+#include "base/fuchsia/service_directory_client.h"
 #include "base/strings/stringprintf.h"
 #include "net/base/ip_endpoint.h"
 #include "net/base/network_interfaces.h"
@@ -103,7 +103,7 @@
   DCHECK(networks);
 
   fuchsia::netstack::NetstackSyncPtr netstack =
-      base::fuchsia::ComponentContext::GetDefault()
+      base::fuchsia::ServiceDirectoryClient::ForCurrentProcess()
           ->ConnectToServiceSync<fuchsia::netstack::Netstack>();
 
   // TODO(kmarshall): Use NetworkChangeNotifier's cached interface list.
diff --git a/net/http/http_proxy_client_socket.cc b/net/http/http_proxy_client_socket.cc
index b00247df..301d4b71 100644
--- a/net/http/http_proxy_client_socket.cc
+++ b/net/http/http_proxy_client_socket.cc
@@ -472,8 +472,8 @@
       if (!is_https_proxy_ || !SanitizeProxyRedirect(&response_))
         return ERR_TUNNEL_CONNECTION_FAILED;
 
-      transport_.reset();
       http_stream_parser_.reset();
+      transport_.reset();
       return ERR_HTTPS_PROXY_TUNNEL_RESPONSE;
 
     case 407:  // Proxy Authentication Required
diff --git a/net/http/http_proxy_client_socket_wrapper.cc b/net/http/http_proxy_client_socket_wrapper.cc
index 4f142a1..0ffb067 100644
--- a/net/http/http_proxy_client_socket_wrapper.cc
+++ b/net/http/http_proxy_client_socket_wrapper.cc
@@ -670,7 +670,7 @@
     }
   } else {
     // Create a session direct to the proxy itself
-    spdy_session = spdy_session_pool_->CreateAvailableSessionFromSocket(
+    spdy_session = spdy_session_pool_->CreateAvailableSessionFromSocketHandle(
         key, is_trusted_proxy_, std::move(transport_socket_handle_), net_log_);
     DCHECK(spdy_session);
   }
diff --git a/net/http/http_stream_factory_job.cc b/net/http/http_stream_factory_job.cc
index 9b2e7989..861c5f2 100644
--- a/net/http/http_stream_factory_job.cc
+++ b/net/http/http_stream_factory_job.cc
@@ -1279,7 +1279,7 @@
       !spdy_session_direct_ && proxy_info_.proxy_server().is_trusted_proxy();
 
   base::WeakPtr<SpdySession> spdy_session =
-      session_->spdy_session_pool()->CreateAvailableSessionFromSocket(
+      session_->spdy_session_pool()->CreateAvailableSessionFromSocketHandle(
           spdy_session_key_, is_trusted_proxy, std::move(connection_),
           net_log_);
 
diff --git a/net/socket/connect_job_test_util.cc b/net/socket/connect_job_test_util.cc
index f72a946..a964986 100644
--- a/net/socket/connect_job_test_util.cc
+++ b/net/socket/connect_job_test_util.cc
@@ -4,6 +4,8 @@
 
 #include "net/socket/connect_job_test_util.h"
 
+#include <utility>
+
 #include "base/logging.h"
 #include "base/run_loop.h"
 #include "net/socket/stream_socket.h"
@@ -49,4 +51,8 @@
   }
 }
 
+std::unique_ptr<StreamSocket> TestConnectJobDelegate::ReleaseSocket() {
+  return std::move(socket_);
+}
+
 }  // namespace net
diff --git a/net/socket/connect_job_test_util.h b/net/socket/connect_job_test_util.h
index aea7693f..73d7f06 100644
--- a/net/socket/connect_job_test_util.h
+++ b/net/socket/connect_job_test_util.h
@@ -46,6 +46,8 @@
 
   StreamSocket* socket() { return socket_.get(); }
 
+  std::unique_ptr<StreamSocket> ReleaseSocket();
+
  private:
   const SocketExpected socket_expected_;
   bool has_result_ = false;
diff --git a/net/spdy/spdy_http_stream.h b/net/spdy/spdy_http_stream.h
index 315cba5b..60fb740a 100644
--- a/net/spdy/spdy_http_stream.h
+++ b/net/spdy/spdy_http_stream.h
@@ -14,6 +14,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "net/base/completion_once_callback.h"
+#include "net/base/load_timing_info.h"
 #include "net/base/net_export.h"
 #include "net/log/net_log_source.h"
 #include "net/spdy/multiplexed_http_stream.h"
diff --git a/net/spdy/spdy_proxy_client_socket_unittest.cc b/net/spdy/spdy_proxy_client_socket_unittest.cc
index 0f31adc..b9ab32c 100644
--- a/net/spdy/spdy_proxy_client_socket_unittest.cc
+++ b/net/spdy/spdy_proxy_client_socket_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/strings/string_piece.h"
 #include "base/strings/utf_string_conversions.h"
 #include "net/base/address_list.h"
+#include "net/base/load_timing_info.h"
 #include "net/base/test_completion_callback.h"
 #include "net/base/winsock_init.h"
 #include "net/dns/mock_host_resolver.h"
@@ -24,9 +25,15 @@
 #include "net/log/test_net_log_entry.h"
 #include "net/log/test_net_log_util.h"
 #include "net/socket/client_socket_factory.h"
+#include "net/socket/connect_job_test_util.h"
 #include "net/socket/socket_tag.h"
 #include "net/socket/socket_test_util.h"
+#include "net/socket/socks_connect_job.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/ssl_connect_job.h"
+#include "net/socket/stream_socket.h"
 #include "net/socket/tcp_client_socket.h"
+#include "net/socket/transport_connect_job.h"
 #include "net/spdy/buffered_spdy_framer.h"
 #include "net/spdy/spdy_http_utils.h"
 #include "net/spdy/spdy_session_pool.h"
@@ -46,6 +53,8 @@
 
 //-----------------------------------------------------------------------------
 
+namespace net {
+
 namespace {
 
 static const char kRequestUrl[] = "https://www.google.com/";
@@ -72,9 +81,57 @@
 
 static const char kRedirectUrl[] = "https://example.com/";
 
-}  // anonymous namespace
+// Creates a SpdySession with a StreamSocket, instead of a ClientSocketHandle.
+base::WeakPtr<SpdySession> CreateSpdyProxySession(
+    HttpNetworkSession* http_session,
+    SpdySessionDependencies* session_deps,
+    const SpdySessionKey& key) {
+  EXPECT_FALSE(http_session->spdy_session_pool()->FindAvailableSession(
+      key, true /* enable_ip_based_pooling */, false /* is_websocket */,
+      NetLogWithSource()));
 
-namespace net {
+  auto transport_params = base::MakeRefCounted<TransportSocketParams>(
+      key.host_port_pair(), false /* disable_resolver_cache */,
+      OnHostResolutionCallback());
+
+  SSLConfig ssl_config;
+  auto ssl_params = base::MakeRefCounted<SSLSocketParams>(
+      transport_params, nullptr, nullptr, key.host_port_pair(), ssl_config,
+      key.privacy_mode());
+  TestConnectJobDelegate connect_job_delegate;
+  SSLConnectJob connect_job(
+      MEDIUM,
+      CommonConnectJobParams(
+          "group_name", SocketTag(), true /* respect_limits */,
+          session_deps->socket_factory.get(), session_deps->host_resolver.get(),
+          SSLClientSocketContext(session_deps->cert_verifier.get(),
+                                 session_deps->channel_id_service.get(),
+                                 session_deps->transport_security_state.get(),
+                                 session_deps->cert_transparency_verifier.get(),
+                                 session_deps->ct_policy_enforcer.get(),
+                                 nullptr /* ssl_client_session_cache_arg */,
+                                 std::string() /* ssl_session_cache_shard_arg */
+                                 ),
+          nullptr /* socket_performance_watcher_factory */,
+          nullptr /* network_quality_estimator */, session_deps->net_log,
+          nullptr /* websocket_endpoint_lock_manager */),
+      ssl_params, nullptr /* socks_pool */, nullptr /* http_proxy_pool */,
+      &connect_job_delegate);
+  connect_job_delegate.StartJobExpectingResult(&connect_job, OK,
+                                               false /* expect_sync_result */);
+
+  base::WeakPtr<SpdySession> spdy_session =
+      http_session->spdy_session_pool()->CreateAvailableSessionFromSocket(
+          key, false /* is_trusted_proxy */,
+          connect_job_delegate.ReleaseSocket(), LoadTimingInfo::ConnectTiming(),
+          NetLogWithSource());
+  // Failure is reported asynchronously.
+  EXPECT_TRUE(spdy_session);
+  EXPECT_TRUE(HasSpdySession(http_session->spdy_session_pool(), key));
+  return spdy_session;
+}
+
+}  // namespace
 
 class SpdyProxyClientSocketTest : public PlatformTest,
                                   public WithScopedTaskEnvironment,
@@ -206,8 +263,9 @@
   session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
 
   // Creates the SPDY session and stream.
-  spdy_session_ = CreateSpdySession(session_.get(), endpoint_spdy_session_key_,
-                                    NetLogWithSource());
+  spdy_session_ = CreateSpdyProxySession(session_.get(), &session_deps_,
+                                         endpoint_spdy_session_key_);
+
   base::WeakPtr<SpdyStream> spdy_stream(
       CreateStreamSynchronously(
           SPDY_BIDIRECTIONAL_STREAM, spdy_session_, url_, LOWEST,
@@ -413,9 +471,8 @@
 spdy::SpdySerializedFrame SpdyProxyClientSocketTest::ConstructBodyFrame(
     const char* data,
     int length) {
-  return spdy_util_.ConstructSpdyDataFrame(kStreamId,
-                                           base::StringPiece(data, length),
-                                           /*fin=*/false);
+  return spdy_util_.ConstructSpdyDataFrame(
+      kStreamId, base::StringPiece(data, length), false /* fin */);
 }
 
 // ----------- Connect
diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc
index 9c3605b..c98317a 100644
--- a/net/spdy/spdy_session.cc
+++ b/net/spdy/spdy_session.cc
@@ -44,6 +44,7 @@
 #include "net/log/net_log_with_source.h"
 #include "net/nqe/network_quality_estimator.h"
 #include "net/quic/quic_http_utils.h"
+#include "net/socket/client_socket_handle.h"
 #include "net/socket/socket.h"
 #include "net/socket/ssl_client_socket.h"
 #include "net/spdy/header_coalescer.h"
@@ -844,6 +845,7 @@
       http_server_properties_(http_server_properties),
       transport_security_state_(transport_security_state),
       ssl_config_service_(ssl_config_service),
+      socket_(nullptr),
       stream_hi_water_mark_(kFirstStreamId),
       last_accepted_push_stream_id_(0),
       push_delegate_(push_delegate),
@@ -920,11 +922,10 @@
   CHECK(!in_io_loop_);
   DcheckDraining();
 
-  // TODO(akalin): Check connection->is_initialized() instead. This
-  // requires re-working CreateFakeSpdySession(), though.
-  DCHECK(connection_->socket());
+  // TODO(akalin): Check connection->is_initialized().
+  DCHECK(socket_);
   // With SPDY we can't recycle sockets.
-  connection_->socket()->Disconnect();
+  socket_->Disconnect();
 
   RecordHistograms();
 
@@ -981,48 +982,40 @@
   ResetStream(stream_id, ERR_ABORTED, "Cancelled push stream.");
 }
 
-void SpdySession::InitializeWithSocket(
-    std::unique_ptr<ClientSocketHandle> connection,
+void SpdySession::InitializeWithSocketHandle(
+    std::unique_ptr<ClientSocketHandle> client_socket_handle,
     SpdySessionPool* pool) {
-  CHECK(!in_io_loop_);
-  DCHECK_EQ(availability_state_, STATE_AVAILABLE);
-  DCHECK_EQ(read_state_, READ_STATE_DO_READ);
-  DCHECK_EQ(write_state_, WRITE_STATE_IDLE);
-  DCHECK(!connection_);
+  DCHECK(!client_socket_handle_);
+  DCHECK(!owned_stream_socket_);
+  DCHECK(!socket_);
 
   // TODO(akalin): Check connection->is_initialized() instead. This
   // requires re-working CreateFakeSpdySession(), though.
-  DCHECK(connection->socket());
+  DCHECK(client_socket_handle->socket());
 
-  connection_ = std::move(connection);
+  client_socket_handle_ = std::move(client_socket_handle);
+  socket_ = client_socket_handle_->socket();
+  client_socket_handle_->AddHigherLayeredPool(this);
 
-  session_send_window_size_ = kDefaultInitialWindowSize;
-  session_recv_window_size_ = kDefaultInitialWindowSize;
+  InitializeInternal(pool);
+}
 
-  auto it = initial_settings_.find(spdy::SETTINGS_MAX_HEADER_LIST_SIZE);
-  uint32_t spdy_max_header_list_size =
-      (it == initial_settings_.end()) ? kSpdyMaxHeaderListSize : it->second;
-  buffered_spdy_framer_ = std::make_unique<BufferedSpdyFramer>(
-      spdy_max_header_list_size, net_log_, time_func_);
-  buffered_spdy_framer_->set_visitor(this);
-  buffered_spdy_framer_->set_debug_visitor(this);
-  buffered_spdy_framer_->UpdateHeaderDecoderTableSize(max_header_table_size_);
+void SpdySession::InitializeWithSocket(
+    std::unique_ptr<StreamSocket> stream_socket,
+    const LoadTimingInfo::ConnectTiming& connect_timing,
+    SpdySessionPool* pool) {
+  DCHECK(!client_socket_handle_);
+  DCHECK(!owned_stream_socket_);
+  DCHECK(!socket_);
 
-  net_log_.AddEvent(NetLogEventType::HTTP2_SESSION_INITIALIZED,
-                    base::Bind(&NetLogSpdyInitializedCallback,
-                               connection_->socket()->NetLog().source()));
+  DCHECK(stream_socket);
 
-  DCHECK_EQ(availability_state_, STATE_AVAILABLE);
-  connection_->AddHigherLayeredPool(this);
-  if (enable_sending_initial_data_)
-    SendInitialData();
-  pool_ = pool;
+  owned_stream_socket_ = std::move(stream_socket);
+  socket_ = owned_stream_socket_.get();
+  connect_timing_ =
+      std::make_unique<LoadTimingInfo::ConnectTiming>(connect_timing);
 
-  // Bootstrap the read loop.
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE,
-      base::Bind(&SpdySession::PumpReadLoop, weak_factory_.GetWeakPtr(),
-                 READ_STATE_DO_READ, OK));
+  InitializeInternal(pool);
 }
 
 bool SpdySession::VerifyDomainAuthentication(const std::string& domain) const {
@@ -1283,15 +1276,15 @@
 }
 
 bool SpdySession::GetSSLInfo(SSLInfo* ssl_info) const {
-  return connection_->socket()->GetSSLInfo(ssl_info);
+  return socket_->GetSSLInfo(ssl_info);
 }
 
 bool SpdySession::WasAlpnNegotiated() const {
-  return connection_->socket()->WasAlpnNegotiated();
+  return socket_->WasAlpnNegotiated();
 }
 
 NextProto SpdySession::GetNegotiatedProtocol() const {
-  return connection_->socket()->GetNegotiatedProtocol();
+  return socket_->GetNegotiatedProtocol();
 }
 
 void SpdySession::SendStreamWindowUpdate(spdy::SpdyStreamId stream_id,
@@ -1388,9 +1381,8 @@
   dict->SetInteger("unclaimed_pushed_streams",
                    pool_->push_promise_index()->CountStreamsForSession(this));
 
-  dict->SetString(
-      "negotiated_protocol",
-      NextProtoToString(connection_->socket()->GetNegotiatedProtocol()));
+  dict->SetString("negotiated_protocol",
+                  NextProtoToString(socket_->GetNegotiatedProtocol()));
 
   dict->SetInteger("error", error_on_close_);
   dict->SetInteger("max_concurrent_streams", max_concurrent_streams_);
@@ -1411,26 +1403,50 @@
 }
 
 bool SpdySession::IsReused() const {
-  return buffered_spdy_framer_->frames_received() > 0 ||
-         connection_->reuse_type() == ClientSocketHandle::UNUSED_IDLE;
+  if (buffered_spdy_framer_->frames_received() > 0)
+    return true;
+
+  // If there's no socket pool in use (i.e., |owned_stream_socket_| is
+  // non-null), then the SpdySession could only have been created with freshly
+  // connected socket, since canceling the H2 session request would have
+  // destroyed the socket.
+  return owned_stream_socket_ ||
+         client_socket_handle_->reuse_type() == ClientSocketHandle::UNUSED_IDLE;
 }
 
 bool SpdySession::GetLoadTimingInfo(spdy::SpdyStreamId stream_id,
                                     LoadTimingInfo* load_timing_info) const {
-  return connection_->GetLoadTimingInfo(stream_id != kFirstStreamId,
-                                        load_timing_info);
+  if (client_socket_handle_) {
+    DCHECK(!connect_timing_);
+    return client_socket_handle_->GetLoadTimingInfo(stream_id != kFirstStreamId,
+                                                    load_timing_info);
+  }
+
+  DCHECK(connect_timing_);
+  DCHECK(socket_);
+
+  // The socket is considered "fresh" (not reused) only for the first stream on
+  // a SPDY session. All others consider it reused, and don't return connection
+  // establishment timing information.
+  load_timing_info->socket_reused = (stream_id != kFirstStreamId);
+  if (!load_timing_info->socket_reused)
+    load_timing_info->connect_timing = *connect_timing_;
+
+  load_timing_info->socket_log_id = socket_->NetLog().source().id;
+
+  return true;
 }
 
 int SpdySession::GetPeerAddress(IPEndPoint* address) const {
-  if (connection_->socket())
-    return connection_->socket()->GetPeerAddress(address);
+  if (socket_)
+    return socket_->GetPeerAddress(address);
 
   return ERR_SOCKET_NOT_CONNECTED;
 }
 
 int SpdySession::GetLocalAddress(IPEndPoint* address) const {
-  if (connection_->socket())
-    return connection_->socket()->GetLocalAddress(address);
+  if (socket_)
+    return socket_->GetLocalAddress(address);
 
   return ERR_SOCKET_NOT_CONNECTED;
 }
@@ -1523,7 +1539,7 @@
   // TODO(xunjieli): Include |pending_create_stream_queues_| when WeakPtr is
   // supported in memory_usage_estimator.h.
   *is_session_active = is_active();
-  connection_->DumpMemoryStats(stats);
+  socket_->DumpMemoryStats(stats);
 
   // |connection_| is estimated in stats->total_size. |read_buffer_| is
   // estimated in |read_buffer_size|. TODO(xunjieli): Make them use EMU().
@@ -1542,7 +1558,7 @@
 }
 
 bool SpdySession::ChangeSocketTag(const SocketTag& new_tag) {
-  if (!IsAvailable() || !connection_->socket())
+  if (!IsAvailable() || !socket_)
     return false;
 
   // Changing the tag on the underlying socket will affect all streams,
@@ -1550,7 +1566,7 @@
   if (is_active())
     return false;
 
-  connection_->socket()->ApplySocketTag(new_tag);
+  socket_->ApplySocketTag(new_tag);
 
   SpdySessionKey new_key(spdy_session_key_.host_port_pair(),
                          spdy_session_key_.proxy_server(),
@@ -1567,6 +1583,40 @@
   UMA_HISTOGRAM_ENUMERATION("Net.SpdyPushedStreamFate", value);
 }
 
+void SpdySession::InitializeInternal(SpdySessionPool* pool) {
+  CHECK(!in_io_loop_);
+  DCHECK_EQ(availability_state_, STATE_AVAILABLE);
+  DCHECK_EQ(read_state_, READ_STATE_DO_READ);
+  DCHECK_EQ(write_state_, WRITE_STATE_IDLE);
+
+  session_send_window_size_ = kDefaultInitialWindowSize;
+  session_recv_window_size_ = kDefaultInitialWindowSize;
+
+  auto it = initial_settings_.find(spdy::SETTINGS_MAX_HEADER_LIST_SIZE);
+  uint32_t spdy_max_header_list_size =
+      (it == initial_settings_.end()) ? kSpdyMaxHeaderListSize : it->second;
+  buffered_spdy_framer_ = std::make_unique<BufferedSpdyFramer>(
+      spdy_max_header_list_size, net_log_, time_func_);
+  buffered_spdy_framer_->set_visitor(this);
+  buffered_spdy_framer_->set_debug_visitor(this);
+  buffered_spdy_framer_->UpdateHeaderDecoderTableSize(max_header_table_size_);
+
+  net_log_.AddEvent(NetLogEventType::HTTP2_SESSION_INITIALIZED,
+                    base::BindRepeating(&NetLogSpdyInitializedCallback,
+                                        socket_->NetLog().source()));
+
+  DCHECK_EQ(availability_state_, STATE_AVAILABLE);
+  if (enable_sending_initial_data_)
+    SendInitialData();
+  pool_ = pool;
+
+  // Bootstrap the read loop.
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindRepeating(&SpdySession::PumpReadLoop,
+                          weak_factory_.GetWeakPtr(), READ_STATE_DO_READ, OK));
+}
+
 // {,Try}CreateStream() can be called with |in_io_loop_| set if a stream is
 // being created in response to another being closed due to received data.
 
@@ -1615,10 +1665,10 @@
   if (availability_state_ == STATE_DRAINING)
     return ERR_CONNECTION_CLOSED;
 
-  DCHECK(connection_->socket());
+  DCHECK(socket_);
   UMA_HISTOGRAM_BOOLEAN("Net.SpdySession.CreateStreamWithSocketConnected",
-                        connection_->socket()->IsConnected());
-  if (!connection_->socket()->IsConnected()) {
+                        socket_->IsConnected());
+  if (!socket_->IsConnected()) {
     DoDrainSession(
         ERR_CONNECTION_CLOSED,
         "Tried to create SPDY stream for a closed socket connection.");
@@ -1962,10 +2012,11 @@
 
   DeleteStream(std::move(owned_stream), status);
 
-  // If there are no active streams and the socket pool is stalled, close the
-  // session to free up a socket slot.
-  if (active_streams_.empty() && created_streams_.empty() &&
-      connection_->IsPoolStalled()) {
+  // If the socket belongs to a socket pool, and there are no active streams,
+  // and the socket pool is stalled, then close the session to free up a socket
+  // slot.
+  if (client_socket_handle_ && active_streams_.empty() &&
+      created_streams_.empty() && client_socket_handle_->IsPoolStalled()) {
     DoDrainSession(ERR_CONNECTION_CLOSED, "Closing idle connection.");
   }
 }
@@ -2112,13 +2163,12 @@
   DCHECK(!read_buffer_);
   CHECK(in_io_loop_);
 
-  CHECK(connection_);
-  CHECK(connection_->socket());
+  CHECK(socket_);
   read_state_ = READ_STATE_DO_READ_COMPLETE;
   int rv = ERR_READ_IF_READY_NOT_IMPLEMENTED;
   read_buffer_ = base::MakeRefCounted<IOBuffer>(kReadBufferSize);
   if (base::FeatureList::IsEnabled(Socket::kReadIfReadyExperiment)) {
-    rv = connection_->socket()->ReadIfReady(
+    rv = socket_->ReadIfReady(
         read_buffer_.get(), kReadBufferSize,
         base::Bind(&SpdySession::PumpReadLoop, weak_factory_.GetWeakPtr(),
                    READ_STATE_DO_READ));
@@ -2130,7 +2180,7 @@
   }
   if (rv == ERR_READ_IF_READY_NOT_IMPLEMENTED) {
     // Fallback to regular Read().
-    return connection_->socket()->Read(
+    return socket_->Read(
         read_buffer_.get(), kReadBufferSize,
         base::Bind(&SpdySession::PumpReadLoop, weak_factory_.GetWeakPtr(),
                    READ_STATE_DO_READ_COMPLETE));
@@ -2297,7 +2347,7 @@
 
   scoped_refptr<IOBuffer> write_io_buffer =
       in_flight_write_->GetIOBufferForRemainingData();
-  return connection_->socket()->Write(
+  return socket_->Write(
       write_io_buffer.get(), in_flight_write_->GetRemainingSize(),
       base::Bind(&SpdySession::PumpWriteLoop, weak_factory_.GetWeakPtr(),
                  WRITE_STATE_DO_WRITE_COMPLETE),
diff --git a/net/spdy/spdy_session.h b/net/spdy/spdy_session.h
index 2f846ffd..2878cf28f 100644
--- a/net/spdy/spdy_session.h
+++ b/net/spdy/spdy_session.h
@@ -27,11 +27,11 @@
 #include "net/base/host_port_pair.h"
 #include "net/base/io_buffer.h"
 #include "net/base/load_states.h"
+#include "net/base/load_timing_info.h"
 #include "net/base/net_errors.h"
 #include "net/base/net_export.h"
 #include "net/base/request_priority.h"
 #include "net/log/net_log_source.h"
-#include "net/socket/client_socket_handle.h"
 #include "net/socket/client_socket_pool.h"
 #include "net/socket/next_proto.h"
 #include "net/socket/ssl_client_socket.h"
@@ -86,7 +86,6 @@
 const spdy::SpdyStreamId kFirstStreamId = 1;
 const spdy::SpdyStreamId kLastStreamId = 0x7fffffff;
 
-struct LoadTimingInfo;
 class NetLog;
 class NetworkQualityEstimator;
 class SpdyStream;
@@ -348,10 +347,19 @@
   // |pool| is the SpdySessionPool that owns us.  Its lifetime must
   // strictly be greater than |this|.
   //
-  // The session begins reading from |connection| on a subsequent event loop
-  // iteration, so the SpdySession may close immediately afterwards if the first
-  // read of |connection| fails.
-  void InitializeWithSocket(std::unique_ptr<ClientSocketHandle> connection,
+  // The session begins reading from |client_socket_handle| on a subsequent
+  // event loop iteration, so the SpdySession may close immediately afterwards
+  // if the first read of |client_socket_handle| fails.
+  void InitializeWithSocketHandle(
+      std::unique_ptr<ClientSocketHandle> client_socket_handle,
+      SpdySessionPool* pool);
+
+  // Just like InitializeWithSocketHandle(), but for use when the session is not
+  // on top of a socket pool, but instead directly on top of a socket, which the
+  // session has sole ownership of, and is responsible for deleting directly
+  // itself.
+  void InitializeWithSocket(std::unique_ptr<StreamSocket> stream_socket,
+                            const LoadTimingInfo::ConnectTiming& connect_timing,
                             SpdySessionPool* pool);
 
   // Check to see if this SPDY session can support an additional domain.
@@ -476,9 +484,7 @@
 
   // Returns true if the underlying transport socket ever had any reads or
   // writes.
-  bool WasEverUsed() const {
-    return connection_->socket()->WasEverUsed();
-  }
+  bool WasEverUsed() const { return socket_->WasEverUsed(); }
 
   // Returns the load timing information from the perspective of the given
   // stream.  If it's not the first stream, the connection is considered reused
@@ -595,6 +601,9 @@
     WRITE_STATE_DO_WRITE_COMPLETE,
   };
 
+  // Has the shared logic for the other two Initialize methods that call it.
+  void InitializeInternal(SpdySessionPool* pool);
+
   // Called by SpdyStreamRequest to start a request to create a
   // stream. If OK is returned, then |stream| will be filled in with a
   // valid stream. If ERR_IO_PENDING is returned, then
@@ -937,8 +946,18 @@
   TransportSecurityState* transport_security_state_;
   SSLConfigService* ssl_config_service_;
 
-  // The socket handle for this session.
-  std::unique_ptr<ClientSocketHandle> connection_;
+  // One of these two owns the socket for this session, which is stored in
+  // |socket_|. If |client_socket_handle_| is non-null, this session is on top
+  // of a socket in a socket pool. If |owned_stream_socket_| is non-null, this
+  // session is directly on top of a socket, which is not in a socket pool.
+  std::unique_ptr<ClientSocketHandle> client_socket_handle_;
+  std::unique_ptr<StreamSocket> owned_stream_socket_;
+
+  // This is non-null only if |owned_stream_socket_| is non-null.
+  std::unique_ptr<LoadTimingInfo::ConnectTiming> connect_timing_;
+
+  // The socket for this session.
+  StreamSocket* socket_;
 
   // The read buffer used to read data from the socket.
   // Non-null if there is a Read() pending.
diff --git a/net/spdy/spdy_session_pool.cc b/net/spdy/spdy_session_pool.cc
index 27e4f563..014d434 100644
--- a/net/spdy/spdy_session_pool.cc
+++ b/net/spdy/spdy_session_pool.cc
@@ -98,48 +98,38 @@
   CertDatabase::GetInstance()->RemoveObserver(this);
 }
 
+base::WeakPtr<SpdySession>
+SpdySessionPool::CreateAvailableSessionFromSocketHandle(
+    const SpdySessionKey& key,
+    bool is_trusted_proxy,
+    std::unique_ptr<ClientSocketHandle> client_socket_handle,
+    const NetLogWithSource& net_log) {
+  TRACE_EVENT0(NetTracingCategory(),
+               "SpdySessionPool::CreateAvailableSessionFromSocketHandle");
+
+  std::unique_ptr<SpdySession> new_session =
+      CreateSession(key, is_trusted_proxy, net_log.net_log());
+  new_session->InitializeWithSocketHandle(std::move(client_socket_handle),
+                                          this);
+  return InsertSession(key, std::move(new_session), net_log);
+}
+
 base::WeakPtr<SpdySession> SpdySessionPool::CreateAvailableSessionFromSocket(
     const SpdySessionKey& key,
     bool is_trusted_proxy,
-    std::unique_ptr<ClientSocketHandle> connection,
+    std::unique_ptr<StreamSocket> socket_stream,
+    const LoadTimingInfo::ConnectTiming& connect_timing,
     const NetLogWithSource& net_log) {
   TRACE_EVENT0(NetTracingCategory(),
                "SpdySessionPool::CreateAvailableSessionFromSocket");
 
-  UMA_HISTOGRAM_ENUMERATION(
-      "Net.SpdySessionGet", IMPORTED_FROM_SOCKET, SPDY_SESSION_GET_MAX);
+  std::unique_ptr<SpdySession> new_session =
+      CreateSession(key, is_trusted_proxy, net_log.net_log());
 
-  auto new_session = std::make_unique<SpdySession>(
-      key, http_server_properties_, transport_security_state_,
-      ssl_config_service_, quic_supported_versions_,
-      enable_sending_initial_data_, enable_ping_based_connection_checking_,
-      support_ietf_format_quic_altsvc_, is_trusted_proxy,
-      session_max_recv_window_size_, initial_settings_, greased_http2_frame_,
-      time_func_, push_delegate_, network_quality_estimator_,
-      net_log.net_log());
+  new_session->InitializeWithSocket(std::move(socket_stream), connect_timing,
+                                    this);
 
-  new_session->InitializeWithSocket(std::move(connection), this);
-
-  base::WeakPtr<SpdySession> available_session = new_session->GetWeakPtr();
-  sessions_.insert(new_session.release());
-  MapKeyToAvailableSession(key, available_session);
-
-  net_log.AddEvent(
-      NetLogEventType::HTTP2_SESSION_POOL_IMPORTED_SESSION_FROM_SOCKET,
-      available_session->net_log().source().ToEventParametersCallback());
-
-  // Look up the IP address for this session so that we can match
-  // future sessions (potentially to different domains) which can
-  // potentially be pooled with this one. Because GetPeerAddress()
-  // reports the proxy's address instead of the origin server, check
-  // to see if this is a direct connection.
-  if (key.proxy_server().is_direct()) {
-    IPEndPoint address;
-    if (available_session->GetPeerAddress(&address) == OK)
-      aliases_.insert(AliasMap::value_type(address, key));
-  }
-
-  return available_session;
+  return InsertSession(key, std::move(new_session), net_log);
 }
 
 base::WeakPtr<SpdySession> SpdySessionPool::FindAvailableSession(
@@ -602,4 +592,46 @@
   }
 }
 
+std::unique_ptr<SpdySession> SpdySessionPool::CreateSession(
+    const SpdySessionKey& key,
+    bool is_trusted_proxy,
+    NetLog* net_log) {
+  UMA_HISTOGRAM_ENUMERATION("Net.SpdySessionGet", IMPORTED_FROM_SOCKET,
+                            SPDY_SESSION_GET_MAX);
+
+  return std::make_unique<SpdySession>(
+      key, http_server_properties_, transport_security_state_,
+      ssl_config_service_, quic_supported_versions_,
+      enable_sending_initial_data_, enable_ping_based_connection_checking_,
+      support_ietf_format_quic_altsvc_, is_trusted_proxy,
+      session_max_recv_window_size_, initial_settings_, greased_http2_frame_,
+      time_func_, push_delegate_, network_quality_estimator_, net_log);
+}
+
+base::WeakPtr<SpdySession> SpdySessionPool::InsertSession(
+    const SpdySessionKey& key,
+    std::unique_ptr<SpdySession> new_session,
+    const NetLogWithSource& source_net_log) {
+  base::WeakPtr<SpdySession> available_session = new_session->GetWeakPtr();
+  sessions_.insert(new_session.release());
+  MapKeyToAvailableSession(key, available_session);
+
+  source_net_log.AddEvent(
+      NetLogEventType::HTTP2_SESSION_POOL_IMPORTED_SESSION_FROM_SOCKET,
+      available_session->net_log().source().ToEventParametersCallback());
+
+  // Look up the IP address for this session so that we can match
+  // future sessions (potentially to different domains) which can
+  // potentially be pooled with this one. Because GetPeerAddress()
+  // reports the proxy's address instead of the origin server, check
+  // to see if this is a direct connection.
+  if (key.proxy_server().is_direct()) {
+    IPEndPoint address;
+    if (available_session->GetPeerAddress(&address) == OK)
+      aliases_.insert(AliasMap::value_type(address, key));
+  }
+
+  return available_session;
+}
+
 }  // namespace net
diff --git a/net/spdy/spdy_session_pool.h b/net/spdy/spdy_session_pool.h
index 4ff38a3f..755d0cd 100644
--- a/net/spdy/spdy_session_pool.h
+++ b/net/spdy/spdy_session_pool.h
@@ -20,6 +20,7 @@
 #include "base/optional.h"
 #include "net/base/host_port_pair.h"
 #include "net/base/ip_endpoint.h"
+#include "net/base/load_timing_info.h"
 #include "net/base/net_errors.h"
 #include "net/base/net_export.h"
 #include "net/base/network_change_notifier.h"
@@ -48,6 +49,7 @@
 class NetLogWithSource;
 class NetworkQualityEstimator;
 class SpdySession;
+class StreamSocket;
 class TransportSecurityState;
 
 // This is a very simple pool for open SpdySessions.
@@ -95,12 +97,28 @@
   // not already be a session for the given key.
   //
   // Returns the new SpdySession. Note that the SpdySession begins reading from
-  // |connection| on a subsequent event loop iteration, so it may be closed
-  // immediately afterwards if the first read of |connection| fails.
+  // |client_socket_handle| on a subsequent event loop iteration, so it may be
+  // closed immediately afterwards if the first read of |client_socket_handle|
+  // fails.
+  base::WeakPtr<SpdySession> CreateAvailableSessionFromSocketHandle(
+      const SpdySessionKey& key,
+      bool is_trusted_proxy,
+      std::unique_ptr<ClientSocketHandle> client_socket_handle,
+      const NetLogWithSource& net_log);
+
+  // Just like the above method, except it takes a SocketStream instead of a
+  // ClientSocketHandle, and separate connect timing information. When this
+  // constructor is used, there is no socket pool beneath the SpdySession.
+  // Instead, the session takes exclusive ownership of the underting socket, and
+  // destroying the session will directly destroy the socket, as opposed to
+  // disconnected it and then returning it to the socket pool. This is intended
+  // for use with H2 proxies, which are layered beneath the socket pools and
+  // can have sockets above them for tunnels, which are put in a socket pool.
   base::WeakPtr<SpdySession> CreateAvailableSessionFromSocket(
       const SpdySessionKey& key,
       bool is_trusted_proxy,
-      std::unique_ptr<ClientSocketHandle> connection,
+      std::unique_ptr<StreamSocket> socket_stream,
+      const LoadTimingInfo::ConnectTiming& connect_timing,
       const NetLogWithSource& net_log);
 
   // If there is an available session for |key|, return it.
@@ -254,6 +272,18 @@
                                   const std::string& description,
                                   bool idle_only);
 
+  // Creates a new session. The session must be initialized before
+  // InsertSession() is invoked.
+  std::unique_ptr<SpdySession> CreateSession(const SpdySessionKey& key,
+                                             bool is_trusted_proxy,
+                                             NetLog* net_log);
+  // Adds a new session previously created with CreateSession to the pool.
+  // |source_net_log| is the NetLog for the object that created the session.
+  base::WeakPtr<SpdySession> InsertSession(
+      const SpdySessionKey& key,
+      std::unique_ptr<SpdySession> new_session,
+      const NetLogWithSource& source_net_log);
+
   HttpServerProperties* http_server_properties_;
 
   TransportSecurityState* transport_security_state_;
diff --git a/net/spdy/spdy_stream.cc b/net/spdy/spdy_stream.cc
index 804ee2c..29fc187 100644
--- a/net/spdy/spdy_stream.cc
+++ b/net/spdy/spdy_stream.cc
@@ -20,6 +20,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/trace_event/memory_usage_estimator.h"
 #include "base/values.h"
+#include "net/base/load_timing_info.h"
 #include "net/log/net_log.h"
 #include "net/log/net_log_capture_mode.h"
 #include "net/log/net_log_event_type.h"
diff --git a/net/spdy/spdy_test_util_common.cc b/net/spdy/spdy_test_util_common.cc
index b3007f34..050a059 100644
--- a/net/spdy/spdy_test_util_common.cc
+++ b/net/spdy/spdy_test_util_common.cc
@@ -509,7 +509,7 @@
   EXPECT_THAT(rv, IsOk());
 
   base::WeakPtr<SpdySession> spdy_session =
-      http_session->spdy_session_pool()->CreateAvailableSessionFromSocket(
+      http_session->spdy_session_pool()->CreateAvailableSessionFromSocketHandle(
           key, is_trusted_proxy, std::move(connection), net_log);
   // Failure is reported asynchronously.
   EXPECT_TRUE(spdy_session);
@@ -612,7 +612,7 @@
   handle->SetSocket(std::make_unique<FakeSpdySessionClientSocket>(
       expected_status == OK ? ERR_IO_PENDING : expected_status));
   base::WeakPtr<SpdySession> spdy_session =
-      pool->CreateAvailableSessionFromSocket(
+      pool->CreateAvailableSessionFromSocketHandle(
           key,
           /*is_trusted_proxy=*/false, std::move(handle), NetLogWithSource());
   // Failure is reported asynchronously.
diff --git a/net/third_party/quic/platform/impl/quic_flag_utils_impl.h b/net/third_party/quic/platform/impl/quic_flag_utils_impl.h
index 417a980..3fa531e 100644
--- a/net/third_party/quic/platform/impl/quic_flag_utils_impl.h
+++ b/net/third_party/quic/platform/impl/quic_flag_utils_impl.h
@@ -8,12 +8,12 @@
 #include "base/logging.h"
 
 #define QUIC_RELOADABLE_FLAG_COUNT_IMPL(flag) \
-  DVLOG(1) << "FLAG_" #flag ": " << FLAGS_quic_reloadable_flag_##flag
+  DVLOG(2) << "FLAG_" #flag ": " << FLAGS_quic_reloadable_flag_##flag
 #define QUIC_RELOADABLE_FLAG_COUNT_N_IMPL(flag, instance, total) \
   QUIC_RELOADABLE_FLAG_COUNT_IMPL(flag)
 
 #define QUIC_RESTART_FLAG_COUNT_IMPL(flag) \
-  DVLOG(1) << "FLAG_" #flag ": " << FLAGS_quic_restart_flag_##flag
+  DVLOG(2) << "FLAG_" #flag ": " << FLAGS_quic_restart_flag_##flag
 #define QUIC_RESTART_FLAG_COUNT_N_IMPL(flag, instance, total) \
   QUIC_RESTART_FLAG_COUNT_IMPL(flag)
 
diff --git a/printing/pwg_raster_settings.h b/printing/pwg_raster_settings.h
index fe7ae4c..b1c3709 100644
--- a/printing/pwg_raster_settings.h
+++ b/printing/pwg_raster_settings.h
@@ -5,6 +5,8 @@
 #ifndef PRINTING_PWG_RASTER_SETTINGS_H_
 #define PRINTING_PWG_RASTER_SETTINGS_H_
 
+#include "printing/print_job_constants.h"
+
 namespace printing {
 
 enum PwgRasterTransformType {
@@ -16,6 +18,7 @@
 };
 
 struct PwgRasterSettings {
+  DuplexMode duplex_mode;
   // How to transform odd-numbered pages.
   PwgRasterTransformType odd_page_transform;
   // Rotate all pages (on top of odd-numbered page transform).
diff --git a/remoting/host/file_transfer/BUILD.gn b/remoting/host/file_transfer/BUILD.gn
index 5a530c65..d898537 100644
--- a/remoting/host/file_transfer/BUILD.gn
+++ b/remoting/host/file_transfer/BUILD.gn
@@ -46,6 +46,7 @@
     "ensure_user.h",
     "file_chooser.h",
     "file_chooser_common_win.h",
+    "file_chooser_mac.mm",
     "file_chooser_main_win.cc",
     "file_chooser_win.cc",
     "file_transfer_message_handler.cc",
diff --git a/remoting/host/file_transfer/file_chooser_mac.mm b/remoting/host/file_transfer/file_chooser_mac.mm
new file mode 100644
index 0000000..45f7389e
--- /dev/null
+++ b/remoting/host/file_transfer/file_chooser_mac.mm
@@ -0,0 +1,168 @@
+// 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 "remoting/host/file_transfer/file_chooser.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/sequence_bound.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "remoting/base/string_resources.h"
+#include "ui/base/l10n/l10n_util_mac.h"
+
+@interface FileTransferOpenPanelDelegate : NSObject <NSOpenSavePanelDelegate> {
+}
+- (BOOL)panel:(id)sender shouldEnableURL:(NSURL*)url;
+- (BOOL)panel:(id)sender validateURL:(NSURL*)url error:(NSError**)outError;
+@end
+
+@implementation FileTransferOpenPanelDelegate
+- (BOOL)panel:(id)sender shouldEnableURL:(NSURL*)url {
+  return [url isFileURL];
+}
+
+- (BOOL)panel:(id)sender validateURL:(NSURL*)url error:(NSError**)outError {
+  // Refuse to accept users closing the dialog with a key repeat, since the key
+  // may have been first pressed while the user was looking at something else.
+  if ([[NSApp currentEvent] type] == NSKeyDown &&
+      [[NSApp currentEvent] isARepeat]) {
+    return NO;
+  }
+
+  return YES;
+}
+@end
+
+namespace remoting {
+
+namespace {
+
+class FileChooserMac;
+
+class MacFileChooserOnUiThread {
+ public:
+  MacFileChooserOnUiThread(
+      scoped_refptr<base::SequencedTaskRunner> caller_task_runner,
+      base::WeakPtr<FileChooserMac> file_chooser_mac);
+
+  ~MacFileChooserOnUiThread();
+
+  void Show();
+
+ private:
+  void RunCallback(FileChooser::Result result);
+
+  base::scoped_nsobject<FileTransferOpenPanelDelegate> delegate_;
+  base::scoped_nsobject<NSOpenPanel> open_panel_;
+  scoped_refptr<base::SequencedTaskRunner> caller_task_runner_;
+  base::WeakPtr<FileChooserMac> file_chooser_mac_;
+
+  DISALLOW_COPY_AND_ASSIGN(MacFileChooserOnUiThread);
+};
+
+class FileChooserMac : public FileChooser {
+ public:
+  FileChooserMac(scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
+                 ResultCallback callback);
+
+  ~FileChooserMac() override;
+
+  // FileChooser implementation.
+  void Show() override;
+
+  void RunCallback(FileChooser::Result result);
+
+ private:
+  FileChooser::ResultCallback callback_;
+  base::SequenceBound<MacFileChooserOnUiThread> mac_file_chooser_on_ui_thread_;
+  base::WeakPtrFactory<FileChooserMac> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileChooserMac);
+};
+
+MacFileChooserOnUiThread::MacFileChooserOnUiThread(
+    scoped_refptr<base::SequencedTaskRunner> caller_task_runner,
+    base::WeakPtr<FileChooserMac> file_chooser_mac)
+    : delegate_([FileTransferOpenPanelDelegate new]),
+      caller_task_runner_(std::move(caller_task_runner)),
+      file_chooser_mac_(std::move(file_chooser_mac)) {}
+
+MacFileChooserOnUiThread::~MacFileChooserOnUiThread() {
+  if (open_panel_) {
+    // Will synchronously invoke completion handler.
+    [open_panel_ cancel:open_panel_];
+  }
+}
+
+void MacFileChooserOnUiThread::Show() {
+  DCHECK(!open_panel_);
+  open_panel_.reset([NSOpenPanel openPanel], base::scoped_policy::RETAIN);
+  [open_panel_
+      setMessage:l10n_util::GetNSString(IDS_DOWNLOAD_FILE_DIALOG_TITLE)];
+  [open_panel_ setAllowsMultipleSelection:NO];
+  [open_panel_ setCanChooseFiles:YES];
+  [open_panel_ setCanChooseDirectories:NO];
+  [open_panel_ setDelegate:delegate_];
+  [open_panel_ beginWithCompletionHandler:^(NSModalResponse result) {
+    if (result == NSFileHandlingPanelOKButton) {
+      NSURL* url = [[open_panel_ URLs] objectAtIndex:0];
+      if (![url isFileURL]) {
+        // Delegate should prevent this.
+        RunCallback(protocol::MakeFileTransferError(
+            FROM_HERE, protocol::FileTransfer_Error_Type_UNEXPECTED_ERROR));
+      }
+      RunCallback(base::mac::NSStringToFilePath([url path]));
+    } else {
+      RunCallback(protocol::MakeFileTransferError(
+          FROM_HERE, protocol::FileTransfer_Error_Type_CANCELED));
+    }
+    open_panel_.reset();
+  }];
+  // Bring to front.
+  [NSApp activateIgnoringOtherApps:YES];
+}
+
+void MacFileChooserOnUiThread::RunCallback(FileChooser::Result result) {
+  caller_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&FileChooserMac::RunCallback, file_chooser_mac_,
+                                std::move(result)));
+}
+
+FileChooserMac::FileChooserMac(
+    scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
+    ResultCallback callback)
+    : callback_(std::move(callback)), weak_ptr_factory_(this) {
+  mac_file_chooser_on_ui_thread_ =
+      base::SequenceBound<MacFileChooserOnUiThread>(
+          ui_task_runner, base::SequencedTaskRunnerHandle::Get(),
+          weak_ptr_factory_.GetWeakPtr());
+}
+
+void FileChooserMac::Show() {
+  mac_file_chooser_on_ui_thread_.Post(FROM_HERE,
+                                      &MacFileChooserOnUiThread::Show);
+}
+
+void FileChooserMac::RunCallback(FileChooser::Result result) {
+  std::move(callback_).Run(std::move(result));
+}
+
+FileChooserMac::~FileChooserMac() = default;
+
+}  // namespace
+
+std::unique_ptr<FileChooser> FileChooser::Create(
+    scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
+    ResultCallback callback) {
+  return std::make_unique<FileChooserMac>(std::move(ui_task_runner),
+                                          std::move(callback));
+}
+
+}  // namespace remoting
diff --git a/services/device/public/cpp/hid/hid_collection.cc b/services/device/public/cpp/hid/hid_collection.cc
index 473c5cf65..60c7b34 100644
--- a/services/device/public/cpp/hid/hid_collection.cc
+++ b/services/device/public/cpp/hid/hid_collection.cc
@@ -5,6 +5,7 @@
 #include "services/device/public/cpp/hid/hid_collection.h"
 
 #include <algorithm>
+#include <limits>
 #include <utility>
 
 #include "base/memory/ptr_util.h"
@@ -13,6 +14,20 @@
 
 namespace device {
 
+namespace {
+// The maximum value of the report size for a single item in a HID report is 32
+// bits. From the Device Class Definition for HID v1.11, sec. 8.2: "An item
+// field cannot span more than 4 bytes in a report. For example, a 32-bit item
+// must start on a byte boundary to satisfy this condition."
+static constexpr uint32_t kMaxItemReportSizeBits = 32;
+
+// On Windows, HID report length is reported (in bytes) as a USHORT which
+// imposes a practical limit of 2^16-1 bytes. Apply the same upper limit when
+// computing the maximum report size.
+static constexpr uint64_t kMaxReasonableReportLengthBits =
+    std::numeric_limits<uint16_t>::max() * 8;
+}  // namespace
+
 HidCollection::HidCollection(HidCollection* parent,
                              uint32_t usage_page,
                              uint32_t usage,
@@ -203,10 +218,30 @@
   for (const auto& entry : report_lists) {
     entry.max_report_bits = 0;
     for (const auto& report : entry.reports) {
-      size_t report_bits = 0;
-      for (const auto& item : report.second)
-        report_bits += item->GetReportSize() * item->GetReportCount();
-      entry.max_report_bits = std::max(entry.max_report_bits, report_bits);
+      uint64_t report_bits = 0;
+      for (const auto& item : report.second) {
+        uint64_t report_size = item->GetReportSize();
+        // Skip reports with items that have invalid report sizes.
+        if (report_size > kMaxItemReportSizeBits) {
+          report_bits = 0;
+          break;
+        }
+        // Report size and report count are both 32-bit values. A 64-bit integer
+        // type is needed to avoid overflow when computing the product.
+        uint64_t report_count = item->GetReportCount();
+        uint64_t item_bits = report_size * report_count;
+        // Ignore this report if adding the size of this item would extend the
+        // total report length beyond the reasonable maximum.
+        if (item_bits > kMaxReasonableReportLengthBits ||
+            report_bits > kMaxReasonableReportLengthBits - item_bits) {
+          report_bits = 0;
+          break;
+        }
+        report_bits += item_bits;
+      }
+      DCHECK_LE(report_bits, kMaxReasonableReportLengthBits);
+      entry.max_report_bits =
+          std::max(entry.max_report_bits, size_t{report_bits});
     }
   }
   return collection_info;
diff --git a/services/device/public/cpp/hid/hid_report_descriptor_unittest.cc b/services/device/public/cpp/hid/hid_report_descriptor_unittest.cc
index 3f63b6a..ff83d0b 100644
--- a/services/device/public/cpp/hid/hid_report_descriptor_unittest.cc
+++ b/services/device/public/cpp/hid/hid_report_descriptor_unittest.cc
@@ -1596,4 +1596,86 @@
                       kBelkinNostromoMouseAndExtraSize);
 }
 
+TEST_F(HidReportDescriptorTest, InvalidReportSizeIgnored) {
+  // Report size can be at most 32 bits. Make sure a report item with invalid
+  // size does not affect the maximum report size. The descriptor below
+  // describes a report with one 64-bit constant field.
+  static const uint8_t kInvalidReportSizeDescriptor[] = {
+      0xA0,        // Collection
+      0x95, 0x01,  //   Report Count (1)
+      0x75, 0x40,  //   Report Size (64)
+      0x90         //   Output
+  };
+  static const size_t kInvalidReportSizeDescriptorSize =
+      base::size(kInvalidReportSizeDescriptor);
+  auto info = HidCollectionInfo::New();
+  info->usage = HidUsageAndPage::New(0, 0);
+  AddTopCollectionInfo(std::move(info));
+  // Maximum report sizes should not be affected by the invalid report item.
+  ValidateDetails(false, 0, 0, 0, kInvalidReportSizeDescriptor,
+                  kInvalidReportSizeDescriptorSize);
+
+  // The report item with invalid size should still be included in the
+  // collection info.
+  auto* top = AddTopCollection(0, kCollectionTypePhysical);
+  SetReportSizeAndCount(64, 1);
+  AddReportConstant(top, kOutput, kNonNullableArray);
+  ValidateCollections(kInvalidReportSizeDescriptor,
+                      kInvalidReportSizeDescriptorSize);
+}
+
+TEST_F(HidReportDescriptorTest, ReasonablyHugeReportNotIgnored) {
+  // The descriptor below defines a 2^16-1 byte output report. Larger reports
+  // are considered unreasonable and are ignored in the max report size
+  // calculation.
+  static const uint8_t kReasonablyHugeReportDescriptor[] = {
+      0xA0,              // Collection
+      0x96, 0xff, 0xff,  //   Report Count (65535)
+      0x75, 0x08,        //   Report Size (8)
+      0x90               //   Output
+  };
+  static const size_t kReasonablyHugeReportDescriptorSize =
+      base::size(kReasonablyHugeReportDescriptor);
+  auto info = HidCollectionInfo::New();
+  info->usage = HidUsageAndPage::New(0, 0);
+  AddTopCollectionInfo(std::move(info));
+  // Maximum report sizes should include the huge report.
+  ValidateDetails(false, 0, 65535, 0, kReasonablyHugeReportDescriptor,
+                  kReasonablyHugeReportDescriptorSize);
+
+  auto* top = AddTopCollection(0, kCollectionTypePhysical);
+  SetReportSizeAndCount(8, 65535);
+  AddReportConstant(top, kOutput, kNonNullableArray);
+  ValidateCollections(kReasonablyHugeReportDescriptor,
+                      kReasonablyHugeReportDescriptorSize);
+}
+
+TEST_F(HidReportDescriptorTest, UnreasonablyHugeReportIgnored) {
+  // The descriptor below defines a 2^16 byte output report. The report is
+  // larger than the maximum report size considered reasonable and will be
+  // ignored when computing the max report size.
+  static const uint8_t kUnreasonablyHugeReportDescriptor[] = {
+      0xA0,                          // Collection
+      0x97, 0x00, 0x00, 0x01, 0x00,  //   Report Count (65536)
+      0x75, 0x08,                    //   Report Size (8)
+      0x90                           //   Output
+  };
+  static const size_t kUnreasonablyHugeReportDescriptorSize =
+      base::size(kUnreasonablyHugeReportDescriptor);
+  auto info = HidCollectionInfo::New();
+  info->usage = HidUsageAndPage::New(0, 0);
+  AddTopCollectionInfo(std::move(info));
+  // Maximum report sizes should not be affected by the huge report.
+  ValidateDetails(false, 0, 0, 0, kUnreasonablyHugeReportDescriptor,
+                  kUnreasonablyHugeReportDescriptorSize);
+
+  // The unreasonably huge report item should still be included in the
+  // collection info.
+  auto* top = AddTopCollection(0, kCollectionTypePhysical);
+  SetReportSizeAndCount(8, 65536);
+  AddReportConstant(top, kOutput, kNonNullableArray);
+  ValidateCollections(kUnreasonablyHugeReportDescriptor,
+                      kUnreasonablyHugeReportDescriptorSize);
+}
+
 }  // namespace device
diff --git a/services/device/public/cpp/hid/hid_report_item.h b/services/device/public/cpp/hid/hid_report_item.h
index eae86f8..44d1d9dd 100644
--- a/services/device/public/cpp/hid/hid_report_item.h
+++ b/services/device/public/cpp/hid/hid_report_item.h
@@ -81,10 +81,10 @@
   bool HasNull() const { return report_info_.null; }
 
   // Returns the width of each field defined by this item, in bits.
-  uint16_t GetReportSize() const { return global_.report_size; }
+  uint32_t GetReportSize() const { return global_.report_size; }
 
   // Returns the number of fields defined by this item.
-  uint16_t GetReportCount() const { return global_.report_count; }
+  uint32_t GetReportCount() const { return global_.report_count; }
 
   // Returns a 32-bit value representing a unit definition for the current item,
   // or 0 if the item is not assigned a unit.
diff --git a/services/service_manager/public/cpp/connector.h b/services/service_manager/public/cpp/connector.h
index faba682..3fc1ebd 100644
--- a/services/service_manager/public/cpp/connector.h
+++ b/services/service_manager/public/cpp/connector.h
@@ -169,11 +169,11 @@
   }
 
   template <typename Interface>
-  void BindInterface(const std::string& service_name,
+  void BindInterface(const ServiceFilter& filter,
                      mojo::InterfaceRequest<Interface> request,
                      mojom::BindInterfacePriority priority) {
-    return BindInterface(ServiceFilter::ByName(service_name), Interface::Name_,
-                         request.PassMessagePipe(), priority, nullptr);
+    return BindInterface(filter, Interface::Name_, request.PassMessagePipe(),
+                         priority, {});
   }
 
   void BindInterface(const ServiceFilter& filter,
diff --git a/services/tracing/public/cpp/perfetto/trace_event_data_source.cc b/services/tracing/public/cpp/perfetto/trace_event_data_source.cc
index c50862b3..6d5525c6 100644
--- a/services/tracing/public/cpp/perfetto/trace_event_data_source.cc
+++ b/services/tracing/public/cpp/perfetto/trace_event_data_source.cc
@@ -130,8 +130,10 @@
  public:
   ThreadLocalEventSink(
       std::unique_ptr<perfetto::StartupTraceWriter> trace_writer,
+      perfetto::BufferID target_buffer,
       bool thread_will_flush)
       : trace_writer_(std::move(trace_writer)),
+        target_buffer_(target_buffer),
         thread_will_flush_(thread_will_flush) {
 #if DCHECK_IS_ON()
     static std::atomic<int32_t> id_counter(1);
@@ -432,8 +434,11 @@
     trace_writer_->Flush();
   }
 
+  perfetto::BufferID target_buffer() const { return target_buffer_; }
+
  private:
   std::unique_ptr<perfetto::StartupTraceWriter> trace_writer_;
+  perfetto::BufferID target_buffer_;
   const bool thread_will_flush_;
   ChromeEventBundleHandle event_bundle_;
   perfetto::TraceWriter::TracePacketHandle trace_packet_handle_;
@@ -504,7 +509,8 @@
 
     DCHECK(!producer_client_);
     producer_client_ = producer_client;
-    target_buffer_ = data_source_config.target_buffer();
+    target_buffer_.store(data_source_config.target_buffer(),
+                         std::memory_order_relaxed);
     // Reduce lock contention by binding the registry without holding the lock.
     unbound_writer_registry = std::move(startup_writer_registry_);
   }
@@ -552,7 +558,7 @@
     base::AutoLock lock(lock_);
     DCHECK(producer_client_);
     producer_client_ = nullptr;
-    target_buffer_ = 0;
+    target_buffer_.store(0, std::memory_order_relaxed);
   }
 
   if (was_enabled) {
@@ -629,12 +635,13 @@
   if (startup_writer_registry_) {
     return new ThreadLocalEventSink(
         startup_writer_registry_->CreateUnboundTraceWriter(),
-        thread_will_flush);
+        target_buffer_.load(std::memory_order_relaxed), thread_will_flush);
   } else if (producer_client_) {
     return new ThreadLocalEventSink(
         std::make_unique<perfetto::StartupTraceWriter>(
-            producer_client_->CreateTraceWriter(target_buffer_)),
-        thread_will_flush);
+            producer_client_->CreateTraceWriter(
+                target_buffer_.load(std::memory_order_relaxed))),
+        target_buffer_.load(std::memory_order_relaxed), thread_will_flush);
   } else {
     return nullptr;
   }
@@ -648,6 +655,24 @@
   auto* thread_local_event_sink =
       static_cast<ThreadLocalEventSink*>(ThreadLocalEventSinkSlot()->Get());
 
+  // Make sure the sink was reset since the last tracing session. Normally, it
+  // is reset on Flush after the session is disabled. However, it may not have
+  // been reset if the current thread doesn't support flushing. In that case, we
+  // need to check here that it writes to the right buffer.
+  //
+  // Because we want to avoid locking for each event, we access |target_buffer_|
+  // racily. It's OK if we don't see it change to the new buffer immediately. In
+  // that case, the first few trace events may get lost, but we will eventually
+  // notice that we are writing to the wrong buffer once the change to
+  // |target_buffer_| has propagated, and reset the sink. Note we will still
+  // acquire the |lock_| to safely recreate the sink in
+  // CreateThreadLocalEventSink().
+  if (!thread_will_flush && thread_local_event_sink &&
+      GetInstance()->target_buffer_.load(std::memory_order_relaxed) !=
+          thread_local_event_sink->target_buffer()) {
+    thread_local_event_sink = nullptr;
+  }
+
   if (!thread_local_event_sink) {
     thread_local_event_sink =
         GetInstance()->CreateThreadLocalEventSink(thread_will_flush);
diff --git a/services/tracing/public/cpp/perfetto/trace_event_data_source.h b/services/tracing/public/cpp/perfetto/trace_event_data_source.h
index a1f9eb7..b627427 100644
--- a/services/tracing/public/cpp/perfetto/trace_event_data_source.h
+++ b/services/tracing/public/cpp/perfetto/trace_event_data_source.h
@@ -121,7 +121,11 @@
   base::OnceClosure stop_complete_callback_;
 
   base::Lock lock_;  // Protects subsequent members.
-  uint32_t target_buffer_ = 0;
+  // Regular accesses to |target_buffer_| in conjunction with other fields are
+  // protected by |lock_|, thus no memory order guarantees are required when
+  // writing or reading it in those places. Note that OnAddTraceEvent()
+  // accesses its atomic value racily without holding |lock_|.
+  std::atomic<uint32_t> target_buffer_{0};
   ProducerClient* producer_client_ = nullptr;
   // We own the registry during startup, but transfer its ownership to the
   // ProducerClient once the perfetto service is available. Only set if
diff --git a/services/tracing/tracing_service.cc b/services/tracing/tracing_service.cc
index 23c4f31..86f0de0 100644
--- a/services/tracing/tracing_service.cc
+++ b/services/tracing/tracing_service.cc
@@ -46,7 +46,8 @@
     mojom::TracedProcessPtr traced_process;
     connector_->BindInterface(
         service_manager::ServiceFilter::ForExactIdentity(identity),
-        &traced_process);
+        mojo::MakeRequest(&traced_process),
+        service_manager::mojom::BindInterfacePriority::kBestEffort);
 
     auto new_connection_request = mojom::ConnectToTracingRequest::New();
 
diff --git a/skia/ext/fontmgr_default_fuchsia.cc b/skia/ext/fontmgr_default_fuchsia.cc
index 2e6811d..4c09946 100644
--- a/skia/ext/fontmgr_default_fuchsia.cc
+++ b/skia/ext/fontmgr_default_fuchsia.cc
@@ -6,7 +6,7 @@
 
 #include <fuchsia/fonts/cpp/fidl.h>
 
-#include "base/fuchsia/component_context.h"
+#include "base/fuchsia/service_directory_client.h"
 #include "third_party/skia/include/core/SkFontMgr.h"
 #include "third_party/skia/include/ports/SkFontMgr_fuchsia.h"
 
@@ -14,7 +14,7 @@
 
 SK_API sk_sp<SkFontMgr> CreateDefaultSkFontMgr() {
   return SkFontMgr_New_Fuchsia(
-      base::fuchsia::ComponentContext::GetDefault()
+      base::fuchsia::ServiceDirectoryClient::ForCurrentProcess()
           ->ConnectToServiceSync<fuchsia::fonts::Provider>());
 }
 
diff --git a/skia/ext/fontmgr_fuchsia_unittest.cc b/skia/ext/fontmgr_fuchsia_unittest.cc
index a5381b8..f49be123 100644
--- a/skia/ext/fontmgr_fuchsia_unittest.cc
+++ b/skia/ext/fontmgr_fuchsia_unittest.cc
@@ -5,7 +5,7 @@
 #include <fuchsia/fonts/cpp/fidl.h>
 #include <lib/fidl/cpp/binding.h>
 
-#include "base/fuchsia/component_context.h"
+#include "base/fuchsia/service_directory_client.h"
 #include "base/path_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/include/core/SkFontMgr.h"
@@ -19,7 +19,7 @@
  public:
   FuchsiaFontManagerTest() {
     font_manager_ = SkFontMgr_New_Fuchsia(
-        base::fuchsia::ComponentContext::GetDefault()
+        base::fuchsia::ServiceDirectoryClient::ForCurrentProcess()
             ->ConnectToServiceSync<fuchsia::fonts::Provider>());
   }
 
diff --git a/sql/database.cc b/sql/database.cc
index e820382..3586bef 100644
--- a/sql/database.cc
+++ b/sql/database.cc
@@ -179,8 +179,6 @@
 }
 
 void Database::ReportDiagnosticInfo(int extended_error, Statement* stmt) {
-  AssertIOAllowed();
-
   std::string debug_info = GetDiagnosticInfo(extended_error, stmt);
   if (!debug_info.empty() && RegisterIntentToUpload()) {
     DEBUG_ALIAS_FOR_CSTR(debug_buf, debug_info.c_str(), 2000);
@@ -231,14 +229,15 @@
 
 void Database::StatementRef::Close(bool forced) {
   if (stmt_) {
-    // Call to AssertIOAllowed() cannot go at the beginning of the function
-    // because Close() is called unconditionally from destructor to clean
-    // database_. And if this is inactive statement this won't cause any
-    // disk access and destructor most probably will be called on thread
-    // not allowing disk access.
+    // Call to InitScopedBlockingCall() cannot go at the beginning of the
+    // function because Close() is called unconditionally from destructor to
+    // clean database_. And if this is inactive statement this won't cause any
+    // disk access and destructor most probably will be called on thread not
+    // allowing disk access.
     // TODO(paivanof@gmail.com): This should move to the beginning
     // of the function. http://crbug.com/136655.
-    AssertIOAllowed();
+    base::Optional<base::ScopedBlockingCall> scoped_blocking_call;
+    InitScopedBlockingCall(&scoped_blocking_call);
     sqlite3_finalize(stmt_);
     stmt_ = nullptr;
   }
@@ -385,13 +384,14 @@
   open_statements_.clear();
 
   if (db_) {
-    // Call to AssertIOAllowed() cannot go at the beginning of the function
-    // because Close() must be called from destructor to clean
+    // Call to InitScopedBlockingCall() cannot go at the beginning of the
+    // function because Close() must be called from destructor to clean
     // statement_cache_, it won't cause any disk access and it most probably
     // will happen on thread not allowing disk access.
     // TODO(paivanof@gmail.com): This should move to the beginning
     // of the function. http://crbug.com/136655.
-    AssertIOAllowed();
+    base::Optional<base::ScopedBlockingCall> scoped_blocking_call;
+    InitScopedBlockingCall(&scoped_blocking_call);
 
     // Reseting acquires a lock to ensure no dump is happening on the database
     // at the same time. Unregister takes ownership of provider and it is safe
@@ -426,7 +426,8 @@
 }
 
 void Database::Preload() {
-  AssertIOAllowed();
+  base::Optional<base::ScopedBlockingCall> scoped_blocking_call;
+  InitScopedBlockingCall(&scoped_blocking_call);
 
   if (!db_) {
     DCHECK(poisoned_) << "Cannot preload null db";
@@ -557,8 +558,6 @@
   static const char* kDiagnosticDumpsKey = "DiagnosticDumps";
   static int kVersion = 1;
 
-  AssertIOAllowed();
-
   if (histogram_tag_.empty())
     return false;
 
@@ -740,8 +739,6 @@
 // TODO(shess): Since this is only called in an error situation, it might be
 // prudent to rewrite in terms of SQLite API calls, and mark the function const.
 std::string Database::CollectCorruptionInfo() {
-  AssertIOAllowed();
-
   // If the file cannot be accessed it is unlikely that an integrity check will
   // turn up actionable information.
   const base::FilePath db_path = DbPath();
@@ -825,7 +822,8 @@
 }
 
 size_t Database::GetAppropriateMmapSize() {
-  AssertIOAllowed();
+  base::Optional<base::ScopedBlockingCall> scoped_blocking_call;
+  InitScopedBlockingCall(&scoped_blocking_call);
 
   // How much to map if no errors are found.  50MB encompasses the 99th
   // percentile of Chrome databases in the wild, so this should be good.
@@ -968,7 +966,8 @@
 // Create an in-memory database with the existing database's page
 // size, then backup that database over the existing database.
 bool Database::Raze() {
-  AssertIOAllowed();
+  base::Optional<base::ScopedBlockingCall> scoped_blocking_call;
+  InitScopedBlockingCall(&scoped_blocking_call);
 
   if (!db_) {
     DCHECK(poisoned_) << "Cannot raze null db";
@@ -1269,7 +1268,9 @@
 // caller wishes to execute multiple statements, that should be explicit, and
 // perhaps tucked into an explicit transaction with rollback in case of error.
 int Database::ExecuteAndReturnErrorCode(const char* sql) {
-  AssertIOAllowed();
+  base::Optional<base::ScopedBlockingCall> scoped_blocking_call;
+  InitScopedBlockingCall(&scoped_blocking_call);
+
   if (!db_) {
     DCHECK(poisoned_) << "Illegal use of Database without a db";
     return SQLITE_ERROR;
@@ -1396,7 +1397,8 @@
 scoped_refptr<Database::StatementRef> Database::GetStatementImpl(
     sql::Database* tracking_db,
     const char* sql) const {
-  AssertIOAllowed();
+  base::Optional<base::ScopedBlockingCall> scoped_blocking_call;
+  InitScopedBlockingCall(&scoped_blocking_call);
   DCHECK(sql);
   DCHECK(!tracking_db || tracking_db == this);
 
@@ -1446,7 +1448,8 @@
 }
 
 bool Database::IsSQLValid(const char* sql) {
-  AssertIOAllowed();
+  base::Optional<base::ScopedBlockingCall> scoped_blocking_call;
+  InitScopedBlockingCall(&scoped_blocking_call);
   if (!db_) {
     DCHECK(poisoned_) << "Illegal use of Database without a db";
     return false;
@@ -1542,7 +1545,8 @@
 
 bool Database::OpenInternal(const std::string& file_name,
                             Database::Retry retry_flag) {
-  AssertIOAllowed();
+  base::Optional<base::ScopedBlockingCall> scoped_blocking_call;
+  InitScopedBlockingCall(&scoped_blocking_call);
 
   if (db_) {
     DLOG(DCHECK) << "sql::Database is already open.";
diff --git a/sql/database.h b/sql/database.h
index f4b6b29..4ea5d77 100644
--- a/sql/database.h
+++ b/sql/database.h
@@ -20,9 +20,9 @@
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "base/optional.h"
 #include "base/sequence_checker.h"
 #include "base/threading/scoped_blocking_call.h"
-#include "base/threading/thread_restrictions.h"
 #include "base/time/tick_clock.h"
 #include "sql/internal_api_token.h"
 #include "sql/statement_id.h"
@@ -554,12 +554,12 @@
   // |forced| indicates that orderly-shutdown checks should not apply.
   void CloseInternal(bool forced);
 
-  // Check whether the current thread is allowed to make IO calls, but only
-  // if database wasn't open in memory. Function is inlined to be a no-op in
-  // official build.
-  void AssertIOAllowed() const {
+  // Construct a ScopedBlockingCall to annotate IO calls, but only if
+  // database wasn't open in memory.
+  void InitScopedBlockingCall(
+      base::Optional<base::ScopedBlockingCall>* scoped_blocking_call) const {
     if (!in_memory_)
-      base::AssertBlockingAllowedDeprecated();
+      scoped_blocking_call->emplace(base::BlockingType::MAY_BLOCK);
   }
 
   // Internal helper for Does*Exist() functions.
@@ -622,11 +622,12 @@
     // orderly-shutdown checks should apply (see Database::RazeAndClose()).
     void Close(bool forced);
 
-    // Check whether the current thread is allowed to make IO calls, but only
-    // if database wasn't open in memory.
-    void AssertIOAllowed() const {
+    // Construct a ScopedBlockingCall to annotate IO calls, but only if
+    // database wasn't open in memory.
+    void InitScopedBlockingCall(
+        base::Optional<base::ScopedBlockingCall>* scoped_blocking_call) const {
       if (database_)
-        database_->AssertIOAllowed();
+        database_->InitScopedBlockingCall(scoped_blocking_call);
     }
 
    private:
diff --git a/sql/statement.cc b/sql/statement.cc
index e1f8509..1ae42a1 100644
--- a/sql/statement.cc
+++ b/sql/statement.cc
@@ -54,7 +54,8 @@
 }
 
 int Statement::StepInternal(bool timer_flag) {
-  ref_->AssertIOAllowed();
+  base::Optional<base::ScopedBlockingCall> scoped_blocking_call;
+  ref_->InitScopedBlockingCall(&scoped_blocking_call);
   if (!CheckValid())
     return SQLITE_ERROR;
 
@@ -98,7 +99,8 @@
 }
 
 void Statement::Reset(bool clear_bound_vars) {
-  ref_->AssertIOAllowed();
+  base::Optional<base::ScopedBlockingCall> scoped_blocking_call;
+  ref_->InitScopedBlockingCall(&scoped_blocking_call);
   if (is_valid()) {
     if (clear_bound_vars)
       sqlite3_clear_bindings(ref_->stmt());
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 224d3f5c..7e9331e 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -6701,6 +6701,14 @@
         }
       },
       {
+        "isolate_name": "webdriver_wpt_tests",
+        "name": "webdriver_tests_suite",
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        }
+      },
+      {
         "args": [
           "--num-retries=3"
         ],
@@ -10860,6 +10868,14 @@
         }
       },
       {
+        "isolate_name": "webdriver_wpt_tests",
+        "name": "webdriver_tests_suite",
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        }
+      },
+      {
         "args": [
           "--num-retries=3"
         ],
diff --git a/testing/buildbot/chromium.linux.json b/testing/buildbot/chromium.linux.json
index d450cf9..ace672ef 100644
--- a/testing/buildbot/chromium.linux.json
+++ b/testing/buildbot/chromium.linux.json
@@ -1735,6 +1735,14 @@
         }
       },
       {
+        "isolate_name": "webdriver_wpt_tests",
+        "name": "webdriver_tests_suite",
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        }
+      },
+      {
         "args": [
           "--num-retries=3"
         ],
@@ -2456,6 +2464,14 @@
         }
       },
       {
+        "isolate_name": "webdriver_wpt_tests",
+        "name": "webdriver_tests_suite",
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        }
+      },
+      {
         "args": [
           "--num-retries=3",
           "--debug"
@@ -3133,6 +3149,14 @@
         }
       },
       {
+        "isolate_name": "webdriver_wpt_tests",
+        "name": "webdriver_tests_suite",
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        }
+      },
+      {
         "isolate_name": "webkit_python_tests",
         "name": "webkit_python_tests",
         "swarming": {
@@ -4399,6 +4423,19 @@
         }
       },
       {
+        "isolate_name": "webdriver_wpt_tests",
+        "name": "webdriver_tests_suite",
+        "results_handler": "layout tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-16.04"
+            }
+          ]
+        }
+      },
+      {
         "args": [
           "--num-retries=3"
         ],
diff --git a/testing/buildbot/chromium.perf.fyi.json b/testing/buildbot/chromium.perf.fyi.json
index 4b7f49e..784671b0 100644
--- a/testing/buildbot/chromium.perf.fyi.json
+++ b/testing/buildbot/chromium.perf.fyi.json
@@ -272,7 +272,7 @@
           "--benchmarks=loading.desktop.network_service",
           "-v",
           "--upload-results",
-          "--output-format=histograms",
+          "--output-format=chartjson",
           "--browser=release"
         ],
         "isolate_name": "performance_test_suite",
diff --git a/testing/buildbot/filters/webui_polymer2_interactive_ui_tests.filter b/testing/buildbot/filters/webui_polymer2_interactive_ui_tests.filter
index 308818c..3257471 100644
--- a/testing/buildbot/filters/webui_polymer2_interactive_ui_tests.filter
+++ b/testing/buildbot/filters/webui_polymer2_interactive_ui_tests.filter
@@ -43,6 +43,7 @@
 CrElementsInputTest.All
 CrElementsProfileAvatarSelectorFocusTest.All
 CrElementsToggleTest.All
+CrExtensionsOptionsPageTest.All
 CrSettingsAnimatedPagesTest.All
 CrSettingsFocusRowBehavior.FocusTest
 CrSettingsSyncPageTest.All
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index 25e996a..cf813ff 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -578,6 +578,17 @@
     "script": "//testing/xvfb.py",
     "type": "script",
   },
+  "webdriver_wpt_tests": {
+    "label": "//:webdriver_wpt_tests",
+    "type": "script",
+    "script": "//testing/xvfb.py",
+    "args": [
+      "../../chrome/test/chromedriver/test/run_webdriver_tests.py",
+      "--chromedriver=chromedriver",
+      "--isolated-script-test-output=${ISOLATED_OUTDIR}/results.json",
+      "--test-path=../../third_party/blink/web_tests/external/wpt/webdriver/tests/navigate_to/navigate.py",
+    ],
+  },
   "chromedriver_replay_unittests": {
     "label": "//chrome/test/chromedriver:chromedriver_replay_unittests",
     "script": "//chrome/test/chromedriver/log_replay/client_replay_unittest.py",
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 7d7faa4c..27c8800 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -3705,6 +3705,13 @@
           'shards': 10,
         },
       },
+      'webdriver_tests_suite': {
+        'isolate_name': 'webdriver_wpt_tests',
+        'results_handler': 'layout tests',
+        'swarming': {
+          'shards': 1,
+        },
+      },
     },
 
     'mac_specific_chromium_gtests': {
diff --git a/third_party/blink/public/mojom/frame/lifecycle.mojom b/third_party/blink/public/mojom/frame/lifecycle.mojom
index 4a97cc5..c29efbc 100644
--- a/third_party/blink/public/mojom/frame/lifecycle.mojom
+++ b/third_party/blink/public/mojom/frame/lifecycle.mojom
@@ -13,3 +13,17 @@
   // Not visible, no layout object created. ie. display: none
   kNotRendered,
 };
+
+// The frame lifecycle state.
+enum FrameLifecycleState {
+  // State is normal running.
+  kRunning,
+  // Paused state is used for nested event loops. Does not fire
+  // frozen, resumed events on the document.
+  kPaused,
+  // State is frozen, pause all media. Do not resume media
+  // when moving to kRunning.
+  kFrozen,
+  // State is frozen, auto resume media when moving to kRunning.
+  kFrozenAutoResumeMedia,
+};
diff --git a/third_party/blink/public/platform/web_data.h b/third_party/blink/public/platform/web_data.h
index 2780763..597188fc 100644
--- a/third_party/blink/public/platform/web_data.h
+++ b/third_party/blink/public/platform/web_data.h
@@ -70,6 +70,7 @@
   void Reset();
   void Assign(const WebData&);
   void Assign(const char* data, size_t size);
+  void Append(const char* data, size_t size);
 
   size_t size() const;
 
diff --git a/third_party/blink/public/web/web_local_frame.h b/third_party/blink/public/web/web_local_frame.h
index 0c72c57..00c5a555 100644
--- a/third_party/blink/public/web/web_local_frame.h
+++ b/third_party/blink/public/web/web_local_frame.h
@@ -14,6 +14,7 @@
 #include "third_party/blink/public/common/frame/sandbox_flags.h"
 #include "third_party/blink/public/mojom/ad_tagging/ad_frame.mojom-shared.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-shared.h"
+#include "third_party/blink/public/mojom/frame/lifecycle.mojom-shared.h"
 #include "third_party/blink/public/platform/task_type.h"
 #include "third_party/blink/public/platform/web_focus_type.h"
 #include "third_party/blink/public/platform/web_size.h"
@@ -731,6 +732,8 @@
   virtual void PerformMediaPlayerAction(const WebPoint&,
                                         const WebMediaPlayerAction&) = 0;
 
+  virtual void SetLifecycleState(mojom::FrameLifecycleState state) = 0;
+
  protected:
   explicit WebLocalFrame(WebTreeScopeType scope) : WebFrame(scope) {}
 
diff --git a/third_party/blink/public/web/web_navigation_params.h b/third_party/blink/public/web/web_navigation_params.h
index 45cc9374..66c8584 100644
--- a/third_party/blink/public/web/web_navigation_params.h
+++ b/third_party/blink/public/web/web_navigation_params.h
@@ -163,6 +163,7 @@
 
   // Fills |body_loader| based on the provided static data.
   static void FillBodyLoader(WebNavigationParams*, base::span<const char> data);
+  static void FillBodyLoader(WebNavigationParams*, WebData data);
 
   // Fills |response| and |body_loader| based on the provided static data.
   // |url| must be set already.
diff --git a/third_party/blink/public/web/web_view.h b/third_party/blink/public/web/web_view.h
index 01ff6cb..79f633d 100644
--- a/third_party/blink/public/web/web_view.h
+++ b/third_party/blink/public/web/web_view.h
@@ -443,9 +443,6 @@
 
   // Suspend and resume ---------------------------------------------------
 
-  // Pausing and unpausing current scheduled tasks.
-  virtual void PausePageScheduledTasks(bool paused) = 0;
-
   // TODO(lfg): Remove this once the refactor of WebView/WebWidget is
   // completed.
   virtual WebWidget* MainFrameWidget() = 0;
diff --git a/third_party/blink/renderer/bindings/scripts/v8_interface.py b/third_party/blink/renderer/bindings/scripts/v8_interface.py
index 5dcbc51f..7ea0940c 100644
--- a/third_party/blink/renderer/bindings/scripts/v8_interface.py
+++ b/third_party/blink/renderer/bindings/scripts/v8_interface.py
@@ -120,7 +120,7 @@
     KEY = 'origin_trial_feature_name'  # pylint: disable=invalid-name
 
     def member_filter(members):
-        return sorted([member for member in members if member.get(KEY) and not member.get('exposed_test')])
+        return sorted([member for member in members if member.get(KEY)])
 
     def member_filter_by_name(members, name):
         return [member for member in members if member[KEY] == name]
@@ -147,10 +147,12 @@
         # TODO(chasej): Need to handle method overloads? e.g.
         # (method['overloads']['secure_context_test_all'] if 'overloads' in method else method['secure_context_test'])
         feature['needs_secure_context'] = any(member.get('secure_context_test', False) for member in members)
+        feature['needs_context'] = feature['needs_secure_context'] or any(member.get('exposed_test', False) for member in members)
 
     if features:
         includes.add('platform/bindings/script_state.h')
         includes.add('core/origin_trials/origin_trials.h')
+
     return features
 
 
diff --git a/third_party/blink/renderer/bindings/templates/interface_base.cc.tmpl b/third_party/blink/renderer/bindings/templates/interface_base.cc.tmpl
index e49940b..91e2184 100644
--- a/third_party/blink/renderer/bindings/templates/interface_base.cc.tmpl
+++ b/third_party/blink/renderer/bindings/templates/interface_base.cc.tmpl
@@ -869,8 +869,10 @@
   v8::Local<v8::Signature> signature = v8::Signature::New(isolate, interface_template);
   ALLOW_UNUSED_LOCAL(signature);
   {% endif %}
-  {% if feature.needs_secure_context %}
+  {% if feature.needs_context %}
   ExecutionContext* execution_context = ToExecutionContext(isolate->GetCurrentContext());
+  {% endif %}{# needs context #}
+  {% if feature.needs_secure_context %}
   bool is_secure_context = (execution_context && execution_context->IsSecureContext());
   {% endif %}{# needs secure context #}
   {# Origin-Trial-enabled attributes #}
@@ -905,6 +907,7 @@
   {% endfor %}
   {# Origin-Trial-enabled methods (no overloads) #}
   {% for method in feature.methods %}
+  {% filter exposed(method.exposed_test) %}
   {% filter secure_context(method.secure_context_test) %}
   static constexpr V8DOMConfiguration::MethodConfiguration
   k{{method.camel_case_name}}Configurations[] = {
@@ -916,6 +919,7 @@
         interface, signature, config);
   }
   {% endfilter %}{# secure_context #}
+  {% endfilter %}{# exposed_test #}
   {% endfor %}
 }
 
diff --git a/third_party/blink/renderer/bindings/tests/idls/core/test_interface.idl b/third_party/blink/renderer/bindings/tests/idls/core/test_interface.idl
index ef4a5e46..92ed409 100644
--- a/third_party/blink/renderer/bindings/tests/idls/core/test_interface.idl
+++ b/third_party/blink/renderer/bindings/tests/idls/core/test_interface.idl
@@ -91,6 +91,7 @@
     void alwaysExposedMethod();
     [Exposed=Worker] void workerExposedMethod();
     [Exposed=Window] void windowExposedMethod();
+    [Exposed=Window,OriginTrialEnabled=TestFeature] void originTrialWindowExposedMethod();
 
     static void alwaysExposedStaticMethod();
     [Exposed=Worker] static void workerExposedStaticMethod();
diff --git a/third_party/blink/renderer/bindings/tests/results/core/origin_trial_features_for_core.cc b/third_party/blink/renderer/bindings/tests/results/core/origin_trial_features_for_core.cc
index 377daa6..33e78fb 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/origin_trial_features_for_core.cc
+++ b/third_party/blink/renderer/bindings/tests/results/core/origin_trial_features_for_core.cc
@@ -11,6 +11,7 @@
 
 #include "third_party/blink/renderer/bindings/core/v8/origin_trial_features_for_core.h"
 
+#include "third_party/blink/renderer/bindings/core/v8/v8_test_interface.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_test_object.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_window.h"
 #include "third_party/blink/renderer/core/context_features/context_feature_settings.h"
@@ -57,6 +58,12 @@
   }
   // TODO(iclelland): Extract this common code out of OriginTrialFeaturesForCore
   // and OriginTrialFeaturesForModules into a block.
+  if (wrapper_type_info == V8TestInterface::GetWrapperTypeInfo()) {
+    if (origin_trials::TestFeatureEnabled(execution_context)) {
+      V8TestInterface::InstallTestFeature(
+          isolate, world, v8::Local<v8::Object>(), prototype_object, interface_object);
+    }
+  }
   if (wrapper_type_info == V8TestObject::GetWrapperTypeInfo()) {
     if (origin_trials::FeatureNameEnabled(execution_context)) {
       V8TestObject::InstallFeatureName(
@@ -83,6 +90,13 @@
           isolate, world, v8::Local<v8::Object>(), prototype_object, interface_object);
     }
   }
+  if (feature == origin_trials::kTestFeatureTrialName) {
+    if (context_data->GetExistingConstructorAndPrototypeForType(
+            V8TestInterface::GetWrapperTypeInfo(), &prototype_object, &interface_object)) {
+      V8TestInterface::InstallTestFeature(
+          isolate, world, v8::Local<v8::Object>(), prototype_object, interface_object);
+    }
+  }
 }
 
 }  // namespace
diff --git a/third_party/blink/renderer/bindings/tests/results/core/v8_test_interface.cc b/third_party/blink/renderer/bindings/tests/results/core/v8_test_interface.cc
index 872638ab..c0ffad6 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/v8_test_interface.cc
+++ b/third_party/blink/renderer/bindings/tests/results/core/v8_test_interface.cc
@@ -32,11 +32,13 @@
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/frame/use_counter.h"
 #include "third_party/blink/renderer/core/inspector/console_message.h"
+#include "third_party/blink/renderer/core/origin_trials/origin_trials.h"
 #include "third_party/blink/renderer/platform/bindings/exception_messages.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/runtime_call_stats.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/bindings/v8_object_constructor.h"
+#include "third_party/blink/renderer/platform/bindings/v8_per_context_data.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/wtf/get_ptr.h"
 
@@ -1743,6 +1745,12 @@
   impl->windowExposedMethod();
 }
 
+static void OriginTrialWindowExposedMethodMethod(const v8::FunctionCallbackInfo<v8::Value>& info) {
+  TestInterfaceImplementation* impl = V8TestInterface::ToImpl(info.Holder());
+
+  impl->originTrialWindowExposedMethod();
+}
+
 static void AlwaysExposedStaticMethodMethod(const v8::FunctionCallbackInfo<v8::Value>& info) {
   TestInterfaceImplementation::alwaysExposedStaticMethod();
 }
@@ -3482,6 +3490,12 @@
   test_interface_implementation_v8_internal::WindowExposedMethodMethod(info);
 }
 
+void V8TestInterface::OriginTrialWindowExposedMethodMethodCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
+  RUNTIME_CALL_TIMER_SCOPE_DISABLED_BY_DEFAULT(info.GetIsolate(), "Blink_TestInterfaceImplementation_originTrialWindowExposedMethod");
+
+  test_interface_implementation_v8_internal::OriginTrialWindowExposedMethodMethod(info);
+}
+
 void V8TestInterface::AlwaysExposedStaticMethodMethodCallback(const v8::FunctionCallbackInfo<v8::Value>& info) {
   RUNTIME_CALL_TIMER_SCOPE_DISABLED_BY_DEFAULT(info.GetIsolate(), "Blink_TestInterfaceImplementation_alwaysExposedStaticMethod");
 
@@ -4175,6 +4189,45 @@
   }
 }
 
+void V8TestInterface::InstallTestFeature(
+    v8::Isolate* isolate,
+    const DOMWrapperWorld& world,
+    v8::Local<v8::Object> instance,
+    v8::Local<v8::Object> prototype,
+    v8::Local<v8::Function> interface) {
+  v8::Local<v8::FunctionTemplate> interface_template =
+      V8TestInterface::GetWrapperTypeInfo()->DomTemplate(isolate, world);
+  v8::Local<v8::Signature> signature = v8::Signature::New(isolate, interface_template);
+  ALLOW_UNUSED_LOCAL(signature);
+  ExecutionContext* execution_context = ToExecutionContext(isolate->GetCurrentContext());
+  if (execution_context && (execution_context->IsDocument())) {
+    static constexpr V8DOMConfiguration::MethodConfiguration
+    kOriginTrialWindowExposedMethodConfigurations[] = {
+        {"originTrialWindowExposedMethod", V8TestInterface::OriginTrialWindowExposedMethodMethodCallback, 0, v8::None, V8DOMConfiguration::kOnPrototype, V8DOMConfiguration::kCheckHolder, V8DOMConfiguration::kDoNotCheckAccess, V8DOMConfiguration::kHasSideEffect, V8DOMConfiguration::kAllWorlds}
+    };
+    for (const auto& config : kOriginTrialWindowExposedMethodConfigurations) {
+      V8DOMConfiguration::InstallMethod(
+          isolate, world, instance, prototype,
+          interface, signature, config);
+    }
+  }
+}
+
+void V8TestInterface::InstallTestFeature(
+    ScriptState* script_state, v8::Local<v8::Object> instance) {
+  V8PerContextData* per_context_data = script_state->PerContextData();
+  v8::Local<v8::Object> prototype = per_context_data->PrototypeForType(
+      V8TestInterface::GetWrapperTypeInfo());
+  v8::Local<v8::Function> interface = per_context_data->ConstructorForType(
+      V8TestInterface::GetWrapperTypeInfo());
+  ALLOW_UNUSED_LOCAL(interface);
+  InstallTestFeature(script_state->GetIsolate(), script_state->World(), instance, prototype, interface);
+}
+
+void V8TestInterface::InstallTestFeature(ScriptState* script_state) {
+  InstallTestFeature(script_state, v8::Local<v8::Object>());
+}
+
 v8::Local<v8::FunctionTemplate> V8TestInterface::DomTemplate(
     v8::Isolate* isolate, const DOMWrapperWorld& world) {
   return V8DOMConfiguration::DomClassTemplate(
diff --git a/third_party/blink/renderer/bindings/tests/results/core/v8_test_interface.h b/third_party/blink/renderer/bindings/tests/results/core/v8_test_interface.h
index c859d00..09775f03 100644
--- a/third_party/blink/renderer/bindings/tests/results/core/v8_test_interface.h
+++ b/third_party/blink/renderer/bindings/tests/results/core/v8_test_interface.h
@@ -27,6 +27,8 @@
 
 namespace blink {
 
+class ScriptState;
+
 CORE_EXPORT extern WrapperTypeInfo v8_test_interface_wrapper_type_info;
 
 class V8TestInterface {
@@ -69,6 +71,10 @@
   CORE_EXPORT static void RegisterPartial2VoidMethodMethodForPartialInterface(void (*)(const v8::FunctionCallbackInfo<v8::Value>&));
   CORE_EXPORT static void RegisterPartial2StaticVoidMethodMethodForPartialInterface(void (*)(const v8::FunctionCallbackInfo<v8::Value>&));
 
+  static void InstallTestFeature(v8::Isolate*, const DOMWrapperWorld&, v8::Local<v8::Object> instance, v8::Local<v8::Object> prototype, v8::Local<v8::Function> interface);
+  static void InstallTestFeature(ScriptState*, v8::Local<v8::Object> instance);
+  static void InstallTestFeature(ScriptState*);
+
   // Callback functions
 
   CORE_EXPORT static void TestInterfaceAttributeAttributeGetterCallback(const v8::FunctionCallbackInfo<v8::Value>&);
@@ -193,6 +199,7 @@
   CORE_EXPORT static void AlwaysExposedMethodMethodCallback(const v8::FunctionCallbackInfo<v8::Value>&);
   CORE_EXPORT static void WorkerExposedMethodMethodCallback(const v8::FunctionCallbackInfo<v8::Value>&);
   CORE_EXPORT static void WindowExposedMethodMethodCallback(const v8::FunctionCallbackInfo<v8::Value>&);
+  CORE_EXPORT static void OriginTrialWindowExposedMethodMethodCallback(const v8::FunctionCallbackInfo<v8::Value>&);
   CORE_EXPORT static void AlwaysExposedStaticMethodMethodCallback(const v8::FunctionCallbackInfo<v8::Value>&);
   CORE_EXPORT static void WorkerExposedStaticMethodMethodCallback(const v8::FunctionCallbackInfo<v8::Value>&);
   CORE_EXPORT static void WindowExposedStaticMethodMethodCallback(const v8::FunctionCallbackInfo<v8::Value>&);
diff --git a/third_party/blink/renderer/core/css/style_change_reason.cc b/third_party/blink/renderer/core/css/style_change_reason.cc
index ccc91f3..0dc147bf 100644
--- a/third_party/blink/renderer/core/css/style_change_reason.cc
+++ b/third_party/blink/renderer/core/css/style_change_reason.cc
@@ -20,6 +20,7 @@
 const char kDeclarativeContent[] = "Extension declarativeContent.css";
 const char kDesignMode[] = "DesignMode";
 const char kFindInvisible[] = "FindInvisible";
+const char kFlatTreeChange[] = "FlatTreeChange";
 const char kFontSizeChange[] = "FontSizeChange";
 const char kFonts[] = "Fonts";
 const char kFrame[] = "Frame";
diff --git a/third_party/blink/renderer/core/css/style_change_reason.h b/third_party/blink/renderer/core/css/style_change_reason.h
index 6e0f0fe..a712e1c3 100644
--- a/third_party/blink/renderer/core/css/style_change_reason.h
+++ b/third_party/blink/renderer/core/css/style_change_reason.h
@@ -23,6 +23,7 @@
 extern const char kDeclarativeContent[];
 extern const char kDesignMode[];
 extern const char kFrame[];
+extern const char kFlatTreeChange[];
 extern const char kFontSizeChange[];
 extern const char kFonts[];
 extern const char kFullscreen[];
diff --git a/third_party/blink/renderer/core/dom/node.cc b/third_party/blink/renderer/core/dom/node.cc
index e840bb7..4e9ef78 100644
--- a/third_party/blink/renderer/core/dom/node.cc
+++ b/third_party/blink/renderer/core/dom/node.cc
@@ -1448,6 +1448,21 @@
       StyleChangeReasonForTracing::Create(style_change_reason::kLazyReattach));
 }
 
+void Node::SetForceReattachLayoutTree() {
+  DCHECK(!GetDocument().GetStyleEngine().InRebuildLayoutTree());
+  if (GetForceReattachLayoutTree())
+    return;
+  if (!InActiveDocument())
+    return;
+  if (!IsContainerNode() && !IsTextNode())
+    return;
+  SetFlag(kForceReattachLayoutTree);
+  if (!NeedsStyleRecalc()) {
+    // Make sure we traverse down to this node during style recalc.
+    MarkAncestorsWithChildNeedsStyleRecalc();
+  }
+}
+
 // FIXME: Shouldn't these functions be in the editing code?  Code that asks
 // questions about HTML in the core DOM class is obviously misplaced.
 bool Node::CanStartSelection() const {
@@ -3049,6 +3064,18 @@
   return false;
 }
 
+void Node::FlatTreeParentChanged() {
+  // The node changed the flat tree position by being slotted to a new slot or
+  // slotted for the first time. We need to recalc style since the inheritance
+  // parent may have changed.
+  SetNeedsStyleRecalc(kLocalStyleChange,
+                      StyleChangeReasonForTracing::Create(
+                          style_change_reason::kFlatTreeChange));
+  // We also need to force a layout tree re-attach since the layout tree parent
+  // box may have changed.
+  SetForceReattachLayoutTree();
+}
+
 void Node::Trace(Visitor* visitor) {
   visitor->Trace(parent_or_shadow_host_node_);
   visitor->Trace(previous_);
diff --git a/third_party/blink/renderer/core/dom/node.h b/third_party/blink/renderer/core/dom/node.h
index e602421..20dc728 100644
--- a/third_party/blink/renderer/core/dom/node.h
+++ b/third_party/blink/renderer/core/dom/node.h
@@ -482,6 +482,11 @@
 
   void MarkAncestorsWithChildNeedsReattachLayoutTree();
 
+  // Mark node for forced layout tree re-attach during next lifecycle update.
+  // This is to trigger layout tree re-attachment when we cannot detect that we
+  // need to re-attach based on the computed style changes. This can happen when
+  // re-slotting shadow host children, for instance.
+  void SetForceReattachLayoutTree();
   bool GetForceReattachLayoutTree() const {
     return GetFlag(kForceReattachLayoutTree);
   }
@@ -684,6 +689,10 @@
     ReattachLayoutTree(context);
   }
   void ReattachLayoutTree(AttachContext&);
+
+  // TODO(futhark): Get rid of this method by replacing it with
+  // SetForceReattachLayoutTree + SetNeedsStyleRecalc, or DetachLayoutTree as
+  // appropriate.
   void LazyReattachIfAttached();
 
   // ---------------------------------------------------------------------------
@@ -855,6 +864,13 @@
     CheckSlotChange(SlotChangeType::kSignalSlotChangeEvent);
   }
 
+  void FlatTreeParentChanged();
+  void RemovedFromFlatTree() {
+    // This node was previously part of the flat tree, but due to slot re-
+    // assignment it no longer is. We need to detach the layout tree.
+    DetachLayoutTree();
+  }
+
   void SetHasDuplicateAttributes() { SetFlag(kHasDuplicateAttributes); }
   bool HasDuplicateAttribute() const {
     return GetFlag(kHasDuplicateAttributes);
diff --git a/third_party/blink/renderer/core/dom/slot_assignment.cc b/third_party/blink/renderer/core/dom/slot_assignment.cc
index 50900696..aa8ae72 100644
--- a/third_party/blink/renderer/core/dom/slot_assignment.cc
+++ b/third_party/blink/renderer/core/dom/slot_assignment.cc
@@ -7,6 +7,7 @@
 #include "third_party/blink/renderer/core/dom/element_traversal.h"
 #include "third_party/blink/renderer/core/dom/flat_tree_traversal_forbidden_scope.h"
 #include "third_party/blink/renderer/core/dom/node.h"
+#include "third_party/blink/renderer/core/dom/node_computed_style.h"
 #include "third_party/blink/renderer/core/dom/node_traversal.h"
 #include "third_party/blink/renderer/core/dom/shadow_root.h"
 #include "third_party/blink/renderer/core/dom/slot_assignment_engine.h"
@@ -289,7 +290,7 @@
     } else {
       if (RuntimeEnabledFeatures::FastFlatTreeTraversalEnabled())
         child.ClearFlatTreeNodeData();
-      child.LazyReattachIfAttached();
+      child.RemovedFromFlatTree();
     }
   }
 
diff --git a/third_party/blink/renderer/core/editing/editing_utilities.cc b/third_party/blink/renderer/core/editing/editing_utilities.cc
index e070814b..3e143fd 100644
--- a/third_party/blink/renderer/core/editing/editing_utilities.cc
+++ b/third_party/blink/renderer/core/editing/editing_utilities.cc
@@ -1140,10 +1140,10 @@
     return nullptr;
 
   ContainerNode* root = HighestEditableRoot(p);
-  Element* ancestor = p.AnchorNode()->IsElementNode()
-                          ? ToElement(p.AnchorNode())
-                          : p.AnchorNode()->parentElement();
-  for (; ancestor; ancestor = ancestor->parentElement()) {
+  for (Node& runner : NodeTraversal::InclusiveAncestorsOf(*p.AnchorNode())) {
+    if (!runner.IsElementNode())
+      continue;
+    Element* ancestor = ToElement(&runner);
     if (root && !HasEditableStyle(*ancestor))
       continue;
     if (ancestor->HasTagName(tag_name))
diff --git a/third_party/blink/renderer/core/editing/iterators/text_searcher_icu.cc b/third_party/blink/renderer/core/editing/iterators/text_searcher_icu.cc
index 58e387e..f4f6724e 100644
--- a/third_party/blink/renderer/core/editing/iterators/text_searcher_icu.cc
+++ b/third_party/blink/renderer/core/editing/iterators/text_searcher_icu.cc
@@ -139,6 +139,7 @@
 
 void TextSearcherICU::SetPattern(const StringView& pattern,
                                  FindOptions options) {
+  DCHECK_GT(pattern.length(), 0u);
   options_ = options;
   SetCaseSensitivity(!(options & kCaseInsensitive));
   SetPattern(pattern.Characters16(), pattern.length());
@@ -186,6 +187,12 @@
 
   result.start = static_cast<wtf_size_t>(match_start);
   result.length = usearch_getMatchedLength(searcher_);
+  // Might be possible to get zero-length result with some Unicode characters
+  // that shouldn't actually match but is matched by ICU such as \u0080.
+  if (result.length == 0u) {
+    result.start = 0;
+    return false;
+  }
   return true;
 }
 
diff --git a/third_party/blink/renderer/core/editing/iterators/text_searcher_icu_test.cc b/third_party/blink/renderer/core/editing/iterators/text_searcher_icu_test.cc
index 336507c..c0c01cf 100644
--- a/third_party/blink/renderer/core/editing/iterators/text_searcher_icu_test.cc
+++ b/third_party/blink/renderer/core/editing/iterators/text_searcher_icu_test.cc
@@ -99,4 +99,18 @@
   EXPECT_EQ(offset_result.length, first_result.length);
 }
 
+TEST(TextSearcherICUTest, FindControlCharacter) {
+  TextSearcherICU searcher;
+  const String& pattern = MakeUTF16(u8"\u0080");
+  searcher.SetPattern(pattern, 0);
+
+  const String& text = MakeUTF16("some text");
+  searcher.SetText(text.Characters16(), text.length());
+
+  MatchResultICU result;
+  EXPECT_FALSE(searcher.NextMatchResult(result));
+  EXPECT_EQ(0u, result.start);
+  EXPECT_EQ(0u, result.length);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/editing/serializers/markup_accumulator.cc b/third_party/blink/renderer/core/editing/serializers/markup_accumulator.cc
index de693791..8b968dd 100644
--- a/third_party/blink/renderer/core/editing/serializers/markup_accumulator.cc
+++ b/third_party/blink/renderer/core/editing/serializers/markup_accumulator.cc
@@ -269,11 +269,14 @@
 // https://w3c.github.io/DOM-Parsing/#dfn-generating-a-prefix
 AtomicString MarkupAccumulator::GeneratePrefix(
     const AtomicString& new_namespace) {
-  // 1. Let generated prefix be the concatenation of the string "ns" and the
-  // current numerical value of prefix index.
-  AtomicString generated_prefix = "ns" + String::Number(prefix_index_);
-  // 2. Let the value of prefix index be incremented by one.
-  ++prefix_index_;
+  AtomicString generated_prefix;
+  do {
+    // 1. Let generated prefix be the concatenation of the string "ns" and the
+    // current numerical value of prefix index.
+    generated_prefix = "ns" + String::Number(prefix_index_);
+    // 2. Let the value of prefix index be incremented by one.
+    ++prefix_index_;
+  } while (LookupNamespaceURI(generated_prefix));
   // 3. Add to map the generated prefix given the new namespace namespace.
   AddPrefix(generated_prefix, new_namespace);
   // 4. Return the value of generated prefix.
diff --git a/third_party/blink/renderer/core/execution_context/pause_state.h b/third_party/blink/renderer/core/execution_context/pause_state.h
index 4ec7b3f5..938938f 100644
--- a/third_party/blink/renderer/core/execution_context/pause_state.h
+++ b/third_party/blink/renderer/core/execution_context/pause_state.h
@@ -9,6 +9,7 @@
 
 // This enum represents the pausing state of the ExecutionContext.
 
+// TODO(dtapuska): Remove this enum and use FrameLifecycleState instead.
 enum class PauseState {
   // Pause tasks only. Used for nested event loops (alert, print).
   kPaused,
diff --git a/third_party/blink/renderer/core/exported/web_navigation_params.cc b/third_party/blink/renderer/core/exported/web_navigation_params.cc
index a0036615..b58f14a 100644
--- a/third_party/blink/renderer/core/exported/web_navigation_params.cc
+++ b/third_party/blink/renderer/core/exported/web_navigation_params.cc
@@ -90,6 +90,19 @@
 }
 
 // static
+void WebNavigationParams::FillBodyLoader(WebNavigationParams* params,
+                                         WebData data) {
+  params->response.SetExpectedContentLength(data.size());
+  auto body_loader = std::make_unique<StaticDataNavigationBodyLoader>();
+  scoped_refptr<SharedBuffer> buffer = data;
+  if (buffer)
+    body_loader->Write(*buffer);
+  body_loader->Finish();
+  params->body_loader = std::move(body_loader);
+  params->is_static_data = true;
+}
+
+// static
 void WebNavigationParams::FillStaticResponse(WebNavigationParams* params,
                                              WebString mime_type,
                                              WebString text_encoding,
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index 03b5aa0a..77be24a 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -726,10 +726,6 @@
   GetPage()->AcceptLanguagesChanged();
 }
 
-void WebViewImpl::PausePageScheduledTasks(bool paused) {
-  GetPage()->SetPaused(paused);
-}
-
 WebInputEventResult WebViewImpl::HandleKeyEvent(const WebKeyboardEvent& event) {
   DCHECK((event.GetType() == WebInputEvent::kRawKeyDown) ||
          (event.GetType() == WebInputEvent::kKeyDown) ||
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.h b/third_party/blink/renderer/core/exported/web_view_impl.h
index 83f02c7..e77ab1e 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.h
+++ b/third_party/blink/renderer/core/exported/web_view_impl.h
@@ -172,7 +172,6 @@
   void DisableAutoResizeMode() override;
   void PerformPluginAction(const WebPluginAction&, const gfx::Point&) override;
   void AudioStateChanged(bool is_audio_playing) override;
-  void PausePageScheduledTasks(bool paused) override;
   WebHitTestResult HitTestResultAt(const gfx::Point&) override;
   WebHitTestResult HitTestResultForTap(const gfx::Point&,
                                        const WebSize&) override;
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
index 82c416ad..c27cd18 100644
--- a/third_party/blink/renderer/core/frame/local_frame.cc
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -1753,4 +1753,54 @@
   Loader().StopAllLoaders();
 }
 
+void LocalFrame::PauseContext() {
+  // TODO(dtapuska): Eventually get rid of PauseState.
+  PauseState pause_state =
+      lifecycle_state_ == mojom::FrameLifecycleState::kFrozenAutoResumeMedia
+          ? PauseState::kFrozen
+          : PauseState::kPaused;
+  if (Document* document = GetDocument()) {
+    document->Fetcher()->SetDefersLoading(true);
+    document->PauseScheduledTasks(pause_state);
+  }
+  Loader().SetDefersLoading(true);
+  GetFrameScheduler()->SetPaused(true);
+}
+
+void LocalFrame::UnpauseContext() {
+  if (Document* document = GetDocument()) {
+    document->Fetcher()->SetDefersLoading(false);
+    document->UnpauseScheduledTasks();
+  }
+  Loader().SetDefersLoading(false);
+  GetFrameScheduler()->SetPaused(false);
+}
+
+void LocalFrame::SetLifecycleState(mojom::FrameLifecycleState state) {
+  if (state == lifecycle_state_)
+    return;
+  bool is_frozen = lifecycle_state_ != mojom::FrameLifecycleState::kRunning;
+  bool freeze = state != mojom::FrameLifecycleState::kRunning;
+
+  // TODO(dtapuska): Determine if we should dispatch events if we are
+  // transitioning across frozen states. ie. kPaused->kFrozen should
+  // pause media.
+
+  // If we are transitioning from one frozen state to another just return.
+  if (is_frozen == freeze)
+    return;
+  mojom::FrameLifecycleState old_state = lifecycle_state_;
+  lifecycle_state_ = state;
+
+  if (freeze) {
+    if (lifecycle_state_ != mojom::FrameLifecycleState::kPaused)
+      DidFreeze();
+    PauseContext();
+  } else {
+    UnpauseContext();
+    if (old_state != mojom::FrameLifecycleState::kPaused)
+      DidResume();
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h
index 92ea373..44c3bfe7 100644
--- a/third_party/blink/renderer/core/frame/local_frame.h
+++ b/third_party/blink/renderer/core/frame/local_frame.h
@@ -34,6 +34,7 @@
 #include "base/macros.h"
 #include "mojo/public/cpp/bindings/strong_binding_set.h"
 #include "third_party/blink/public/mojom/ad_tagging/ad_frame.mojom-blink.h"
+#include "third_party/blink/public/mojom/frame/lifecycle.mojom-blink.h"
 #include "third_party/blink/public/mojom/loader/pause_subresource_loading_handle.mojom-blink.h"
 #include "third_party/blink/public/mojom/loader/previews_resource_loading_hints.mojom-blink.h"
 #include "third_party/blink/public/platform/reporting.mojom-blink.h"
@@ -43,6 +44,7 @@
 #include "third_party/blink/renderer/core/dom/user_gesture_indicator.h"
 #include "third_party/blink/renderer/core/dom/weak_identifier_map.h"
 #include "third_party/blink/renderer/core/editing/forward.h"
+#include "third_party/blink/renderer/core/execution_context/pause_state.h"
 #include "third_party/blink/renderer/core/frame/frame.h"
 #include "third_party/blink/renderer/core/frame/frame_types.h"
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
@@ -433,6 +435,8 @@
   // To be called from OomInterventionImpl.
   void ForciblyPurgeV8Memory();
 
+  void SetLifecycleState(mojom::FrameLifecycleState state);
+
  private:
   friend class FrameNavigationDisabler;
 
@@ -478,6 +482,9 @@
 
   void SetFrameColorOverlay(SkColor color);
 
+  void PauseContext();
+  void UnpauseContext();
+
   std::unique_ptr<FrameScheduler> frame_scheduler_;
 
   // Holds all PauseSubresourceLoadingHandles allowing either |this| to delete
@@ -563,6 +570,9 @@
   const bool is_save_data_enabled_;
 
   std::unique_ptr<FrameOverlay> frame_color_overlay_;
+
+  mojom::FrameLifecycleState lifecycle_state_ =
+      mojom::FrameLifecycleState::kRunning;
 };
 
 inline FrameLoader& LocalFrame::Loader() const {
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index 2a21139c..27e1529c 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -285,7 +285,6 @@
 
 void LocalFrameView::Trace(blink::Visitor* visitor) {
   visitor->Trace(frame_);
-  visitor->Trace(parent_);
   visitor->Trace(fragment_anchor_);
   visitor->Trace(scrollable_areas_);
   visitor->Trace(animating_scrollable_areas_);
@@ -1970,8 +1969,10 @@
 
   auto* detached_frame_view = this;
   while (detached_frame_view->is_attached_ &&
-         detached_frame_view != local_frame_view_root)
-    detached_frame_view = detached_frame_view->parent_.Get();
+         detached_frame_view != local_frame_view_root) {
+    detached_frame_view = detached_frame_view->ParentFrameView();
+    CHECK(detached_frame_view);
+  }
 
   if (detached_frame_view == local_frame_view_root)
     return;
@@ -3305,23 +3306,16 @@
 }
 
 void LocalFrameView::AttachToLayout() {
-  // TODO(crbug.com/729196): Trace why LocalFrameView::DetachFromLayout crashes.
   CHECK(!is_attached_);
   if (frame_->GetDocument())
     CHECK_NE(Lifecycle().GetState(), DocumentLifecycle::kStopping);
   is_attached_ = true;
-  parent_ = ParentFrameView();
-  if (!parent_) {
-    Frame* parent_frame = frame_->Tree().Parent();
-    CHECK(parent_frame);
-    CHECK(parent_frame->IsLocalFrame());
-    CHECK(parent_frame->View());
-  }
-  CHECK(parent_);
-  if (parent_->IsVisible())
+  LocalFrameView* parent_view = ParentFrameView();
+  CHECK(parent_view);
+  if (parent_view->IsVisible())
     SetParentVisible(true);
   SetupRenderThrottling();
-  subtree_throttled_ = ParentFrameView()->CanThrottleRendering();
+  subtree_throttled_ = parent_view->CanThrottleRendering();
 
   // We may have updated paint properties in detached frame subtree for
   // printing (see UpdateLifecyclePhasesForPrinting()). The paint properties
@@ -3333,16 +3327,7 @@
 }
 
 void LocalFrameView::DetachFromLayout() {
-  // TODO(crbug.com/729196): Trace why LocalFrameView::DetachFromLayout crashes.
   CHECK(is_attached_);
-  LocalFrameView* parent = ParentFrameView();
-  if (!parent) {
-    Frame* parent_frame = frame_->Tree().Parent();
-    CHECK(parent_frame);
-    CHECK(parent_frame->IsLocalFrame());
-    CHECK(parent_frame->View());
-  }
-  CHECK(parent == parent_);
   SetParentVisible(false);
   is_attached_ = false;
 
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.h b/third_party/blink/renderer/core/frame/local_frame_view.h
index 752a0b7..778f3ef 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.h
+++ b/third_party/blink/renderer/core/frame/local_frame_view.h
@@ -871,7 +871,6 @@
   EmbeddedObjectSet part_update_set_;
 
   Member<LocalFrame> frame_;
-  Member<LocalFrameView> parent_;
 
   IntRect frame_rect_;
   bool is_attached_;
diff --git a/third_party/blink/renderer/core/frame/mhtml_loading_test.cc b/third_party/blink/renderer/core/frame/mhtml_loading_test.cc
index 65dd524..85fcd95 100644
--- a/third_party/blink/renderer/core/frame/mhtml_loading_test.cc
+++ b/third_party/blink/renderer/core/frame/mhtml_loading_test.cc
@@ -32,7 +32,6 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/web_string.h"
 #include "third_party/blink/public/platform/web_url.h"
-#include "third_party/blink/public/platform/web_url_loader_mock_factory.h"
 #include "third_party/blink/public/web/web_view.h"
 #include "third_party/blink/renderer/core/dom/class_collection.h"
 #include "third_party/blink/renderer/core/dom/document.h"
@@ -44,6 +43,7 @@
 #include "third_party/blink/renderer/core/frame/location.h"
 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
 #include "third_party/blink/renderer/core/page/page.h"
+#include "third_party/blink/renderer/platform/loader/static_data_navigation_body_loader.h"
 #include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
 #include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
@@ -62,22 +62,22 @@
  protected:
   void SetUp() override { helper_.Initialize(); }
 
-  void TearDown() override {
-    platform_->GetURLLoaderMockFactory()
-        ->UnregisterAllURLsAndClearMemoryCache();
-  }
-
-  void RegisterMockedURLLoad(const std::string& url,
-                             const std::string& file_name) {
-    url_test_helpers::RegisterMockedURLLoad(
-        ToKURL(url),
-        test::CoreTestDataPath(WebString::FromUTF8("mhtml/" + file_name)),
-        WebString::FromUTF8("multipart/related"));
-  }
-
-  void LoadURLInTopFrame(const WebURL& url) {
-    frame_test_helpers::LoadFrame(helper_.GetWebView()->MainFrameImpl(),
-                                  url.GetString().Utf8().data());
+  void LoadURLInTopFrame(const WebURL& url, const std::string& file_name) {
+    scoped_refptr<SharedBuffer> buffer = test::ReadFromFile(
+        test::CoreTestDataPath(WebString::FromUTF8("mhtml/" + file_name)));
+    WebLocalFrameImpl* frame = helper_.GetWebView()->MainFrameImpl();
+    auto params = std::make_unique<WebNavigationParams>();
+    params->url = url;
+    params->response = WebURLResponse(url);
+    params->response.SetMIMEType("multipart/related");
+    params->response.SetHTTPStatusCode(200);
+    params->response.SetExpectedContentLength(buffer->size());
+    auto body_loader = std::make_unique<StaticDataNavigationBodyLoader>();
+    body_loader->Write(*buffer);
+    body_loader->Finish();
+    params->body_loader = std::move(body_loader);
+    frame->CommitNavigation(std::move(params), nullptr /* extra_data */);
+    frame_test_helpers::PumpPendingRequestsForFrameToLoad(frame);
   }
 
   Page* GetPage() const { return helper_.GetWebView()->GetPage(); }
@@ -92,10 +92,7 @@
 TEST_F(MHTMLLoadingTest, CheckDomain) {
   const char kFileURL[] = "file:///simple_test.mht";
 
-  // Register the mocked frame and load it.
-  WebURL url = ToKURL(kFileURL);
-  RegisterMockedURLLoad(kFileURL, "simple_test.mht");
-  LoadURLInTopFrame(url);
+  LoadURLInTopFrame(ToKURL(kFileURL), "simple_test.mht");
   ASSERT_TRUE(GetPage());
   LocalFrame* frame = ToLocalFrame(GetPage()->MainFrame());
   ASSERT_TRUE(frame);
@@ -113,9 +110,7 @@
 TEST_F(MHTMLLoadingTest, EnforceSandboxFlags) {
   const char kURL[] = "http://www.example.com";
 
-  // Register the mocked frame and load it.
-  RegisterMockedURLLoad(kURL, "page_with_javascript.mht");
-  LoadURLInTopFrame(ToKURL(kURL));
+  LoadURLInTopFrame(ToKURL(kURL), "page_with_javascript.mht");
   ASSERT_TRUE(GetPage());
   LocalFrame* frame = ToLocalFrame(GetPage()->MainFrame());
   ASSERT_TRUE(frame);
@@ -159,9 +154,7 @@
 TEST_F(MHTMLLoadingTest, EnforceSandboxFlagsInXSLT) {
   const char kURL[] = "http://www.example.com";
 
-  // Register the mocked frame and load it.
-  RegisterMockedURLLoad(kURL, "xslt.mht");
-  LoadURLInTopFrame(ToKURL(kURL));
+  LoadURLInTopFrame(ToKURL(kURL), "xslt.mht");
   ASSERT_TRUE(GetPage());
   LocalFrame* frame = ToLocalFrame(GetPage()->MainFrame());
   ASSERT_TRUE(frame);
@@ -183,9 +176,7 @@
 TEST_F(MHTMLLoadingTest, ShadowDom) {
   const char kURL[] = "http://www.example.com";
 
-  // Register the mocked frame and load it.
-  RegisterMockedURLLoad(kURL, "shadow.mht");
-  LoadURLInTopFrame(ToKURL(kURL));
+  LoadURLInTopFrame(ToKURL(kURL), "shadow.mht");
   ASSERT_TRUE(GetPage());
   LocalFrame* frame = ToLocalFrame(GetPage()->MainFrame());
   ASSERT_TRUE(frame);
@@ -211,9 +202,7 @@
 TEST_F(MHTMLLoadingTest, FormControlElements) {
   const char kURL[] = "http://www.example.com";
 
-  // Register the mocked frame and load it.
-  RegisterMockedURLLoad(kURL, "form.mht");
-  LoadURLInTopFrame(ToKURL(kURL));
+  LoadURLInTopFrame(ToKURL(kURL), "form.mht");
   ASSERT_TRUE(GetPage());
   LocalFrame* frame = ToLocalFrame(GetPage()->MainFrame());
   ASSERT_TRUE(frame);
@@ -232,9 +221,7 @@
 TEST_F(MHTMLLoadingTest, LoadMHTMLContainingSoftLineBreaks) {
   const char kURL[] = "http://www.example.com";
 
-  // Register the mocked frame and load it.
-  RegisterMockedURLLoad(kURL, "soft_line_break.mht");
-  LoadURLInTopFrame(ToKURL(kURL));
+  LoadURLInTopFrame(ToKURL(kURL), "soft_line_break.mht");
   ASSERT_TRUE(GetPage());
   LocalFrame* frame = ToLocalFrame(GetPage()->MainFrame());
   ASSERT_TRUE(frame);
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
index 03e4a1dd..a605fe6c 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
@@ -2577,4 +2577,9 @@
                          std::move(devtools_agent_request)));
 }
 
+void WebLocalFrameImpl::SetLifecycleState(mojom::FrameLifecycleState state) {
+  DCHECK(GetFrame());
+  GetFrame()->SetLifecycleState(state);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.h b/third_party/blink/renderer/core/frame/web_local_frame_impl.h
index 53f569c1..985cdbb 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.h
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.h
@@ -39,6 +39,7 @@
 #include "third_party/blink/public/mojom/ad_tagging/ad_frame.mojom-blink.h"
 #include "third_party/blink/public/mojom/devtools/devtools_agent.mojom-blink.h"
 #include "third_party/blink/public/mojom/frame/find_in_page.mojom-blink.h"
+#include "third_party/blink/public/mojom/frame/lifecycle.mojom-blink.h"
 #include "third_party/blink/public/mojom/portal/portal.mojom-blink.h"
 #include "third_party/blink/public/platform/web_file_system_type.h"
 #include "third_party/blink/public/web/web_history_commit_type.h"
@@ -323,6 +324,8 @@
       const WebNavigationInfo&,
       std::unique_ptr<WebDocumentLoader::ExtraData>) override;
 
+  void SetLifecycleState(mojom::FrameLifecycleState state) override;
+
   void InitializeCoreFrame(Page&, FrameOwner*, const AtomicString& name);
   LocalFrame* GetFrame() const { return frame_.Get(); }
 
diff --git a/third_party/blink/renderer/core/html/html_slot_element.cc b/third_party/blink/renderer/core/html/html_slot_element.cc
index 1b2f27996..c9a77a8 100644
--- a/third_party/blink/renderer/core/html/html_slot_element.cc
+++ b/third_party/blink/renderer/core/html/html_slot_element.cc
@@ -232,9 +232,16 @@
       flat_tree_children_.push_back(child);
   } else {
     flat_tree_children_ = assigned_nodes_;
+    for (auto& node : old_flat_tree_children) {
+      // Detach fallback nodes. Host children which are no longer slotted are
+      // detached in SlotAssignment::RecalcAssignment().
+      if (node->parentNode() == this)
+        node->RemovedFromFlatTree();
+    }
   }
 
-  LazyReattachNodesIfNeeded(old_flat_tree_children, flat_tree_children_);
+  NotifySlottedNodesOfFlatTreeChange(old_flat_tree_children,
+                                     flat_tree_children_);
 }
 
 void HTMLSlotElement::DispatchSlotChangeEvent() {
@@ -414,9 +421,9 @@
   }
 }
 
-void HTMLSlotElement::LazyReattachNodesByDynamicProgramming(
-    const HeapVector<Member<Node>>& nodes1,
-    const HeapVector<Member<Node>>& nodes2) {
+void HTMLSlotElement::NotifySlottedNodesOfFlatTreeChangeByDynamicProgramming(
+    const HeapVector<Member<Node>>& old_slotted,
+    const HeapVector<Member<Node>>& new_slotted) {
   // Use dynamic programming to minimize the number of nodes being reattached.
   using LCSTable = std::array<std::array<wtf_size_t, kLCSTableSizeLimit>,
                               kLCSTableSizeLimit>;
@@ -428,45 +435,40 @@
   DEFINE_STATIC_LOCAL(BacktrackTable*, backtrack_table, (new BacktrackTable));
 
   FillLongestCommonSubsequenceDynamicProgrammingTable(
-      nodes1, nodes2, *lcs_table, *backtrack_table);
+      old_slotted, new_slotted, *lcs_table, *backtrack_table);
 
-  wtf_size_t r = nodes1.size();
-  wtf_size_t c = nodes2.size();
+  wtf_size_t r = old_slotted.size();
+  wtf_size_t c = new_slotted.size();
   while (r > 0 && c > 0) {
     Backtrack backtrack = (*backtrack_table)[r][c];
     if (backtrack == std::make_pair(r - 1, c - 1)) {
-      DCHECK_EQ(nodes1[r - 1], nodes2[c - 1]);
-    } else if (backtrack == std::make_pair(r - 1, c)) {
-      nodes1[r - 1]->LazyReattachIfAttached();
-    } else {
-      DCHECK(backtrack == std::make_pair(r, c - 1));
-      nodes2[c - 1]->LazyReattachIfAttached();
+      DCHECK_EQ(old_slotted[r - 1], new_slotted[c - 1]);
+    } else if (backtrack == std::make_pair(r, c - 1)) {
+      new_slotted[c - 1]->FlatTreeParentChanged();
     }
     std::tie(r, c) = backtrack;
   }
-  if (r > 0) {
-    for (wtf_size_t i = 0; i < r; ++i)
-      nodes1[i]->LazyReattachIfAttached();
-  } else if (c > 0) {
+  if (c > 0) {
     for (wtf_size_t i = 0; i < c; ++i)
-      nodes2[i]->LazyReattachIfAttached();
+      new_slotted[i]->FlatTreeParentChanged();
   }
 }
 
-void HTMLSlotElement::LazyReattachNodesIfNeeded(
-    const HeapVector<Member<Node>>& nodes1,
-    const HeapVector<Member<Node>>& nodes2) {
-  if (nodes1 == nodes2)
+void HTMLSlotElement::NotifySlottedNodesOfFlatTreeChange(
+    const HeapVector<Member<Node>>& old_slotted,
+    const HeapVector<Member<Node>>& new_slotted) {
+  if (old_slotted == new_slotted)
     return;
   probe::didPerformSlotDistribution(this);
 
-  if (nodes1.size() + 1 > kLCSTableSizeLimit ||
-      nodes2.size() + 1 > kLCSTableSizeLimit) {
+  if (old_slotted.size() + 1 > kLCSTableSizeLimit ||
+      new_slotted.size() + 1 > kLCSTableSizeLimit) {
     // Since DP takes O(N^2), we don't use DP if the size is larger than the
     // pre-defined limit.
-    LazyReattachNodesNaive(nodes1, nodes2);
+    NotifySlottedNodesOfFlatTreeChangeNaive(new_slotted);
   } else {
-    LazyReattachNodesByDynamicProgramming(nodes1, nodes2);
+    NotifySlottedNodesOfFlatTreeChangeByDynamicProgramming(old_slotted,
+                                                           new_slotted);
   }
 }
 
@@ -483,14 +485,11 @@
   CheckSlotChange(SlotChangeType::kSuppressSlotChangeEvent);
 }
 
-void HTMLSlotElement::LazyReattachNodesNaive(
-    const HeapVector<Member<Node>>& nodes1,
-    const HeapVector<Member<Node>>& nodes2) {
+void HTMLSlotElement::NotifySlottedNodesOfFlatTreeChangeNaive(
+    const HeapVector<Member<Node>>& new_slotted) {
   // TODO(hayato): Use some heuristic to avoid reattaching all nodes
-  for (auto& node : nodes1)
-    node->LazyReattachIfAttached();
-  for (auto& node : nodes2)
-    node->LazyReattachIfAttached();
+  for (auto& node : new_slotted)
+    node->FlatTreeParentChanged();
 }
 
 void HTMLSlotElement::
diff --git a/third_party/blink/renderer/core/html/html_slot_element.h b/third_party/blink/renderer/core/html/html_slot_element.h
index 674dc52..e5dcacca 100644
--- a/third_party/blink/renderer/core/html/html_slot_element.h
+++ b/third_party/blink/renderer/core/html/html_slot_element.h
@@ -128,13 +128,14 @@
 
   bool HasSlotableChild() const;
 
-  void LazyReattachNodesIfNeeded(const HeapVector<Member<Node>>& nodes1,
-                                 const HeapVector<Member<Node>>& nodes2);
-  static void LazyReattachNodesNaive(const HeapVector<Member<Node>>& nodes1,
-                                     const HeapVector<Member<Node>>& nodes2);
-  static void LazyReattachNodesByDynamicProgramming(
-      const HeapVector<Member<Node>>& nodes1,
-      const HeapVector<Member<Node>>& nodes2);
+  void NotifySlottedNodesOfFlatTreeChange(
+      const HeapVector<Member<Node>>& old_slotted,
+      const HeapVector<Member<Node>>& new_slotted);
+  static void NotifySlottedNodesOfFlatTreeChangeNaive(
+      const HeapVector<Member<Node>>& new_slotted);
+  static void NotifySlottedNodesOfFlatTreeChangeByDynamicProgramming(
+      const HeapVector<Member<Node>>& old_slotted,
+      const HeapVector<Member<Node>>& new_slotted);
 
   void SetNeedsDistributionRecalcWillBeSetNeedsAssignmentRecalc();
 
diff --git a/third_party/blink/renderer/core/html/html_summary_element.cc b/third_party/blink/renderer/core/html/html_summary_element.cc
index 68c1f20..d900ae6 100644
--- a/third_party/blink/renderer/core/html/html_summary_element.cc
+++ b/third_party/blink/renderer/core/html/html_summary_element.cc
@@ -20,6 +20,7 @@
 
 #include "third_party/blink/renderer/core/html/html_summary_element.h"
 
+#include "third_party/blink/renderer/core/css/style_change_reason.h"
 #include "third_party/blink/renderer/core/dom/flat_tree_traversal.h"
 #include "third_party/blink/renderer/core/dom/shadow_root.h"
 #include "third_party/blink/renderer/core/events/keyboard_event.h"
@@ -44,7 +45,9 @@
 }
 
 HTMLSummaryElement::HTMLSummaryElement(Document& document)
-    : HTMLElement(kSummaryTag, document) {}
+    : HTMLElement(kSummaryTag, document) {
+  SetHasCustomStyleCallbacks();
+}
 
 LayoutObject* HTMLSummaryElement::CreateLayoutObject(
     const ComputedStyle& style) {
@@ -152,4 +155,14 @@
   return IsMainSummary() || HTMLElement::WillRespondToMouseClickEvents();
 }
 
+void HTMLSummaryElement::WillRecalcStyle(const StyleRecalcChange) {
+  if (GetForceReattachLayoutTree() && IsMainSummary()) {
+    if (Element* marker = MarkerControl()) {
+      marker->SetNeedsStyleRecalc(
+          kLocalStyleChange,
+          StyleChangeReasonForTracing::Create(style_change_reason::kControl));
+    }
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/html_summary_element.h b/third_party/blink/renderer/core/html/html_summary_element.h
index 8d13bdb2..4d8974a 100644
--- a/third_party/blink/renderer/core/html/html_summary_element.h
+++ b/third_party/blink/renderer/core/html/html_summary_element.h
@@ -43,6 +43,7 @@
   void DefaultEventHandler(Event&) override;
   bool HasActivationBehavior() const override;
   void DidAddUserAgentShadowRoot(ShadowRoot&) override;
+  void WillRecalcStyle(const StyleRecalcChange) override;
   HTMLDetailsElement* DetailsElement() const;
 
   bool SupportsFocus() const override;
diff --git a/third_party/blink/renderer/core/html/list_item_ordinal_test.cc b/third_party/blink/renderer/core/html/list_item_ordinal_test.cc
index cb64c79..fbbf6da 100644
--- a/third_party/blink/renderer/core/html/list_item_ordinal_test.cc
+++ b/third_party/blink/renderer/core/html/list_item_ordinal_test.cc
@@ -16,7 +16,7 @@
 
 TEST_F(ListItemOrdinalTest, ItemInsertedOrRemoved_ListItemsInSlot) {
   // Note: We should have more than |kLCSTableSizeLimit|(16) child nodes in
-  // host to invoke |LazyReattachNodesNaive()|.
+  // host to invoke |NotifySlottedNodesOfFlatTreeChangeNaive()|.
   SetBodyContent(
       "<div id=host>"
       "<li id=item1>1</li>"
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index 255d21c5..4c71429 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -568,6 +568,26 @@
       CommitData(span.data(), span.size());
   }
 
+  if (loading_mhtml_archive_) {
+    ArchiveResource* main_resource =
+        fetcher_->CreateArchive(url_, data_buffer_);
+    data_buffer_ = nullptr;
+    if (main_resource) {
+      // The origin is the MHTML file, we need to set the base URL to the
+      // document encoded in the MHTML so relative URLs are resolved properly.
+      CommitNavigation(main_resource->MimeType(), main_resource->Url());
+      // CommitNavigation runs unload listeners which can detach the frame.
+      if (!frame_)
+        return;
+      scoped_refptr<SharedBuffer> data(main_resource->Data());
+      for (const auto& span : *data)
+        CommitData(span.data(), span.size());
+    } else {
+      // Cannot parse mhtml archive - load empty document instead.
+      CommitNavigation(response_.MimeType());
+    }
+  }
+
   TimeTicks response_end_time = finish_time;
   if (response_end_time.is_null())
     response_end_time = time_of_last_data_received_;
@@ -575,12 +595,10 @@
     response_end_time = CurrentTimeTicks();
   GetTiming().SetResponseEnd(response_end_time);
 
-  if (!MaybeCreateArchive()) {
-    // If this is an empty document, it might not have actually been
-    // committed yet. Force a commit so that the Document actually gets created.
-    if (state_ == kProvisional)
-      CommitNavigation(response_.MimeType());
-  }
+  // If this is an empty document, it might not have actually been
+  // committed yet. Force a commit so that the Document actually gets created.
+  if (state_ == kProvisional)
+    CommitNavigation(response_.MimeType());
   DCHECK_GE(state_, kCommitted);
 
   if (!frame_)
@@ -775,12 +793,6 @@
     }
   }
 
-  // The MHTML mime type should be same as the one we check in the browser
-  // process's IsDownload (navigation_url_loader_network_service.cc).
-  loading_mhtml_archive_ =
-      DeprecatedEqualIgnoringCase("multipart/related", response_.MimeType()) ||
-      DeprecatedEqualIgnoringCase("message/rfc822", response_.MimeType());
-
   if (!ShouldContinueForResponse()) {
     StopLoading();
     return false;
@@ -970,28 +982,6 @@
   frame_ = nullptr;
 }
 
-bool DocumentLoader::MaybeCreateArchive() {
-  // Give the archive machinery a crack at this document.
-  if (!loading_mhtml_archive_)
-    return false;
-
-  ArchiveResource* main_resource = fetcher_->CreateArchive(Url(), data_buffer_);
-  if (!main_resource)
-    return false;
-
-  data_buffer_ = nullptr;
-  // The origin is the MHTML file, we need to set the base URL to the document
-  // encoded in the MHTML so relative URLs are resolved properly.
-  CommitNavigation(main_resource->MimeType(), main_resource->Url());
-  if (!frame_)
-    return false;
-
-  scoped_refptr<SharedBuffer> data(main_resource->Data());
-  for (const auto& span : *data)
-    CommitData(span.data(), span.size());
-  return true;
-}
-
 const KURL& DocumentLoader::UnreachableURL() const {
   return unreachable_url_;
 }
@@ -1160,6 +1150,22 @@
   if (!HandleResponse(response))
     return;
 
+  loading_mhtml_archive_ =
+      DeprecatedEqualIgnoringCase("multipart/related", response_.MimeType()) ||
+      DeprecatedEqualIgnoringCase("message/rfc822", response_.MimeType());
+  if (loading_mhtml_archive_) {
+    // To commit an mhtml archive synchronously we have to load the whole body
+    // synchronously and parse it, and it's already loaded in a buffer usually.
+    // This means we should not defer, and we'll finish loading synchronously
+    // from StartLoadingBody().
+    body_loader_->StartLoadingBody(this, false /* use_isolated_code_cache */);
+    if (body_loader_) {
+      // If we did not finish synchronously, load empty document instead.
+      FinishedLoading(CurrentTimeTicks());
+    }
+    return;
+  }
+
   if (defers_loading_)
     body_loader_->SetDefersLoading(true);
 
diff --git a/third_party/blink/renderer/core/loader/document_loader.h b/third_party/blink/renderer/core/loader/document_loader.h
index bb7228f..8960d67 100644
--- a/third_party/blink/renderer/core/loader/document_loader.h
+++ b/third_party/blink/renderer/core/loader/document_loader.h
@@ -291,8 +291,6 @@
 
   void CommitData(const char* bytes, size_t length);
 
-  bool MaybeCreateArchive();
-
   void StartLoadingInternal();
   void FinishedLoading(TimeTicks finish_time);
   void CancelLoadAfterCSPDenied(const ResourceResponse&);
diff --git a/third_party/blink/renderer/core/loader/frame_loader.cc b/third_party/blink/renderer/core/loader/frame_loader.cc
index c1c5ae40e3..fdb10ea9 100644
--- a/third_party/blink/renderer/core/loader/frame_loader.cc
+++ b/third_party/blink/renderer/core/loader/frame_loader.cc
@@ -240,7 +240,7 @@
   // generate start notifications.
   document_loader_->SetSentDidFinishLoad();
   if (frame_->GetPage()->Paused())
-    SetDefersLoading(true);
+    frame_->SetLifecycleState(mojom::FrameLifecycleState::kPaused);
 
   TakeObjectSnapshot();
 }
@@ -250,19 +250,10 @@
 }
 
 void FrameLoader::SetDefersLoading(bool defers) {
-  if (Document* document = frame_->GetDocument()) {
-    document->Fetcher()->SetDefersLoading(defers);
-    if (defers)
-      document->PauseScheduledTasks(PauseState::kPaused);
-    else
-      document->UnpauseScheduledTasks();
-  }
-
   if (document_loader_)
     document_loader_->SetDefersLoading(defers);
   if (provisional_document_loader_)
     provisional_document_loader_->SetDefersLoading(defers);
-
   if (!defers)
     frame_->GetNavigationScheduler().StartTimer();
 }
diff --git a/third_party/blink/renderer/core/page/page.cc b/third_party/blink/renderer/core/page/page.cc
index 7058be4..0157616 100644
--- a/third_party/blink/renderer/core/page/page.cc
+++ b/third_party/blink/renderer/core/page/page.cc
@@ -363,13 +363,14 @@
     return;
 
   paused_ = paused;
+  mojom::FrameLifecycleState state = paused
+                                         ? mojom::FrameLifecycleState::kPaused
+                                         : mojom::FrameLifecycleState::kRunning;
   for (Frame* frame = MainFrame(); frame;
        frame = frame->Tree().TraverseNext()) {
     if (!frame->IsLocalFrame())
       continue;
-    LocalFrame* local_frame = ToLocalFrame(frame);
-    local_frame->Loader().SetDefersLoading(paused);
-    local_frame->GetFrameScheduler()->SetPaused(paused);
+    ToLocalFrame(frame)->SetLifecycleState(state);
   }
 }
 
diff --git a/third_party/blink/renderer/core/page/page.h b/third_party/blink/renderer/core/page/page.h
index f696f06..47bea74 100644
--- a/third_party/blink/renderer/core/page/page.h
+++ b/third_party/blink/renderer/core/page/page.h
@@ -230,9 +230,6 @@
   // are allowed to start/continue in this state, and all background processing
   // is also paused.
   bool Paused() const { return paused_; }
-  // This function is public to be used for suspending/resuming Page's tasks.
-  // Refer to |WebContentImpl::PausePageScheduledTasks| and
-  // http://crbug.com/822564 for more details.
   void SetPaused(bool);
 
   void SetPageScaleFactor(float);
diff --git a/third_party/blink/renderer/core/paint/object_paint_properties.h b/third_party/blink/renderer/core/paint/object_paint_properties.h
index 168fff1..c1a1f74 100644
--- a/third_party/blink/renderer/core/paint/object_paint_properties.h
+++ b/third_party/blink/renderer/core/paint/object_paint_properties.h
@@ -142,8 +142,8 @@
   // follows:
   // [ effect ]
   // |     Isolated group to apply various CSS effects, including opacity,
-  // |     mix-blend-mode, and for isolation if a mask needs to be applied or
-  // |     backdrop-dependent children are present.
+  // |     mix-blend-mode, backdrop-filter, and for isolation if a mask needs
+  // |     to be applied or backdrop-dependent children are present.
   // +-[ filter ]
   // |     Isolated group for CSS filter.
   // +-[ vertical/horizontal scrollbar effect ]
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index a197d63e..47822f3 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -3262,6 +3262,7 @@
   const auto& style = GetLayoutObject().StyleRef();
   if (style.BackdropFilter().IsEmpty()) {
     operations.Clear();
+    *backdrop_filter_bounds = gfx::RRectF();
     return;
   }
   FloatRect reference_box = BackdropFilterReferenceBox();
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index e0b3d16..3158aa38 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -801,6 +801,9 @@
   if (blend_mode != SkBlendMode::kSrcOver)
     return true;
 
+  if (!style.BackdropFilter().IsEmpty())
+    return true;
+
   if (style.Opacity() != 1.0f || style.HasWillChangeOpacityHint())
     return true;
 
@@ -928,6 +931,25 @@
         state.blend_mode = WebCoreCompositeToSkiaComposite(
             kCompositeSourceOver, style.GetBlendMode());
       }
+      if (object_.IsBoxModelObject()) {
+        if (auto* layer = ToLayoutBoxModelObject(object_).Layer()) {
+          // Try to use the cached effect for backdrop-filter.
+          if (properties_->Effect()) {
+            state.backdrop_filter = properties_->Effect()->BackdropFilter();
+            state.backdrop_filter_bounds =
+                properties_->Effect()->BackdropFilterBounds();
+          }
+          // With BGPT disabled, UpdateFilterReferenceBox gets called from
+          // CompositedLayerMapping::UpdateGraphicsLayerGeometry, but only
+          // for composited layers.
+          if (RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled() ||
+              layer->GetCompositingState() != kPaintsIntoOwnBacking) {
+            layer->UpdateFilterReferenceBox();
+          }
+          layer->UpdateCompositorFilterOperationsForBackdropFilter(
+              state.backdrop_filter, &state.backdrop_filter_bounds);
+        }
+      }
       if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled() ||
           RuntimeEnabledFeatures::BlinkGenPropertyTreesEnabled()) {
         // We may begin to composite our subtree prior to an animation starts,
@@ -1025,8 +1047,7 @@
     return false;
 
   // TODO(trchen): SVG caches filters in SVGResources. Implement it.
-  if (object.StyleRef().HasFilter() || object.HasReflection() ||
-      object.HasBackdropFilter())
+  if (object.StyleRef().HasFilter() || object.HasReflection())
     return true;
 
   // TODO(flackr): Check for nodes for each KeyframeModel target
@@ -1056,9 +1077,6 @@
         // Try to use the cached filter.
         if (properties_->Filter()) {
           state.filter = properties_->Filter()->Filter();
-          state.backdrop_filter = properties_->Filter()->BackdropFilter();
-          state.backdrop_filter_bounds =
-              properties_->Filter()->BackdropFilterBounds();
         }
 
         // With BGPT disabled, UpdateFilterReferenceBox gets called from
@@ -1069,8 +1087,6 @@
           layer->UpdateFilterReferenceBox();
         }
         layer->UpdateCompositorFilterOperationsForFilter(state.filter);
-        layer->UpdateCompositorFilterOperationsForBackdropFilter(
-            state.backdrop_filter, &state.backdrop_filter_bounds);
         layer->ClearFilterOnEffectNodeDirty();
       }
 
diff --git a/third_party/blink/renderer/devtools/front_end/emulated_devices/module.json b/third_party/blink/renderer/devtools/front_end/emulated_devices/module.json
index f43a66b..7fa5317 100644
--- a/third_party/blink/renderer/devtools/front_end/emulated_devices/module.json
+++ b/third_party/blink/renderer/devtools/front_end/emulated_devices/module.json
@@ -821,6 +821,43 @@
         },
         {
             "type": "emulated-device",
+            "order": 1,
+            "device": {
+                "show-by-default": false,
+                "title": "JioPhone 2",
+                "screen": {
+                    "horizontal": {
+                        "width": 320,
+                        "height": 240
+                    },
+                    "device-pixel-ratio": 1,
+                    "vertical": {
+                        "width": 240,
+                        "height": 320
+                    }
+                },
+                "capabilities": [
+                    "touch",
+                    "mobile"
+                ],
+                "user-agent": "Mozilla/5.0 (Mobile; LYF/F300B/LYF-F300B-001-01-15-130718-i;Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5",
+                "type": "phone",
+                "modes": [
+                    {
+                        "title": "default",
+                        "orientation": "vertical",
+                        "insets": { "left": 0, "top": 0, "right": 0, "bottom": 0 }
+                    },
+                    {
+                        "title": "default",
+                        "orientation": "horizontal",
+                        "insets": { "left": 0, "top": 0, "right": 0, "bottom": 0 }
+                    }
+                ]
+            }
+        },
+        {
+            "type": "emulated-device",
             "device": {
                 "show-by-default": false,
                 "title": "Kindle Fire HDX",
diff --git a/third_party/blink/renderer/devtools/front_end/timeline/TimelineController.js b/third_party/blink/renderer/devtools/front_end/timeline/TimelineController.js
index a8d853a..548b1bb 100644
--- a/third_party/blink/renderer/devtools/front_end/timeline/TimelineController.js
+++ b/third_party/blink/renderer/devtools/front_end/timeline/TimelineController.js
@@ -65,7 +65,7 @@
 
     if (Runtime.experiments.isEnabled('timelineV8RuntimeCallStats') && options.enableJSSampling)
       categoriesArray.push(disabledByDefault('v8.runtime_stats_sampling'));
-    if (Runtime.queryParam('timelineTracingJSProfile') && options.enableJSSampling) {
+    if (!Runtime.queryParam('timelineTracingJSProfileDisabled') && options.enableJSSampling) {
       categoriesArray.push(disabledByDefault('v8.cpu_profiler'));
       if (Common.moduleSetting('highResolutionCpuProfiling').get())
         categoriesArray.push(disabledByDefault('v8.cpu_profiler.hires'));
@@ -175,7 +175,7 @@
    */
   async _startRecordingWithCategories(categories, enableJSSampling) {
     SDK.targetManager.suspendAllTargets();
-    if (enableJSSampling && !Runtime.queryParam('timelineTracingJSProfile'))
+    if (enableJSSampling && Runtime.queryParam('timelineTracingJSProfileDisabled'))
       await this._startProfilingOnAllModels();
     if (!this._tracingManager)
       return;
diff --git a/third_party/blink/renderer/devtools/front_end/timeline/TimelineUIUtils.js b/third_party/blink/renderer/devtools/front_end/timeline/TimelineUIUtils.js
index 1bba2d8..747eb1f 100644
--- a/third_party/blink/renderer/devtools/front_end/timeline/TimelineUIUtils.js
+++ b/third_party/blink/renderer/devtools/front_end/timeline/TimelineUIUtils.js
@@ -1912,6 +1912,11 @@
       case warnings.LongRecurringHandler:
         span.textContent = Common.UIString('Recurring handler took %s', Number.millisToString(event.duration, true));
         break;
+      case warnings.LongTask:
+        span.appendChild(
+            UI.createDocumentationLink('../../fundamentals/performance/rail#goals-and-guidelines', ls`Long task`));
+        span.createTextChild(ls` took ${Number.millisToString(event.duration, true)}.`);
+        break;
       case warnings.V8Deopt:
         span.appendChild(UI.XLink.create(
             'https://github.com/GoogleChrome/devtools-docs/issues/53', Common.UIString('Not optimized')));
diff --git a/third_party/blink/renderer/devtools/front_end/timeline_model/TimelineModel.js b/third_party/blink/renderer/devtools/front_end/timeline_model/TimelineModel.js
index 2b440bc3c..cbf8f54 100644
--- a/third_party/blink/renderer/devtools/front_end/timeline_model/TimelineModel.js
+++ b/third_party/blink/renderer/devtools/front_end/timeline_model/TimelineModel.js
@@ -744,6 +744,11 @@
         break;
       }
 
+      case recordTypes.Task:
+        if (event.duration > TimelineModel.TimelineModel.Thresholds.LongTask)
+          timelineData.warning = TimelineModel.TimelineModel.WarningType.LongTask;
+        break;
+
       case recordTypes.EventDispatch:
         if (event.duration > TimelineModel.TimelineModel.Thresholds.RecurringHandler)
           timelineData.warning = TimelineModel.TimelineModel.WarningType.LongHandler;
@@ -1314,6 +1319,7 @@
  * @enum {string}
  */
 TimelineModel.TimelineModel.WarningType = {
+  LongTask: 'LongTask',
   ForcedStyle: 'ForcedStyle',
   ForcedLayout: 'ForcedLayout',
   IdleDeadlineExceeded: 'IdleDeadlineExceeded',
@@ -1337,6 +1343,7 @@
 };
 
 TimelineModel.TimelineModel.Thresholds = {
+  LongTask: 200,
   Handler: 150,
   RecurringHandler: 50,
   ForcedLayout: 30,
diff --git a/third_party/blink/renderer/platform/exported/web_data.cc b/third_party/blink/renderer/platform/exported/web_data.cc
index c09b94b..86a304e 100644
--- a/third_party/blink/renderer/platform/exported/web_data.cc
+++ b/third_party/blink/renderer/platform/exported/web_data.cc
@@ -48,6 +48,13 @@
   private_ = SharedBuffer::Create(data, size);
 }
 
+void WebData::Append(const char* data, size_t size) {
+  if (private_.IsNull())
+    private_ = SharedBuffer::Create(data, size);
+  else
+    private_->Append(data, size);
+}
+
 size_t WebData::size() const {
   if (private_.IsNull())
     return 0;
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
index 392ca60..b59a2d9 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
@@ -970,13 +970,17 @@
   HashSet<int> pending_render_surfaces;
   auto& effect_tree = host.property_trees()->effect_tree;
   for (const auto& layer : layers) {
+    bool found_backdrop_filter = false;
     for (auto* effect = effect_tree.Node(layer->effect_tree_index());
-         !effect->has_render_surface;
+         !effect->has_render_surface || !effect->backdrop_filters.IsEmpty();
          effect = effect_tree.Node(effect->parent_id)) {
+      found_backdrop_filter |= !effect->backdrop_filters.IsEmpty();
       if (effect->opacity != 1.f &&
-          !pending_render_surfaces.insert(effect->id).is_new_entry) {
-        // The opacity-only effect is seen the second time, which means that it
-        // has more than one compositing child and needs a render surface.
+          (!pending_render_surfaces.insert(effect->id).is_new_entry ||
+           found_backdrop_filter)) {
+        // The opacity-only effect is seen a second time, which means that it
+        // has more than one compositing child and needs a render surface. Or
+        // the opacity effect has a backdrop-filter child.
         effect->has_render_surface = true;
         break;
       }
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
index c5ea197..f5a6823 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
@@ -3346,6 +3346,32 @@
                  kHasRenderSurface);
 }
 
+TEST_P(PaintArtifactCompositorTest, OpacityRenderSurfacesWithBackdropChildren) {
+  // Opacity effect with a single compositing backdrop-filter child. Normally
+  // the opacity effect would not get a render surface. However, because
+  // backdrop-filter needs to only filter up to the backdrop root, it always
+  // gets a render surface.
+  auto e = CreateOpacityEffect(e0(), 0.4f);
+  auto a = CreateOpacityEffect(*e, 0.5f);
+  CompositorFilterOperations blur_filter;
+  blur_filter.AppendBlurFilter(5);
+  auto bd = CreateBackdropFilterEffect(
+      *a, blur_filter, FloatPoint(),
+      CompositingReason::kActiveBackdropFilterAnimation);
+
+  TestPaintArtifact artifact;
+  FloatRect r(150, 150, 100, 100);
+  artifact.Chunk(t0(), c0(), *a).RectDrawing(r, Color::kWhite);
+  artifact.Chunk(t0(), c0(), *bd).RectDrawing(r, Color::kWhite);
+  Update(artifact.Build());
+  ASSERT_EQ(2u, ContentLayerCount());
+
+  EXPECT_OPACITY(ContentLayerAt(0)->effect_tree_index(), 0.5,
+                 kHasRenderSurface);
+  EXPECT_OPACITY(ContentLayerAt(1)->effect_tree_index(), 1.0,
+                 kHasRenderSurface);
+}
+
 TEST_P(PaintArtifactCompositorTest, OpacityIndirectlyAffectingTwoLayers) {
   auto opacity = CreateOpacityEffect(e0(), 0.5f);
   auto child_composited_effect = CreateOpacityEffect(
diff --git a/third_party/blink/renderer/platform/testing/paint_property_test_helpers.h b/third_party/blink/renderer/platform/testing/paint_property_test_helpers.h
index 12a40b2..9ad4051 100644
--- a/third_party/blink/renderer/platform/testing/paint_property_test_helpers.h
+++ b/third_party/blink/renderer/platform/testing/paint_property_test_helpers.h
@@ -72,6 +72,33 @@
                             compositing_reasons);
 }
 
+inline scoped_refptr<EffectPaintPropertyNode> CreateBackdropFilterEffect(
+    const EffectPaintPropertyNode& parent,
+    const TransformPaintPropertyNode& local_transform_space,
+    const ClipPaintPropertyNode* output_clip,
+    CompositorFilterOperations backdrop_filter,
+    const FloatPoint& filters_origin = FloatPoint(),
+    CompositingReasons compositing_reasons = CompositingReason::kNone) {
+  EffectPaintPropertyNode::State state;
+  state.local_transform_space = &local_transform_space;
+  state.output_clip = output_clip;
+  state.backdrop_filter = std::move(backdrop_filter);
+  state.filters_origin = filters_origin;
+  state.direct_compositing_reasons = compositing_reasons;
+  return EffectPaintPropertyNode::Create(parent, std::move(state));
+}
+
+inline scoped_refptr<EffectPaintPropertyNode> CreateBackdropFilterEffect(
+    const EffectPaintPropertyNode& parent,
+    CompositorFilterOperations backdrop_filter,
+    const FloatPoint& paint_offset = FloatPoint(),
+    CompositingReasons compositing_reasons = CompositingReason::kNone) {
+  return CreateBackdropFilterEffect(
+      parent, parent.Unalias().LocalTransformSpace(),
+      parent.Unalias().OutputClip(), backdrop_filter, paint_offset,
+      compositing_reasons);
+}
+
 inline scoped_refptr<ClipPaintPropertyNode> CreateClip(
     const ClipPaintPropertyNode& parent,
     const TransformPaintPropertyNode& local_transform_space,
diff --git a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint
index d6e3678..d0d1a1e 100644
--- a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint
+++ b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=CompositeAfterPaint
@@ -425,11 +425,15 @@
 crbug.com/923429 compositing/layer-creation/fixed-position-out-of-view-with-backdrop-filter.html [ Failure ]
 crbug.com/923429 compositing/overflow/scroll-parent-absolute-with-backdrop-filter.html [ Failure ]
 crbug.com/923429 css3/filters/backdrop-filter-basic-blur.html [ Failure ]
+crbug.com/923429 css3/filters/backdrop-filter-border-radius.html [ Failure ]
+crbug.com/923429 css3/filters/backdrop-filter-rendering.html [ Failure ]
 crbug.com/923429 css3/filters/backdrop-filter-rendering-no-background.html [ Failure ]
 crbug.com/923429 external/wpt/css/filter-effects/backdrop-filter-basic-background-color.html [ Failure ]
 crbug.com/923429 external/wpt/css/filter-effects/backdrop-filter-basic-opacity-2.html [ Failure ]
 crbug.com/923429 external/wpt/css/filter-effects/backdrop-filter-basic.html [ Failure ]
 crbug.com/923429 external/wpt/css/filter-effects/backdrop-filter-fixed-clip.html [ Failure ]
+crbug.com/923429 external/wpt/css/filter-effects/backdrop-filter-isolation.html [ Failure ]
+
 
 Bug(none) css3/filters/blur-filter-page-scroll-self.html [ Failure ]
 
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index ce40db9..15a0fc59 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1918,13 +1918,10 @@
 crbug.com/497522 css3/filters/backdrop-filter-svg.html [ Failure ]
 #crbug.com/497522 css3/filters/backdrop-filter-plus-filter.html [ Failure ]
 # This fails, but only in CAP mode:
-crbug.com/497522 css3/filters/backdrop-filter-rendering.html [ Failure ]
 crbug.com/497522 external/wpt/css/filter-effects/backdrop-filter-isolation-isolate.html [ Failure ]
-crbug.com/497522 external/wpt/css/filter-effects/backdrop-filter-border-radius.html [ Failure ]
 crbug.com/497522 external/wpt/css/filter-effects/backdrop-filter-paint-order.html [ Failure ]
 crbug.com/497522 external/wpt/css/filter-effects/backdrop-filter-isolation-fixed.html [ Failure ]
 crbug.com/497522 external/wpt/css/filter-effects/backdrop-filter-edge-pixels.html [ Pass Failure ]
-crbug.com/497522 external/wpt/css/filter-effects/backdrop-filter-isolation.html [ Failure ]
 # Until backdrop filter is enabled by default, backdrop tests will not pass in virtual/stable.
 Bug(none) virtual/stable/compositing/layer-creation/fixed-position-out-of-view-with-backdrop-filter.html [ Skip ]
 Bug(none) virtual/stable/compositing/overflow/scroll-parent-absolute-with-backdrop-filter.html [ Skip ]
@@ -6023,6 +6020,11 @@
 crbug.com/v8/8319 external/wpt/wasm/jsapi/module/customSections.any.html [ Pass Failure ]
 crbug.com/v8/8319 external/wpt/wasm/jsapi/module/customSections.any.worker.html [ Pass Failure ]
 
+crbug.com/927296 virtual/wasm-site-isolated-code-cache/http/tests/devtools/wasm-isolated-code-cache/wasm-cache-test.js [ Pass Failure ]
+
+# Disabled until FreezeFramesOnVisiblity feature enabled by default.
+crbug.com/907125 external/wpt/lifecycle/child-display-none.tentative.html [ Skip ]
+
 # Sheriff 2019-02-01
 # These are crashy on Win10. The first is also flaky on other platforms, but apparently
 # this file doesn't support having two lines referring to the same test with nested specificity.
@@ -6039,4 +6041,5 @@
 
 # Sheriff 2019-02-06
 crbug.com/929122 [ Linux ] external/wpt/html/dom/interfaces.worker.html [ Timeout Failure ]
-crbug.com/929355 [ Linux ] external/wpt/webrtc/RTCPeerConnection-track-stats.https.html [ Failure ]
+crbug.com/929355 [ Linux ] external/wpt/webrtc/RTCPeerConnection-track-stats.https.html [ Pass Failure ]
+crbug.com/929435 [ Mac ] external/wpt/webaudio/the-audio-api/the-audiobuffersourcenode-interface/sub-sample-buffer-stitching.html [ Pass Failure ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 8024702..02e81358 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -1040,6 +1040,11 @@
     "args": ["--enable-blink-features=BidiCaretAffinity,EditingNG"]
   },
   {
+    "prefix": "freeze-frames",
+    "base": "external/wpt/lifecycle",
+    "args": ["--enable-features=FreezeFramesOnVisibility"]
+  },
+  {
     "prefix": "feature-policy-for-sandbox",
     "base": "http/tests/navigation",
     "args": ["--enable-blink-features=FeaturePolicyForSandbox"]
diff --git a/third_party/blink/web_tests/css3/filters/backdrop-filter-border-radius-expected.png b/third_party/blink/web_tests/css3/filters/backdrop-filter-border-radius-expected.png
new file mode 100644
index 0000000..1ead16785
--- /dev/null
+++ b/third_party/blink/web_tests/css3/filters/backdrop-filter-border-radius-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/css3/filters/backdrop-filter-border-radius.html b/third_party/blink/web_tests/css3/filters/backdrop-filter-border-radius.html
new file mode 100644
index 0000000..2981af3
--- /dev/null
+++ b/third_party/blink/web_tests/css3/filters/backdrop-filter-border-radius.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<div style="opacity: 0.9999;">
+  <div class="circle filter"></div>
+
+</div>
+
+
+<style>
+div {
+  position: absolute;
+  width: 100px;
+  height: 100px;
+  top: 10px;
+  left: 10px;
+  background: green;
+}
+.circle {
+  top: 30px;
+  left: 30px;
+  border-radius: 50px;
+  background: #ffff0060;
+}
+.filter {
+  backdrop-filter: invert(1);
+}
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom-view/long_scroll_composited-ref.html b/third_party/blink/web_tests/external/wpt/css/cssom-view/long_scroll_composited-ref.html
new file mode 100644
index 0000000..6914cba3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/cssom-view/long_scroll_composited-ref.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Long scrolling should work properly</title>
+<link rel="author" title="Mason Freed" href="mailto:masonfreed@chromium.org">
+
+
+
+<style>
+.post {
+  height: 1000px;
+  width: 300px;
+  border: 1px solid black;
+
+}
+.before {
+  height: 213px;
+  border-top: 0;
+}
+.scroller {
+  overflow-y: scroll;
+  width: 500px;
+  height: 500px;
+  will-change: transform;
+}
+::-webkit-scrollbar {
+  display: none;
+}
+</style>
+
+<p>The number 7 should be visible in the scrolled window below.</p>
+
+<div id="scroller" class="scroller">
+  <div style="position: relative;">
+    <div style="position: relative;">
+      <div class="post before"></div>
+      <div class="post">7</div>
+    </div>
+  </div>
+</div>
+
+
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom-view/long_scroll_composited.html b/third_party/blink/web_tests/external/wpt/css/cssom-view/long_scroll_composited.html
new file mode 100644
index 0000000..aa91023
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/cssom-view/long_scroll_composited.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Long scrolling should work properly</title>
+<link rel="author" title="Mason Freed" href="mailto:masonfreed@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/cssom-view/#scrolling">
+<link rel="match" href="long_scroll_composited-ref.html">
+
+<style>
+.post {
+  height: 1000px;
+  width: 300px;
+  border: 1px solid black;
+
+}
+.scroller {
+  overflow-y: scroll;
+  width: 500px;
+  height: 500px;
+  will-change: transform;
+}
+::-webkit-scrollbar {
+  display: none;
+}
+</style>
+
+<p>The number 7 should be visible in the scrolled window below.</p>
+
+<div id="scroller" class="scroller">
+  <div style="position: relative;">
+    <div style="position: relative;">
+      <div class="post">0</div>
+      <div class="post">1</div>
+      <div class="post">2</div>
+      <div class="post">3</div>
+      <div class="post">4</div>
+      <div class="post">5</div>
+      <div class="post">6</div>
+      <div class="post">7</div>
+      <div class="post">8</div>
+      <div class="post">9</div>
+    </div>
+  </div>
+</div>
+
+<script>
+onload = function() {
+  scroller=document.getElementById("scroller");
+  scroller.scrollTop = 6800;
+};
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filter-border-radius-ref.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filter-border-radius-ref.html
deleted file mode 100644
index e5712a23..0000000
--- a/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filter-border-radius-ref.html
+++ /dev/null
@@ -1,36 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>backdrop-filter: Should clip using border radius.</title>
-<link rel="author" title="Mason Freed" href="mailto:masonfreed@chromium.org">
-
-
-
-<div>
-  <div class="circle outside"></div>
-  <div class="circle inside"></div>
-</div>
-
-
-<style>
-div {
-  position: absolute;
-  width: 100px;
-  height: 100px;
-  top: 10px;
-  left: 10px;
-  background: green;
-}
-.circle {
-  top: 30px;
-  left: 30px;
-  border-radius: 50px;
-  background: #ffff0060;
-}
-.inside {
-  background: #ffaf9f;
-  clip-path: inset(0px 30px 30px 0px);
-}
-.outside {
-  background: #ffff9f;
-}
-</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filter-border-radius.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filter-border-radius.html
deleted file mode 100644
index ec93de6..0000000
--- a/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filter-border-radius.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>backdrop-filter: Should clip using border radius.</title>
-<link rel="author" title="Mason Freed" href="mailto:masonfreed@chromium.org">
-<link rel="help" href="https://drafts.fxtf.org/filter-effects-2/#BackdropFilterProperty">
-<link rel="match"  href="backdrop-filter-border-radius-ref.html">
-
-<div style="opacity: 0.9999;">
-  <div class="circle filter"></div>
-
-</div>
-
-
-<style>
-div {
-  position: absolute;
-  width: 100px;
-  height: 100px;
-  top: 10px;
-  left: 10px;
-  background: green;
-}
-.circle {
-  top: 30px;
-  left: 30px;
-  border-radius: 50px;
-  background: #ffff0060;
-}
-.filter {
-  backdrop-filter: invert(1);
-}
-</style>
diff --git a/third_party/blink/web_tests/external/wpt/domparsing/XMLSerializer-serializeToString-expected.txt b/third_party/blink/web_tests/external/wpt/domparsing/XMLSerializer-serializeToString-expected.txt
new file mode 100644
index 0000000..8729df1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/domparsing/XMLSerializer-serializeToString-expected.txt
@@ -0,0 +1,9 @@
+This is a testharness.js-based test.
+PASS check XMLSerializer.serializeToString method could parsing xmldoc to string
+PASS Check if the default namespace is correctly reset.
+PASS Check if there is no redundant empty namespace declaration.
+PASS check XMLSerializer.serializeToString escapes attribute values for roundtripping
+PASS Check if generated prefixes match to "ns${index}".
+FAIL Check if "ns1" is generated even if the element already has xmlns:ns1. assert_equals: expected "<root xmlns:ns2=\"uri2\"><child xmlns:ns1=\"uri1\" xmlns:ns1=\"uri3\" ns1:attr1=\"value1\"/></root>" but got "<root xmlns:ns2=\"uri2\"><child xmlns:ns1=\"uri1\" xmlns:ns3=\"uri3\" ns3:attr1=\"value1\"/></root>"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/domparsing/XMLSerializer-serializeToString.html b/third_party/blink/web_tests/external/wpt/domparsing/XMLSerializer-serializeToString.html
index 7c084e78..d71da49 100644
--- a/third_party/blink/web_tests/external/wpt/domparsing/XMLSerializer-serializeToString.html
+++ b/third_party/blink/web_tests/external/wpt/domparsing/XMLSerializer-serializeToString.html
@@ -65,6 +65,16 @@
   assert_equals(xmlString, '<root><child1 xmlns:ns1="uri1" ns1:attr1="value1" xmlns:ns2="uri2" ns2:attr2="value2"/><child2 xmlns:ns3="uri3" ns3:attr3="value3"/></root>');
 }, 'Check if generated prefixes match to "ns${index}".');
 
+test(function() {
+  const input = '<root xmlns:ns2="uri2"><child xmlns:ns1="uri1"/></root>';
+  const root = (new DOMParser()).parseFromString(input, 'text/xml').documentElement;
+  root.firstChild.setAttributeNS('uri3', 'attr1', 'value1');
+  const xmlString = (new XMLSerializer()).serializeToString(root);
+  // According to 'DOM Parsing and Serialization' draft as of 2018-12-11,
+  // 'generate a prefix' result can conflict with an existing xmlns:ns* declaration.
+  assert_equals(xmlString, '<root xmlns:ns2="uri2"><child xmlns:ns1="uri1" xmlns:ns1="uri3" ns1:attr1="value1"/></root>');
+}, 'Check if "ns1" is generated even if the element already has xmlns:ns1.');
+
 </script>
  </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/lifecycle/child-display-none.tentative.html b/third_party/blink/web_tests/external/wpt/lifecycle/child-display-none.tentative.html
new file mode 100644
index 0000000..f76b4e7b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/lifecycle/child-display-none.tentative.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Child frame marked as frozen</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+async_test((t) => {
+
+  var child = document.createElement('iframe');
+
+  var loaded = false;
+  var frozen = false;
+
+  window.addEventListener('message', t.step_func((e) => {
+    if (e.data == "load") {
+      loaded = true;
+    } else if (e.data == "freeze") {
+      assert_true(loaded);
+      frozen = true;
+      child.style = "display: block";
+    } else if (e.data == "resume") {
+      assert_true(loaded);
+      assert_true(frozen);
+      t.done();
+    }
+  }));
+
+  child.src = "resources/subframe.html";
+  document.body.appendChild(child);
+  child.style = "display: none";
+}, "Child frame frozen");
+
+</script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/lifecycle/resources/subframe.html b/third_party/blink/web_tests/external/wpt/lifecycle/resources/subframe.html
new file mode 100644
index 0000000..2f1d70a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/lifecycle/resources/subframe.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script>
+window.addEventListener('load', () => {
+  window.parent.postMessage('load');
+});
+
+document.addEventListener('freeze', () => {
+  window.parent.postMessage('freeze');
+});
+
+document.addEventListener('resume', () => {
+  window.parent.postMessage('resume');
+});
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/META.yml b/third_party/blink/web_tests/external/wpt/webdriver/META.yml
new file mode 100644
index 0000000..a397b49
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/META.yml
@@ -0,0 +1,7 @@
+spec: https://w3c.github.io/webdriver/
+suggested_reviewers:
+  - AutomatedTester
+  - andreastt
+  - mjzffr
+  - shs96c
+  - whimboo
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/README.md b/third_party/blink/web_tests/external/wpt/webdriver/README.md
new file mode 100644
index 0000000..78d9aba
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/README.md
@@ -0,0 +1,17 @@
+# WebDriver specification tests
+
+Herein lies a set of conformance tests
+for the W3C web browser automation specification
+known as [WebDriver](http://w3c.github.io/webdriver/webdriver-spec.html).
+The purpose of these tests is determine implementation compliance
+so that different driver implementations can determine
+whether they meet the recognized standard.
+
+## Chapters of the Spec that still need tests
+
+We are using a [tracking spreadsheet](https://docs.google.com/spreadsheets/d/1GUK_sdY2cv59VAJNDxZQIfypnOpapSQhMjfcJ9Wc42U/edit#gid=0)
+to coordinate work on these tests. Please look there to see who
+is working on what, and which areas are currently under-tested.
+
+The spec contributors and editors can frequently be found on the W3C
+#webdriver IRC channel.
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/setup.py b/third_party/blink/web_tests/external/wpt/webdriver/setup.py
new file mode 100644
index 0000000..c473961
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/setup.py
@@ -0,0 +1,14 @@
+from setuptools import setup, find_packages
+
+setup(name="webdriver",
+      version="1.0",
+      description="WebDriver client compatible with "
+                  "the W3C browser automation specification.",
+      author="Mozilla Engineering Productivity",
+      author_email="tools@lists.mozilla.org",
+      license="BSD",
+      packages=find_packages(),
+      classifiers=["Development Status :: 4 - Beta",
+                   "Intended Audience :: Developers",
+                   "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
+                   "Operating System :: OS Independent"])
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/__init__.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/__init__.py
new file mode 100644
index 0000000..0ba172ff
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/__init__.py
@@ -0,0 +1,4 @@
+import pytest
+
+# Enable pytest assert introspection for assertion helper
+pytest.register_assert_rewrite('tests.support.asserts')
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/conftest.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/conftest.py
new file mode 100644
index 0000000..42b82c9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/conftest.py
@@ -0,0 +1 @@
+pytest_plugins = "tests.support.fixtures"
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/navigate_to/__init__.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/navigate_to/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/navigate_to/__init__.py
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/navigate_to/navigate.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/navigate_to/navigate.py
new file mode 100644
index 0000000..e478e10b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/navigate_to/navigate.py
@@ -0,0 +1,27 @@
+from webdriver.transport import Response
+
+from tests.support.asserts import assert_error, assert_success
+from tests.support.inline import inline
+
+
+def navigate_to(session, url):
+    return session.transport.send(
+        "POST", "session/{session_id}/url".format(**vars(session)),
+        {"url": url})
+
+
+def test_null_parameter_value(session, http):
+    path = "/session/{session_id}/url".format(**vars(session))
+    with http.post(path, None) as response:
+        assert_error(Response.from_http(response), "invalid argument")
+
+
+def test_null_response_value(session):
+    response = navigate_to(session, inline("<div/>"))
+    value = assert_success(response)
+    assert value is None
+
+
+def test_no_browsing_context(session, closed_window):
+    response = navigate_to(session, "foo")
+    assert_error(response, "no such window")
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/support/__init__.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/__init__.py
new file mode 100644
index 0000000..e5e43c4e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/__init__.py
@@ -0,0 +1,10 @@
+import sys
+
+from merge_dictionaries import merge_dictionaries
+
+platform_name = {
+    "linux2": "linux",
+    "win32": "windows",
+    "cygwin": "windows",
+    "darwin": "mac"
+}.get(sys.platform)
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/support/asserts.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/asserts.py
new file mode 100644
index 0000000..6771198
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/asserts.py
@@ -0,0 +1,202 @@
+import base64
+import imghdr
+import struct
+
+from webdriver import Element, NoSuchAlertException, WebDriverException
+
+
+# WebDriver specification ID: dfn-error-response-data
+errors = {
+    "element click intercepted": 400,
+    "element not selectable": 400,
+    "element not interactable": 400,
+    "insecure certificate": 400,
+    "invalid argument": 400,
+    "invalid cookie domain": 400,
+    "invalid coordinates": 400,
+    "invalid element state": 400,
+    "invalid selector": 400,
+    "invalid session id": 404,
+    "javascript error": 500,
+    "move target out of bounds": 500,
+    "no such alert": 404,
+    "no such cookie": 404,
+    "no such element": 404,
+    "no such frame": 404,
+    "no such window": 404,
+    "script timeout": 500,
+    "session not created": 500,
+    "stale element reference": 404,
+    "timeout": 500,
+    "unable to set cookie": 500,
+    "unable to capture screen": 500,
+    "unexpected alert open": 500,
+    "unknown command": 404,
+    "unknown error": 500,
+    "unknown method": 405,
+    "unsupported operation": 500,
+}
+
+
+def assert_error(response, error_code):
+    """
+    Verify that the provided webdriver.Response instance described
+    a valid error response as defined by `dfn-send-an-error` and
+    the provided error code.
+
+    :param response: ``webdriver.Response`` instance.
+    :param error_code: String value of the expected error code
+    """
+    assert response.status == errors[error_code]
+    assert "value" in response.body
+    assert response.body["value"]["error"] == error_code
+    assert isinstance(response.body["value"]["message"], basestring)
+    assert isinstance(response.body["value"]["stacktrace"], basestring)
+
+
+def assert_success(response, value=None):
+    """
+    Verify that the provided webdriver.Response instance described
+    a valid error response as defined by `dfn-send-an-error` and
+    the provided error code.
+
+    :param response: ``webdriver.Response`` instance.
+    :param value: Expected value of the response body, if any.
+    """
+    assert response.status == 200, str(response.error)
+
+    if value is not None:
+        assert response.body["value"] == value
+    return response.body.get("value")
+
+
+def assert_dialog_handled(session, expected_text, expected_retval):
+    # If there were any existing dialogs prior to the creation of this
+    # fixture's dialog, then the "Get Alert Text" command will return
+    # successfully. In that case, the text must be different than that
+    # of this fixture's dialog.
+    try:
+        assert session.alert.text != expected_text, (
+            "User prompt with text '{}' was not handled.".format(expected_text))
+
+    except NoSuchAlertException:
+        # If dialog has been closed and no other one is open, check its return value
+        prompt_retval = session.execute_script(" return window.dialog_return_value;")
+        assert prompt_retval == expected_retval
+
+
+def assert_files_uploaded(session, element, files):
+
+    def get_file_contents(file_index):
+        return session.execute_async_script("""
+            let files = arguments[0].files;
+            let index = arguments[1];
+            let resolve = arguments[2];
+
+            var reader = new FileReader();
+            reader.onload = function(event) {
+              resolve(reader.result);
+            };
+            reader.readAsText(files[index]);
+        """, (element, file_index))
+
+    def get_uploaded_file_names():
+        return session.execute_script("""
+            let fileList = arguments[0].files;
+            let files = [];
+
+            for (var i = 0; i < fileList.length; i++) {
+              files.push(fileList[i].name);
+            }
+
+            return files;
+        """, args=(element,))
+
+    expected_file_names = [str(f.basename) for f in files]
+    assert get_uploaded_file_names() == expected_file_names
+
+    for index, f in enumerate(files):
+        assert get_file_contents(index) == f.read()
+
+
+def assert_is_active_element(session, element):
+    """Verify that element reference is the active element."""
+    from_js = session.execute_script("return document.activeElement")
+
+    if element is None:
+        assert from_js is None
+    else:
+        assert_same_element(session, element, from_js)
+
+
+def assert_same_element(session, a, b):
+    """Verify that two element references describe the same element."""
+    if isinstance(a, dict):
+        assert Element.identifier in a, "Actual value does not describe an element"
+        a_id = a[Element.identifier]
+    elif isinstance(a, Element):
+        a_id = a.id
+    else:
+        raise AssertionError("Actual value is not a dictionary or web element")
+
+    if isinstance(b, dict):
+        assert Element.identifier in b, "Expected value does not describe an element"
+        b_id = b[Element.identifier]
+    elif isinstance(b, Element):
+        b_id = b.id
+    else:
+        raise AssertionError("Expected value is not a dictionary or web element")
+
+    if a_id == b_id:
+        return
+
+    message = ("Expected element references to describe the same element, " +
+               "but they did not.")
+
+    # Attempt to provide more information, accounting for possible errors such
+    # as stale element references or not visible elements.
+    try:
+        a_markup = session.execute_script("return arguments[0].outerHTML;", args=(a,))
+        b_markup = session.execute_script("return arguments[0].outerHTML;", args=(b,))
+        message += " Actual: `%s`. Expected: `%s`." % (a_markup, b_markup)
+    except WebDriverException:
+        pass
+
+    raise AssertionError(message)
+
+
+def assert_in_events(session, expected_events):
+    actual_events = session.execute_script("return window.events")
+    for expected_event in expected_events:
+        assert expected_event in actual_events
+
+
+def assert_events_equal(session, expected_events):
+    actual_events = session.execute_script("return window.events")
+    assert actual_events == expected_events
+
+
+def assert_element_has_focus(target_element):
+    session = target_element.session
+
+    active_element = session.execute_script("return document.activeElement")
+    active_tag = active_element.property("localName")
+    target_tag = target_element.property("localName")
+
+    assert active_element == target_element, (
+        "Focussed element is <%s>, not <%s>" % (active_tag, target_tag))
+
+
+def assert_move_to_coordinates(point, target, events):
+    for e in events:
+        if e["type"] != "mousemove":
+            assert e["pageX"] == point["x"]
+            assert e["pageY"] == point["y"]
+            assert e["target"] == target
+
+
+def assert_png(screenshot):
+    """Test that screenshot is a Base64 encoded PNG file."""
+    image = base64.decodestring(screenshot)
+    mime_type = imghdr.what("", image)
+    assert mime_type == "png", "Expected image to be PNG, but it was {}".format(mime_type)
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/support/defaults.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/defaults.py
new file mode 100644
index 0000000..c2020527
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/defaults.py
@@ -0,0 +1,8 @@
+SCRIPT_TIMEOUT = 30
+PAGE_LOAD_TIMEOUT = 300
+IMPLICIT_WAIT_TIMEOUT = 0
+
+WINDOW_SIZE = (800, 600)
+
+DRIVER_HOST = '127.0.0.1'
+DRIVER_PORT = 4444
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/support/fixtures.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/fixtures.py
new file mode 100644
index 0000000..149350c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/fixtures.py
@@ -0,0 +1,227 @@
+import copy
+import json
+import os
+import urlparse
+
+import pytest
+import webdriver
+
+from tests.support import defaults
+from tests.support.helpers import cleanup_session
+from tests.support.http_request import HTTPRequest
+from tests.support.sync import Poll
+
+
+_current_session = None
+_custom_session = False
+
+
+def pytest_configure(config):
+    # register the capabilities marker
+    config.addinivalue_line("markers",
+        "capabilities: mark test to use capabilities")
+
+
+@pytest.fixture
+def capabilities():
+    """Default capabilities to use for a new WebDriver session."""
+    return {}
+
+
+
+@pytest.fixture
+def add_event_listeners(session):
+    """Register listeners for tracked events on element."""
+    def add_event_listeners(element, tracked_events):
+        element.session.execute_script("""
+            let element = arguments[0];
+            let trackedEvents = arguments[1];
+
+            if (!("events" in window)) {
+              window.events = [];
+            }
+
+            for (var i = 0; i < trackedEvents.length; i++) {
+              element.addEventListener(trackedEvents[i], function (event) {
+                window.events.push(event.type);
+              });
+            }
+            """, args=(element, tracked_events))
+    return add_event_listeners
+
+
+@pytest.fixture
+def create_cookie(session, url):
+    """Create a cookie"""
+    def create_cookie(name, value, **kwargs):
+        if kwargs.get("path", None) is not None:
+            session.url = url(kwargs["path"])
+
+        session.set_cookie(name, value, **kwargs)
+        return session.cookies(name)
+
+    return create_cookie
+
+
+@pytest.fixture
+def create_frame(session):
+    """Create an `iframe` element in the current browsing context and insert it
+    into the document. Return a reference to the newly-created element."""
+    def create_frame():
+        append = """
+            var frame = document.createElement('iframe');
+            document.body.appendChild(frame);
+            return frame;
+        """
+        return session.execute_script(append)
+
+    return create_frame
+
+
+@pytest.fixture
+def create_window(session):
+    """Open new window and return the window handle."""
+    def create_window():
+        windows_before = session.handles
+        name = session.execute_script("window.open()")
+        assert len(session.handles) == len(windows_before) + 1
+        new_windows = list(set(session.handles) - set(windows_before))
+        return new_windows.pop()
+    return create_window
+
+
+@pytest.fixture
+def http(configuration):
+    return HTTPRequest(configuration["host"], configuration["port"])
+
+
+@pytest.fixture
+def server_config():
+    return json.loads(os.environ.get("WD_SERVER_CONFIG"))
+
+
+@pytest.fixture(scope="session")
+def configuration():
+    host = os.environ.get("WD_HOST", defaults.DRIVER_HOST)
+    port = int(os.environ.get("WD_PORT", str(defaults.DRIVER_PORT)))
+    capabilities = json.loads(os.environ.get("WD_CAPABILITIES", "{}"))
+
+    return {
+        "host": host,
+        "port": port,
+        "capabilities": capabilities
+    }
+
+
+@pytest.fixture(scope="function")
+def session(capabilities, configuration, request):
+    """Create and start a session for a test that does not itself test session creation.
+
+    By default the session will stay open after each test, but we always try to start a
+    new one and assume that if that fails there is already a valid session. This makes it
+    possible to recover from some errors that might leave the session in a bad state, but
+    does not demand that we start a new session per test."""
+    global _current_session
+
+    # Update configuration capabilities with custom ones from the
+    # capabilities fixture, which can be set by tests
+    caps = copy.deepcopy(configuration["capabilities"])
+    caps.update(capabilities)
+    caps = {"alwaysMatch": caps}
+
+    # If there is a session with different capabilities active, end it now
+    if _current_session is not None and (
+            caps != _current_session.requested_capabilities):
+        _current_session.end()
+        _current_session = None
+
+    if _current_session is None:
+        _current_session = webdriver.Session(
+            configuration["host"],
+            configuration["port"],
+            capabilities=caps)
+    try:
+        _current_session.start()
+    except webdriver.error.SessionNotCreatedException:
+        if not _current_session.session_id:
+            raise
+
+    # Enforce a fixed default window size
+    _current_session.window.size = defaults.WINDOW_SIZE
+
+    yield _current_session
+
+    cleanup_session(_current_session)
+
+
+@pytest.fixture(scope="function")
+def current_session():
+    return _current_session
+
+
+@pytest.fixture
+def url(server_config):
+    def inner(path, protocol="http", query="", fragment=""):
+        port = server_config["ports"][protocol][0]
+        host = "%s:%s" % (server_config["browser_host"], port)
+        return urlparse.urlunsplit((protocol, host, path, query, fragment))
+
+    inner.__name__ = "url"
+    return inner
+
+
+@pytest.fixture
+def create_dialog(session):
+    """Create a dialog (one of "alert", "prompt", or "confirm") and provide a
+    function to validate that the dialog has been "handled" (either accepted or
+    dismissed) by returning some value."""
+
+    def create_dialog(dialog_type, text=None):
+        assert dialog_type in ("alert", "confirm", "prompt"), (
+            "Invalid dialog type: '%s'" % dialog_type)
+
+        if text is None:
+            text = ""
+
+        assert isinstance(text, basestring), "`text` parameter must be a string"
+
+        # Script completes itself when the user prompt has been opened.
+        # For prompt() dialogs, add a value for the 'default' argument,
+        # as some user agents (IE, for example) do not produce consistent
+        # values for the default.
+        session.execute_async_script("""
+            let dialog_type = arguments[0];
+            let text = arguments[1];
+
+            setTimeout(function() {
+              if (dialog_type == 'prompt') {
+                window.dialog_return_value = window[dialog_type](text, '');
+              } else {
+                window.dialog_return_value = window[dialog_type](text);
+              }
+            }, 0);
+            """, args=(dialog_type, text))
+
+        wait = Poll(
+            session,
+            timeout=15,
+            ignored_exceptions=webdriver.NoSuchAlertException,
+            message="No user prompt with text '{}' detected".format(text))
+        wait.until(lambda s: s.alert.text == text)
+
+    return create_dialog
+
+
+@pytest.fixture
+def closed_window(session, create_window):
+    original_handle = session.window_handle
+
+    new_handle = create_window()
+    session.window_handle = new_handle
+
+    session.close()
+    assert new_handle not in session.handles, "Unable to close window {}".format(new_handle)
+
+    yield new_handle
+
+    session.window_handle = original_handle
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/support/helpers.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/helpers.py
new file mode 100644
index 0000000..a0e6a75
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/helpers.py
@@ -0,0 +1,215 @@
+from __future__ import print_function
+
+import math
+import sys
+
+import webdriver
+
+from tests.support import defaults
+from tests.support.sync import Poll
+
+
+def ignore_exceptions(f):
+    def inner(*args, **kwargs):
+        try:
+            return f(*args, **kwargs)
+        except webdriver.error.WebDriverException as e:
+            #Remove it temporaryily because of the wpt lint
+            return
+    inner.__name__ = f.__name__
+    return inner
+
+
+def cleanup_session(session):
+    """Clean-up the current session for a clean state."""
+    @ignore_exceptions
+    def _dismiss_user_prompts(session):
+        """Dismiss any open user prompts in windows."""
+        current_window = session.window_handle
+
+        for window in _windows(session):
+            session.window_handle = window
+            try:
+                session.alert.dismiss()
+            except webdriver.NoSuchAlertException:
+                pass
+
+        session.window_handle = current_window
+
+    @ignore_exceptions
+    def _ensure_valid_window(session):
+        """If current window was closed, ensure to have a valid one selected."""
+        try:
+            session.window_handle
+        except webdriver.NoSuchWindowException:
+            session.window_handle = session.handles[0]
+
+    @ignore_exceptions
+    def _restore_timeouts(session):
+        """Restore modified timeouts to their default values."""
+        session.timeouts.implicit = defaults.IMPLICIT_WAIT_TIMEOUT
+        session.timeouts.page_load = defaults.PAGE_LOAD_TIMEOUT
+        session.timeouts.script = defaults.SCRIPT_TIMEOUT
+
+    @ignore_exceptions
+    def _restore_window_state(session):
+        """Reset window to an acceptable size.
+
+        This also includes bringing it out of maximized, minimized,
+        or fullscreened state.
+        """
+        session.window.size = defaults.WINDOW_SIZE
+
+    @ignore_exceptions
+    def _restore_windows(session):
+        """Close superfluous windows opened by the test.
+
+        It will not end the session implicitly by closing the last window.
+        """
+        current_window = session.window_handle
+
+        for window in _windows(session, exclude=[current_window]):
+            session.window_handle = window
+            if len(session.handles) > 1:
+                session.close()
+
+        session.window_handle = current_window
+
+    _restore_timeouts(session)
+    _ensure_valid_window(session)
+    _dismiss_user_prompts(session)
+    _restore_windows(session)
+    _restore_window_state(session)
+    _switch_to_top_level_browsing_context(session)
+
+
+@ignore_exceptions
+def _switch_to_top_level_browsing_context(session):
+    """If the current browsing context selected by WebDriver is a
+    `<frame>` or an `<iframe>`, switch it back to the top-level
+    browsing context.
+    """
+    session.switch_frame(None)
+
+
+def _windows(session, exclude=None):
+    """Set of window handles, filtered by an `exclude` list if
+    provided.
+    """
+    if exclude is None:
+        exclude = []
+    wins = [w for w in session.handles if w not in exclude]
+    return set(wins)
+
+
+def clear_all_cookies(session):
+    """Removes all cookies associated with the current active document"""
+    session.transport.send("DELETE", "session/%s/cookie" % session.session_id)
+
+
+def document_dimensions(session):
+    return tuple(session.execute_script("""
+        let rect = document.documentElement.getBoundingClientRect();
+        return [rect.width, rect.height];
+        """))
+
+
+def center_point(element):
+    """Calculates the in-view center point of a web element."""
+    inner_width, inner_height = element.session.execute_script(
+        "return [window.innerWidth, window.innerHeight]")
+    rect = element.rect
+
+    # calculate the intersection of the rect that is inside the viewport
+    visible = {
+        "left": max(0, min(rect["x"], rect["x"] + rect["width"])),
+        "right": min(inner_width, max(rect["x"], rect["x"] + rect["width"])),
+        "top": max(0, min(rect["y"], rect["y"] + rect["height"])),
+        "bottom": min(inner_height, max(rect["y"], rect["y"] + rect["height"])),
+    }
+
+    # arrive at the centre point of the visible rectangle
+    x = (visible["left"] + visible["right"]) / 2.0
+    y = (visible["top"] + visible["bottom"]) / 2.0
+
+    # convert to CSS pixels, as centre point can be float
+    return (math.floor(x), math.floor(y))
+
+
+def document_hidden(session):
+    """Polls for the document to become hidden."""
+    def hidden(session):
+        return session.execute_script("return document.hidden")
+    return Poll(session, timeout=3, raises=None).until(hidden)
+
+
+def element_rect(session, element):
+    return session.execute_script("""
+        let element = arguments[0];
+        let rect = element.getBoundingClientRect();
+
+        return {
+            x: rect.left + window.pageXOffset,
+            y: rect.top + window.pageYOffset,
+            width: rect.width,
+            height: rect.height,
+        };
+        """, args=(element,))
+
+
+def is_element_in_viewport(session, element):
+    """Check if element is outside of the viewport"""
+    return session.execute_script("""
+        let el = arguments[0];
+
+        let rect = el.getBoundingClientRect();
+        let viewport = {
+          height: window.innerHeight || document.documentElement.clientHeight,
+          width: window.innerWidth || document.documentElement.clientWidth,
+        };
+
+        return !(rect.right < 0 || rect.bottom < 0 ||
+            rect.left > viewport.width || rect.top > viewport.height)
+    """, args=(element,))
+
+
+def is_fullscreen(session):
+    # At the time of writing, WebKit does not conform to the
+    # Fullscreen API specification.
+    #
+    # Remove the prefixed fallback when
+    # https://bugs.webkit.org/show_bug.cgi?id=158125 is fixed.
+    return session.execute_script("""
+        return !!(window.fullScreen || document.webkitIsFullScreen)
+        """)
+
+
+def document_dimensions(session):
+    return tuple(session.execute_script("""
+        let {devicePixelRatio} = window;
+        let {width, height} = document.documentElement.getBoundingClientRect();
+        return [width * devicePixelRatio, height * devicePixelRatio];
+        """))
+
+
+def screen_size(session):
+    """Returns the available width/height size of the screen."""
+    return tuple(session.execute_script("""
+        return [
+            screen.availWidth,
+            screen.availHeight,
+        ];
+        """))
+
+
+def available_screen_size(session):
+    """
+    Returns the effective available screen width/height size,
+    excluding any fixed window manager elements.
+    """
+    return tuple(session.execute_script("""
+        return [
+            screen.availWidth - screen.availLeft,
+            screen.availHeight - screen.availTop,
+        ];
+        """))
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/support/http_request.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/http_request.py
new file mode 100644
index 0000000..5e46d97
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/http_request.py
@@ -0,0 +1,41 @@
+import contextlib
+import httplib
+import json
+
+from six import text_type
+
+
+class HTTPRequest(object):
+    def __init__(self, host, port):
+        self.host = host
+        self.port = port
+
+    def head(self, path):
+        return self._request("HEAD", path)
+
+    def get(self, path):
+        return self._request("GET", path)
+
+    def post(self, path, body):
+        return self._request("POST", path, body)
+
+    @contextlib.contextmanager
+    def _request(self, method, path, body=None):
+        payload = None
+
+        if body is not None:
+            try:
+                payload = json.dumps(body)
+            except ValueError:
+                raise ValueError("Failed to encode request body as JSON: {}".format(
+                    json.dumps(body, indent=2)))
+
+            if isinstance(payload, text_type):
+                payload = body.encode("utf-8")
+
+        conn = httplib.HTTPConnection(self.host, self.port)
+        try:
+            conn.request(method, path, payload)
+            yield conn.getresponse()
+        finally:
+            conn.close()
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/support/image.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/image.py
new file mode 100644
index 0000000..81dd933
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/image.py
@@ -0,0 +1,12 @@
+import base64
+import math
+import struct
+
+from tests.support.asserts import assert_png
+
+
+def png_dimensions(screenshot):
+    assert_png(screenshot)
+    image = base64.decodestring(screenshot)
+    width, height = struct.unpack(">LL", image[16:24])
+    return int(width), int(height)
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/support/inline.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/inline.py
new file mode 100644
index 0000000..8b4f165
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/inline.py
@@ -0,0 +1,51 @@
+import urllib
+
+
+def inline(doc, doctype="html", mime="text/html;charset=utf-8", protocol="http"):
+    from .fixtures import server_config, url
+    build_url = url(server_config())
+
+    if doctype == "html":
+        mime = "text/html;charset=utf-8"
+    elif doctype == "xhtml":
+        mime = "application/xhtml+xml"
+        doc = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+  <head>
+    <title>XHTML might be the future</title>
+  </head>
+
+  <body>
+    {}
+  </body>
+</html>""".format(doc)
+    elif doctype == "xml":
+        mime = "text/xml"
+        doc = """<?xml version="1.0" encoding="UTF-8"?>{}""".format(doc)
+
+    query = {"doc": doc}
+    if mime != "text/html;charset=utf8":
+        query["content-type"] = mime
+
+    return build_url("/webdriver/tests/support/inline.py",
+                     query=urllib.urlencode(query),
+                     protocol=protocol)
+
+
+def iframe(doc):
+    return "<iframe src='%s'></iframe>" % inline(doc)
+
+
+def main(request, response):
+    doc = request.GET.first("doc", None)
+    content_type = request.GET.first("content-type", "text/html;charset=utf8")
+    if doc is None:
+        rv = 404, [("Content-Type", "text/plain")], "Missing doc parameter in query"
+    else:
+        response.headers.update([
+            ("Content-Type", content_type),
+            ("X-XSS-Protection", "0")
+        ])
+        rv = doc
+    return rv
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/support/merge_dictionaries.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/merge_dictionaries.py
new file mode 100644
index 0000000..cf06c9b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/merge_dictionaries.py
@@ -0,0 +1,49 @@
+def iteritems(d):
+    """Create a key-value iterator for the given dict in both Python 2.x and
+    Python 3.x environments"""
+    if hasattr(d, "iteritems"):
+        return d.iteritems()
+    return d.items()
+
+def merge_dictionaries(first, second):
+    """Given two dictionaries, create a third that defines all specified
+    key/value pairs. This merge_dictionaries is performed "deeply" on any nested
+    dictionaries. If a value is defined for the same key by both dictionaries,
+    an exception will be raised."""
+    result = dict(first)
+
+    for key, value in iteritems(second):
+        if key in result and result[key] != value:
+            if isinstance(result[key], dict) and isinstance(value, dict):
+                result[key] = merge_dictionaries(result[key], value)
+            elif result[key] != value:
+                raise TypeError("merge_dictionaries: refusing to overwrite " +
+                                  "attribute: `%s`" % key)
+        else:
+            result[key] = value
+
+    return result
+
+if __name__ == "__main__":
+    assert merge_dictionaries({}, {}) == {}
+    assert merge_dictionaries({}, {"a": 23}) == {"a": 23}
+    assert merge_dictionaries({"a": 23}, {"b": 45}) == {"a": 23, "b": 45}
+
+    e = None
+    try:
+        merge_dictionaries({"a": 23}, {"a": 45})
+    except Exception as _e:
+        e = _e
+    assert isinstance(e, TypeError)
+
+    assert merge_dictionaries({"a": 23}, {"a": 23}) == {"a": 23}
+
+    assert merge_dictionaries({"a": {"b": 23}}, {"a": {"c": 45}}) == {"a": {"b": 23, "c": 45}}
+    assert merge_dictionaries({"a": {"b": 23}}, {"a": {"b": 23}}) == {"a": {"b": 23}}
+
+    e = None
+    try:
+        merge_dictionaries({"a": {"b": 23}}, {"a": {"b": 45}})
+    except Exception as _e:
+        e = _e
+    assert isinstance(e, TypeError)
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/support/sync.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/sync.py
new file mode 100644
index 0000000..561fcd8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/sync.py
@@ -0,0 +1,146 @@
+import collections
+import sys
+import time
+
+from webdriver import error
+
+
+DEFAULT_TIMEOUT = 5
+DEFAULT_INTERVAL = 0.1
+
+
+class Poll(object):
+    """
+    An explicit conditional utility primitive for polling until a
+    condition evaluates to something truthy.
+
+    A `Poll` instance defines the maximum amount of time to wait
+    for a condition, as well as the frequency with which to check
+    the condition.  Furthermore, the user may configure the wait
+    to ignore specific types of exceptions whilst waiting, such as
+    `error.NoSuchElementException` when searching for an element
+    on the page.
+    """
+
+    def __init__(self,
+                 session,
+                 timeout=DEFAULT_TIMEOUT,
+                 interval=DEFAULT_INTERVAL,
+                 raises=error.TimeoutException,
+                 message=None,
+                 ignored_exceptions=None,
+                 clock=time):
+        """
+        Configure the poller to have a custom timeout, interval,
+        and list of ignored exceptions.  Optionally a different time
+        implementation than the one provided by the standard library
+        (`time`) can also be provided.
+
+        Sample usage::
+
+            # Wait 30 seconds for window to open,
+            # checking for its presence once every 5 seconds.
+            from support.sync import Poll
+            wait = Poll(session, timeout=30, interval=5,
+                        ignored_exceptions=error.NoSuchWindowException)
+            window = wait.until(lambda s: s.switch_to_window(42))
+
+        :param session: The input value to be provided to conditions,
+            usually a `webdriver.Session` instance.
+
+        :param timeout: How long to wait for the evaluated condition
+            to become true.
+
+        :param interval: How often the condition should be evaluated.
+            In reality the interval may be greater as the cost of
+            evaluating the condition function. If that is not the case the
+            interval for the next condition function call is shortend to keep
+            the original interval sequence as best as possible.
+
+        :param raises: Optional exception to raise when poll elapses.
+            If not used, an `error.TimeoutException` is raised.
+            If it is `None`, no exception is raised on the poll elapsing.
+
+        :param message: An optional message to include in `raises`'s
+            message if the `until` condition times out.
+
+        :param ignored_exceptions: Ignore specific types of exceptions
+            whilst waiting for the condition.  Any exceptions not
+            whitelisted will be allowed to propagate, terminating the
+            wait.
+
+        :param clock: Allows overriding the use of the runtime's
+            default time library.  See `sync.SystemClock` for
+            implementation details.
+        """
+        self.session = session
+        self.timeout = timeout
+        self.interval = interval
+        self.exc_cls = raises
+        self.exc_msg = message
+        self.clock = clock
+
+        exceptions = []
+        if ignored_exceptions is not None:
+            if isinstance(ignored_exceptions, collections.Iterable):
+                exceptions.extend(iter(ignored_exceptions))
+            else:
+                exceptions.append(ignored_exceptions)
+        self.exceptions = tuple(set(exceptions))
+
+    def until(self, condition):
+        """
+        This will repeatedly evaluate `condition` in anticipation
+        for a truthy return value, or the timeout to expire.
+
+        A condition that returns `None` or does not evaluate to
+        true will fully elapse its timeout before raising, unless
+        the `raises` keyword argument is `None`, in which case the
+        condition's return value is propagated unconditionally.
+
+        If an exception is raised in `condition` and it's not ignored,
+        this function will raise immediately.  If the exception is
+        ignored it will be swallowed and polling will resume until
+        either the condition meets the return requirements or the
+        timeout duration is reached.
+
+        :param condition: A callable function whose return value will
+            be returned by this function.
+        """
+        rv = None
+        last_exc = None
+        start = self.clock.time()
+        end = start + self.timeout
+
+        while not self.clock.time() >= end:
+            try:
+                next = self.clock.time() + self.interval
+                rv = condition(self.session)
+            except (KeyboardInterrupt, SystemExit):
+                raise
+            except self.exceptions:
+                last_exc = sys.exc_info()
+
+            # re-adjust the interval depending on how long
+            # the callback took to evaluate the condition
+            interval_new = max(next - self.clock.time(), 0)
+
+            if not rv:
+                self.clock.sleep(interval_new)
+                continue
+
+            if rv is not None:
+                return rv
+
+            self.clock.sleep(interval_new)
+
+        if self.exc_cls is not None:
+            elapsed = round((self.clock.time() - start), 1)
+            message = ""
+            if self.exc_msg is not None:
+                message = " with message: {}".format(self.exc_msg)
+            raise self.exc_cls(
+                "Timed out after {} seconds{}".format(elapsed, message),
+                stacktrace=last_exc)
+        else:
+            return rv
diff --git a/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-js/timeline-has-cpu-profile-events-expected.txt b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-js/timeline-has-cpu-profile-events-expected.txt
new file mode 100644
index 0000000..5ac52f49
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-js/timeline-has-cpu-profile-events-expected.txt
@@ -0,0 +1,2 @@
+Test the Timeline instrumentation contains Profile event.
+
diff --git a/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-js/timeline-has-cpu-profile-events.js b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-js/timeline-has-cpu-profile-events.js
new file mode 100644
index 0000000..ab0a2ab
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-js/timeline-has-cpu-profile-events.js
@@ -0,0 +1,15 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+(async function() {
+  TestRunner.addResult(`Test the Timeline instrumentation contains Profile event.`);
+  await TestRunner.loadModule('performance_test_runner');
+  await TestRunner.showPanel('timeline');
+
+  await PerformanceTestRunner.evaluateWithTimeline('42');
+  const profileEvent = PerformanceTestRunner.findTimelineEvent(TimelineModel.TimelineModel.RecordType.Profile);
+  TestRunner.check(profileEvent, 'Profile trace event not found.');
+
+  TestRunner.completeTest();
+})();
diff --git a/third_party/blink/web_tests/paint/paintlayer/scrolling_issues.html b/third_party/blink/web_tests/paint/paintlayer/scrolling_issues.html
new file mode 100644
index 0000000..391cc29
--- /dev/null
+++ b/third_party/blink/web_tests/paint/paintlayer/scrolling_issues.html
@@ -0,0 +1,47 @@
+<!doctype html>
+
+<p>The scrollbar should be roughly in the middle of the scroll range.</p>
+<p>This is intended to duplicate https://crbug.com/927560</p>
+
+<div id="scroller">
+  <div style="width: 200px; height: 5000px;"></div>
+</div>
+
+<style>
+#scroller {
+  overflow-y: scroll;
+  width: 500px;
+  height: 500px;
+  border: 1px solid black;
+}
+</style>
+
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src='../../resources/gesture-util.js'></script>
+
+<script>
+var scroller = document.getElementById('scroller');
+var rect = scroller.getBoundingClientRect();
+var start_x = (rect.left + rect.right) / 2;
+var start_y = (rect.top + rect.bottom) / 2;
+
+async function scrollDown(pixels_to_scroll, gesture_source_type) {
+  const target_scroll_offset = scroller.scrollTop + pixels_to_scroll;
+  await waitForCompositorCommit();
+  await smoothScroll(pixels_to_scroll, start_x, start_y, gesture_source_type,
+                    'down', SPEED_INSTANT);
+  await waitFor(() => {
+    return approx_equals(scroller.scrollTop, target_scroll_offset, 20);
+  }, "Didn't scroll by expected amount: " + pixels_to_scroll + "  scroller.scrollTop is " + scroller.scrollTop + ", target is " + target_scroll_offset);
+  await waitForCompositorCommit();
+}
+
+promise_test(async () => {
+  await scrollDown(800, GestureSourceType.TOUCHPAD_INPUT);
+  await scrollDown(800, GestureSourceType.TOUCH_INPUT);
+  await scrollDown(800, GestureSourceType.MOUSE_INPUT);
+  assert_approx_equals(scroller.scrollTop,2400,60,"Scroll didn't work: ")
+}, "Scrolling by wheel then touch should work.");
+
+</script>
diff --git a/third_party/blink/web_tests/virtual/freeze-frames/external/wpt/lifecycle/README.txt b/third_party/blink/web_tests/virtual/freeze-frames/external/wpt/lifecycle/README.txt
new file mode 100644
index 0000000..2b0e32f
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/freeze-frames/external/wpt/lifecycle/README.txt
@@ -0,0 +1,2 @@
+# This suite runs the tests with
+# --enable-features=FreezeFramesOnVisibility
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index ab9660a..57beddb 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -292,6 +292,7 @@
       'Windows Clang deterministic': 'clang_release_bot_minimal_symbols_x86',
       'win-annotator-rel': 'release_bot',
       'win-autofill-captured-sites-rel': 'release_bot',
+      'win32-arm64-rel': 'win32_arm64_release_bot',
     },
 
     'chromium.goma': {
@@ -1829,6 +1830,10 @@
       'no_clang', 'release_bot',
     ],
 
+    'win32_arm64_release_bot': [
+      'arm64', 'disable_nacl', 'release_bot',
+    ],
+
     'clang_tot_win_release_cross': [
       'clang_tot', 'win_cross', 'minimal_symbols', 'shared', 'release', 'dcheck_always_on',
     ],
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index ef64b53..1880accb 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -7886,7 +7886,7 @@
 </enum>
 
 <enum name="ConnectionFailureReason">
-  <int value="0" label="Unknown"/>
+  <int value="0" label="Unknown (deprecated)"/>
   <int value="1" label="Bad Passphrase"/>
   <int value="2" label="Bad WEP Key"/>
   <int value="3" label="Failed to Connect"/>
@@ -7897,6 +7897,8 @@
   <int value="8" label="EAP Remote TLS"/>
   <int value="9" label="Out-of-range"/>
   <int value="10" label="Pin Missing"/>
+  <int value="11" label="Unknown"/>
+  <int value="12" label="No failure"/>
 </enum>
 
 <enum name="ConnectionInfo">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 6d68d2c1..9a35d811 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -12052,16 +12052,6 @@
   </summary>
 </histogram>
 
-<histogram name="Bluetooth.Availability" enum="BluetoothAvailability"
-    expires_after="2019-12-31">
-  <owner>kenrb@chromium.org</owner>
-  <owner>kpaulhamus@chromium.org</owner>
-  <summary>
-    Determines the availability and capabilities of the Bluetooth driver. This
-    metric is logged on startup.
-  </summary>
-</histogram>
-
 <histogram name="Bluetooth.ConnectedDeviceCount" units="devices">
   <owner>adlr@chromium.org</owner>
   <summary>
@@ -76012,9 +76002,6 @@
 
 <histogram name="OSX.BluetoothAvailability" enum="BluetoothAvailability"
     expires_after="2018-08-30">
-  <obsolete>
-    Obsolete as of Chrome 73. This has been replaced by Bluetooth.Availability.
-  </obsolete>
   <owner>erikchen@chromium.org</owner>
   <summary>
     The availability and capabilities of the Bluetooth driver on OSX devices.
diff --git a/tools/perf/contrib/network_service/loading.py b/tools/perf/contrib/network_service/loading.py
index 2b17f08..538c568 100644
--- a/tools/perf/contrib/network_service/loading.py
+++ b/tools/perf/contrib/network_service/loading.py
@@ -15,8 +15,6 @@
 from telemetry.value import none_values
 from telemetry.value.list_of_scalar_values import StandardDeviation
 
-from tracing.value import convert_chart_json
-
 def _ListSubtraction(diff_list, control_list):
   """Subtract |control_list|'s elements from the corresponding elements in
   |diff_list|, and store the results in |diff_list|.
@@ -159,12 +157,10 @@
   def Run(self, finder_options):
     """We shouldn't be overriding this according to
     telemetry.benchmark.Benchmark"""
-    assert 'histograms' in finder_options.output_formats, (
-      'loading.desktop.network_service requires --output-format=histograms.')
-
-    # feed the story_runner with 'chartjson' output formats.
-    finder_options.output_formats = ['chartjson']
-
+    assert 'chartjson' in finder_options.output_formats, (
+      'loading.desktop.network_service requires --output-format=chartjson. '
+      'Please contact owner to rewrite the benchmark if chartjson is going '
+      'away.')
     assert finder_options.output_dir
     output_dir = finder_options.output_dir
     temp_file_path = os.path.join(output_dir, 'results-chart.json')
@@ -202,17 +198,6 @@
       with open(temp_file_path, 'w') as f:
         json.dump(enabled_chart_json, f, indent=2, separators=(',', ': '))
         f.write('\n')
-      logging.info('Converting chartjsons to histograms')
-      histogram_result = convert_chart_json.ConvertChartJson(temp_file_path)
-      if histogram_result.returncode != 0:
-        logging.error('Error converting chart json to Histograms:\n' +
-            histogram_result.stdout)
-        return 1
-
-      temp_file_path = os.path.join(output_dir, 'histograms.json')
-      with open(temp_file_path, 'w') as f:
-        f.write(histogram_result.stdout)
-
     return 0
 
   def SetExtraBrowserOptions(self, options):
diff --git a/ui/accessibility/ax_role_properties.cc b/ui/accessibility/ax_role_properties.cc
index a3642dc..78e2b98 100644
--- a/ui/accessibility/ax_role_properties.cc
+++ b/ui/accessibility/ax_role_properties.cc
@@ -343,6 +343,16 @@
   }
 }
 
+bool IsTextOrLineBreak(ax::mojom::Role role) {
+  switch (role) {
+    case ax::mojom::Role::kLineBreak:
+    case ax::mojom::Role::kStaticText:
+      return true;
+    default:
+      return false;
+  }
+}
+
 bool SupportsExpandCollapse(const ax::mojom::Role role) {
   switch (role) {
     case ax::mojom::Role::kComboBoxGrouping:
diff --git a/ui/accessibility/ax_role_properties.h b/ui/accessibility/ax_role_properties.h
index d79ec98..a9ea967e 100644
--- a/ui/accessibility/ax_role_properties.h
+++ b/ui/accessibility/ax_role_properties.h
@@ -86,6 +86,9 @@
 // table is not used for layout purposes.
 AX_EXPORT bool IsTableRow(ax::mojom::Role role);
 
+// Returns true if it's a text or line break node.
+AX_EXPORT bool IsTextOrLineBreak(ax::mojom::Role role);
+
 // Returns true if the provided role supports expand/collapse.
 AX_EXPORT bool SupportsExpandCollapse(const ax::mojom::Role role);
 
diff --git a/ui/aura/test/mus/test_window_tree.cc b/ui/aura/test/mus/test_window_tree.cc
index 98bf5a3..487fbbc 100644
--- a/ui/aura/test/mus/test_window_tree.cc
+++ b/ui/aura/test/mus/test_window_tree.cc
@@ -332,7 +332,10 @@
   OnChangeReceived(change_id, WindowTreeChangeType::FOCUS);
 }
 
-void TestWindowTree::SetCanFocus(ws::Id window_id, bool can_focus) {}
+void TestWindowTree::SetCanFocus(ws::Id window_id, bool can_focus) {
+  ++can_focus_count_;
+  last_can_focus_ = can_focus;
+}
 
 void TestWindowTree::SetEventTargetingPolicy(
     ws::Id window_id,
diff --git a/ui/aura/test/mus/test_window_tree.h b/ui/aura/test/mus/test_window_tree.h
index 142c259..c42155c 100644
--- a/ui/aura/test/mus/test_window_tree.h
+++ b/ui/aura/test/mus/test_window_tree.h
@@ -146,6 +146,13 @@
     return value;
   }
 
+  bool last_can_focus() const { return last_can_focus_; }
+  size_t get_and_clear_can_focus_count() {
+    const int value = can_focus_count_;
+    can_focus_count_ = 0u;
+    return value;
+  }
+
   int last_move_hit_test() const { return last_move_hit_test_; }
 
   size_t get_and_clear_window_resize_shadow_count() {
@@ -336,6 +343,8 @@
   ws::Id last_transfer_new_ = 0u;
   bool last_transfer_should_cancel_ = false;
   bool last_accepts_drops_ = false;
+  size_t can_focus_count_ = 0u;
+  bool last_can_focus_ = false;
 
   size_t accepts_drops_count_ = 0u;
 
diff --git a/ui/base/dragdrop/os_exchange_data_provider_win.cc b/ui/base/dragdrop/os_exchange_data_provider_win.cc
index db7425bb..a9bd4912f 100644
--- a/ui/base/dragdrop/os_exchange_data_provider_win.cc
+++ b/ui/base/dragdrop/os_exchange_data_provider_win.cc
@@ -345,8 +345,7 @@
   data_->contents_.push_back(std::make_unique<DataObjectImpl::StoredDataInfo>(
       ClipboardFormatType::GetUrlType().ToFormatEtc(), storage));
 
-  // TODO(beng): add CF_HTML.
-  // http://code.google.com/p/chromium/issues/detail?id=6767GetIDListStorageForFileName
+  // TODO(https://crbug.com/6767): add CF_HTML.
 
   // Also add text representations (these should be last since they're the
   // least preferable).
@@ -715,11 +714,10 @@
 }
 
 void DataObjectImpl::StopDownloads() {
-  for (StoredData::iterator iter = contents_.begin();
-       iter != contents_.end(); ++iter) {
-    if ((*iter)->downloader.get()) {
-      (*iter)->downloader->Stop();
-      (*iter)->downloader = 0;
+  for (const std::unique_ptr<StoredDataInfo>& content : contents_) {
+    if (content->downloader.get()) {
+      content->downloader->Stop();
+      content->downloader = 0;
     }
   }
 }
@@ -741,44 +739,32 @@
 }
 
 void DataObjectImpl::OnDownloadCompleted(const base::FilePath& file_path) {
-  DataObjectImpl::StoredData::iterator iter = contents_.begin();
-  for (; iter != contents_.end(); ++iter) {
-    if ((*iter)->format_etc.cfFormat == CF_HDROP) {
-      // Release the old storage.
-      if ((*iter)->owns_medium) {
-        ReleaseStgMedium((*iter)->medium);
-        delete (*iter)->medium;
-      }
-
-      // Update the storage.
+  for (std::unique_ptr<StoredDataInfo>& content : contents_) {
+    if (content->format_etc.cfFormat == CF_HDROP) {
+      // Replace stored data.
       STGMEDIUM* storage =
           GetStorageForFileNames({FileInfo(file_path, base::FilePath())});
-      if (storage) {
-        (*iter)->owns_medium = true;
-        (*iter)->medium = storage;
-      }
+      content.reset(new StoredDataInfo(
+          ClipboardFormatType::GetCFHDropType().ToFormatEtc(), storage));
 
       break;
     }
   }
-  DCHECK(iter != contents_.end());
 }
 
-void DataObjectImpl::OnDownloadAborted() {
-}
+void DataObjectImpl::OnDownloadAborted() {}
 
 HRESULT DataObjectImpl::GetData(FORMATETC* format_etc, STGMEDIUM* medium) {
   if (is_aborting_)
     return DV_E_FORMATETC;
 
-  StoredData::iterator iter = contents_.begin();
-  while (iter != contents_.end()) {
-    if ((*iter)->format_etc.cfFormat == format_etc->cfFormat &&
-        (*iter)->format_etc.lindex == format_etc->lindex &&
-        ((*iter)->format_etc.tymed & format_etc->tymed)) {
+  for (const std::unique_ptr<StoredDataInfo>& content : contents_) {
+    if (content->format_etc.cfFormat == format_etc->cfFormat &&
+        content->format_etc.lindex == format_etc->lindex &&
+        (content->format_etc.tymed & format_etc->tymed)) {
       // If medium is NULL, delay-rendering will be used.
-      if ((*iter)->medium) {
-        DuplicateMedium((*iter)->format_etc.cfFormat, (*iter)->medium, medium);
+      if (content->medium) {
+        DuplicateMedium(content->format_etc.cfFormat, content->medium, medium);
       } else {
         // Fail all GetData() attempts for DownloadURL data if the drag and drop
         // operation is still in progress.
@@ -803,9 +789,9 @@
           observer_->OnWaitForData();
 
         // Now we can start the download.
-        if ((*iter)->downloader.get()) {
-          (*iter)->downloader->Start(this);
-          if (!(*iter)->downloader->Wait()) {
+        if (content->downloader.get()) {
+          content->downloader->Start(this);
+          if (!content->downloader->Wait()) {
             is_aborting_ = true;
             return DV_E_FORMATETC;
           }
@@ -817,7 +803,6 @@
       }
       return S_OK;
     }
-    ++iter;
   }
 
   return DV_E_FORMATETC;
@@ -829,11 +814,9 @@
 }
 
 HRESULT DataObjectImpl::QueryGetData(FORMATETC* format_etc) {
-  StoredData::const_iterator iter = contents_.begin();
-  while (iter != contents_.end()) {
-    if ((*iter)->format_etc.cfFormat == format_etc->cfFormat)
+  for (const std::unique_ptr<StoredDataInfo>& content : contents_) {
+    if (content->format_etc.cfFormat == format_etc->cfFormat)
       return S_OK;
-    ++iter;
   }
   return DV_E_FORMATETC;
 }
@@ -1014,7 +997,7 @@
   // For example,
   //| DROPFILES | FILENAME 1 | NULL | ... | FILENAME n | NULL | NULL |
   // For more details, please refer to
-  // https://docs.microsoft.com/ko-kr/windows/desktop/shell/clipboard#cf_hdrop
+  // https://docs.microsoft.com/en-us/windows/desktop/shell/clipboard#cf_hdrop
 
   if (filenames.empty())
     return nullptr;
diff --git a/ui/base/ime/fuchsia/input_method_keyboard_controller_fuchsia.cc b/ui/base/ime/fuchsia/input_method_keyboard_controller_fuchsia.cc
index a2abea8..47ca7597 100644
--- a/ui/base/ime/fuchsia/input_method_keyboard_controller_fuchsia.cc
+++ b/ui/base/ime/fuchsia/input_method_keyboard_controller_fuchsia.cc
@@ -6,8 +6,8 @@
 
 #include <utility>
 
-#include "base/fuchsia/component_context.h"
 #include "base/fuchsia/fuchsia_logging.h"
+#include "base/fuchsia/service_directory_client.h"
 #include "base/logging.h"
 
 namespace ui {
@@ -16,7 +16,7 @@
     fuchsia::ui::input::ImeService* ime_service)
     : ime_service_(ime_service),
       ime_visibility_(
-          base::fuchsia::ComponentContext::GetDefault()
+          base::fuchsia::ServiceDirectoryClient::ForCurrentProcess()
               ->ConnectToService<fuchsia::ui::input::ImeVisibilityService>()) {
   DCHECK(ime_service_);
 
diff --git a/ui/base/ime/input_method_fuchsia.cc b/ui/base/ime/input_method_fuchsia.cc
index 0ce7f33..e8c3bd6 100644
--- a/ui/base/ime/input_method_fuchsia.cc
+++ b/ui/base/ime/input_method_fuchsia.cc
@@ -9,7 +9,7 @@
 #include <utility>
 
 #include "base/bind_helpers.h"
-#include "base/fuchsia/component_context.h"
+#include "base/fuchsia/service_directory_client.h"
 #include "ui/base/ime/text_input_client.h"
 #include "ui/events/base_event_utils.h"
 #include "ui/events/keycodes/dom/dom_code.h"
@@ -21,7 +21,7 @@
     : InputMethodBase(delegate),
       event_converter_(this),
       ime_client_binding_(this),
-      ime_service_(base::fuchsia::ComponentContext::GetDefault()
+      ime_service_(base::fuchsia::ServiceDirectoryClient::ForCurrentProcess()
                        ->ConnectToService<fuchsia::ui::input::ImeService>()),
       virtual_keyboard_controller_(ime_service_.get()) {}
 
diff --git a/ui/chromeos/ime/candidate_window_view.cc b/ui/chromeos/ime/candidate_window_view.cc
index f34fba82..c3d8c43 100644
--- a/ui/chromeos/ime/candidate_window_view.cc
+++ b/ui/chromeos/ime/candidate_window_view.cc
@@ -154,7 +154,7 @@
       should_show_upper_side_(false),
       was_candidate_window_open_(false),
       window_shell_id_(window_shell_id) {
-  set_can_activate(false);
+  SetCanActivate(false);
   DCHECK(parent || features::IsUsingWindowService());
   set_parent_window(parent);
   set_margins(gfx::Insets());
diff --git a/ui/chromeos/ime/infolist_window.cc b/ui/chromeos/ime/infolist_window.cc
index 9a6de77..bc63378 100644
--- a/ui/chromeos/ime/infolist_window.cc
+++ b/ui/chromeos/ime/infolist_window.cc
@@ -173,7 +173,7 @@
       title_font_list_(gfx::Font(kJapaneseFontName, kFontSizeDelta + 15)),
       description_font_list_(
           gfx::Font(kJapaneseFontName, kFontSizeDelta + 11)) {
-  set_can_activate(false);
+  SetCanActivate(false);
   set_accept_events(false);
   set_margins(gfx::Insets());
 
diff --git a/ui/compositor/layer.cc b/ui/compositor/layer.cc
index 0e3bc3285..c596a628 100644
--- a/ui/compositor/layer.cc
+++ b/ui/compositor/layer.cc
@@ -1212,6 +1212,10 @@
 
 void Layer::SetVisibilityFromAnimation(bool visible,
                                        PropertyChangeReason reason) {
+  // Sync changes with the mirror layers.
+  for (const auto& mirror : mirrors_)
+    mirror->dest()->SetVisible(visible);
+
   if (visible_ == visible)
     return;
 
diff --git a/ui/compositor/layer.h b/ui/compositor/layer.h
index 3e1771ff..e90777f 100644
--- a/ui/compositor/layer.h
+++ b/ui/compositor/layer.h
@@ -249,8 +249,10 @@
   void SetMaskLayer(Layer* layer_mask);
   Layer* layer_mask_layer() { return layer_mask_; }
 
-  // Sets the visibility of the Layer. A Layer may be visible but not
-  // drawn. This happens if any ancestor of a Layer is not visible.
+  // Sets the visibility of the Layer. A Layer may be visible but not drawn.
+  // This happens if any ancestor of a Layer is not visible.
+  // Any changes made to this in the source layer will override the visibility
+  // of its mirror layer.
   void SetVisible(bool visible);
   bool visible() const { return visible_; }
 
diff --git a/ui/compositor/layer_unittest.cc b/ui/compositor/layer_unittest.cc
index fdde61a..3036ff3 100644
--- a/ui/compositor/layer_unittest.cc
+++ b/ui/compositor/layer_unittest.cc
@@ -1117,6 +1117,110 @@
   EXPECT_TRUE(l3->cc_layer_for_testing()->hide_layer_and_subtree());
 }
 
+// Various visible/drawn assertions.
+TEST_F(LayerWithNullDelegateTest, MirroringVisibility) {
+  std::unique_ptr<Layer> l1(new Layer(LAYER_TEXTURED));
+  std::unique_ptr<Layer> l2(new Layer(LAYER_TEXTURED));
+  std::unique_ptr<Layer> l2_mirror = l2->Mirror();
+  l1->Add(l2.get());
+  l1->Add(l2_mirror.get());
+
+  NullLayerDelegate delegate;
+  l1->set_delegate(&delegate);
+  l2->set_delegate(&delegate);
+  l2_mirror->set_delegate(&delegate);
+
+  // Layers should initially be drawn.
+  EXPECT_TRUE(l1->IsDrawn());
+  EXPECT_TRUE(l2->IsDrawn());
+  EXPECT_TRUE(l2_mirror->IsDrawn());
+  EXPECT_FALSE(l1->cc_layer_for_testing()->hide_layer_and_subtree());
+  EXPECT_FALSE(l2->cc_layer_for_testing()->hide_layer_and_subtree());
+  EXPECT_FALSE(l2_mirror->cc_layer_for_testing()->hide_layer_and_subtree());
+
+  compositor()->SetRootLayer(l1.get());
+
+  Draw();
+
+  // Hiding the root layer should hide that specific layer and its subtree.
+  l1->SetVisible(false);
+
+  // Since the entire subtree is hidden, no layer should be drawn.
+  EXPECT_FALSE(l1->IsDrawn());
+  EXPECT_FALSE(l2->IsDrawn());
+  EXPECT_FALSE(l2_mirror->IsDrawn());
+
+  // The visibitily property for the subtree is rooted at |l1|.
+  EXPECT_TRUE(l1->cc_layer_for_testing()->hide_layer_and_subtree());
+  EXPECT_FALSE(l2->cc_layer_for_testing()->hide_layer_and_subtree());
+  EXPECT_FALSE(l2_mirror->cc_layer_for_testing()->hide_layer_and_subtree());
+
+  // Hiding |l2| should also set the visibility on its mirror layer. In this
+  // case the visibility of |l2| will be mirrored by |l2_mirror|.
+  l2->SetVisible(false);
+
+  // None of the layers are drawn since the visibility is false at every node.
+  EXPECT_FALSE(l1->IsDrawn());
+  EXPECT_FALSE(l2->IsDrawn());
+  EXPECT_FALSE(l2_mirror->IsDrawn());
+
+  // Visibility property is set on every node and hence their subtree is also
+  // hidden.
+  EXPECT_TRUE(l1->cc_layer_for_testing()->hide_layer_and_subtree());
+  EXPECT_TRUE(l2->cc_layer_for_testing()->hide_layer_and_subtree());
+  EXPECT_TRUE(l2_mirror->cc_layer_for_testing()->hide_layer_and_subtree());
+
+  // Setting visibility on the root layer should make that layer visible and its
+  // subtree ready for visibility.
+  l1->SetVisible(true);
+  EXPECT_TRUE(l1->IsDrawn());
+  EXPECT_FALSE(l2->IsDrawn());
+  EXPECT_FALSE(l2_mirror->IsDrawn());
+  EXPECT_FALSE(l1->cc_layer_for_testing()->hide_layer_and_subtree());
+  EXPECT_TRUE(l2->cc_layer_for_testing()->hide_layer_and_subtree());
+  EXPECT_TRUE(l2_mirror->cc_layer_for_testing()->hide_layer_and_subtree());
+
+  // Setting visibility on the mirrored layer should not effect its source
+  // layer.
+  l2_mirror->SetVisible(true);
+  EXPECT_TRUE(l1->IsDrawn());
+  EXPECT_FALSE(l2->IsDrawn());
+  EXPECT_TRUE(l2_mirror->IsDrawn());
+  EXPECT_FALSE(l1->cc_layer_for_testing()->hide_layer_and_subtree());
+  EXPECT_TRUE(l2->cc_layer_for_testing()->hide_layer_and_subtree());
+  EXPECT_FALSE(l2_mirror->cc_layer_for_testing()->hide_layer_and_subtree());
+
+  // Setting visibility on the source layer should keep the mirror layer in
+  // sync and not cause any invalid state.
+  l2->SetVisible(true);
+  EXPECT_TRUE(l1->IsDrawn());
+  EXPECT_TRUE(l2->IsDrawn());
+  EXPECT_TRUE(l2_mirror->IsDrawn());
+  EXPECT_FALSE(l1->cc_layer_for_testing()->hide_layer_and_subtree());
+  EXPECT_FALSE(l2->cc_layer_for_testing()->hide_layer_and_subtree());
+  EXPECT_FALSE(l2_mirror->cc_layer_for_testing()->hide_layer_and_subtree());
+
+  // Setting visibility on the mirrored layer should not effect its source
+  // layer.
+  l2_mirror->SetVisible(false);
+  EXPECT_TRUE(l1->IsDrawn());
+  EXPECT_TRUE(l2->IsDrawn());
+  EXPECT_FALSE(l2_mirror->IsDrawn());
+  EXPECT_FALSE(l1->cc_layer_for_testing()->hide_layer_and_subtree());
+  EXPECT_FALSE(l2->cc_layer_for_testing()->hide_layer_and_subtree());
+  EXPECT_TRUE(l2_mirror->cc_layer_for_testing()->hide_layer_and_subtree());
+
+  // Setting source layer's visibility to true should update the mirror layer
+  // even if the source layer did not change in the process.
+  l2->SetVisible(true);
+  EXPECT_TRUE(l1->IsDrawn());
+  EXPECT_TRUE(l2->IsDrawn());
+  EXPECT_TRUE(l2_mirror->IsDrawn());
+  EXPECT_FALSE(l1->cc_layer_for_testing()->hide_layer_and_subtree());
+  EXPECT_FALSE(l2->cc_layer_for_testing()->hide_layer_and_subtree());
+  EXPECT_FALSE(l2_mirror->cc_layer_for_testing()->hide_layer_and_subtree());
+}
+
 // Checks that stacking-related methods behave as advertised.
 TEST_F(LayerWithNullDelegateTest, Stacking) {
   std::unique_ptr<Layer> root(new Layer(LAYER_NOT_DRAWN));
diff --git a/ui/file_manager/file_manager/background/js/entry_location_impl.js b/ui/file_manager/file_manager/background/js/entry_location_impl.js
index 65843c1..dda0643f 100644
--- a/ui/file_manager/file_manager/background/js/entry_location_impl.js
+++ b/ui/file_manager/file_manager/background/js/entry_location_impl.js
@@ -47,7 +47,8 @@
   /** @type{boolean} */
   this.hasFixedLabel = this.isRootEntry &&
       (rootType !== VolumeManagerCommon.RootType.TEAM_DRIVE &&
-       rootType !== VolumeManagerCommon.RootType.COMPUTER);
+       rootType !== VolumeManagerCommon.RootType.COMPUTER &&
+       rootType !== VolumeManagerCommon.RootType.REMOVABLE);
 
   Object.freeze(this);
 }
diff --git a/ui/file_manager/file_manager/background/js/mock_volume_manager.js b/ui/file_manager/file_manager/background/js/mock_volume_manager.js
index e02d30a..f01b6de 100644
--- a/ui/file_manager/file_manager/background/js/mock_volume_manager.js
+++ b/ui/file_manager/file_manager/background/js/mock_volume_manager.js
@@ -146,9 +146,11 @@
  * @param {!VolumeManagerCommon.VolumeType} type Volume type.
  * @param {string} volumeId Volume id.
  * @param {string=} label Label.
+ * @param {string=} devicePath Device path.
  * @return {!VolumeInfo} Created mock VolumeInfo.
  */
-MockVolumeManager.createMockVolumeInfo = function(type, volumeId, label) {
+MockVolumeManager.createMockVolumeInfo = function(
+    type, volumeId, label, devicePath) {
   var fileSystem = new MockFileSystem(volumeId, 'filesystem:' + volumeId);
 
   // If there's no label set it to volumeId to make it shorter to write tests.
@@ -156,7 +158,7 @@
       type, volumeId, fileSystem,
       '',                                          // error
       '',                                          // deviceType
-      '',                                          // devicePath
+      devicePath || '',                            // devicePath
       false,                                       // isReadOnly
       false,                                       // isReadOnlyRemovableDevice
       {isCurrentProfile: true, displayName: ''},   // profile
diff --git a/ui/file_manager/file_manager/common/js/files_app_entry_types.js b/ui/file_manager/file_manager/common/js/files_app_entry_types.js
index 7ed3d31..e1cd989e 100644
--- a/ui/file_manager/file_manager/common/js/files_app_entry_types.js
+++ b/ui/file_manager/file_manager/common/js/files_app_entry_types.js
@@ -258,9 +258,9 @@
    * @param {string} label: Label to be used when displaying to user, it should
    *    already translated.
    * @param {VolumeManagerCommon.RootType} rootType root type.
-   *
+   * @param {string} devicePath Device path
    */
-  constructor(label, rootType) {
+  constructor(label, rootType, devicePath = '') {
     /**
      * @private {string} label: Label to be used when displaying to user, it
      *      should be already translated. */
@@ -270,6 +270,12 @@
     this.rootType_ = rootType;
 
     /**
+     * @private {string} devicePath Path belonging to the external media
+     * device. Partitions on the same external drive have the same device path.
+     */
+    this.devicePath_ = devicePath;
+
+    /**
      * @private {!Array<!Entry|!FilesAppEntry>} children entries of
      * this EntryList instance.
      */
@@ -317,6 +323,11 @@
    * @override
    */
   toURL() {
+    // There may be multiple entry lists. Append the device path to return
+    // a unique identifiable URL for the entry list.
+    if (this.devicePath_) {
+      return 'entry-list://' + this.rootType + '/' + this.devicePath_;
+    }
     return 'entry-list://' + this.rootType;
   }
 
diff --git a/ui/file_manager/file_manager/foreground/css/file_types.css b/ui/file_manager/file_manager/foreground/css/file_types.css
index 9a8da83..e54c611d 100644
--- a/ui/file_manager/file_manager/foreground/css/file_types.css
+++ b/ui/file_manager/file_manager/foreground/css/file_types.css
@@ -455,8 +455,8 @@
       url(../images/volumes/2x/phone_active.png) 2x);
 }
 
-[volume-type-icon='removable'][volume-subtype='partition'],
 [volume-type-icon='removable'][volume-subtype='unknown'],
+.tree-item .tree-item [volume-type-icon='removable'],
 [file-type-icon='removable'] {
   background-image: -webkit-image-set(
       url(../images/volumes/hard_drive.png) 1x,
@@ -464,8 +464,15 @@
 }
 
 tree .tree-item[selected] > .tree-row >
-[volume-type-icon='removable'][volume-subtype='partition'],
-.tree-row[selected] [volume-type-icon='removable'][volume-subtype='unknown']  {
+.tree-row[selected] [volume-type-icon='removable'][volume-subtype='unknown'],
+.tree-item .tree-item[selected] [volume-type-icon='removable'] {
+  background-image: -webkit-image-set(
+      url(../images/volumes/hard_drive_active.png) 1x,
+      url(../images/volumes/2x/hard_drive_active.png) 2x);
+}
+
+tree .tree-item > .tree-item > .tree-row >
+[volume-type-icon='removable'] {
   background-image: -webkit-image-set(
       url(../images/volumes/hard_drive_active.png) 1x,
       url(../images/volumes/2x/hard_drive_active.png) 2x);
diff --git a/ui/file_manager/file_manager/foreground/images/files/ui/delete.svg b/ui/file_manager/file_manager/foreground/images/files/ui/delete.svg
index 592fe13..9507cdf 100644
--- a/ui/file_manager/file_manager/foreground/images/files/ui/delete.svg
+++ b/ui/file_manager/file_manager/foreground/images/files/ui/delete.svg
@@ -1,4 +1,4 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewBox="0 0 24 24" fill="#616161">
+<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 24 24" fill="#616161">
     <path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
     <path d="M0 0h24v24H0z" fill="none"/>
 </svg>
diff --git a/ui/file_manager/file_manager/foreground/images/files/ui/share.svg b/ui/file_manager/file_manager/foreground/images/files/ui/share.svg
index 032ae7d..748a8b6 100644
--- a/ui/file_manager/file_manager/foreground/images/files/ui/share.svg
+++ b/ui/file_manager/file_manager/foreground/images/files/ui/share.svg
@@ -1,4 +1,4 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewBox="0 0 24 24" fill="#616161">
+<svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 24 24" fill="#616161">
     <path d="M0 0h24v24H0z" fill="none"/>
     <path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z"/>
 </svg>
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager_commands.js b/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
index 3733c63..03599eb5 100644
--- a/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
+++ b/ui/file_manager/file_manager/foreground/js/file_manager_commands.js
@@ -603,6 +603,63 @@
 });
 
 /**
+ * Unmounts external drive which contains partitions.
+ * @type {Command}
+ */
+CommandHandler.COMMANDS_['unmount-all-partitions'] = /** @type {Command} */ ({
+  /**
+   * @param {!Event} event Command event.
+   * @param {!CommandHandlerDeps} fileManager The file manager instance.
+   */
+  execute: function(event, fileManager) {
+    const errorCallback = () => {
+      fileManager.ui.alertDialog.showHtml(
+          '', str('UNMOUNT_FAILED'), null, null, null);
+    };
+
+    const successCallback = () => {
+      const rootLabel = entry.label || '';
+      const msg = strf('A11Y_VOLUME_EJECT', rootLabel);
+      fileManager.ui.speakA11yMessage(msg);
+    };
+
+    const entry = event.target.entry;
+    if (!entry) {
+      errorCallback();
+      return;
+    }
+
+    // Eject partitions one by one.
+    const partitions = entry.getUIChildren();
+    for (let i = 0; i < partitions.length; i++) {
+      const volumeInfo = partitions[i].volumeInfo;
+      fileManager.volumeManager.unmount(
+          volumeInfo, (i == partitions.length) ? successCallback : () => {},
+          errorCallback);
+    }
+  },
+
+  /**
+   * @param {!Event} event Command event.
+   * @this {CommandHandler}
+   */
+  canExecute: function(event, fileManager) {
+    const entry = event.target.entry;
+    if (!entry) {
+      event.canExecute = false;
+      event.command.setHidden(true);
+      return;
+    }
+
+    event.canExecute =
+        (entry.rootType === VolumeManagerCommon.VolumeType.REMOVABLE &&
+         entry.type_name === 'EntryList');
+    event.command.label = str('UNMOUNT_DEVICE_BUTTON_LABEL');
+    event.command.setHidden(!event.canExecute);
+  }
+});
+
+/**
  * Formats external drive.
  * @type {Command}
  */
diff --git a/ui/file_manager/file_manager/foreground/js/file_selection.js b/ui/file_manager/file_manager/foreground/js/file_selection.js
index 0ba0e74..c543e04 100644
--- a/ui/file_manager/file_manager/foreground/js/file_selection.js
+++ b/ui/file_manager/file_manager/foreground/js/file_selection.js
@@ -62,11 +62,11 @@
    */
   this.additionalPromise_ = null;
 
-  entries.forEach(function(entry) {
+  entries.forEach(entry => {
     if (this.iconType == null) {
       this.iconType = FileType.getIcon(entry);
     } else if (this.iconType != 'unknown') {
-      var iconType = FileType.getIcon(entry);
+      const iconType = FileType.getIcon(entry);
       if (this.iconType != iconType) {
         this.iconType = 'unknown';
       }
@@ -78,7 +78,7 @@
       this.directoryCount += 1;
     }
     this.totalCount++;
-  }.bind(this));
+  });
 }
 
 FileSelection.prototype.computeAdditional = function(metadataModel) {
@@ -88,21 +88,21 @@
             .get(
                 this.entries,
                 constants.FILE_SELECTION_METADATA_PREFETCH_PROPERTY_NAMES)
-            .then(function(props) {
-              var present = props.filter(function(p) {
+            .then(props => {
+              const present = props.filter(p => {
                 // If no availableOffline property, then assume it's available.
                 return !('availableOffline' in p) || p.availableOffline;
               });
-              const hosted = props.filter(function(p) {
+              const hosted = props.filter(p => {
                 return p.hosted;
               });
               this.anyFilesNotInCache = present.length !== props.length;
               this.anyFilesHosted = !!hosted.length;
-              this.mimeTypes = props.map(function(value) {
+              this.mimeTypes = props.map(value => {
                 return value.contentMimeType || '';
               });
               return true;
-            }.bind(this));
+            });
   }
   return this.additionalPromise_;
 };
@@ -220,11 +220,11 @@
  * Update the UI when the selection model changes.
  */
 FileSelectionHandler.prototype.onFileSelectionChanged = function() {
-  var indexes = this.listContainer_.selectionModel.selectedIndexes;
-  var entries = indexes.map(function(index) {
+  const indexes = this.listContainer_.selectionModel.selectedIndexes;
+  const entries = indexes.map(index => {
     return /** @type {!Entry} */ (
         this.directoryModel_.getFileList().item(index));
-  }.bind(this));
+  });
   this.selection = new FileSelection(indexes, entries);
 
   if (this.selectionUpdateTimer_) {
@@ -236,8 +236,8 @@
   // asynchronous calls. We initiate these calls after a timeout. If the
   // selection is changing quickly we only do this once when it slows down.
 
-  var updateDelay = FileSelectionHandler.UPDATE_DELAY;
-  var now = Date.now();
+  let updateDelay = FileSelectionHandler.UPDATE_DELAY;
+  const now = Date.now();
 
   if (now > (this.lastFileSelectionTime_ || 0) + updateDelay &&
       indexes.length < FileSelectionHandler.NUMBER_OF_ITEMS_HEAVY_TO_COMPUTE) {
@@ -248,13 +248,13 @@
   }
   this.lastFileSelectionTime_ = now;
 
-  var selection = this.selection;
-  this.selectionUpdateTimer_ = setTimeout(function() {
+  const selection = this.selection;
+  this.selectionUpdateTimer_ = setTimeout(() => {
     this.selectionUpdateTimer_ = null;
     if (this.selection === selection) {
       this.updateFileSelectionAsync_(selection);
     }
-  }.bind(this), updateDelay);
+  }, updateDelay);
 
   cr.dispatchSimpleEvent(this, FileSelectionHandler.EventType.CHANGE);
 };
diff --git a/ui/file_manager/file_manager/foreground/js/navigation_list_model.js b/ui/file_manager/file_manager/foreground/js/navigation_list_model.js
index 3e15389..ef36a99 100644
--- a/ui/file_manager/file_manager/foreground/js/navigation_list_model.js
+++ b/ui/file_manager/file_manager/foreground/js/navigation_list_model.js
@@ -199,6 +199,14 @@
   this.myFilesModel_ = null;
 
   /**
+   * A collection of NavigationModel objects for Removable partition groups.
+   * Store the reference to each model here since DirectoryTree expects it to
+   * always have the same reference.
+   * @private {!Map<string, !NavigationModelFakeItem>}
+   */
+  this.removableModels_ = new Map();
+
+  /**
    * True when MyFiles should be a volume and Downloads just a plain folder
    * inside it. When false MyFiles is an EntryList, which means UI only type,
    * which contains Downloads as a child volume.
@@ -504,6 +512,32 @@
     return indexes.map(idx => volumeList[idx]);
   };
 
+  /**
+   * Removable volumes which share the same device path (i.e. partitions) are
+   * grouped.
+   * @return !Map<string, !Array<!NavigationModelVolumeItem>>
+   */
+  const groupRemovables = function() {
+    const removableGroups = new Map();
+    const removableVolumes =
+        getVolumes(VolumeManagerCommon.VolumeType.REMOVABLE);
+
+    for (const removable of removableVolumes) {
+      // Partitions on the same physical device share device path and drive
+      // label. Create keys using these two identifiers.
+      let key = removable.volumeInfo.devicePath + '/' +
+          removable.volumeInfo.driveLabel;
+      if (!removableGroups.has(key)) {
+        // New key, so create a new array to hold partitions.
+        removableGroups.set(key, []);
+      }
+      // Add volume to array of volumes matching device path and drive label.
+      removableGroups.get(key).push(removable);
+    }
+
+    return removableGroups;
+  };
+
   // Items as per required order.
   this.navigationItems_ = [];
 
@@ -637,11 +671,49 @@
     provider.section = NavigationSection.CLOUD;
   }
 
-  // Join MTP, ARCHIVE and REMOVABLE. These types belong to same section.
+  // Add REMOVABLE volumes and partitions.
+  const removableModels = new Map();
+  for (const [devicePath, removableGroup] of groupRemovables().entries()) {
+    if (removableGroup.length == 1) {
+      // Add unpartitioned removable device as a regular volume.
+      this.navigationItems_.push(removableGroup[0]);
+      removableGroup[0].section = NavigationSection.REMOVABLE;
+      continue;
+    }
+
+    // Multiple partitions found.
+    let removableModel;
+    if (this.removableModels_.has(devicePath)) {
+      // Removable model has been seen before. Use the same reference.
+      removableModel = this.removableModels_.get(devicePath);
+    } else {
+      // Create an EntryList for new removable group.
+      const rootLabel = removableGroup[0].volumeInfo.driveLabel ?
+          removableGroup[0].volumeInfo.driveLabel :
+          /*default*/ 'External Drive';
+      const removableEntry = new EntryList(
+          rootLabel, VolumeManagerCommon.RootType.REMOVABLE, devicePath);
+      removableModel = new NavigationModelFakeItem(
+          removableEntry.label, NavigationModelItemType.ENTRY_LIST,
+          removableEntry);
+      removableModel.section = NavigationSection.REMOVABLE;
+      // Add partitions as entries.
+      for (const partition of removableGroup) {
+        // Only add partition if it doesn't exist as a child already.
+        if (removableEntry.findIndexByVolumeInfo(partition.volumeInfo) === -1) {
+          removableEntry.addEntry(new VolumeEntry(partition.volumeInfo));
+        }
+      }
+    }
+    removableModels.set(devicePath, removableModel);
+    this.navigationItems_.push(removableModel);
+  }
+  this.removableModels_ = removableModels;
+
+  // Join MTP, ARCHIVE. These types belong to same section.
   const zipIndexes = volumeIndexes[NavigationListModel.ZIP_VOLUME_TYPE] || [];
   const otherVolumes =
       [].concat(
-            getVolumes(VolumeManagerCommon.VolumeType.REMOVABLE),
             getVolumes(VolumeManagerCommon.VolumeType.ARCHIVE),
             getVolumes(VolumeManagerCommon.VolumeType.MTP),
             zipIndexes.map(idx => volumeList[idx]))
diff --git a/ui/file_manager/file_manager/foreground/js/navigation_list_model_unittest.js b/ui/file_manager/file_manager/foreground/js/navigation_list_model_unittest.js
index 13afa9717..a75581c 100644
--- a/ui/file_manager/file_manager/foreground/js/navigation_list_model_unittest.js
+++ b/ui/file_manager/file_manager/foreground/js/navigation_list_model_unittest.js
@@ -193,7 +193,8 @@
 
   // Mount removable volume 'hoge'.
   volumeManager.volumeInfoList.add(MockVolumeManager.createMockVolumeInfo(
-      VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:hoge'));
+      VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:hoge', '',
+      'device/path/1'));
 
   assertEquals(4, model.length);
   assertEquals(
@@ -207,9 +208,11 @@
       'removable:hoge', /** @type {!NavigationModelVolumeItem} */
       (model.item(3)).volumeInfo.volumeId);
 
-  // Mount removable volume 'fuga'.
+  // Mount removable volume 'fuga'. Not a partition, so set a different device
+  // path to 'hoge'.
   volumeManager.volumeInfoList.add(MockVolumeManager.createMockVolumeInfo(
-      VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:fuga'));
+      VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:fuga', '',
+      'device/path/2'));
 
   assertEquals(5, model.length);
   assertEquals(
@@ -268,14 +271,18 @@
 
   // Create different volumes.
   volumeManager.volumeInfoList.add(MockVolumeManager.createMockVolumeInfo(
-      VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:hoge'));
-  volumeManager.volumeInfoList.add(MockVolumeManager.createMockVolumeInfo(
       VolumeManagerCommon.VolumeType.PROVIDED, 'provided:prov1'));
+  // Set the device paths of the removable volumes to different strings to
+  // test the behaviour of two physically separate external devices.
+  volumeManager.volumeInfoList.add(MockVolumeManager.createMockVolumeInfo(
+      VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:hoge', '',
+      'device/path/1'));
+  volumeManager.volumeInfoList.add(MockVolumeManager.createMockVolumeInfo(
+      VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:fuga', '',
+      'device/path/2'));
   volumeManager.volumeInfoList.add(MockVolumeManager.createMockVolumeInfo(
       VolumeManagerCommon.VolumeType.ARCHIVE, 'archive:a-rar'));
   volumeManager.volumeInfoList.add(MockVolumeManager.createMockVolumeInfo(
-      VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:fuga'));
-  volumeManager.volumeInfoList.add(MockVolumeManager.createMockVolumeInfo(
       VolumeManagerCommon.VolumeType.MTP, 'mtp:a-phone'));
   volumeManager.volumeInfoList.add(MockVolumeManager.createMockVolumeInfo(
       VolumeManagerCommon.VolumeType.PROVIDED, 'provided:prov2'));
@@ -311,8 +318,8 @@
   // 10.  provided:prov2
   //
   // 11.  removable:hoge
-  // 12.  archive:a-rar  - mounted as archive
-  // 13.  removable:fuga
+  // 12.  removable:fuga
+  // 13.  archive:a-rar  - mounted as archive
   // 14.  mtp:a-phone
   // 15.  provided:"zip" - mounted as provided: $zipVolumeId
 
@@ -338,8 +345,9 @@
   assertEquals('provided:prov2', model.item(9).label);
 
   assertEquals('removable:hoge', model.item(10).label);
-  assertEquals('archive:a-rar', model.item(11).label);
-  assertEquals('removable:fuga', model.item(12).label);
+  assertEquals('removable:fuga', model.item(11).label);
+
+  assertEquals('archive:a-rar', model.item(12).label);
   assertEquals('mtp:a-phone', model.item(13).label);
   assertEquals(zipVolumeId, model.item(14).label);
 
@@ -372,9 +380,9 @@
   // MTP/Archive/Removable are grouped together.
   // removable:hoge.
   assertEquals(NavigationSection.REMOVABLE, model.item(10).section);
-  // archive:a-rar.
-  assertEquals(NavigationSection.REMOVABLE, model.item(11).section);
   // removable:fuga.
+  assertEquals(NavigationSection.REMOVABLE, model.item(11).section);
+  // archive:a-rar.
   assertEquals(NavigationSection.REMOVABLE, model.item(12).section);
   // mtp:a-phone.
   assertEquals(NavigationSection.REMOVABLE, model.item(13).section);
diff --git a/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js b/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
index 634f718..4066b88 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/directory_tree.js
@@ -662,7 +662,9 @@
   ejectButton.setAttribute('tabindex', '0');
   ejectButton.addEventListener('click', (event) => {
     event.stopPropagation();
-    const unmountCommand = cr.doc.querySelector('command#unmount');
+    const unmountCommand = (this instanceof EntryListItem) ?
+        cr.doc.querySelector('command#unmount-all-partitions') :
+        cr.doc.querySelector('command#unmount');
     // Let's make sure 'canExecute' state of the command is properly set for
     // the root before executing it.
     unmountCommand.canExecuteChange(this);
@@ -716,6 +718,7 @@
     if (window.IN_TEST && location.volumeInfo) {
       item.setAttribute(
           'volume-type-for-testing', location.volumeInfo.volumeType);
+      item.setAttribute('drive-label', location.volumeInfo.driveLabel);
     }
   } else {
     const rootType = location.rootType || null;
@@ -803,6 +806,10 @@
   item.dirEntry_ = modelItem.entry;
   item.parentTree_ = tree;
 
+  if (rootType === VolumeManagerCommon.RootType.REMOVABLE) {
+    item.setupEjectButton_(item.rowElement);
+  }
+
   const icon = queryRequiredElement('.icon', item);
   if (window.IN_TEST && item.entry && item.entry.volumeInfo) {
     item.setAttribute(
@@ -1806,8 +1813,11 @@
           /** @type {!NavigationModelFakeItem} */ (modelItem), tree);
       break;
     case NavigationModelItemType.ENTRY_LIST:
+      const rootType = modelItem.section === NavigationSection.REMOVABLE ?
+          VolumeManagerCommon.RootType.REMOVABLE :
+          VolumeManagerCommon.RootType.MY_FILES;
       return new EntryListItem(
-          VolumeManagerCommon.RootType.MY_FILES,
+          rootType,
           /** @type {!NavigationModelFakeItem} */ (modelItem), tree);
       break;
   }
diff --git a/ui/file_manager/file_manager/foreground/js/ui/location_line.js b/ui/file_manager/file_manager/foreground/js/ui/location_line.js
index 5bae388..0390a2e 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/location_line.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/location_line.js
@@ -75,7 +75,7 @@
 
   if (util.isFakeEntry(entry)) {
     components.push(new LocationLine.PathComponent(
-        util.getRootTypeLabel(locationInfo), entry.toURL(),
+        util.getEntryLabel(locationInfo, entry), entry.toURL(),
         /** @type {!FakeEntry} */ (entry)));
     return components;
   }
diff --git a/ui/file_manager/file_manager/main.html b/ui/file_manager/file_manager/main.html
index 9b88cdd..aa682e4 100644
--- a/ui/file_manager/file_manager/main.html
+++ b/ui/file_manager/file_manager/main.html
@@ -138,6 +138,7 @@
 
       <command id="unmount" label="$i18n{UNMOUNT_DEVICE_BUTTON_LABEL}"
                shortcut="E|Shift|Ctrl">
+      <command id="unmount-all-partitions" label="$i18n{UNMOUNT_DEVICE_BUTTON_LABEL}">
       <command id="format" label="$i18n{FORMAT_DEVICE_BUTTON_LABEL}">
       <command id="configure" label="$i18n{CONFIGURE_VOLUME_BUTTON_LABEL}">
 
@@ -226,6 +227,7 @@
              menu-item-selector="cr-menu-item">
       <cr-menu-item command="#configure"></cr-menu-item>
       <cr-menu-item command="#unmount"></cr-menu-item>
+      <cr-menu-item command="#unmount-all-partitions"></cr-menu-item>
       <cr-menu-item command="#format"></cr-menu-item>
       <cr-menu-item command="#rename"></cr-menu-item>
       <cr-menu-item command="#remove-folder-shortcut"></cr-menu-item>
diff --git a/ui/file_manager/integration_tests/file_manager/file_display.js b/ui/file_manager/integration_tests/file_manager/file_display.js
index d772dd1..5c316d6 100644
--- a/ui/file_manager/integration_tests/file_manager/file_display.js
+++ b/ui/file_manager/integration_tests/file_manager/file_display.js
@@ -180,7 +180,7 @@
 };
 
 /**
- * Tests files display for partitions on a removable USB volume.
+ * Tests files display on a removable USB volume with and without partitions.
  */
 testcase.fileDisplayUsbPartition = async function() {
   // Open Files app on local downloads.
@@ -207,13 +207,27 @@
   chrome.test.assertEq(
       'removable', singleUSB.attributes['volume-type-for-testing']);
 
-  // Check whether the drive label is shared by the partitions.
-  chrome.test.assertEq(
-      'PARTITION_DRIVE_LABEL', partitionOne.attributes['drive-label']);
-  chrome.test.assertEq(
-      'PARTITION_DRIVE_LABEL', partitionTwo.attributes['drive-label']);
-  chrome.test.assertEq(
-      'SINGLE_DRIVE_LABEL', singleUSB.attributes['drive-label']);
+  // Wait for removable root to appear in the directory tree.
+  const removableRoot = await remoteCall.waitForElement(
+      appId, '#directory-tree [entry-label="PARTITION_DRIVE_LABEL"]');
+
+  // Check partitions are children of the root label.
+  const childEntriesQuery =
+      ['[entry-label="PARTITION_DRIVE_LABEL"] .tree-children .tree-item'];
+  const childEntries = await remoteCall.callRemoteTestUtil(
+      'queryAllElements', appId, childEntriesQuery);
+  const childEntryLabels =
+      childEntries.map(child => child.attributes['entry-label']);
+  chrome.test.assertEq(['partition-1', 'partition-2'], childEntryLabels);
+
+  // Check single USB does not have partitions as tree children.
+  const itemEntriesQuery =
+      ['[entry-label="singleUSB"] .tree-children .tree-item'];
+  const itemEntries = await remoteCall.callRemoteTestUtil(
+      'queryAllElements', appId, itemEntriesQuery);
+  chrome.test.assertEq(1, itemEntries.length);
+  const childVolumeType = itemEntries[0].attributes['volume-type-for-testing'];
+  chrome.test.assertTrue('removable' !== childVolumeType);
 };
 
 /**
diff --git a/ui/file_manager/integration_tests/video_player/background.js b/ui/file_manager/integration_tests/video_player/background.js
index 58e88ab..6839ff4 100644
--- a/ui/file_manager/integration_tests/video_player/background.js
+++ b/ui/file_manager/integration_tests/video_player/background.js
@@ -46,8 +46,6 @@
     return Promise.all([
       remoteCallVideoPlayer.waitForElement(
           appWindow, '#video-player[first-video][last-video]'),
-      remoteCallVideoPlayer.waitForElement(
-          appWindow, '.play.media-button[state="playing"]'),
     ]);
   }).then(function(args) {
     return [appWindow, args[0]];
diff --git a/ui/file_manager/integration_tests/video_player/check_elements.js b/ui/file_manager/integration_tests/video_player/check_elements.js
index 52a9a907..e379229 100644
--- a/ui/file_manager/integration_tests/video_player/check_elements.js
+++ b/ui/file_manager/integration_tests/video_player/check_elements.js
@@ -17,12 +17,6 @@
         remoteCallVideoPlayer.waitForElement(appId, 'html[i18n-processed]'),
         remoteCallVideoPlayer.waitForElement(appId, 'div#video-player'),
         remoteCallVideoPlayer.waitForElement(appId, '#video-container > video'),
-        remoteCallVideoPlayer.waitForElement(appId, 'files-icon-button.play')
-            .then(function(element) {
-              // files-icon-button is a Polymer element and should have a
-              // shadowRoot.
-              chrome.test.assertTrue(element.hasShadowRoot);
-            }),
       ]);
     });
 };
diff --git a/ui/file_manager/integration_tests/video_player/click_control_buttons.js b/ui/file_manager/integration_tests/video_player/click_control_buttons.js
index 2a4fd08..24e337d 100644
--- a/ui/file_manager/integration_tests/video_player/click_control_buttons.js
+++ b/ui/file_manager/integration_tests/video_player/click_control_buttons.js
@@ -28,68 +28,6 @@
 }
 
 /**
- * The openSingleImage test for Downloads.
- * @return {Promise} Promise to be fulfilled with on success.
- */
-testcase.clickControlButtons = function() {
-  var openVideo = openSingleVideo('local', 'downloads', ENTRIES.world);
-  var appId;
-  return openVideo
-      .then(function(args) {
-        appId = args[0];
-        // Video player starts playing given file automatically.
-        return waitForFunctionResult('isPlaying', 'world.ogv', true);
-      })
-      .then(function() {
-        // Play will finish in 2 seconds (world.ogv is 2-second short movie.)
-        return waitForFunctionResult('isPlaying', 'world.ogv', false);
-      })
-      .then(function() {
-        // Conform that clicking play button will re-play the video.
-        return remoteCallVideoPlayer.callRemoteTestUtil(
-            'fakeMouseClick', appId, ['.media-button.play']);
-      })
-      .then(function() {
-        return waitForFunctionResult('isPlaying', 'world.ogv', true);
-      })
-      .then(function() {
-        // Confirm that clicking volume button mutes the video.
-        return remoteCallVideoPlayer.callRemoteTestUtil(
-            'fakeMouseClick', appId, ['.media-button.sound']);
-      })
-      .then(function() {
-        return waitForFunctionResult('isMuted', 'world.ogv', true);
-      })
-      .then(function() {
-        // Confirm that clicking volume button again unmutes the video.
-        return remoteCallVideoPlayer.callRemoteTestUtil(
-            'fakeMouseClick', appId, ['.media-button.sound']);
-      })
-      .then(function() {
-        return waitForFunctionResult('isMuted', 'world.ogv', false);
-      })
-      .then(function() {
-        // Confirm that clicking fullscreen button enables fullscreen mode.
-        return remoteCallVideoPlayer.callRemoteTestUtil(
-            'fakeMouseClick', appId, ['.media-button.fullscreen']);
-      })
-      .then(function() {
-        return remoteCallVideoPlayer.waitForElement(
-            appId, '#controls[fullscreen]');
-      })
-      .then(function() {
-        // Confirm that clicking fullscreen-exit button disables fullscreen
-        // mode.
-        return remoteCallVideoPlayer.callRemoteTestUtil(
-            'fakeMouseClick', appId, ['.media-button.fullscreen']);
-      })
-      .then(function() {
-        return remoteCallVideoPlayer.waitForElement(
-            appId, '#controls:not([fullscreen])');
-      });
-};
-
-/**
  * Confirms that native media keys are dispatched correctly.
  * @return {Promise} Promise to be fulfilled on success.
  */
diff --git a/ui/file_manager/integration_tests/video_player/open_video_files.js b/ui/file_manager/integration_tests/video_player/open_video_files.js
index e0685039..fcdf2d2 100644
--- a/ui/file_manager/integration_tests/video_player/open_video_files.js
+++ b/ui/file_manager/integration_tests/video_player/open_video_files.js
@@ -9,11 +9,16 @@
  * @return {Promise} Promise to be fulfilled with on success.
  */
 testcase.openSingleVideoOnDownloads = function() {
-    var test = openSingleVideo('local', 'downloads', ENTRIES.world);
-    return test.then(function(args) {
-      var videoPlayer = args[1];
-      chrome.test.assertFalse('cast-available' in videoPlayer.attributes);
-    });
+  var test = openSingleVideo('local', 'downloads', ENTRIES.world);
+  return test
+      .then(function() {
+        // Video player starts playing given file automatically.
+        return waitForFunctionResult('isPlaying', 'world.ogv', true);
+      })
+      .then(function() {
+        // Play will finish in 2 seconds (world.ogv is 2-second short movie.)
+        return waitForFunctionResult('isPlaying', 'world.ogv', false);
+      });
 };
 
 /**
@@ -21,17 +26,14 @@
  * @return {Promise} Promise to be fulfilled with on success.
  */
 testcase.openSingleVideoOnDrive = function() {
-    var test = openSingleVideo('drive', 'drive', ENTRIES.world);
-    return test.then(function(args) {
-      var appWindow = args[0];
-      var videoPlayer = args[1];
-      chrome.test.assertFalse('cast-available' in videoPlayer.attributes);
-
-      return remoteCallVideoPlayer.callRemoteTestUtil(
-          'loadMockCastExtension', appWindow, []).then(function() {
-        // Loads cast extension and wait for available cast.
-        return remoteCallVideoPlayer.waitForElement(
-            appWindow, '#video-player[cast-available]');
+  var test = openSingleVideo('drive', 'drive', ENTRIES.world);
+  return test
+      .then(function() {
+        // Video player starts playing given file automatically.
+        return waitForFunctionResult('isPlaying', 'world.ogv', true);
+      })
+      .then(function() {
+        // Play will finish in 2 seconds (world.ogv is 2-second short movie.)
+        return waitForFunctionResult('isPlaying', 'world.ogv', false);
       });
-    });
 };
diff --git a/ui/file_manager/video_player/js/video_player_native_controls.js b/ui/file_manager/video_player/js/video_player_native_controls.js
index 70eb6443b..71bb4b0 100644
--- a/ui/file_manager/video_player/js/video_player_native_controls.js
+++ b/ui/file_manager/video_player/js/video_player_native_controls.js
@@ -278,6 +278,8 @@
  * @private
  */
 NativeControlsVideoPlayer.prototype.loadVideo_ = function(video, opt_callback) {
+  document.title = video.name;
+
   this.videoElement_.src = video.toURL();
   if (opt_callback) {
     this.videoElement_.addEventListener(
diff --git a/ui/gl/gl_context_egl.cc b/ui/gl/gl_context_egl.cc
index a8f1c62..00471969 100644
--- a/ui/gl/gl_context_egl.cc
+++ b/ui/gl/gl_context_egl.cc
@@ -62,11 +62,7 @@
 namespace gl {
 
 GLContextEGL::GLContextEGL(GLShareGroup* share_group)
-    : GLContextReal(share_group),
-      context_(EGL_NO_CONTEXT),
-      display_(EGL_NO_DISPLAY),
-      config_(nullptr),
-      unbind_fbo_on_makecurrent_(false) {}
+    : GLContextReal(share_group) {}
 
 bool GLContextEGL::Initialize(GLSurface* compatible_surface,
                               const GLContextAttribs& attribs) {
@@ -278,8 +274,10 @@
 
 bool GLContextEGL::MakeCurrent(GLSurface* surface) {
   DCHECK(context_);
+  if (lost_)
+    return false;
   if (IsCurrent(surface))
-      return true;
+    return true;
 
   ScopedReleaseCurrent release_current;
   TRACE_EVENT2("gpu", "GLContextEGL::MakeCurrent",
@@ -326,14 +324,20 @@
     glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
 
   SetCurrent(nullptr);
-  eglMakeCurrent(display_,
-                 EGL_NO_SURFACE,
-                 EGL_NO_SURFACE,
-                 EGL_NO_CONTEXT);
+  if (!eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE,
+                      EGL_NO_CONTEXT)) {
+    DVLOG(1) << "eglMakeCurrent failed to release current with error "
+             << GetLastEGLErrorString();
+    lost_ = true;
+  }
+
+  DCHECK(!IsCurrent(nullptr));
 }
 
 bool GLContextEGL::IsCurrent(GLSurface* surface) {
   DCHECK(context_);
+  if (lost_)
+    return false;
 
   bool native_context_is_current = context_ == eglGetCurrentContext();
 
diff --git a/ui/gl/gl_context_egl.h b/ui/gl/gl_context_egl.h
index 3e0346c..09ffe6a 100644
--- a/ui/gl/gl_context_egl.h
+++ b/ui/gl/gl_context_egl.h
@@ -45,10 +45,11 @@
   void Destroy();
   void ReleaseYUVToRGBConverters();
 
-  EGLContext context_;
-  EGLDisplay display_;
-  EGLConfig config_;
-  bool unbind_fbo_on_makecurrent_;
+  EGLContext context_ = nullptr;
+  EGLDisplay display_ = nullptr;
+  EGLConfig config_ = nullptr;
+  bool unbind_fbo_on_makecurrent_ = false;
+  bool lost_ = false;
   std::map<gfx::ColorSpace, std::unique_ptr<YUVToRGBConverter>>
       yuv_to_rgb_converters_;
 
diff --git a/ui/gl/gl_surface_egl_surface_control.cc b/ui/gl/gl_surface_egl_surface_control.cc
index 4df26ab..8dfa5bde 100644
--- a/ui/gl/gl_surface_egl_surface_control.cc
+++ b/ui/gl/gl_surface_egl_surface_control.cc
@@ -31,7 +31,11 @@
 GLSurfaceEGLSurfaceControl::GLSurfaceEGLSurfaceControl(
     ANativeWindow* window,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner)
-    : root_surface_(new SurfaceControl::Surface(window, kRootSurfaceName)),
+    : window_rect_(0,
+                   0,
+                   ANativeWindow_getWidth(window),
+                   ANativeWindow_getHeight(window)),
+      root_surface_(new SurfaceControl::Surface(window, kRootSurfaceName)),
       gpu_task_runner_(std::move(task_runner)),
       weak_factory_(this) {}
 
@@ -57,7 +61,7 @@
                                         float scale_factor,
                                         ColorSpace color_space,
                                         bool has_alpha) {
-  // Resizing requires resizing the SurfaceView in the browser.
+  window_rect_ = gfx::Rect(0, 0, size.width(), size.height());
   return true;
 }
 
@@ -71,31 +75,68 @@
   return gfx::SwapResult::SWAP_FAILED;
 }
 
-void GLSurfaceEGLSurfaceControl::SwapBuffersAsync(
-    SwapCompletionCallback completion_callback,
-    PresentationCallback presentation_callback) {
-  CommitPendingTransaction(std::move(completion_callback),
-                           std::move(presentation_callback));
-}
-
 gfx::SwapResult GLSurfaceEGLSurfaceControl::CommitOverlayPlanes(
     PresentationCallback callback) {
   NOTREACHED();
   return gfx::SwapResult::SWAP_FAILED;
 }
 
+gfx::SwapResult GLSurfaceEGLSurfaceControl::PostSubBuffer(
+    int x,
+    int y,
+    int width,
+    int height,
+    PresentationCallback callback) {
+  NOTREACHED();
+  return gfx::SwapResult::SWAP_FAILED;
+}
+
+void GLSurfaceEGLSurfaceControl::SwapBuffersAsync(
+    SwapCompletionCallback completion_callback,
+    PresentationCallback presentation_callback) {
+  CommitPendingTransaction(window_rect_, std::move(completion_callback),
+                           std::move(presentation_callback));
+}
+
 void GLSurfaceEGLSurfaceControl::CommitOverlayPlanesAsync(
     SwapCompletionCallback completion_callback,
     PresentationCallback presentation_callback) {
-  CommitPendingTransaction(std::move(completion_callback),
+  CommitPendingTransaction(window_rect_, std::move(completion_callback),
+                           std::move(presentation_callback));
+}
+
+void GLSurfaceEGLSurfaceControl::PostSubBufferAsync(
+    int x,
+    int y,
+    int width,
+    int height,
+    SwapCompletionCallback completion_callback,
+    PresentationCallback presentation_callback) {
+  CommitPendingTransaction(gfx::Rect(x, y, width, height),
+                           std::move(completion_callback),
                            std::move(presentation_callback));
 }
 
 void GLSurfaceEGLSurfaceControl::CommitPendingTransaction(
+    const gfx::Rect& damage_rect,
     SwapCompletionCallback completion_callback,
     PresentationCallback present_callback) {
   DCHECK(pending_transaction_);
 
+  // Mark the intersection of a surface's rect with the damage rect as the dirty
+  // rect for that surface.
+  DCHECK_LE(pending_surfaces_count_, surface_list_.size());
+  for (size_t i = 0; i < pending_surfaces_count_; ++i) {
+    const auto& surface_state = surface_list_[i];
+    if (!surface_state.buffer_updated_in_pending_transaction)
+      continue;
+
+    gfx::Rect surface_damage_rect = surface_state.dst;
+    surface_damage_rect.Intersect(damage_rect);
+    pending_transaction_->SetDamageRect(*surface_state.surface,
+                                        surface_damage_rect);
+  }
+
   // Release resources for the current frame once the next frame is acked.
   ResourceRefs resources_to_release;
   resources_to_release.swap(current_frame_resources_);
@@ -167,7 +208,9 @@
     resource_ref.scoped_buffer = std::move(scoped_hardware_buffer);
   }
 
-  if (uninitialized || surface_state.hardware_buffer != hardware_buffer) {
+  surface_state.buffer_updated_in_pending_transaction =
+      uninitialized || surface_state.hardware_buffer != hardware_buffer;
+  if (surface_state.buffer_updated_in_pending_transaction) {
     surface_state.hardware_buffer = hardware_buffer;
 
     if (!fence_fd.is_valid() && gpu_fence && surface_state.hardware_buffer) {
@@ -220,6 +263,10 @@
   return nullptr;
 }
 
+bool GLSurfaceEGLSurfaceControl::SupportsPostSubBuffer() {
+  return true;
+}
+
 bool GLSurfaceEGLSurfaceControl::SupportsAsyncSwap() {
   return true;
 }
@@ -232,11 +279,6 @@
   return true;
 }
 
-bool GLSurfaceEGLSurfaceControl::SupportsSwapBuffersWithBounds() {
-  // TODO(khushalsagar): Add support for partial swap.
-  return false;
-}
-
 bool GLSurfaceEGLSurfaceControl::SupportsCommitOverlayPlanes() {
   return true;
 }
diff --git a/ui/gl/gl_surface_egl_surface_control.h b/ui/gl/gl_surface_egl_surface_control.h
index 6a7ab0a..ec5e93b 100644
--- a/ui/gl/gl_surface_egl_surface_control.h
+++ b/ui/gl/gl_surface_egl_surface_control.h
@@ -41,13 +41,7 @@
               ColorSpace color_space,
               bool has_alpha) override;
   bool IsOffscreen() override;
-  gfx::SwapResult SwapBuffers(PresentationCallback callback) override;
-  void SwapBuffersAsync(SwapCompletionCallback completion_callback,
-                        PresentationCallback presentation_callback) override;
-  gfx::SwapResult CommitOverlayPlanes(PresentationCallback callback) override;
-  void CommitOverlayPlanesAsync(
-      SwapCompletionCallback completion_callback,
-      PresentationCallback presentation_callback) override;
+
   gfx::Size GetSize() override;
   bool OnMakeCurrent(GLContext* context) override;
   bool ScheduleOverlayPlane(int z_order,
@@ -60,10 +54,31 @@
   bool IsSurfaceless() const override;
   void* GetHandle() override;
 
+  // Sync versions of frame update, should never be used.
+  gfx::SwapResult SwapBuffers(PresentationCallback callback) override;
+  gfx::SwapResult CommitOverlayPlanes(PresentationCallback callback) override;
+  gfx::SwapResult PostSubBuffer(int x,
+                                int y,
+                                int width,
+                                int height,
+                                PresentationCallback callback) override;
+
+  void SwapBuffersAsync(SwapCompletionCallback completion_callback,
+                        PresentationCallback presentation_callback) override;
+  void CommitOverlayPlanesAsync(
+      SwapCompletionCallback completion_callback,
+      PresentationCallback presentation_callback) override;
+  void PostSubBufferAsync(int x,
+                          int y,
+                          int width,
+                          int height,
+                          SwapCompletionCallback completion_callback,
+                          PresentationCallback presentation_callback) override;
+
   bool SupportsAsyncSwap() override;
   bool SupportsPlaneGpuFences() const override;
   bool SupportsPresentationCallback() override;
-  bool SupportsSwapBuffersWithBounds() override;
+  bool SupportsPostSubBuffer() override;
   bool SupportsCommitOverlayPlanes() override;
 
  private:
@@ -84,6 +99,11 @@
     gfx::OverlayTransform transform = gfx::OVERLAY_TRANSFORM_NONE;
     bool opaque = true;
 
+    // Indicates whether buffer for this layer was updated in the currently
+    // pending transaction, or the last transaction submitted if there isn't
+    // one pending.
+    bool buffer_updated_in_pending_transaction = true;
+
     scoped_refptr<SurfaceControl::Surface> surface;
   };
 
@@ -99,7 +119,8 @@
   };
   using ResourceRefs = base::flat_map<ASurfaceControl*, ResourceRef>;
 
-  void CommitPendingTransaction(SwapCompletionCallback completion_callback,
+  void CommitPendingTransaction(const gfx::Rect& damage_rect,
+                                SwapCompletionCallback completion_callback,
                                 PresentationCallback callback);
 
   // Called on the |gpu_task_runner_| when a transaction is acked by the
@@ -110,6 +131,9 @@
       ResourceRefs released_resources,
       SurfaceControl::TransactionStats transaction_stats);
 
+  // The rect of the native window backing this surface.
+  gfx::Rect window_rect_;
+
   // Holds the surface state changes made since the last call to SwapBuffers.
   base::Optional<SurfaceControl::Transaction> pending_transaction_;
 
diff --git a/ui/message_center/views/message_popup_collection_unittest.cc b/ui/message_center/views/message_popup_collection_unittest.cc
index e6935b3..a6a23e9 100644
--- a/ui/message_center/views/message_popup_collection_unittest.cc
+++ b/ui/message_center/views/message_popup_collection_unittest.cc
@@ -161,7 +161,7 @@
   }
 
   void Activate() {
-    set_can_activate(true);
+    SetCanActivate(true);
     GetWidget()->Activate();
   }
 
diff --git a/ui/message_center/views/notification_view_md.cc b/ui/message_center/views/notification_view_md.cc
index 9da43a6..b5025f3 100644
--- a/ui/message_center/views/notification_view_md.cc
+++ b/ui/message_center/views/notification_view_md.cc
@@ -1291,7 +1291,7 @@
 }
 
 void NotificationViewMD::Activate() {
-  GetWidget()->widget_delegate()->set_can_activate(true);
+  GetWidget()->widget_delegate()->SetCanActivate(true);
   GetWidget()->Activate();
 }
 
diff --git a/ui/message_center/views/notification_view_md_unittest.cc b/ui/message_center/views/notification_view_md_unittest.cc
index bf98dcaf..bccfaad 100644
--- a/ui/message_center/views/notification_view_md_unittest.cc
+++ b/ui/message_center/views/notification_view_md_unittest.cc
@@ -317,7 +317,7 @@
     widget_->SetContentsView(notification_view_.get());
     widget_->SetSize(notification_view_->GetPreferredSize());
     widget_->Show();
-    widget_->widget_delegate()->set_can_activate(true);
+    widget_->widget_delegate()->SetCanActivate(true);
     widget_->Activate();
   } else {
     notification_view_->UpdateWithNotification(notification);
diff --git a/ui/ozone/platform/scenic/scenic_surface_factory.cc b/ui/ozone/platform/scenic/scenic_surface_factory.cc
index ed0c5e4..c896272 100644
--- a/ui/ozone/platform/scenic/scenic_surface_factory.cc
+++ b/ui/ozone/platform/scenic/scenic_surface_factory.cc
@@ -7,8 +7,8 @@
 #include <lib/zx/event.h>
 
 #include "base/bind.h"
-#include "base/fuchsia/component_context.h"
 #include "base/fuchsia/fuchsia_logging.h"
+#include "base/fuchsia/service_directory_client.h"
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "ui/gfx/native_pixmap.h"
@@ -215,7 +215,7 @@
     fidl::InterfaceHandle<fuchsia::ui::scenic::SessionListener> listener) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   if (!scenic_) {
-    scenic_ = base::fuchsia::ComponentContext::GetDefault()
+    scenic_ = base::fuchsia::ServiceDirectoryClient::ForCurrentProcess()
                   ->ConnectToService<fuchsia::ui::scenic::Scenic>();
     scenic_.set_error_handler([](zx_status_t status) {
       ZX_LOG(FATAL, status) << "Scenic connection failed";
diff --git a/ui/ozone/platform/scenic/scenic_window_manager.cc b/ui/ozone/platform/scenic/scenic_window_manager.cc
index 82b0e01f..c19d20d 100644
--- a/ui/ozone/platform/scenic/scenic_window_manager.cc
+++ b/ui/ozone/platform/scenic/scenic_window_manager.cc
@@ -4,8 +4,8 @@
 
 #include "ui/ozone/platform/scenic/scenic_window_manager.h"
 
-#include "base/fuchsia/component_context.h"
 #include "base/fuchsia/fuchsia_logging.h"
+#include "base/fuchsia/service_directory_client.h"
 #include "ui/ozone/platform/scenic/ozone_platform_scenic.h"
 
 namespace ui {
@@ -22,7 +22,7 @@
 
 fuchsia::ui::viewsv1::ViewManager* ScenicWindowManager::GetViewManager() {
   if (!view_manager_) {
-    view_manager_ = base::fuchsia::ComponentContext::GetDefault()
+    view_manager_ = base::fuchsia::ServiceDirectoryClient::ForCurrentProcess()
                         ->ConnectToService<fuchsia::ui::viewsv1::ViewManager>();
     view_manager_.set_error_handler([](zx_status_t status) {
       ZX_LOG(FATAL, status) << " ViewManager lost.";
@@ -34,7 +34,7 @@
 
 fuchsia::ui::scenic::Scenic* ScenicWindowManager::GetScenic() {
   if (!scenic_) {
-    scenic_ = base::fuchsia::ComponentContext::GetDefault()
+    scenic_ = base::fuchsia::ServiceDirectoryClient::ForCurrentProcess()
                   ->ConnectToService<fuchsia::ui::scenic::Scenic>();
     scenic_.set_error_handler(
         [](zx_status_t status) { ZX_LOG(FATAL, status) << " Scenic lost."; });
diff --git a/ui/platform_window/fuchsia/initialize_presenter_api_view.cc b/ui/platform_window/fuchsia/initialize_presenter_api_view.cc
index af7be03..353538a 100644
--- a/ui/platform_window/fuchsia/initialize_presenter_api_view.cc
+++ b/ui/platform_window/fuchsia/initialize_presenter_api_view.cc
@@ -7,8 +7,8 @@
 #include <fuchsia/ui/policy/cpp/fidl.h>
 #include <lib/zx/eventpair.h>
 
-#include "base/fuchsia/component_context.h"
 #include "base/fuchsia/fuchsia_logging.h"
+#include "base/fuchsia/service_directory_client.h"
 
 namespace ui {
 namespace fuchsia {
@@ -26,7 +26,7 @@
   ZX_CHECK(status == ZX_OK, status) << "zx_eventpair_create";
 
   // Request Presenter to show the view full-screen.
-  auto presenter = base::fuchsia::ComponentContext::GetDefault()
+  auto presenter = base::fuchsia::ServiceDirectoryClient::ForCurrentProcess()
                        ->ConnectToService<::fuchsia::ui::policy::Presenter>();
 
   presenter->Present2(std::move(view_holder_token), nullptr);
diff --git a/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc b/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc
index 5b2313f..a7ba97b1 100644
--- a/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc
+++ b/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc
@@ -301,13 +301,13 @@
   EXPECT_TRUE(bubble_widget->IsVisible());
 }
 
-// Test that setting WidgetDelegate::set_can_activate() to false makes the
+// Test that setting WidgetDelegate::SetCanActivate() to false makes the
 // widget created via BubbleDialogDelegateView::CreateBubble() not activatable.
 TEST_F(BubbleDialogDelegateViewTest, NotActivatable) {
   std::unique_ptr<Widget> anchor_widget(CreateTestWidget());
   BubbleDialogDelegateView* bubble_delegate =
       new TestBubbleDialogDelegateView(anchor_widget->GetContentsView());
-  bubble_delegate->set_can_activate(false);
+  bubble_delegate->SetCanActivate(false);
   Widget* bubble_widget =
       BubbleDialogDelegateView::CreateBubble(bubble_delegate);
   bubble_widget->Show();
diff --git a/ui/views/bubble/info_bubble.cc b/ui/views/bubble/info_bubble.cc
index 442287d..1caa3895 100644
--- a/ui/views/bubble/info_bubble.cc
+++ b/ui/views/bubble/info_bubble.cc
@@ -51,7 +51,7 @@
 
   set_margins(LayoutProvider::Get()->GetInsetsMetric(
       InsetsMetric::INSETS_TOOLTIP_BUBBLE));
-  set_can_activate(false);
+  SetCanActivate(false);
 
   SetLayoutManager(std::make_unique<FillLayout>());
   Label* label = new Label(message);
diff --git a/ui/views/bubble/tooltip_icon.cc b/ui/views/bubble/tooltip_icon.cc
index 09be527..0ff1e6bd 100644
--- a/ui/views/bubble/tooltip_icon.cc
+++ b/ui/views/bubble/tooltip_icon.cc
@@ -87,7 +87,7 @@
   bubble_->SetArrow(anchor_point_arrow_);
   // When shown due to a gesture event, close on deactivate (i.e. don't use
   // "focusless").
-  bubble_->set_can_activate(!mouse_inside_);
+  bubble_->SetCanActivate(!mouse_inside_);
 
   bubble_->Show();
   observer_.Add(bubble_->GetWidget());
diff --git a/ui/views/mus/desktop_window_tree_host_mus.cc b/ui/views/mus/desktop_window_tree_host_mus.cc
index d12dbd9..7ed01f8 100644
--- a/ui/views/mus/desktop_window_tree_host_mus.cc
+++ b/ui/views/mus/desktop_window_tree_host_mus.cc
@@ -934,6 +934,11 @@
   SetBoundsInPixels(rect, viz::LocalSurfaceIdAllocation());
 }
 
+void DesktopWindowTreeHostMus::OnCanActivateChanged() {
+  MusClient::Get()->window_tree_client()->SetCanFocus(
+      window(), native_widget_delegate_->CanActivate());
+}
+
 void DesktopWindowTreeHostMus::OnWindowManagerFrameValuesChanged() {
   NonClientView* non_client_view =
       native_widget_delegate_->AsWidget()->non_client_view();
diff --git a/ui/views/mus/desktop_window_tree_host_mus.h b/ui/views/mus/desktop_window_tree_host_mus.h
index ed0ba6c..4c424c8 100644
--- a/ui/views/mus/desktop_window_tree_host_mus.h
+++ b/ui/views/mus/desktop_window_tree_host_mus.h
@@ -149,6 +149,7 @@
   bool ShouldUseDesktopNativeCursorManager() const override;
   bool ShouldCreateVisibilityController() const override;
   void SetBoundsInDIP(const gfx::Rect& bounds_in_dip) override;
+  void OnCanActivateChanged() override;
 
   // MusClientObserver:
   void OnWindowManagerFrameValuesChanged() override;
diff --git a/ui/views/mus/desktop_window_tree_host_mus_unittest.cc b/ui/views/mus/desktop_window_tree_host_mus_unittest.cc
index 1e83dbb..5b61586fe 100644
--- a/ui/views/mus/desktop_window_tree_host_mus_unittest.cc
+++ b/ui/views/mus/desktop_window_tree_host_mus_unittest.cc
@@ -1075,6 +1075,30 @@
   EXPECT_FALSE(window->GetProperty(aura::client::kMaximumSize));
 }
 
+TEST_F(DesktopWindowTreeHostMusTest, SetCanFocus) {
+  auto* delegate = new WidgetDelegateView;
+  std::unique_ptr<Widget> widget = CreateWidget(delegate);
+  // Swap the WindowTree implementation to verify SetCanFocus() is called when
+  // the active state changes.
+  aura::TestWindowTree test_window_tree;
+  aura::WindowTreeClientTestApi window_tree_client_private(
+      MusClient::Get()->window_tree_client());
+  ws::mojom::WindowTree* old_tree =
+      window_tree_client_private.SwapTree(&test_window_tree);
+
+  delegate->SetCanActivate(false);
+  EXPECT_FALSE(delegate->CanActivate());
+  EXPECT_FALSE(test_window_tree.last_can_focus());
+  EXPECT_EQ(1u, test_window_tree.get_and_clear_can_focus_count());
+
+  delegate->SetCanActivate(true);
+  EXPECT_TRUE(delegate->CanActivate());
+  EXPECT_TRUE(test_window_tree.last_can_focus());
+  EXPECT_EQ(1u, test_window_tree.get_and_clear_can_focus_count());
+
+  window_tree_client_private.SwapTree(old_tree);
+}
+
 // DesktopWindowTreeHostMusTest with --force-device-scale-factor=1.25.
 class DesktopWindowTreeHostMusTestFractionalDPI
     : public DesktopWindowTreeHostMusTest {
diff --git a/ui/views/touchui/touch_selection_menu_views.cc b/ui/views/touchui/touch_selection_menu_views.cc
index 0c339bfe..f8287f13 100644
--- a/ui/views/touchui/touch_selection_menu_views.cc
+++ b/ui/views/touchui/touch_selection_menu_views.cc
@@ -50,7 +50,7 @@
   set_shadow(BubbleBorder::SMALL_SHADOW);
   set_parent_window(context);
   set_margins(gfx::Insets(kMenuMargin, kMenuMargin, kMenuMargin, kMenuMargin));
-  set_can_activate(false);
+  SetCanActivate(false);
   set_adjust_if_offscreen(true);
   EnableCanvasFlippingForRTLUI(true);
 
diff --git a/ui/views/view_unittest.cc b/ui/views/view_unittest.cc
index 0f09d44f..50a8a92 100644
--- a/ui/views/view_unittest.cc
+++ b/ui/views/view_unittest.cc
@@ -2143,7 +2143,7 @@
 
   // TYPE_POPUP widgets default to non-activatable, so the Show() above wouldn't
   // have activated the Widget. First, allow activation.
-  widget->widget_delegate()->set_can_activate(true);
+  widget->widget_delegate()->SetCanActivate(true);
 
   // When a non-child view is active, it should handle accelerators.
   view->accelerator_count_map_[return_accelerator] = 0;
diff --git a/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc b/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
index 0015739..6d1eb72 100644
--- a/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
+++ b/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
@@ -1005,6 +1005,10 @@
   desktop_window_tree_host_->SizeConstraintsChanged();
 }
 
+void DesktopNativeWidgetAura::OnCanActivateChanged() {
+  desktop_window_tree_host_->OnCanActivateChanged();
+}
+
 std::string DesktopNativeWidgetAura::GetName() const {
   return name_;
 }
diff --git a/ui/views/widget/desktop_aura/desktop_native_widget_aura.h b/ui/views/widget/desktop_aura/desktop_native_widget_aura.h
index 8e85a95..f21c8eb 100644
--- a/ui/views/widget/desktop_aura/desktop_native_widget_aura.h
+++ b/ui/views/widget/desktop_aura/desktop_native_widget_aura.h
@@ -188,6 +188,7 @@
   bool IsTranslucentWindowOpacitySupported() const override;
   ui::GestureRecognizer* GetGestureRecognizer() override;
   void OnSizeConstraintsChanged() override;
+  void OnCanActivateChanged() override;
   std::string GetName() const override;
 
   // Overridden from aura::WindowDelegate:
diff --git a/ui/views/widget/desktop_aura/desktop_native_widget_aura_unittest.cc b/ui/views/widget/desktop_aura/desktop_native_widget_aura_unittest.cc
index ecac029..58f5681 100644
--- a/ui/views/widget/desktop_aura/desktop_native_widget_aura_unittest.cc
+++ b/ui/views/widget/desktop_aura/desktop_native_widget_aura_unittest.cc
@@ -731,7 +731,7 @@
 
   // We should be able to activate the window even if the WidgetDelegate
   // says no, when a modal dialog is active.
-  widget_delegate.set_can_activate(false);
+  widget_delegate.SetCanActivate(false);
 
   Widget* modal_dialog_widget = views::DialogDelegate::CreateDialogWidget(
       dialog_delegate, NULL, top_level_widget->GetNativeView());
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host.h b/ui/views/widget/desktop_aura/desktop_window_tree_host.h
index a0ec589..d2bc1a78 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host.h
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host.h
@@ -191,6 +191,9 @@
   // Sets the bounds in screen coordinate DIPs (WindowTreeHost generally
   // operates in pixels). This function is implemented in terms of Screen.
   virtual void SetBoundsInDIP(const gfx::Rect& bounds);
+
+  // See description in Widget::OnCanActivateChanged().
+  virtual void OnCanActivateChanged() {}
 };
 
 }  // namespace views
diff --git a/ui/views/widget/native_widget_mac_unittest.mm b/ui/views/widget/native_widget_mac_unittest.mm
index e3b1af7..ceea26b 100644
--- a/ui/views/widget/native_widget_mac_unittest.mm
+++ b/ui/views/widget/native_widget_mac_unittest.mm
@@ -1699,7 +1699,7 @@
                    canBecomeMainWindow]);
 
   // Disabling activation should prevent key and main status.
-  regular_widget->widget_delegate()->set_can_activate(false);
+  regular_widget->widget_delegate()->SetCanActivate(false);
   EXPECT_FALSE([regular_widget->GetNativeWindow().GetNativeNSWindow()
                     canBecomeKeyWindow]);
   EXPECT_FALSE([regular_widget->GetNativeWindow().GetNativeNSWindow()
diff --git a/ui/views/widget/native_widget_private.cc b/ui/views/widget/native_widget_private.cc
index a5933a8..1983a9c 100644
--- a/ui/views/widget/native_widget_private.cc
+++ b/ui/views/widget/native_widget_private.cc
@@ -26,5 +26,7 @@
   ui::ShowEmojiPanel();
 }
 
+void NativeWidgetPrivate::OnCanActivateChanged() {}
+
 }  // namespace internal
 }  // namespace views
diff --git a/ui/views/widget/native_widget_private.h b/ui/views/widget/native_widget_private.h
index e4af6f1..0373ddb 100644
--- a/ui/views/widget/native_widget_private.h
+++ b/ui/views/widget/native_widget_private.h
@@ -231,6 +231,7 @@
   virtual bool IsTranslucentWindowOpacitySupported() const = 0;
   virtual ui::GestureRecognizer* GetGestureRecognizer() = 0;
   virtual void OnSizeConstraintsChanged() = 0;
+  virtual void OnCanActivateChanged();
 
   // Returns an internal name that matches the name of the associated Widget.
   virtual std::string GetName() const = 0;
diff --git a/ui/views/widget/widget.cc b/ui/views/widget/widget.cc
index 496e724..04a14bf9 100644
--- a/ui/views/widget/widget.cc
+++ b/ui/views/widget/widget.cc
@@ -329,7 +329,7 @@
 
   widget_delegate_ = params.delegate ?
       params.delegate : new DefaultWidgetDelegate(this);
-  widget_delegate_->set_can_activate(can_activate);
+  widget_delegate_->SetCanActivate(can_activate);
 
   // Henceforth, ensure the delegate outlives the Widget.
   widget_delegate_->can_delete_this_ = false;
@@ -1027,6 +1027,11 @@
 
 void Widget::OnOwnerClosing() {}
 
+void Widget::OnCanActivateChanged() {
+  if (native_widget_)
+    native_widget_->OnCanActivateChanged();
+}
+
 std::string Widget::GetName() const {
   return native_widget_->GetName();
 }
diff --git a/ui/views/widget/widget.h b/ui/views/widget/widget.h
index 0323bbb..bb13f4e9 100644
--- a/ui/views/widget/widget.h
+++ b/ui/views/widget/widget.h
@@ -795,6 +795,9 @@
   // Called when the delegate's CanResize or CanMaximize changes.
   void OnSizeConstraintsChanged();
 
+  // Called when WidgetDelegate::CanActivate() changes.
+  void OnCanActivateChanged();
+
   // Notification that our owner is closing.
   // NOTE: this is not invoked for aura as it's currently not needed there.
   // Under aura menus close by way of activation getting reset when the owner
diff --git a/ui/views/widget/widget_delegate.cc b/ui/views/widget/widget_delegate.cc
index fb1eb96f..c27fb35 100644
--- a/ui/views/widget/widget_delegate.cc
+++ b/ui/views/widget/widget_delegate.cc
@@ -23,6 +23,19 @@
   CHECK(can_delete_this_) << "A WidgetDelegate must outlive its Widget";
 }
 
+void WidgetDelegate::SetCanActivate(bool can_activate) {
+  if (can_activate == can_activate_)
+    return;
+
+  const bool previous_value = CanActivate();
+  can_activate_ = can_activate;
+  if (previous_value != CanActivate()) {
+    Widget* widget = GetWidget();
+    if (widget)
+      widget->OnCanActivateChanged();
+  }
+}
+
 void WidgetDelegate::OnWidgetMove() {
 }
 
diff --git a/ui/views/widget/widget_delegate.h b/ui/views/widget/widget_delegate.h
index 186a5509..75ace4b 100644
--- a/ui/views/widget/widget_delegate.h
+++ b/ui/views/widget/widget_delegate.h
@@ -32,9 +32,7 @@
   WidgetDelegate();
 
   // Sets the return value of CanActivate(). Default is true.
-  void set_can_activate(bool can_activate) {
-    can_activate_ = can_activate;
-  }
+  void SetCanActivate(bool can_activate);
 
   // Called whenever the widget's position changes.
   virtual void OnWidgetMove();
diff --git a/ui/views/widget/widget_interactive_uitest.cc b/ui/views/widget/widget_interactive_uitest.cc
index f5e2d3e..d2cf16d1 100644
--- a/ui/views/widget/widget_interactive_uitest.cc
+++ b/ui/views/widget/widget_interactive_uitest.cc
@@ -202,12 +202,12 @@
   // active. But we can simulate deactivation (e.g. as if another application
   // became active) by temporarily making |widget| non-activatable, then
   // activating (and closing) a temporary widget.
-  widget->widget_delegate()->set_can_activate(false);
+  widget->widget_delegate()->SetCanActivate(false);
   Widget* stealer = new Widget;
   stealer->Init(Widget::InitParams(Widget::InitParams::TYPE_WINDOW));
   ShowSync(stealer);
   stealer->CloseNow();
-  widget->widget_delegate()->set_can_activate(true);
+  widget->widget_delegate()->SetCanActivate(true);
 #else
   views::test::WidgetActivationWaiter waiter(widget, false);
   widget->Deactivate();