diff --git a/AUTHORS b/AUTHORS
index b1f3fdbf..72262c4 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -139,6 +139,7 @@
 Bruno Roy <brusi_roy@hotmail.com>
 Bryan Donlan <bdonlan@gmail.com>
 Bryce Thomas <bryct@amazon.com>
+Burton <burton@typewritten.net>
 Byounghoon Yoon <bill.2714@kakaocorp.com>
 Byoungkwon Ko <codeimpl@gmail.com>
 Byungwoo Lee <bw80.lee@samsung.com>
diff --git a/DEPS b/DEPS
index db566d9..09dfc29 100644
--- a/DEPS
+++ b/DEPS
@@ -133,11 +133,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'cb4eb612f02fb2e21dd8264fb573b73610c53f83',
+  'skia_revision': '52e620ffd0bded92a6884d42c2515487539b19e0',
   # 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': '359fddbf26f906930f8780b7be3c24bdd4370d59',
+  'v8_revision': '0a7a6252f822ffe94ee5a8879ee34f5b2c8f7322',
   # 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.
@@ -145,15 +145,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'f094bac949922dabc3dd6944f3dec04111e953b6',
+  'angle_revision': '14126505b237ed3f56b7a3860fef43403e0accd9',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': '9584fe43a9c2a00beaf0a4ce2f5945292f487660',
+  'swiftshader_revision': '2995dc2a67c7d7b15ef10932757770b392dc3a5a',
   # 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': '3df5c7cf7b585025bee1f5e30918dd475e5f4363',
+  'pdfium_revision': 'a07afc58f2d765dd02a571354855facdaa6921b8',
   # 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.
@@ -244,7 +244,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.
-  'spv_tools_revision': '07f80c4df1b0619ee484c38e79a7ad71f672ca14',
+  'spv_tools_revision': 'a2ef7be242bcacaa9127a3ce011602ec54b2c9ed',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -260,11 +260,11 @@
   # 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': 'b47470daa7377a804df5d94e7a93b5925a57f84a',
+  'dawn_revision': '579cf621f3dcd72128e60721754d0966f3249ed2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'quiche_revision': 'c5db41e3e5d07c019e556964e5bbeb891e5b70fd',
+  'quiche_revision': 'b6880f79b56f38c05be91191f9630e9aa66ecc2d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling clang format
   # and whatever else without interference from each other.
@@ -272,7 +272,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libc++
   # and whatever else without interference from each other.
-  "libcxx_revision": "22d3f6dd25e5efc59124ba1c00b8f98b14be4201",
+  "libcxx_revision": "9ae8fb4a3c5fef4e9d41e97bbd9397a412932ab0",
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libc++abi
   # and whatever else without interference from each other.
@@ -752,7 +752,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'c3ec692344cb0e764ed0e52a898059ab23fae94e',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'a6918d7b72a9b0c1ff2c41d9515e14ec72256f93',
       'condition': 'checkout_linux',
   },
 
@@ -777,7 +777,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '61a1b5f31059b4ab7ca6870dd798803bdfec4de7',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'ffb1ffb8228460b9c495216507daa90717c6193a',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -1119,7 +1119,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '5abc126b0795e0711364ea43ce9ec9d31f3106d7',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  'df42f3b1d646fef4d27aef6be1567d0ba50e0745',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + 'ac0d98b5cee6c024b0cffeb4f8f45b6fc5ccdb78',
@@ -1334,7 +1334,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@0ab64c504cf5433358e0f4a98827a8ca94ed7fae',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@45209e43f286b950277bb13f64f8aefe3f1129b1',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/tracing/aw_trace_event_args_whitelist.cc b/android_webview/browser/tracing/aw_trace_event_args_whitelist.cc
index c23c5b2..90c6a888 100644
--- a/android_webview/browser/tracing/aw_trace_event_args_whitelist.cc
+++ b/android_webview/browser/tracing/aw_trace_event_args_whitelist.cc
@@ -76,4 +76,8 @@
   return false;
 }
 
+bool IsTraceMetadataWhitelisted(const std::string& name) {
+  return false;
+}
+
 }  // namespace android_webview
diff --git a/android_webview/browser/tracing/aw_trace_event_args_whitelist.h b/android_webview/browser/tracing/aw_trace_event_args_whitelist.h
index e5dc071e..f51da8f 100644
--- a/android_webview/browser/tracing/aw_trace_event_args_whitelist.h
+++ b/android_webview/browser/tracing/aw_trace_event_args_whitelist.h
@@ -5,6 +5,8 @@
 #ifndef ANDROID_WEBVIEW_BROWSER_TRACING_AW_TRACE_EVENT_ARGS_WHITELIST_H_
 #define ANDROID_WEBVIEW_BROWSER_TRACING_AW_TRACE_EVENT_ARGS_WHITELIST_H_
 
+#include <string>
+
 #include "base/trace_event/trace_event_impl.h"
 
 namespace android_webview {
@@ -16,6 +18,10 @@
     const char* event_name,
     base::trace_event::ArgumentNameFilterPredicate* arg_name_filter);
 
+// Used to filter metadata events that have been manually vetted to not include
+// any PII.
+bool IsTraceMetadataWhitelisted(const std::string& name);
+
 }  // namespace android_webview
 
 #endif  // ANDROID_WEBVIEW_BROWSER_TRACING_AW_TRACE_EVENT_ARGS_WHITELIST_H_
diff --git a/android_webview/lib/aw_main_delegate.cc b/android_webview/lib/aw_main_delegate.cc
index b49c21d..48ce7bcc 100644
--- a/android_webview/lib/aw_main_delegate.cc
+++ b/android_webview/lib/aw_main_delegate.cc
@@ -215,6 +215,8 @@
   // as is the case by default in aw_tracing_controller.cc
   base::trace_event::TraceLog::GetInstance()->SetArgumentFilterPredicate(
       base::BindRepeating(&IsTraceEventArgsWhitelisted));
+  base::trace_event::TraceLog::GetInstance()->SetMetadataFilterPredicate(
+      base::BindRepeating(&IsTraceMetadataWhitelisted));
 
   // The TLS slot used by the memlog allocator shim needs to be initialized
   // early to ensure that it gets assigned a low slot number. If it gets
diff --git a/ash/DEPS b/ash/DEPS
index 56f0244..35ba450 100644
--- a/ash/DEPS
+++ b/ash/DEPS
@@ -62,6 +62,7 @@
   "+chromeos/dbus/dbus_thread_manager.h",
   "+chromeos/dbus/fake_power_manager_client.h",
   "+chromeos/dbus/fake_session_manager_client.h",
+  "+chromeos/dbus/hammerd",
   "+chromeos/dbus/power_manager",
   "+chromeos/dbus/power_manager_client.h",
   "+chromeos/dbus/power_policy_controller.h",
diff --git a/ash/app_list/BUILD.gn b/ash/app_list/BUILD.gn
index d8a3a47..0e3b4521 100644
--- a/ash/app_list/BUILD.gn
+++ b/ash/app_list/BUILD.gn
@@ -57,6 +57,8 @@
     "views/folder_header_view.cc",
     "views/folder_header_view.h",
     "views/folder_header_view_delegate.h",
+    "views/ghost_image_view.cc",
+    "views/ghost_image_view.h",
     "views/horizontal_page.cc",
     "views/horizontal_page.h",
     "views/horizontal_page_container.cc",
@@ -227,6 +229,7 @@
     "//ash/public/cpp/app_list/vector_icons",
     "//base",
     "//base/test:test_support",
+    "//chromeos/constants",
     "//mojo/core/embedder",
     "//mojo/public/cpp/bindings",
     "//services/content/public/cpp",
diff --git a/ash/app_list/DEPS b/ash/app_list/DEPS
index c8d91c6..828f97b 100644
--- a/ash/app_list/DEPS
+++ b/ash/app_list/DEPS
@@ -4,6 +4,7 @@
   "+ash/assistant/util",
   "+ash/resources/vector_icons",
   "+ash/strings",
+  "+chromeos/constants",
   "+components/keyed_service/core",
   "+components/sync",
   "+mojo/public/cpp",
diff --git a/ash/app_list/test/app_list_test_view_delegate.cc b/ash/app_list/test/app_list_test_view_delegate.cc
index 9715d6a..4d1072d 100644
--- a/ash/app_list/test/app_list_test_view_delegate.cc
+++ b/ash/app_list/test/app_list_test_view_delegate.cc
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "ash/app_list/model/app_list_model.h"
+#include "ash/public/cpp/app_list/app_list_features.h"
 #include "ash/public/cpp/app_list/app_list_switches.h"
 #include "ash/public/cpp/menu_utils.h"
 #include "base/callback.h"
@@ -40,6 +41,10 @@
   for (size_t i = 0; i < results->item_count(); ++i) {
     if (results->GetItemAt(i)->id() == result_id) {
       open_search_result_counts_[i]++;
+      if (app_list_features::IsEmbeddedAssistantUIEnabled() &&
+          results->GetItemAt(i)->is_omnibox_search()) {
+        ++open_assistant_ui_count_;
+      }
       break;
     }
   }
diff --git a/ash/app_list/test/app_list_test_view_delegate.h b/ash/app_list/test/app_list_test_view_delegate.h
index 9bbfd6e4..36b1897d 100644
--- a/ash/app_list/test/app_list_test_view_delegate.h
+++ b/ash/app_list/test/app_list_test_view_delegate.h
@@ -35,6 +35,7 @@
 
   int dismiss_count() const { return dismiss_count_; }
   int open_search_result_count() const { return open_search_result_count_; }
+  int open_assistant_ui_count() const { return open_assistant_ui_count_; }
   std::map<size_t, int>& open_search_result_counts() {
     return open_search_result_counts_;
   }
@@ -108,6 +109,7 @@
 
   int dismiss_count_ = 0;
   int open_search_result_count_ = 0;
+  int open_assistant_ui_count_ = 0;
   int next_profile_app_count_ = 0;
   int show_wallpaper_context_menu_count_ = 0;
   std::map<size_t, int> open_search_result_counts_;
diff --git a/ash/app_list/views/app_list_view_unittest.cc b/ash/app_list/views/app_list_view_unittest.cc
index cfbf92f..611c5a3b 100644
--- a/ash/app_list/views/app_list_view_unittest.cc
+++ b/ash/app_list/views/app_list_view_unittest.cc
@@ -40,10 +40,12 @@
 #include "ash/public/cpp/app_list/app_list_features.h"
 #include "base/macros.h"
 #include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/icu_test_util.h"
 #include "base/test/scoped_feature_list.h"
+#include "chromeos/constants/chromeos_switches.h"
 #include "services/content/public/cpp/test/fake_navigable_contents.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/models/simple_menu_model.h"
@@ -346,6 +348,34 @@
     RunPendingMessages();
   }
 
+  // Add search results for test on embedded Assistant UI.
+  void SetUpSearchResultsForAssistantUI(int list_results_num,
+                                        int index_open_assistant_ui) {
+    SearchModel::SearchResults* results =
+        delegate_->GetSearchModel()->results();
+    results->DeleteAll();
+    double display_score = list_results_num;
+    for (int i = 0; i < list_results_num; ++i) {
+      // Set the display score of the results in decreasing order
+      // (so the earlier groups have higher display score, and therefore appear
+      // first).
+      display_score -= 1;
+      std::unique_ptr<TestSearchResult> result =
+          std::make_unique<TestSearchResult>();
+      result->set_display_type(ash::SearchResultDisplayType::kList);
+      result->set_display_score(display_score);
+      result->set_title(base::ASCIIToUTF16("Test" + base::NumberToString(i)));
+      result->set_result_id("Test" + base::NumberToString(i));
+      if (i == index_open_assistant_ui)
+        result->set_is_omnibox_search(true);
+
+      results->Add(std::move(result));
+    }
+
+    // Adding results will schedule Update().
+    RunPendingMessages();
+  }
+
   void ClearSearchResults() {
     delegate_->GetSearchModel()->results()->DeleteAll();
   }
@@ -372,6 +402,10 @@
     return delegate_->open_search_result_count();
   }
 
+  int GetTotalOpenAssistantUICount() {
+    return delegate_->open_assistant_ui_count();
+  }
+
   // Test focus traversal across all the views in |view_list|. The initial focus
   // is expected to be on the first view in |view_list|. The final focus is
   // expected to be on the last view in |view_list| after |view_list.size()-1|
@@ -2099,5 +2133,128 @@
   EXPECT_EQ(0, apps_grid_view()->pagination_model()->selected_page());
 }
 
+// Tests selecting search result to show embedded Assistant UI.
+TEST_F(AppListViewFocusTest, ShowEmbeddedAssistantUI) {
+  scoped_feature_list_.InitWithFeatures(
+      {chromeos::switches::kAssistantFeature,
+       app_list_features::kEnableEmbeddedAssistantUI},
+      {});
+  Show();
+
+  // Initially the search box is inactive, hitting Enter to activate it.
+  EXPECT_FALSE(search_box_view()->is_search_box_active());
+  SimulateKeyPress(ui::VKEY_RETURN, false);
+  EXPECT_TRUE(search_box_view()->is_search_box_active());
+
+  // Type something in search box to transition to HALF state and populate
+  // fake list results. Then hit Enter key.
+  search_box_view()->search_box()->InsertText(base::UTF8ToUTF16("test"));
+  const int kListResults = 2;
+  const int kIndexOpenAssistantUi = 1;
+  SetUpSearchResultsForAssistantUI(kListResults, kIndexOpenAssistantUi);
+  SimulateKeyPress(ui::VKEY_RETURN, false);
+  EXPECT_EQ(1, GetOpenFirstSearchResultCount());
+  EXPECT_EQ(1, GetTotalOpenSearchResultCount());
+  EXPECT_EQ(0, GetTotalOpenAssistantUICount());
+
+  SearchResultListView* list_view =
+      contents_view()->search_result_list_view_for_test();
+  ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_RETURN, ui::EF_NONE);
+  list_view->GetResultViewAt(kIndexOpenAssistantUi)->OnKeyEvent(&key_event);
+  EXPECT_EQ(1, GetOpenFirstSearchResultCount());
+  EXPECT_EQ(2, GetTotalOpenSearchResultCount());
+  EXPECT_EQ(1, GetTotalOpenAssistantUICount());
+}
+
+// Tests that no answer card view when kEnableEmbeddedAssistantUI is enabled.
+TEST_F(AppListViewTest, NoAnswerCardWhenEmbeddedAssistantUIEnabled) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      {chromeos::switches::kAssistantFeature,
+       app_list_features::kEnableEmbeddedAssistantUI},
+      {});
+  ASSERT_TRUE(app_list_features::IsEmbeddedAssistantUIEnabled());
+
+  Initialize(0, false, false);
+  Show();
+
+  EXPECT_FALSE(contents_view()->search_result_answer_card_view_for_test());
+}
+
+// Tests that pressing escape when in embedded Assistant UI to search page view.
+TEST_F(AppListViewTest, EscapeKeyEmbeddedAssistantUIToSearch) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      {chromeos::switches::kAssistantFeature,
+       app_list_features::kEnableEmbeddedAssistantUI},
+      {});
+  ASSERT_TRUE(app_list_features::IsEmbeddedAssistantUIEnabled());
+
+  Initialize(0, false, false);
+  Show();
+
+  // Set search_box_view active.
+  ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_RETURN, ui::EF_NONE);
+  view_->GetWidget()->OnKeyEvent(&key_event);
+
+  contents_view()->ShowEmbeddedAssistantUI(true);
+  EXPECT_TRUE(contents_view()->IsShowingEmbeddedAssistantUI());
+
+  view_->AcceleratorPressed(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE));
+  EXPECT_TRUE(contents_view()->IsShowingSearchResults());
+}
+
+// Tests that clicking empty region in AppListview when showing Assistant UI
+// should go back to peeking state.
+TEST_F(AppListViewTest, ClickOutsideEmbeddedAssistantUIToPeeking) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      {chromeos::switches::kAssistantFeature,
+       app_list_features::kEnableEmbeddedAssistantUI},
+      {});
+  ASSERT_TRUE(app_list_features::IsEmbeddedAssistantUIEnabled());
+
+  Initialize(0, false, false);
+  Show();
+
+  // Set search_box_view active.
+  ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_RETURN, ui::EF_NONE);
+  view_->GetWidget()->OnKeyEvent(&key_event);
+
+  contents_view()->ShowEmbeddedAssistantUI(true);
+  EXPECT_TRUE(contents_view()->IsShowingEmbeddedAssistantUI());
+
+  // Click on the same empty region, the AppList should be peeking state.
+  const gfx::Point empty_region = view_->GetBoundsInScreen().origin();
+  ui::MouseEvent mouse_click(ui::ET_MOUSE_PRESSED, empty_region, empty_region,
+                             base::TimeTicks(), 0, 0);
+  ui::Event::DispatcherApi mouse_click_dispatcher_api(
+      static_cast<ui::Event*>(&mouse_click));
+  mouse_click_dispatcher_api.set_target(view_);
+  view_->OnMouseEvent(&mouse_click);
+  EXPECT_EQ(AppListViewState::PEEKING, view_->app_list_state());
+}
+
+// Tests that expand arrow is not visible when showing embedded Assistant UI.
+TEST_F(AppListViewTest, ExpandArrowNotVisibleInEmbeddedAssistantUI) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      {chromeos::switches::kAssistantFeature,
+       app_list_features::kEnableEmbeddedAssistantUI},
+      {});
+  ASSERT_TRUE(app_list_features::IsEmbeddedAssistantUIEnabled());
+
+  Initialize(0, false, false);
+  Show();
+
+  // Set search_box_view active.
+  ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_RETURN, ui::EF_NONE);
+  view_->GetWidget()->OnKeyEvent(&key_event);
+
+  contents_view()->ShowEmbeddedAssistantUI(true);
+  EXPECT_TRUE(contents_view()->IsShowingEmbeddedAssistantUI());
+  EXPECT_TRUE(contents_view()->expand_arrow_view()->layer()->opacity() == 0.0f);
+}
+
 }  // namespace test
 }  // namespace app_list
diff --git a/ash/app_list/views/apps_grid_view.cc b/ash/app_list/views/apps_grid_view.cc
index 156eab09..5a1f400 100644
--- a/ash/app_list/views/apps_grid_view.cc
+++ b/ash/app_list/views/apps_grid_view.cc
@@ -8,6 +8,7 @@
 #include <memory>
 #include <set>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "ash/app_list/app_list_metrics.h"
@@ -22,6 +23,7 @@
 #include "ash/app_list/views/app_list_main_view.h"
 #include "ash/app_list/views/apps_container_view.h"
 #include "ash/app_list/views/contents_view.h"
+#include "ash/app_list/views/ghost_image_view.h"
 #include "ash/app_list/views/pulsing_block_view.h"
 #include "ash/app_list/views/search_box_view.h"
 #include "ash/app_list/views/search_result_tile_item_view.h"
@@ -705,6 +707,9 @@
     }
   }
 
+  // Hide the |current_ghost_view_| for item drag that started
+  // within |apps_grid_view_|.
+  BeginHideCurrentGhostImageView();
   StopPageFlipTimer();
 }
 
@@ -808,6 +813,7 @@
 }
 
 void AppsGridView::ClearDragState() {
+  current_ghost_location_ = GridIndex();
   last_folder_dropping_a11y_event_location_ = GridIndex();
   last_reorder_a11y_event_location_ = GridIndex();
   drop_target_region_ = NO_TARGET;
@@ -939,6 +945,13 @@
     if (drag_view_ == details.child)
       EndDrag(true);
 
+    if (app_list_features::IsAppGridGhostEnabled()) {
+      if (current_ghost_view_ == details.child)
+        current_ghost_view_ = nullptr;
+      if (last_ghost_view_ == details.child)
+        last_ghost_view_ = nullptr;
+    }
+
     bounds_animator_.StopAnimatingView(details.child);
   }
 }
@@ -1464,6 +1477,7 @@
   reorder_placeholder_ = drop_target_;
   MaybeCreateReorderAccessibilityEvent();
   AnimateToIdealBounds();
+  CreateGhostImageView();
 }
 
 void AppsGridView::OnFolderItemReparentTimer() {
@@ -1485,6 +1499,7 @@
 void AppsGridView::OnFolderDroppingTimer() {
   MaybeCreateFolderDroppingAccessibilityEvent();
   SetAsFolderDroppingTarget(drop_target_, true);
+  BeginHideCurrentGhostImageView();
 }
 
 void AppsGridView::UpdateDragStateInsideFolder(Pointer pointer,
@@ -1701,6 +1716,9 @@
                                  drag_source_bounds);
   }
 
+  // Hide the |current_ghost_view_| after completed drag from within
+  // folder to |apps_grid_view_|.
+  BeginHideCurrentGhostImageView();
   StopPageFlipTimer();
 }
 
@@ -1713,6 +1731,9 @@
 
   SetAsFolderDroppingTarget(drop_target_, false);
   ClearDragState();
+
+  // Hide |current_ghost_view_| in the hidden folder grid view.
+  BeginHideCurrentGhostImageView();
 }
 
 void AppsGridView::OnFolderItemRemoved() {
@@ -1922,6 +1943,8 @@
   pagination_model_.SelectPage(page_flip_target_, true);
   UMA_HISTOGRAM_ENUMERATION(kAppListPageSwitcherSourceHistogram,
                             kDragAppToBorder, kMaxAppListPageSwitcherSource);
+
+  BeginHideCurrentGhostImageView();
 }
 
 void AppsGridView::MoveItemInModel(AppListItemView* item_view,
@@ -2635,6 +2658,18 @@
     pulsing_blocks_model_.set_ideal_bounds(i, tile_slot);
     ++pulsing_block_index.slot;
   }
+
+  // Ensure GhostImageView's transition during page change.
+  if (app_list_features::IsAppGridGhostEnabled()) {
+    if (current_ghost_view_) {
+      current_ghost_view_->SetTransitionOffset(
+          CalculateTransitionOffset(current_ghost_view_->page()));
+    }
+    if (last_ghost_view_) {
+      last_ghost_view_->SetTransitionOffset(
+          CalculateTransitionOffset(last_ghost_view_->page()));
+    }
+  }
 }
 
 int AppsGridView::GetModelIndexOfItem(const AppListItem* item) {
@@ -2806,4 +2841,51 @@
   announcement_view->NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
 }
 
+void AppsGridView::CreateGhostImageView() {
+  if (!app_list_features::IsAppGridGhostEnabled())
+    return;
+  if (!drag_view_)
+    return;
+
+  // OnReorderTimer() can trigger this function even when the
+  // |reorder_placeholder_| does not change, no need to set a new GhostImageView
+  // in this case.
+  if (reorder_placeholder_ == current_ghost_location_)
+    return;
+
+  // When the item is dragged outside the boundaries of the app grid, if the
+  // |reorder_placeholder_| moves to another page, then do not show a ghost.
+  if (pagination_model_.selected_page() != reorder_placeholder_.page) {
+    BeginHideCurrentGhostImageView();
+    return;
+  }
+
+  BeginHideCurrentGhostImageView();
+  current_ghost_location_ = reorder_placeholder_;
+
+  if (last_ghost_view_)
+    delete last_ghost_view_;
+
+  // Preserve |current_ghost_view_| while it fades out and instantiate a new
+  // GhostImageView that will fade in.
+  last_ghost_view_ = current_ghost_view_;
+
+  current_ghost_view_ = new GhostImageView(
+      drag_view_, IsFolderItem(drag_view_->item()) /* is_folder */,
+      folder_delegate_, GetExpectedTileBounds(reorder_placeholder_),
+      reorder_placeholder_.page);
+  AddChildView(current_ghost_view_);
+  current_ghost_view_->FadeIn();
+}
+
+void AppsGridView::BeginHideCurrentGhostImageView() {
+  if (!app_list_features::IsAppGridGhostEnabled())
+    return;
+
+  current_ghost_location_ = GridIndex();
+
+  if (current_ghost_view_)
+    current_ghost_view_->FadeOut();
+}
+
 }  // namespace app_list
diff --git a/ash/app_list/views/apps_grid_view.h b/ash/app_list/views/apps_grid_view.h
index b378895..7e787fa1 100644
--- a/ash/app_list/views/apps_grid_view.h
+++ b/ash/app_list/views/apps_grid_view.h
@@ -49,6 +49,7 @@
 class ContentsView;
 class PaginationController;
 class PulsingBlockView;
+class GhostImageView;
 
 // Represents the index to an item view in the grid.
 struct GridIndex {
@@ -598,6 +599,12 @@
   // location.
   void MaybeCreateReorderAccessibilityEvent();
 
+  // Creates a new GhostImageView at |reorder_placeholder_| and initializes
+  // |current_ghost_view_| and |last_ghost_view_|.
+  void CreateGhostImageView();
+
+  void BeginHideCurrentGhostImageView();
+
   AppListModel* model_ = nullptr;         // Owned by AppListView.
   AppListItemList* item_list_ = nullptr;  // Not owned.
 
@@ -722,6 +729,12 @@
   // The location of the most recent foldering drag related accessibility event.
   GridIndex last_folder_dropping_a11y_event_location_;
 
+  // The location when |current_ghost_view_| was shown.
+  GridIndex current_ghost_location_;
+
+  GhostImageView* current_ghost_view_ = nullptr;
+  GhostImageView* last_ghost_view_ = nullptr;
+
   DISALLOW_COPY_AND_ASSIGN(AppsGridView);
 };
 
diff --git a/ash/app_list/views/assistant/assistant_page_view.cc b/ash/app_list/views/assistant/assistant_page_view.cc
index cc5e7fa8..4e42f02c 100644
--- a/ash/app_list/views/assistant/assistant_page_view.cc
+++ b/ash/app_list/views/assistant/assistant_page_view.cc
@@ -50,8 +50,11 @@
 
   SetLayoutManager(std::make_unique<views::FillLayout>());
 
-  assistant_main_view_ = new AssistantMainView(assistant_view_delegate_);
-  AddChildView(assistant_main_view_);
+  // |assistant_view_delegate_| could be nullptr in test.
+  if (assistant_view_delegate_) {
+    assistant_main_view_ = new AssistantMainView(assistant_view_delegate_);
+    AddChildView(assistant_main_view_);
+  }
 }
 
 const char* AssistantPageView::GetClassName() const {
@@ -63,7 +66,9 @@
 }
 
 void AssistantPageView::RequestFocus() {
-  assistant_main_view_->RequestFocus();
+  // |assistant_main_view_| could be nullptr in test.
+  if (assistant_main_view_)
+    assistant_main_view_->RequestFocus();
 }
 
 void AssistantPageView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
diff --git a/ash/app_list/views/ghost_image_view.cc b/ash/app_list/views/ghost_image_view.cc
new file mode 100644
index 0000000..96c7e835
--- /dev/null
+++ b/ash/app_list/views/ghost_image_view.cc
@@ -0,0 +1,127 @@
+// 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/app_list/views/ghost_image_view.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "ash/app_list/model/app_list_folder_item.h"
+#include "ash/app_list/model/app_list_item_list.h"
+#include "ash/app_list/views/app_list_item_view.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/animation/tween.h"
+#include "ui/gfx/canvas.h"
+
+namespace app_list {
+
+namespace {
+
+constexpr int kGhostCircleStrokeWidth = 2;
+constexpr int kGhostColor = SkColorSetARGB(0x4C, 232, 234, 237);
+constexpr int kFolderGhostColor = SkColorSetARGB(0x4C, 95, 99, 104);
+constexpr base::TimeDelta kGhostFadeInOutLength =
+    base::TimeDelta::FromMilliseconds(180);
+constexpr int kInnerFolderGhostIconRadius = 14;
+constexpr gfx::Tween::Type kGhostTween = gfx::Tween::FAST_OUT_SLOW_IN;
+
+}  // namespace
+
+GhostImageView::GhostImageView(AppListItemView* drag_view,
+                               bool is_folder,
+                               bool is_in_folder,
+                               const gfx::Rect& drop_target_bounds,
+                               int page)
+    : is_hiding_(false),
+      is_in_folder_(is_in_folder),
+      is_folder_(is_folder),
+      page_(page),
+      drop_target_bounds_(drop_target_bounds),
+      icon_bounds_(drag_view->GetIconBounds()) {
+  SetPaintToLayer();
+  layer()->SetFillsBoundsOpaquely(false);
+  layer()->SetOpacity(0.0f);
+  SetBoundsRect(drop_target_bounds);
+
+  if (is_folder_) {
+    AppListFolderItem* folder_item =
+        static_cast<AppListFolderItem*>(drag_view->item());
+    num_items_ = std::min(FolderImage::kNumFolderTopItems,
+                          folder_item->item_list()->item_count());
+  }
+}
+
+GhostImageView::~GhostImageView() {
+  StopObservingImplicitAnimations();
+}
+
+void GhostImageView::FadeOut() {
+  if (is_hiding_)
+    return;
+  is_hiding_ = true;
+  DoAnimation(true /* fade out */);
+}
+
+void GhostImageView::FadeIn() {
+  DoAnimation(false /* fade in */);
+}
+
+void GhostImageView::SetTransitionOffset(
+    const gfx::Vector2d& transition_offset) {
+  SetPosition(drop_target_bounds_.origin() + transition_offset);
+}
+
+void GhostImageView::DoAnimation(bool hide) {
+  ui::ScopedLayerAnimationSettings animation(layer()->GetAnimator());
+  animation.SetTransitionDuration(kGhostFadeInOutLength);
+  animation.SetTweenType(kGhostTween);
+
+  if (hide) {
+    animation.AddObserver(this);
+    animation.SetPreemptionStrategy(
+        ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+    layer()->SetOpacity(0.0f);
+    return;
+  }
+  layer()->SetOpacity(1.0f);
+}
+
+void GhostImageView::OnPaint(gfx::Canvas* canvas) {
+  const gfx::PointF circle_center(icon_bounds_.CenterPoint());
+  const float ghost_radius = icon_bounds_.width() / 2;
+
+  // Draw a circle to represent the ghost image icon.
+  cc::PaintFlags circle_flags;
+  circle_flags.setAntiAlias(true);
+  circle_flags.setColor(is_in_folder_ ? kFolderGhostColor : kGhostColor);
+  circle_flags.setStyle(cc::PaintFlags::kStroke_Style);
+  circle_flags.setStrokeWidth(kGhostCircleStrokeWidth);
+  canvas->DrawCircle(circle_center, ghost_radius, circle_flags);
+
+  if (is_folder_) {
+    // Draw a mask so inner folder icons do not overlap the outer circle.
+    SkPath outer_circle_mask;
+    outer_circle_mask.addCircle(circle_center.x(), circle_center.y(),
+                                ghost_radius - kGhostCircleStrokeWidth / 2);
+    canvas->ClipPath(outer_circle_mask, true);
+
+    // Returns the bounds for each inner icon in the folder icon.
+    std::vector<gfx::Rect> top_icon_bounds =
+        FolderImage::GetTopIconsBounds(icon_bounds_, num_items_.value());
+
+    // Draw ghost items within the ghost folder circle.
+    for (gfx::Rect bounds : top_icon_bounds) {
+      canvas->DrawCircle(gfx::PointF(bounds.CenterPoint()),
+                         kInnerFolderGhostIconRadius, circle_flags);
+    }
+  }
+  ImageView::OnPaint(canvas);
+}
+
+void GhostImageView::OnImplicitAnimationsCompleted() {
+  // Delete this GhostImageView when the fade out animation is done.
+  delete this;
+}
+
+}  // namespace app_list
diff --git a/ash/app_list/views/ghost_image_view.h b/ash/app_list/views/ghost_image_view.h
new file mode 100644
index 0000000..61e3f8d
--- /dev/null
+++ b/ash/app_list/views/ghost_image_view.h
@@ -0,0 +1,79 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_APP_LIST_VIEWS_GHOST_IMAGE_VIEW_H_
+#define ASH_APP_LIST_VIEWS_GHOST_IMAGE_VIEW_H_
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "ui/compositor/layer_animation_observer.h"
+#include "ui/views/controls/image_view.h"
+
+namespace app_list {
+
+class AppListItemView;
+
+// An ImageView of the ghosting icon to show where a dragged app or folder
+// will drop on the app list. This view is owned by the client and not the
+// view hierarchy.
+class GhostImageView : public views::ImageView,
+                       public ui::ImplicitAnimationObserver {
+ public:
+  GhostImageView(AppListItemView* drag_view,
+                 bool is_folder,
+                 bool is_in_folder,
+                 const gfx::Rect& drop_target_bounds,
+                 int page);
+  ~GhostImageView() override;
+
+  // Begins the fade out animation.
+  void FadeOut();
+
+  // Begins the fade in animation
+  void FadeIn();
+
+  // Set the offset used for page transitions.
+  void SetTransitionOffset(const gfx::Vector2d& bounds_rect);
+
+  // Returns the page number which this view belongs to.
+  int page() const { return page_; }
+
+ private:
+  // Start the animation for showing or for hiding the GhostImageView.
+  void DoAnimation(bool hide);
+
+  // views::ImageView overrides:
+  void OnPaint(gfx::Canvas* canvas) override;
+
+  // ui::ImplicitAnimationObserver overrides:
+  void OnImplicitAnimationsCompleted() override;
+
+  // Whether the view is hiding.
+  bool is_hiding_;
+
+  // Whether the view is in a folder.
+  bool is_in_folder_;
+
+  // Whether the view is the ghost of a folder.
+  bool is_folder_;
+
+  // Page this this view belongs to, used to calculate transition offset.
+  int page_;
+
+  // Bounds for the location of the GhostImageView in parent AppGridView's
+  // coordinates.
+  gfx::Rect drop_target_bounds_;
+
+  // Icon bounds used to determine size and placement of the GhostImageView.
+  gfx::Rect icon_bounds_;
+
+  // The number of items within the GhostImageView folder.
+  base::Optional<size_t> num_items_;
+
+  DISALLOW_COPY_AND_ASSIGN(GhostImageView);
+};
+
+}  // namespace app_list
+
+#endif  // ASH_APP_LIST_VIEWS_GHOST_IMAGE_VIEW_H_
diff --git a/ash/ash_service.cc b/ash/ash_service.cc
index b00ba5d..faefa20 100644
--- a/ash/ash_service.cc
+++ b/ash/ash_service.cc
@@ -17,6 +17,7 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "chromeos/audio/cras_audio_handler.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/hammerd/hammerd_client.h"
 #include "chromeos/dbus/power_policy_controller.h"
 #include "chromeos/dbus/system_clock/system_clock_client.h"
 #include "chromeos/network/network_connect.h"
@@ -80,15 +81,19 @@
   Shell::DeleteInstance();
 
   statistics_provider_.reset();
-  // NOTE: PowerStatus is shutdown by Shell.
-  chromeos::CrasAudioHandler::Shutdown();
+
   chromeos::NetworkConnect::Shutdown();
   network_connect_delegate_.reset();
   chromeos::NetworkHandler::Shutdown();
   device::BluetoothAdapterFactory::Shutdown();
   bluez::BluezDBusManager::Shutdown();
   chromeos::PowerPolicyController::Shutdown();
+
+  chromeos::SystemClockClient::Shutdown();
   chromeos::PowerManagerClient::Shutdown();
+  chromeos::HammerdClient::Shutdown();
+  chromeos::CrasAudioHandler::Shutdown();
+
   chromeos::DBusThreadManager::Shutdown();
 
   // |gpu_host_| must be completely destroyed before Env as GpuHost depends on
@@ -154,9 +159,13 @@
   // dbus::Thread, dbus::Bus and required clients directly.
   chromeos::DBusThreadManager::Initialize(chromeos::DBusThreadManager::kShared);
   dbus::Bus* bus = chromeos::DBusThreadManager::Get()->GetSystemBus();
+
+  // TODO(jamescook): Initialize real audio handler.
+  chromeos::CrasAudioHandler::InitializeForTesting();
+  chromeos::HammerdClient::Initialize(bus);
+  chromeos::PowerManagerClient::Initialize(bus);
   chromeos::SystemClockClient::Initialize(bus);
 
-  chromeos::PowerManagerClient::Initialize(bus);
   chromeos::PowerPolicyController::Initialize(
       chromeos::PowerManagerClient::Get());
 
@@ -168,9 +177,6 @@
   chromeos::NetworkHandler::Initialize();
   network_connect_delegate_ = std::make_unique<NetworkConnectDelegateMus>();
   chromeos::NetworkConnect::Initialize(network_connect_delegate_.get());
-
-  // TODO(jamescook): Initialize real audio handler.
-  chromeos::CrasAudioHandler::InitializeForTesting();
 }
 
 void AshService::OnStart() {
diff --git a/ash/components/shortcut_viewer/BUILD.gn b/ash/components/shortcut_viewer/BUILD.gn
index 961a084..bb72c0d 100644
--- a/ash/components/shortcut_viewer/BUILD.gn
+++ b/ash/components/shortcut_viewer/BUILD.gn
@@ -60,6 +60,7 @@
     "//base/test:test_support",
     "//services/ws/public/cpp/input_devices:test_support",
     "//testing/gtest",
+    "//ui/compositor:test_support",
     "//ui/events:test_support",
     "//ui/views",
   ]
diff --git a/ash/components/shortcut_viewer/DEPS b/ash/components/shortcut_viewer/DEPS
index b4de305..f7657e5 100644
--- a/ash/components/shortcut_viewer/DEPS
+++ b/ash/components/shortcut_viewer/DEPS
@@ -12,5 +12,6 @@
   "keyboard_shortcut_view_unittest\.cc": [
     "+ash/shell.h",
     "+ash/test/ash_test_base.h",
+    "+ui/compositor",
   ],
 }
diff --git a/ash/components/shortcut_viewer/views/keyboard_shortcut_view_unittest.cc b/ash/components/shortcut_viewer/views/keyboard_shortcut_view_unittest.cc
index 02d37a78..38287fc 100644
--- a/ash/components/shortcut_viewer/views/keyboard_shortcut_view_unittest.cc
+++ b/ash/components/shortcut_viewer/views/keyboard_shortcut_view_unittest.cc
@@ -15,6 +15,7 @@
 #include "services/ws/public/cpp/input_devices/input_device_client_test_api.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/aura/window.h"
+#include "ui/compositor/test/test_utils.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
 #include "ui/events/test/event_generator.h"
@@ -92,12 +93,7 @@
 
 TEST_F(KeyboardShortcutViewTest, StartupTimeHistogram) {
   views::Widget* widget = Toggle();
-  base::RunLoop runloop;
-  widget->GetCompositor()->RequestPresentationTimeForNextFrame(base::BindOnce(
-      [](base::RepeatingClosure closure,
-         const gfx::PresentationFeedback& feedback) { closure.Run(); },
-      runloop.QuitClosure()));
-  runloop.Run();
+  ui::WaitForNextFrameToBePresented(widget->GetCompositor());
   histograms_.ExpectTotalCount("Keyboard.ShortcutViewer.StartupTime", 1);
   widget->CloseNow();
 }
diff --git a/ash/detachable_base/detachable_base_handler.cc b/ash/detachable_base/detachable_base_handler.cc
index a1ad7d31..0e3b1c1 100644
--- a/ash/detachable_base/detachable_base_handler.cc
+++ b/ash/detachable_base/detachable_base_handler.cc
@@ -55,7 +55,8 @@
   if (shell_)
     shell_->AddShellObserver(this);
 
-  hammerd_observer_.Add(chromeos::DBusThreadManager::Get()->GetHammerdClient());
+  if (chromeos::HammerdClient::Get())  // May be null in tests
+    hammerd_observer_.Add(chromeos::HammerdClient::Get());
   chromeos::PowerManagerClient* power_manager_client =
       chromeos::PowerManagerClient::Get();
   power_manager_observer_.Add(power_manager_client);
diff --git a/ash/detachable_base/detachable_base_handler.h b/ash/detachable_base/detachable_base_handler.h
index 1dbbce3..a8d482f 100644
--- a/ash/detachable_base/detachable_base_handler.h
+++ b/ash/detachable_base/detachable_base_handler.h
@@ -18,7 +18,7 @@
 #include "base/observer_list.h"
 #include "base/optional.h"
 #include "base/scoped_observer.h"
-#include "chromeos/dbus/hammerd_client.h"
+#include "chromeos/dbus/hammerd/hammerd_client.h"
 #include "chromeos/dbus/power_manager_client.h"
 
 class PrefRegistrySimple;
diff --git a/ash/detachable_base/detachable_base_handler_unittest.cc b/ash/detachable_base/detachable_base_handler_unittest.cc
index b97cdb3..12c736ca 100644
--- a/ash/detachable_base/detachable_base_handler_unittest.cc
+++ b/ash/detachable_base/detachable_base_handler_unittest.cc
@@ -14,9 +14,8 @@
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/time/time.h"
-#include "chromeos/dbus/dbus_thread_manager.h"
-#include "chromeos/dbus/fake_hammerd_client.h"
 #include "chromeos/dbus/fake_power_manager_client.h"
+#include "chromeos/dbus/hammerd/fake_hammerd_client.h"
 #include "components/account_id/account_id.h"
 #include "components/prefs/testing_pref_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -86,12 +85,8 @@
 
   // testing::Test:
   void SetUp() override {
-    std::unique_ptr<chromeos::DBusThreadManagerSetter> dbus_setter =
-        chromeos::DBusThreadManager::GetSetterForTesting();
-
-    auto hammerd_client = std::make_unique<chromeos::FakeHammerdClient>();
-    hammerd_client_ = hammerd_client.get();
-    dbus_setter->SetHammerdClient(std::move(hammerd_client));
+    chromeos::HammerdClient::Initialize(nullptr /* bus */);
+    hammerd_client_ = chromeos::FakeHammerdClient::Get();
 
     chromeos::PowerManagerClient::Initialize();
     chromeos::FakePowerManagerClient::Get()->SetTabletMode(
@@ -110,7 +105,7 @@
     handler_.reset();
     hammerd_client_ = nullptr;
     chromeos::PowerManagerClient::Shutdown();
-    chromeos::DBusThreadManager::Shutdown();
+    chromeos::HammerdClient::Shutdown();
   }
 
  protected:
@@ -142,7 +137,6 @@
     handler_->OnLocalStatePrefServiceInitialized(&local_state_);
   }
 
-  // Owned by DBusThreadManager:
   chromeos::FakeHammerdClient* hammerd_client_ = nullptr;
 
   TestBaseObserver detachable_base_observer_;
diff --git a/ash/public/cpp/app_list/app_list_config.cc b/ash/public/cpp/app_list/app_list_config.cc
index b5b1974..e1ed457 100644
--- a/ash/public/cpp/app_list/app_list_config.cc
+++ b/ash/public/cpp/app_list/app_list_config.cc
@@ -12,8 +12,8 @@
 namespace app_list {
 
 AppListConfig::AppListConfig()
-    : grid_tile_width_(120),
-      grid_tile_height_(112),
+    : grid_tile_width_(112),
+      grid_tile_height_(120),
       grid_tile_spacing_(0),
       grid_icon_dimension_(64),
       grid_icon_bottom_padding_(24),
diff --git a/ash/public/cpp/app_list/app_list_config.h b/ash/public/cpp/app_list/app_list_config.h
index 07ec9e4..0334302 100644
--- a/ash/public/cpp/app_list/app_list_config.h
+++ b/ash/public/cpp/app_list/app_list_config.h
@@ -74,12 +74,12 @@
   int folder_unclipped_icon_dimension() const {
     return folder_unclipped_icon_dimension_;
   }
-  int item_icon_in_folder_icon_dimension() const {
-    return item_icon_in_folder_icon_dimension_;
-  }
   int folder_icon_radius() const { return folder_icon_radius_; }
   int folder_background_radius() const { return folder_background_radius_; }
   int folder_bubble_color() const { return folder_bubble_color_; }
+  int item_icon_in_folder_icon_dimension() const {
+    return item_icon_in_folder_icon_dimension_;
+  }
   int folder_dropping_circle_radius() const {
     return folder_dropping_circle_radius_;
   }
diff --git a/ash/public/cpp/app_list/app_list_features.cc b/ash/public/cpp/app_list/app_list_features.cc
index 75330823..d7c0c46 100644
--- a/ash/public/cpp/app_list/app_list_features.cc
+++ b/ash/public/cpp/app_list/app_list_features.cc
@@ -35,6 +35,8 @@
     "EnableAppReinstallZeroState", base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kEnableEmbeddedAssistantUI{
     "EnableEmbeddedAssistantUI", base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kEnableAppGridGhost{"EnableAppGridGhost",
+                                        base::FEATURE_DISABLED_BY_DEFAULT};
 
 bool IsAnswerCardEnabled() {
   // Not using local static variable to allow tests to change this value.
@@ -89,6 +91,10 @@
          base::FeatureList::IsEnabled(kEnableEmbeddedAssistantUI);
 }
 
+bool IsAppGridGhostEnabled() {
+  return base::FeatureList::IsEnabled(kEnableAppGridGhost);
+}
+
 std::string AnswerServerUrl() {
   const std::string experiment_url =
       base::GetFieldTrialParamValueByFeature(kEnableAnswerCard, "ServerUrl");
diff --git a/ash/public/cpp/app_list/app_list_features.h b/ash/public/cpp/app_list/app_list_features.h
index 3f3eaea..b7c7d69 100644
--- a/ash/public/cpp/app_list/app_list_features.h
+++ b/ash/public/cpp/app_list/app_list_features.h
@@ -57,6 +57,9 @@
 // Enables the embedded Assistant UI in the app list.
 ASH_PUBLIC_EXPORT extern const base::Feature kEnableEmbeddedAssistantUI;
 
+// Enables ghosting in any AppsGridView (folder or root) when dragging an item.
+ASH_PUBLIC_EXPORT extern const base::Feature kEnableAppGridGhost;
+
 bool ASH_PUBLIC_EXPORT IsAnswerCardEnabled();
 bool ASH_PUBLIC_EXPORT IsAppShortcutSearchEnabled();
 bool ASH_PUBLIC_EXPORT IsBackgroundBlurEnabled();
@@ -69,6 +72,7 @@
 bool ASH_PUBLIC_EXPORT IsAppSearchResultRankerEnabled();
 bool ASH_PUBLIC_EXPORT IsAppReinstallZeroStateEnabled();
 bool ASH_PUBLIC_EXPORT IsEmbeddedAssistantUIEnabled();
+bool ASH_PUBLIC_EXPORT IsAppGridGhostEnabled();
 
 std::string ASH_PUBLIC_EXPORT AnswerServerUrl();
 std::string ASH_PUBLIC_EXPORT AnswerServerQuerySuffix();
diff --git a/ash/test/ash_test_helper.cc b/ash/test/ash_test_helper.cc
index 7b40345..c952bb3 100644
--- a/ash/test/ash_test_helper.cc
+++ b/ash/test/ash_test_helper.cc
@@ -31,6 +31,7 @@
 #include "base/token.h"
 #include "chromeos/audio/cras_audio_handler.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/hammerd/hammerd_client.h"
 #include "chromeos/dbus/power_policy_controller.h"
 #include "chromeos/network/network_handler.h"
 #include "chromeos/system/fake_statistics_provider.h"
@@ -160,6 +161,8 @@
   if (!chromeos::DBusThreadManager::IsInitialized()) {
     chromeos::DBusThreadManager::Initialize(
         chromeos::DBusThreadManager::kShared);
+    chromeos::HammerdClient::Initialize(
+        chromeos::DBusThreadManager::Get()->GetSystemBus());
     dbus_thread_manager_initialized_ = true;
   }
 
@@ -272,6 +275,7 @@
   }
 
   if (dbus_thread_manager_initialized_) {
+    chromeos::HammerdClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
     dbus_thread_manager_initialized_ = false;
   }
diff --git a/ash/wm/always_on_top_controller.cc b/ash/wm/always_on_top_controller.cc
index 752554f..1b3e823 100644
--- a/ash/wm/always_on_top_controller.cc
+++ b/ash/wm/always_on_top_controller.cc
@@ -13,6 +13,8 @@
 
 namespace ash {
 
+DEFINE_UI_CLASS_PROPERTY_KEY(bool, kDisallowReparentKey, false)
+
 AlwaysOnTopController::AlwaysOnTopController(
     aura::Window* always_on_top_container,
     aura::Window* pip_container)
@@ -56,6 +58,10 @@
   always_on_top_container_->SetLayoutManager(layout_manager.release());
 }
 
+void AlwaysOnTopController::SetDisallowReparent(aura::Window* window) {
+  window->SetProperty(kDisallowReparentKey, true);
+}
+
 void AlwaysOnTopController::AddWindow(aura::Window* window) {
   window->AddObserver(this);
   wm::GetWindowState(window)->AddObserver(this);
@@ -70,7 +76,8 @@
   DCHECK(window->type() == aura::client::WINDOW_TYPE_NORMAL ||
          window->type() == aura::client::WINDOW_TYPE_POPUP);
   aura::Window* container = GetContainer(window);
-  if (window->parent() != container)
+  if (window->parent() != container &&
+      !window->GetProperty(ash::kDisallowReparentKey))
     container->AddChild(window);
 }
 
diff --git a/ash/wm/always_on_top_controller.h b/ash/wm/always_on_top_controller.h
index 3bd5b0fc..8ee362ae 100644
--- a/ash/wm/always_on_top_controller.h
+++ b/ash/wm/always_on_top_controller.h
@@ -33,6 +33,8 @@
   void SetLayoutManagerForTest(
       std::unique_ptr<WorkspaceLayoutManager> layout_manager);
 
+  static void SetDisallowReparent(aura::Window* window);
+
  private:
   void AddWindow(aura::Window* window);
   void RemoveWindow(aura::Window* window);
diff --git a/ash/wm/default_window_resizer.cc b/ash/wm/default_window_resizer.cc
index f6563a4..e43713e9 100644
--- a/ash/wm/default_window_resizer.cc
+++ b/ash/wm/default_window_resizer.cc
@@ -27,7 +27,7 @@
     if (!did_move_or_resize_ && !details().restore_bounds.IsEmpty())
       window_state_->ClearRestoreBounds();
     did_move_or_resize_ = true;
-    GetTarget()->SetBounds(bounds);
+    SetBoundsDuringResize(bounds);
   }
 }
 
diff --git a/ash/wm/default_window_resizer_unittest.cc b/ash/wm/default_window_resizer_unittest.cc
index f7a807c..0c935bb 100644
--- a/ash/wm/default_window_resizer_unittest.cc
+++ b/ash/wm/default_window_resizer_unittest.cc
@@ -8,10 +8,12 @@
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/window_factory.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/test/test_window_delegate.h"
 #include "ui/base/hit_test.h"
 #include "ui/base/ui_base_types.h"
+#include "ui/compositor/test/test_utils.h"
 
 namespace ash {
 
@@ -50,6 +52,7 @@
 
   aura::test::TestWindowDelegate delegate_;
   std::unique_ptr<aura::Window> aspect_ratio_window_;
+  base::HistogramTester histograms_;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(DefaultWindowResizerTest);
@@ -155,4 +158,40 @@
   EXPECT_EQ("250,250 200x400", aspect_ratio_window_->bounds().ToString());
 }
 
+TEST_F(DefaultWindowResizerTest, NoResizeHistogramOnMove) {
+  std::unique_ptr<aura::Window> window =
+      window_factory::NewWindow(&delegate_, aura::client::WINDOW_TYPE_NORMAL);
+  window->Init(ui::LAYER_NOT_DRAWN);
+  ParentWindowInPrimaryRootWindow(window.get());
+  window->SetBounds(gfx::Rect(0, 0, 50, 50));
+  std::unique_ptr<WindowResizer> resizer(
+      CreateDefaultWindowResizer(window.get(), gfx::Point(), HTCAPTION));
+  ASSERT_TRUE(resizer.get());
+
+  // Move the window. A move should not generate a resize histogram.
+  resizer->Drag(gfx::Point(50, 50), 0);
+  EXPECT_EQ(gfx::Point(50, 50), window->bounds().origin());
+  resizer->CompleteDrag();
+  ui::WaitForNextFrameToBePresented(window->GetHost()->compositor());
+  histograms_.ExpectTotalCount("Ash.InteractiveWindowResize.TimeToPresent", 0);
+}
+
+TEST_F(DefaultWindowResizerTest, ResizeHistogram) {
+  std::unique_ptr<aura::Window> window =
+      window_factory::NewWindow(&delegate_, aura::client::WINDOW_TYPE_NORMAL);
+  window->Init(ui::LAYER_NOT_DRAWN);
+  ParentWindowInPrimaryRootWindow(window.get());
+  window->SetBounds(gfx::Rect(0, 0, 50, 50));
+  std::unique_ptr<WindowResizer> resizer(
+      CreateDefaultWindowResizer(window.get(), gfx::Point(), HTRIGHT));
+  ASSERT_TRUE(resizer.get());
+
+  // Resize the window, which should generate a resize histogram.
+  resizer->Drag(gfx::Point(50, 50), 0);
+  EXPECT_NE(gfx::Size(50, 50), window->bounds().size());
+  resizer->CompleteDrag();
+  ui::WaitForNextFrameToBePresented(window->GetHost()->compositor());
+  histograms_.ExpectTotalCount("Ash.InteractiveWindowResize.TimeToPresent", 1);
+}
+
 }  // namespace ash
diff --git a/ash/wm/pip/pip_window_resizer.cc b/ash/wm/pip/pip_window_resizer.cc
index 86bd201..fb129d1e 100644
--- a/ash/wm/pip/pip_window_resizer.cc
+++ b/ash/wm/pip/pip_window_resizer.cc
@@ -183,7 +183,7 @@
   ::wm::ConvertRectFromScreen(GetTarget()->parent(), &new_bounds);
   if (new_bounds != GetTarget()->bounds()) {
     moved_or_resized_ = true;
-    GetTarget()->SetBounds(new_bounds);
+    SetBoundsDuringResize(new_bounds);
   }
 }
 
diff --git a/ash/wm/window_resizer.cc b/ash/wm/window_resizer.cc
index 19a184d..cb73eb2 100644
--- a/ash/wm/window_resizer.cc
+++ b/ash/wm/window_resizer.cc
@@ -7,21 +7,41 @@
 #include "ash/wm/root_window_finder.h"
 #include "ash/wm/window_positioning_utils.h"
 #include "ash/wm/window_state.h"
+#include "base/bind.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/time/time.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_delegate.h"
+#include "ui/aura/window_tracker.h"
+#include "ui/aura/window_tree_host.h"
 #include "ui/base/hit_test.h"
 #include "ui/base/ui_base_types.h"
+#include "ui/compositor/compositor.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
 #include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/presentation_feedback.h"
 #include "ui/views/window/window_resize_utils.h"
 #include "ui/wm/core/coordinate_conversion.h"
 
 namespace ash {
-
 namespace {
 
+void OnFramePresented(base::TimeTicks start_time,
+                      const gfx::PresentationFeedback& feedback) {
+  UMA_HISTOGRAM_TIMES("Ash.InteractiveWindowResize.TimeToPresent",
+                      feedback.timestamp - start_time);
+}
+
+void RecordMetricsForResize(base::TimeTicks start_time, aura::Window* window) {
+  DCHECK(window);
+  ui::Compositor* compositor = window->GetHost()->compositor();
+  DCHECK(compositor);
+  compositor->RequestPresentationTimeForNextFrame(
+      base::BindOnce(&OnFramePresented, start_time));
+}
+
 // Returns true for resize components along the right edge, where a drag in
 // positive x will make the window larger.
 bool IsRightEdge(int window_component) {
@@ -256,6 +276,22 @@
          window_component == HTBOTTOMRIGHT || window_component == HTGROWBOX;
 }
 
+void WindowResizer::SetBoundsDuringResize(const gfx::Rect& bounds) {
+  aura::Window* window = GetTarget();
+  DCHECK(window);
+  // Consider having this time come from the event.
+  base::TimeTicks start = base::TimeTicks::Now();
+  const gfx::Rect original_bounds = window->bounds();
+  window->SetBounds(bounds);
+  aura::WindowTracker tracker;
+  tracker.Add(window);
+  if (tracker.windows().empty())
+    return;  // Assume we've been destroyed.
+  if (bounds.size() == original_bounds.size())
+    return;
+  RecordMetricsForResize(start, window);
+}
+
 void WindowResizer::AdjustDeltaForTouchResize(int* delta_x, int* delta_y) {
   if (details().source != ::wm::WINDOW_MOVE_SOURCE_TOUCH ||
       !(details().bounds_change & kBoundsChange_Resizes))
diff --git a/ash/wm/window_resizer.h b/ash/wm/window_resizer.h
index 8fd1908..ae689505 100644
--- a/ash/wm/window_resizer.h
+++ b/ash/wm/window_resizer.h
@@ -81,6 +81,10 @@
 
   static bool IsBottomEdge(int component);
 
+  // Call during an active resize to change the bounds of the window. This
+  // should not be called as the result of a revert.
+  void SetBoundsDuringResize(const gfx::Rect& bounds);
+
   // WindowState of the drag target.
   wm::WindowState* window_state_;
 
diff --git a/ash/wm/workspace/backdrop_controller.cc b/ash/wm/workspace/backdrop_controller.cc
index 26297d1..c76141c4 100644
--- a/ash/wm/workspace/backdrop_controller.cc
+++ b/ash/wm/workspace/backdrop_controller.cc
@@ -18,6 +18,7 @@
 #include "ash/screen_util.h"
 #include "ash/shell.h"
 #include "ash/wallpaper/wallpaper_controller.h"
+#include "ash/wm/always_on_top_controller.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
@@ -231,6 +232,9 @@
   backdrop_->Init(params);
   backdrop_window_ = backdrop_->GetNativeWindow();
   backdrop_window_->SetName("Backdrop");
+  // The backdrop window in always on top container can be reparented without
+  // this when the window is set to fullscreen.
+  AlwaysOnTopController::SetDisallowReparent(backdrop_window_);
   ::wm::SetWindowVisibilityAnimationType(
       backdrop_window_, ::wm::WINDOW_VISIBILITY_ANIMATION_TYPE_FADE);
   backdrop_window_->layer()->SetColor(SK_ColorBLACK);
diff --git a/ash/wm/workspace/workspace_layout_manager_unittest.cc b/ash/wm/workspace/workspace_layout_manager_unittest.cc
index 0a9189a..92a2e57 100644
--- a/ash/wm/workspace/workspace_layout_manager_unittest.cc
+++ b/ash/wm/workspace/workspace_layout_manager_unittest.cc
@@ -29,6 +29,7 @@
 #include "ash/test/ash_test_base.h"
 #include "ash/wallpaper/wallpaper_controller_test_api.h"
 #include "ash/window_factory.h"
+#include "ash/wm/always_on_top_controller.h"
 #include "ash/wm/fullscreen_window_finder.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/splitview/split_view_controller.h"
@@ -38,6 +39,7 @@
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
 #include "ash/wm/wm_event.h"
+#include "ash/wm/workspace/backdrop_controller.h"
 #include "ash/wm/workspace/backdrop_delegate.h"
 #include "ash/wm/workspace/workspace_window_resizer.h"
 #include "ash/wm/workspace_controller_test_api.h"
@@ -2044,4 +2046,32 @@
   EXPECT_GE(test_state()->num_system_ui_area_changes(), 1);
 }
 
+
+TEST_F(WorkspaceLayoutManagerBackdropTest,
+       BackdropWindowIsNotReparentedFromAlwaysOnTopContainer) {
+  WorkspaceController* wc = ShellTestApi(Shell::Get()).workspace_controller();
+  WorkspaceControllerTestApi test_helper(wc);
+  RootWindowController* controller = Shell::GetPrimaryRootWindowController();
+  AlwaysOnTopController* always_on_top_controller =
+      controller->always_on_top_controller();
+
+  std::unique_ptr<aura::Window> always_on_top_window(
+      CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 3, 4)));
+  always_on_top_window->Show();
+  always_on_top_window->SetProperty(aura::client::kAlwaysOnTopKey, true);
+
+  aura::Window* always_on_top_container =
+  always_on_top_controller->GetContainer(always_on_top_window.get());
+  ShowTopWindowBackdropForContainer(always_on_top_container, true);
+  // AlwaysOnTopContainer has |always_on_top_window| and a backdrop window
+  // at this moment.
+  ASSERT_EQ(always_on_top_container->children().size(), 2U);
+
+  always_on_top_window->SetProperty(aura::client::kAlwaysOnTopKey, false);
+  // Make sure the backdrop window stays in AlwaysOnTopContainer even after
+  // |always_on_top_window| moves to the default container.
+  ASSERT_EQ(always_on_top_container->children().size(), 1U);
+  EXPECT_EQ(always_on_top_container->children()[0]->GetName(), "Backdrop");
+}
+
 }  // namespace ash
diff --git a/ash/wm/workspace/workspace_window_resizer.cc b/ash/wm/workspace/workspace_window_resizer.cc
index 5a656565..faa2c4fa 100644
--- a/ash/wm/workspace/workspace_window_resizer.cc
+++ b/ash/wm/workspace/workspace_window_resizer.cc
@@ -474,7 +474,7 @@
     // the drag and quit early if so.
     base::WeakPtr<WorkspaceWindowResizer> resizer(
         weak_ptr_factory_.GetWeakPtr());
-    GetTarget()->SetBounds(bounds);
+    SetBoundsDuringResize(bounds);
     if (!resizer)
       return;
   }
diff --git a/ash/wm/workspace/workspace_window_resizer_unittest.cc b/ash/wm/workspace/workspace_window_resizer_unittest.cc
index cffcd81..8483e709 100644
--- a/ash/wm/workspace/workspace_window_resizer_unittest.cc
+++ b/ash/wm/workspace/workspace_window_resizer_unittest.cc
@@ -20,12 +20,14 @@
 #include "base/command_line.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "services/ws/public/mojom/window_tree_constants.mojom.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/test/test_window_delegate.h"
 #include "ui/aura/test/test_windows.h"
 #include "ui/aura/window_event_dispatcher.h"
 #include "ui/base/hit_test.h"
+#include "ui/compositor/test/test_utils.h"
 #include "ui/display/display_layout.h"
 #include "ui/display/manager/display_manager.h"
 #include "ui/display/screen.h"
@@ -1882,4 +1884,23 @@
             touch_resize_window_->bounds().ToString());
 }
 
+TEST_F(WorkspaceWindowResizerTest, ResizeHistogram) {
+  base::HistogramTester histograms;
+  window_->SetBounds(gfx::Rect(20, 30, 400, 60));
+  std::unique_ptr<WindowResizer> resizer(
+      CreateResizerForTest(window_.get(), gfx::Point(), HTRIGHT));
+  ASSERT_TRUE(resizer.get());
+  resizer->Drag(gfx::Point(50, 50), 0);
+
+  // A resize should generate a histogram.
+  EXPECT_NE(gfx::Size(400, 60), window_->bounds().size());
+  ui::WaitForNextFrameToBePresented(window_->GetHost()->compositor());
+  histograms.ExpectTotalCount("Ash.InteractiveWindowResize.TimeToPresent", 1);
+
+  // Completing the drag should not generate another histogram.
+  resizer->CompleteDrag();
+  ui::WaitForNextFrameToBePresented(window_->GetHost()->compositor());
+  histograms.ExpectTotalCount("Ash.InteractiveWindowResize.TimeToPresent", 1);
+}
+
 }  // namespace ash
diff --git a/base/allocator/partition_allocator/page_allocator_internals_posix.h b/base/allocator/partition_allocator/page_allocator_internals_posix.h
index cb84cf4..29fac43 100644
--- a/base/allocator/partition_allocator/page_allocator_internals_posix.h
+++ b/base/allocator/partition_allocator/page_allocator_internals_posix.h
@@ -86,8 +86,18 @@
 #endif
 
   int access_flag = GetAccessFlags(accessibility);
+  int map_flags = MAP_ANONYMOUS | MAP_PRIVATE;
+
+  // TODO(https://crbug.com/927411): Remove once Fuchsia uses a native page
+  // allocator, rather than relying on POSIX compatibility.
+#if defined(OS_FUCHSIA)
+  if (page_tag == PageTag::kV8) {
+    map_flags |= MAP_JIT;
+  }
+#endif
+
   void* ret =
-      mmap(hint, length, access_flag, MAP_ANONYMOUS | MAP_PRIVATE, fd, 0);
+      mmap(hint, length, access_flag, map_flags, fd, 0);
   if (ret == MAP_FAILED) {
     s_allocPageErrorCode = errno;
     ret = nullptr;
diff --git a/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java b/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java
index c2f8d895..68e551f 100644
--- a/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java
+++ b/base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java
@@ -8,13 +8,11 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
-import android.app.PendingIntent;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
 import android.graphics.Bitmap;
@@ -56,7 +54,7 @@
 import java.io.UnsupportedEncodingException;
 
 /**
- * Utility class to use new APIs that were added after ICS (API level 14).
+ * Utility class to use new APIs that were added after KitKat (API level 19).
  */
 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
 public class ApiCompatibilityUtils {
@@ -115,32 +113,6 @@
     }
 
     /**
-     * Returns true if view's layout direction is right-to-left.
-     *
-     * @param view the View whose layout is being considered
-     */
-    public static boolean isLayoutRtl(View view) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            return view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
-        } else {
-            // All layouts are LTR before JB MR1.
-            return false;
-        }
-    }
-
-    /**
-     * @see Configuration#getLayoutDirection()
-     */
-    public static int getLayoutDirection(Configuration configuration) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            return configuration.getLayoutDirection();
-        } else {
-            // All layouts are LTR before JB MR1.
-            return View.LAYOUT_DIRECTION_LTR;
-        }
-    }
-
-    /**
      * @return True if the running version of the Android supports printing.
      */
     public static boolean isPrintingSupported() {
@@ -156,116 +128,6 @@
     }
 
     /**
-     * @see android.view.View#setLayoutDirection(int)
-     */
-    public static void setLayoutDirection(View view, int layoutDirection) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            view.setLayoutDirection(layoutDirection);
-        } else {
-            // Do nothing. RTL layouts aren't supported before JB MR1.
-        }
-    }
-
-    /**
-     * @see android.view.View#setTextAlignment(int)
-     */
-    public static void setTextAlignment(View view, int textAlignment) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            view.setTextAlignment(textAlignment);
-        } else {
-            // Do nothing. RTL text isn't supported before JB MR1.
-        }
-    }
-
-    /**
-     * @see android.view.View#setTextDirection(int)
-     */
-    public static void setTextDirection(View view, int textDirection) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            view.setTextDirection(textDirection);
-        } else {
-            // Do nothing. RTL text isn't supported before JB MR1.
-        }
-    }
-
-    /**
-     * See {@link android.view.View#setLabelFor(int)}.
-     */
-    public static void setLabelFor(View labelView, int id) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            labelView.setLabelFor(id);
-        } else {
-            // Do nothing. #setLabelFor() isn't supported before JB MR1.
-        }
-    }
-
-    /**
-     * @see android.widget.TextView#getCompoundDrawablesRelative()
-     */
-    public static Drawable[] getCompoundDrawablesRelative(TextView textView) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            return textView.getCompoundDrawablesRelative();
-        } else {
-            return textView.getCompoundDrawables();
-        }
-    }
-
-    /**
-     * @see android.widget.TextView#setCompoundDrawablesRelative(Drawable, Drawable, Drawable,
-     *      Drawable)
-     */
-    public static void setCompoundDrawablesRelative(TextView textView, Drawable start, Drawable top,
-            Drawable end, Drawable bottom) {
-        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            // On JB MR1, due to a platform bug, setCompoundDrawablesRelative() is a no-op if the
-            // view has ever been measured. As a workaround, use setCompoundDrawables() directly.
-            // See: http://crbug.com/368196 and http://crbug.com/361709
-            boolean isRtl = isLayoutRtl(textView);
-            textView.setCompoundDrawables(isRtl ? end : start, top, isRtl ? start : end, bottom);
-        } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            textView.setCompoundDrawablesRelative(start, top, end, bottom);
-        } else {
-            textView.setCompoundDrawables(start, top, end, bottom);
-        }
-    }
-
-    /**
-     * @see android.widget.TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable,
-     *      Drawable, Drawable, Drawable)
-     */
-    public static void setCompoundDrawablesRelativeWithIntrinsicBounds(TextView textView,
-            Drawable start, Drawable top, Drawable end, Drawable bottom) {
-        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            // Work around the platform bug described in setCompoundDrawablesRelative() above.
-            boolean isRtl = isLayoutRtl(textView);
-            textView.setCompoundDrawablesWithIntrinsicBounds(isRtl ? end : start, top,
-                    isRtl ? start : end, bottom);
-        } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
-        } else {
-            textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
-        }
-    }
-
-    /**
-     * @see android.widget.TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int,
-     *      int)
-     */
-    public static void setCompoundDrawablesRelativeWithIntrinsicBounds(TextView textView,
-            int start, int top, int end, int bottom) {
-        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            // Work around the platform bug described in setCompoundDrawablesRelative() above.
-            boolean isRtl = isLayoutRtl(textView);
-            textView.setCompoundDrawablesWithIntrinsicBounds(isRtl ? end : start, top,
-                    isRtl ? start : end, bottom);
-        } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
-        } else {
-            textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
-        }
-    }
-
-    /**
      * @see android.text.Html#toHtml(Spanned, int)
      * @param option is ignored on below N
      */
@@ -281,30 +143,6 @@
     // These methods have a new name, and the old name is deprecated.
 
     /**
-     * @see android.app.PendingIntent#getCreatorPackage()
-     */
-    @SuppressWarnings("deprecation")
-    public static String getCreatorPackage(PendingIntent intent) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            return intent.getCreatorPackage();
-        } else {
-            return intent.getTargetPackage();
-        }
-    }
-
-    /**
-     * @see android.provider.Settings.Global#DEVICE_PROVISIONED
-     */
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
-    public static boolean isDeviceProvisioned(Context context) {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) return true;
-        if (context == null) return true;
-        if (context.getContentResolver() == null) return true;
-        return Settings.Global.getInt(
-                context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0;
-    }
-
-    /**
      * @see android.app.Activity#finishAndRemoveTask()
      */
     public static void finishAndRemoveTask(Activity activity) {
@@ -662,19 +500,6 @@
     }
 
     /**
-     * Get a URI for |file| which has the image capture. This function assumes that path of |file|
-     * is based on the result of UiUtils.getDirectoryForImageCapture().
-     *
-     * @param file image capture file.
-     * @return URI for |file|.
-     */
-    public static Uri getUriForImageCaptureFile(File file) {
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2
-                ? ContentUriUtils.getContentUriFromFile(file)
-                : Uri.fromFile(file);
-    }
-
-    /**
      * Get the URI for a downloaded file.
      *
      * @param file A downloaded file.
diff --git a/base/android/java/src/org/chromium/base/ContentUriUtils.java b/base/android/java/src/org/chromium/base/ContentUriUtils.java
index 78601ed..8cc1984 100644
--- a/base/android/java/src/org/chromium/base/ContentUriUtils.java
+++ b/base/android/java/src/org/chromium/base/ContentUriUtils.java
@@ -56,6 +56,13 @@
         }
     }
 
+    /**
+     * Get a URI for |file| which has the image capture. This function assumes that path of |file|
+     * is based on the result of UiUtils.getDirectoryForImageCapture().
+     *
+     * @param file image capture file.
+     * @return URI for |file|.
+     */
     public static Uri getContentUriFromFile(File file) {
         synchronized (sLock) {
             if (sFileProviderUtil != null) {
diff --git a/base/synchronization/lock.h b/base/synchronization/lock.h
index e526ed1..df306e6 100644
--- a/base/synchronization/lock.h
+++ b/base/synchronization/lock.h
@@ -110,47 +110,11 @@
 };
 
 // A helper class that acquires the given Lock while the AutoLock is in scope.
-class SCOPED_LOCKABLE AutoLock {
- public:
-  struct AlreadyAcquired {};
-
-  explicit AutoLock(Lock& lock) EXCLUSIVE_LOCK_FUNCTION(lock) : lock_(lock) {
-    lock_.Acquire();
-  }
-
-  AutoLock(Lock& lock, const AlreadyAcquired&) EXCLUSIVE_LOCKS_REQUIRED(lock)
-      : lock_(lock) {
-    lock_.AssertAcquired();
-  }
-
-  ~AutoLock() UNLOCK_FUNCTION() {
-    lock_.AssertAcquired();
-    lock_.Release();
-  }
-
- private:
-  Lock& lock_;
-  DISALLOW_COPY_AND_ASSIGN(AutoLock);
-};
+using AutoLock = internal::BasicAutoLock<Lock>;
 
 // AutoUnlock is a helper that will Release() the |lock| argument in the
 // constructor, and re-Acquire() it in the destructor.
-class AutoUnlock {
- public:
-  explicit AutoUnlock(Lock& lock) : lock_(lock) {
-    // We require our caller to have the lock.
-    lock_.AssertAcquired();
-    lock_.Release();
-  }
-
-  ~AutoUnlock() {
-    lock_.Acquire();
-  }
-
- private:
-  Lock& lock_;
-  DISALLOW_COPY_AND_ASSIGN(AutoUnlock);
-};
+using AutoUnlock = internal::BasicAutoUnlock<Lock>;
 
 }  // namespace base
 
diff --git a/base/synchronization/lock_impl.h b/base/synchronization/lock_impl.h
index 221d763..9107392 100644
--- a/base/synchronization/lock_impl.h
+++ b/base/synchronization/lock_impl.h
@@ -8,6 +8,7 @@
 #include "base/base_export.h"
 #include "base/logging.h"
 #include "base/macros.h"
+#include "base/thread_annotations.h"
 #include "build/build_config.h"
 
 #if defined(OS_WIN)
@@ -72,6 +73,50 @@
 }
 #endif
 
+// This is an implementation used for AutoLock templated on the lock type.
+template <class LockType>
+class SCOPED_LOCKABLE BasicAutoLock {
+ public:
+  struct AlreadyAcquired {};
+
+  explicit BasicAutoLock(LockType& lock) EXCLUSIVE_LOCK_FUNCTION(lock)
+      : lock_(lock) {
+    lock_.Acquire();
+  }
+
+  BasicAutoLock(LockType& lock, const AlreadyAcquired&)
+      EXCLUSIVE_LOCKS_REQUIRED(lock)
+      : lock_(lock) {
+    lock_.AssertAcquired();
+  }
+
+  ~BasicAutoLock() UNLOCK_FUNCTION() {
+    lock_.AssertAcquired();
+    lock_.Release();
+  }
+
+ private:
+  LockType& lock_;
+  DISALLOW_COPY_AND_ASSIGN(BasicAutoLock);
+};
+
+// This is an implementation used for AutoUnlock templated on the lock type.
+template <class LockType>
+class BasicAutoUnlock {
+ public:
+  explicit BasicAutoUnlock(LockType& lock) : lock_(lock) {
+    // We require our caller to have the lock.
+    lock_.AssertAcquired();
+    lock_.Release();
+  }
+
+  ~BasicAutoUnlock() { lock_.Acquire(); }
+
+ private:
+  LockType& lock_;
+  DISALLOW_COPY_AND_ASSIGN(BasicAutoUnlock);
+};
+
 }  // namespace internal
 }  // namespace base
 
diff --git a/base/task/task_scheduler/scheduler_lock.h b/base/task/task_scheduler/scheduler_lock.h
index 61341fc..d6b3d402 100644
--- a/base/task/task_scheduler/scheduler_lock.h
+++ b/base/task/task_scheduler/scheduler_lock.h
@@ -79,23 +79,10 @@
 #endif  // DCHECK_IS_ON()
 
 // Provides the same functionality as base::AutoLock for SchedulerLock.
-class SCOPED_LOCKABLE AutoSchedulerLock {
- public:
-  explicit AutoSchedulerLock(SchedulerLock& lock) EXCLUSIVE_LOCK_FUNCTION(lock)
-      : lock_(lock) {
-    lock_.Acquire();
-  }
+using AutoSchedulerLock = internal::BasicAutoLock<SchedulerLock>;
 
-  ~AutoSchedulerLock() UNLOCK_FUNCTION() {
-    lock_.AssertAcquired();
-    lock_.Release();
-  }
-
- private:
-  SchedulerLock& lock_;
-
-  DISALLOW_COPY_AND_ASSIGN(AutoSchedulerLock);
-};
+// Provides the same functionality as base::AutoUnlock for SchedulerLock.
+using AutoSchedulerUnlock = internal::BasicAutoUnlock<SchedulerLock>;
 
 // Informs the clang thread safety analysis that an aliased lock is acquired.
 // Because the clang thread safety analysis doesn't understand aliased locks
diff --git a/base/task/task_scheduler/scheduler_worker_pool_impl.cc b/base/task/task_scheduler/scheduler_worker_pool_impl.cc
index 3aaba18..08145b5f 100644
--- a/base/task/task_scheduler/scheduler_worker_pool_impl.cc
+++ b/base/task/task_scheduler/scheduler_worker_pool_impl.cc
@@ -18,6 +18,7 @@
 #include "base/location.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram.h"
+#include "base/numerics/clamped_math.h"
 #include "base/sequence_token.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
@@ -100,20 +101,7 @@
   SchedulerWorkerActionExecutor(SchedulerWorkerPoolImpl* outer)
       : outer_(outer) {}
 
-  ~SchedulerWorkerActionExecutor() {
-    SchedulerLock::AssertNoLockHeldOnCurrentThread();
-
-    // Wake up workers.
-    workers_to_wake_up_.ForEachWorker(
-        [](SchedulerWorker* worker) { worker->WakeUp(); });
-
-    // Start workers. Happens after wake ups to prevent the case where a worker
-    // enters its main function, is descheduled because it wasn't woken up yet,
-    // and is woken up immediately after.
-    workers_to_start_.ForEachWorker([&](SchedulerWorker* worker) {
-      worker->Start(outer_->after_start().scheduler_worker_observer);
-    });
-  }
+  ~SchedulerWorkerActionExecutor() { FlushImpl(); }
 
   void ScheduleWakeUp(scoped_refptr<SchedulerWorker> worker) {
     workers_to_wake_up_.AddWorker(std::move(worker));
@@ -123,6 +111,15 @@
     workers_to_start_.AddWorker(std::move(worker));
   }
 
+  void Flush(SchedulerLock* held_lock) {
+    if (workers_to_wake_up_.empty() && workers_to_start_.empty())
+      return;
+    AutoSchedulerUnlock auto_unlock(*held_lock);
+    FlushImpl();
+    workers_to_wake_up_.clear();
+    workers_to_start_.clear();
+  }
+
  private:
   class WorkerContainer {
    public:
@@ -148,6 +145,13 @@
       }
     }
 
+    bool empty() const { return first_worker_ == nullptr; }
+
+    void clear() {
+      first_worker_.reset();
+      additional_workers_.clear();
+    }
+
    private:
     // The purpose of |first_worker| is to avoid a heap allocation by the vector
     // in the case where there is only one worker in the container.
@@ -157,6 +161,21 @@
     DISALLOW_COPY_AND_ASSIGN(WorkerContainer);
   };
 
+  void FlushImpl() {
+    SchedulerLock::AssertNoLockHeldOnCurrentThread();
+
+    // Wake up workers.
+    workers_to_wake_up_.ForEachWorker(
+        [](SchedulerWorker* worker) { worker->WakeUp(); });
+
+    // Start workers. Happens after wake ups to prevent the case where a worker
+    // enters its main function, is descheduled because it wasn't woken up yet,
+    // and is woken up immediately after.
+    workers_to_start_.ForEachWorker([&](SchedulerWorker* worker) {
+      worker->Start(outer_->after_start().scheduler_worker_observer);
+    });
+  }
+
   SchedulerWorkerPoolImpl* const outer_;
 
   WorkerContainer workers_to_wake_up_;
@@ -370,7 +389,7 @@
 
   DCHECK(workers_.empty());
 
-  in_start().may_block_without_delay_ =
+  in_start().may_block_without_delay =
       FeatureList::IsEnabled(kMayBlockWithoutDelay);
   in_start().may_block_threshold =
       may_block_threshold ? may_block_threshold.value()
@@ -603,6 +622,12 @@
 
   DCHECK(ContainsWorker(outer_->workers_, worker));
 
+  // Use this opportunity, before assigning work to this worker, to create/wake
+  // additional workers if needed (doing this here allows us to reduce
+  // potentially expensive create/wake directly on PostTask()).
+  outer_->EnsureEnoughWorkersLockRequired(&executor);
+  executor.Flush(&outer_->lock_);
+
   if (!CanGetWorkLockRequired(worker))
     return nullptr;
 
@@ -622,9 +647,6 @@
     return nullptr;
   }
 
-  // Replace this worker if it was the last one, capacity permitting.
-  outer_->MaintainAtLeastOneIdleWorkerLockRequired(&executor);
-
   // Running task bookkeeping.
   worker_only().is_running_task = true;
   ++outer_->num_running_tasks_;
@@ -833,7 +855,7 @@
   DCHECK(worker_only().is_running_task);
 
   // MayBlock with no delay reuses WillBlock implementation.
-  if (outer_->after_start().may_block_without_delay_)
+  if (outer_->after_start().may_block_without_delay)
     blocking_type = BlockingType::WILL_BLOCK;
 
   switch (blocking_type) {
@@ -853,7 +875,7 @@
 
   // The blocking type always being WILL_BLOCK in this experiment, it should
   // never be considered "upgraded".
-  if (outer_->after_start().may_block_without_delay_)
+  if (outer_->after_start().may_block_without_delay)
     return;
 
   {
@@ -1063,27 +1085,31 @@
 
 void SchedulerWorkerPoolImpl::EnsureEnoughWorkersLockRequired(
     SchedulerWorkerActionExecutor* executor) {
+  // Don't do anything if the pool isn't started.
+  if (max_tasks_ == 0)
+    return;
+
   const size_t desired_num_awake_workers =
       GetDesiredNumAwakeWorkersLockRequired();
-  workers_.reserve(desired_num_awake_workers);
+  const size_t num_awake_workers = GetNumAwakeWorkersLockRequired();
+
+  size_t num_workers_to_wake_up =
+      ClampSub(desired_num_awake_workers, num_awake_workers);
+  num_workers_to_wake_up = std::min(num_workers_to_wake_up, size_t(2U));
 
   // Wake up the appropriate number of workers.
-  for (size_t i = GetNumAwakeWorkersLockRequired();
-       i < desired_num_awake_workers; ++i) {
+  for (size_t i = 0; i < num_workers_to_wake_up; ++i) {
     MaintainAtLeastOneIdleWorkerLockRequired(executor);
     SchedulerWorker* worker_to_wakeup = idle_workers_stack_.Pop();
     DCHECK(worker_to_wakeup);
     executor->ScheduleWakeUp(worker_to_wakeup);
   }
 
-  // If no worker is about to call MaintainAtLeastOneIdleWorkerLockRequired(),
-  // call it here. This is useful in the case where the loop above didn't wake
-  // up any worker but a recent increase in |max_tasks| now makes it possible to
-  // keep an idle worker.
-  DCHECK_GE(GetNumAwakeWorkersLockRequired(), num_running_tasks_);
-  const size_t num_awake_workers_not_running_task =
-      GetNumAwakeWorkersLockRequired() - num_running_tasks_;
-  if (num_awake_workers_not_running_task == 0)
+  // In the case where the loop above didn't wake up any worker and we don't
+  // have excess workers, the idle worker should be maintained. This happens
+  // when called from the last worker awake, or a recent increase in |max_tasks|
+  // now makes it possible to keep an idle worker.
+  if (desired_num_awake_workers == num_awake_workers)
     MaintainAtLeastOneIdleWorkerLockRequired(executor);
 }
 
diff --git a/base/task/task_scheduler/scheduler_worker_pool_impl.h b/base/task/task_scheduler/scheduler_worker_pool_impl.h
index 181d0ca..e1fbe2f 100644
--- a/base/task/task_scheduler/scheduler_worker_pool_impl.h
+++ b/base/task/task_scheduler/scheduler_worker_pool_impl.h
@@ -259,7 +259,7 @@
     // Optional observer notified when a worker enters and exits its main.
     SchedulerWorkerObserver* scheduler_worker_observer = nullptr;
 
-    bool may_block_without_delay_;
+    bool may_block_without_delay;
 
     // Threshold after which the max tasks is increased to compensate for a
     // worker that is within a MAY_BLOCK ScopedBlockingCall.
diff --git a/base/task/task_scheduler/scheduler_worker_pool_impl_unittest.cc b/base/task/task_scheduler/scheduler_worker_pool_impl_unittest.cc
index 4b857f0..9e55160fc 100644
--- a/base/task/task_scheduler/scheduler_worker_pool_impl_unittest.cc
+++ b/base/task/task_scheduler/scheduler_worker_pool_impl_unittest.cc
@@ -454,22 +454,42 @@
 }
 
 // Verify that posting many tasks before Start will cause the number of workers
-// to grow to |max_tasks_| during Start.
+// to grow to |max_tasks_| after Start.
 TEST_F(TaskSchedulerWorkerPoolImplStartInBodyTest, PostManyTasks) {
   scoped_refptr<TaskRunner> task_runner = test::CreateTaskRunnerWithTraits(
       {WithBaseSyncPrimitives()}, &mock_scheduler_task_runner_delegate_);
   constexpr size_t kNumTasksPosted = 2 * kMaxTasks;
-  for (size_t i = 0; i < kNumTasksPosted; ++i)
+
+  WaitableEvent threads_running;
+  WaitableEvent threads_continue;
+
+  RepeatingClosure threads_running_barrier = BarrierClosure(
+      kMaxTasks,
+      BindOnce(&WaitableEvent::Signal, Unretained(&threads_running)));
+  // Posting these tasks should cause new workers to be created.
+  for (size_t i = 0; i < kMaxTasks; ++i) {
+    task_runner->PostTask(
+        FROM_HERE, BindLambdaForTesting([&]() {
+          threads_running_barrier.Run();
+          test::WaitWithoutBlockingObserver(&threads_continue);
+        }));
+  }
+  // Post the remaining |kNumTasksPosted - kMaxTasks| tasks, don't wait for them
+  // as they'll be blocked behind the above kMaxtasks.
+  for (size_t i = kMaxTasks; i < kNumTasksPosted; ++i)
     task_runner->PostTask(FROM_HERE, DoNothing());
 
   EXPECT_EQ(0U, worker_pool_->NumberOfWorkersForTesting());
 
   StartWorkerPool(TimeDelta::Max(), kMaxTasks);
-  ASSERT_GT(kNumTasksPosted, worker_pool_->GetMaxTasksForTesting());
+  EXPECT_GT(worker_pool_->NumberOfWorkersForTesting(), 0U);
   EXPECT_EQ(kMaxTasks, worker_pool_->GetMaxTasksForTesting());
 
+  threads_running.Wait();
   EXPECT_EQ(worker_pool_->NumberOfWorkersForTesting(),
             worker_pool_->GetMaxTasksForTesting());
+  threads_continue.Signal();
+  task_tracker_.FlushForTesting();
 }
 
 namespace {
@@ -1698,7 +1718,7 @@
           // to accommodate queued and running sequences.
           ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                   BlockingType::WILL_BLOCK);
-          EXPECT_EQ(kNumWorkers, worker_pool_->NumberOfWorkersForTesting());
+          EXPECT_LE(kNumWorkers + 1, worker_pool_->NumberOfWorkersForTesting());
         }
 
         worker_observer.UnblockWorkers();
@@ -1706,7 +1726,7 @@
 
   runner->PostTask(FROM_HERE, BindLambdaForTesting([&]() {
                      EXPECT_LE(worker_pool_->NumberOfWorkersForTesting(),
-                               kNumWorkers);
+                               kNumWorkers + 1);
                    }));
   hold_will_block_task.Signal();
 
diff --git a/base/test/BUILD.gn b/base/test/BUILD.gn
index ef351155..e334ed0 100644
--- a/base/test/BUILD.gn
+++ b/base/test/BUILD.gn
@@ -20,6 +20,7 @@
   ]
   deps = [
     "//base",
+    "//base:clang_coverage_buildflags",
   ]
 }
 
diff --git a/base/test/test_timeouts.cc b/base/test/test_timeouts.cc
index 9328aaba..191e911d1 100644
--- a/base/test/test_timeouts.cc
+++ b/base/test/test_timeouts.cc
@@ -6,6 +6,7 @@
 
 #include <algorithm>
 
+#include "base/clang_coverage_buildflags.h"
 #include "base/command_line.h"
 #include "base/debug/debugger.h"
 #include "base/logging.h"
@@ -55,6 +56,9 @@
   constexpr int kTimeoutMultiplier = 3;
 #elif defined(ADDRESS_SANITIZER) || defined(THREAD_SANITIZER)
   constexpr int kTimeoutMultiplier = 2;
+#elif BUILDFLAG(CLANG_COVERAGE)
+  // On coverage build, tests run 3x slower.
+  constexpr int kTimeoutMultiplier = 3;
 #else
   constexpr int kTimeoutMultiplier = 1;
 #endif
diff --git a/base/trace_event/trace_event_impl.h b/base/trace_event/trace_event_impl.h
index 93eed622..97cd581 100644
--- a/base/trace_event/trace_event_impl.h
+++ b/base/trace_event/trace_event_impl.h
@@ -30,13 +30,17 @@
 namespace base {
 namespace trace_event {
 
-typedef base::Callback<bool(const char* arg_name)> ArgumentNameFilterPredicate;
+typedef base::RepeatingCallback<bool(const char* arg_name)>
+    ArgumentNameFilterPredicate;
 
-typedef base::Callback<bool(const char* category_group_name,
-                            const char* event_name,
-                            ArgumentNameFilterPredicate*)>
+typedef base::RepeatingCallback<bool(const char* category_group_name,
+                                     const char* event_name,
+                                     ArgumentNameFilterPredicate*)>
     ArgumentFilterPredicate;
 
+typedef base::RepeatingCallback<bool(const std::string& metadata_name)>
+    MetadataFilterPredicate;
+
 struct TraceEventHandle {
   uint32_t chunk_seq;
   // These numbers of bits must be kept consistent with
diff --git a/base/trace_event/trace_log.cc b/base/trace_event/trace_log.cc
index bbf71941..83e38020 100644
--- a/base/trace_event/trace_log.cc
+++ b/base/trace_event/trace_log.cc
@@ -648,7 +648,7 @@
     const ArgumentFilterPredicate& argument_filter_predicate) {
   AutoLock lock(lock_);
   DCHECK(!argument_filter_predicate.is_null());
-  DCHECK(argument_filter_predicate_.is_null());
+  // Replace the existing argument filter.
   argument_filter_predicate_ = argument_filter_predicate;
 }
 
@@ -657,6 +657,19 @@
   return argument_filter_predicate_;
 }
 
+void TraceLog::SetMetadataFilterPredicate(
+    const MetadataFilterPredicate& metadata_filter_predicate) {
+  AutoLock lock(lock_);
+  DCHECK(!metadata_filter_predicate.is_null());
+  // Replace the existing argument filter.
+  metadata_filter_predicate_ = metadata_filter_predicate;
+}
+
+MetadataFilterPredicate TraceLog::GetMetadataFilterPredicate() const {
+  AutoLock lock(lock_);
+  return metadata_filter_predicate_;
+}
+
 TraceLog::InternalTraceOptions TraceLog::GetInternalOptionsFromTraceConfig(
     const TraceConfig& config) {
   InternalTraceOptions ret = config.IsArgumentFilterEnabled()
diff --git a/base/trace_event/trace_log.h b/base/trace_event/trace_log.h
index 6996225b..eb37b2a 100644
--- a/base/trace_event/trace_log.h
+++ b/base/trace_event/trace_log.h
@@ -171,6 +171,10 @@
       const ArgumentFilterPredicate& argument_filter_predicate);
   ArgumentFilterPredicate GetArgumentFilterPredicate() const;
 
+  void SetMetadataFilterPredicate(
+      const MetadataFilterPredicate& metadata_filter_predicate);
+  MetadataFilterPredicate GetMetadataFilterPredicate() const;
+
   // Flush all collected events to the given output callback. The callback will
   // be called one or more times either synchronously or asynchronously from
   // the current thread with IPC-bite-size chunks. The string format is
@@ -527,6 +531,7 @@
   OutputCallback flush_output_callback_;
   scoped_refptr<SequencedTaskRunner> flush_task_runner_;
   ArgumentFilterPredicate argument_filter_predicate_;
+  MetadataFilterPredicate metadata_filter_predicate_;
   subtle::AtomicWord generation_;
   bool use_worker_thread_;
   std::atomic<AddTraceEventOverrideCallback> add_trace_event_override_;
diff --git a/build/android/lint/suppressions.xml b/build/android/lint/suppressions.xml
index b4a8870..a8ec796 100644
--- a/build/android/lint/suppressions.xml
+++ b/build/android/lint/suppressions.xml
@@ -118,7 +118,7 @@
     <ignore regexp="ui/android/java/res/drawable-xxhdpi"/>
     <ignore regexp="ui/android/java/res/drawable-xxxhdpi"/>
     <!-- This is intentional to reduce APK size. See: http://crrev/c/1352161 -->
-    <ignore regexp="chrome/android/java/res_autofill_assistant/drawable-*"/>
+    <ignore regexp="chrome/android/features/autofill_assistant/java/res/drawable-*"/>
   </issue>
   <issue id="IconDipSize">
     <ignore regexp="chromecast/internal"/>
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index 402dac4..9487da4 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -1592,7 +1592,7 @@
     # Fuchsia: https://crbug.com/935588
     # iOS: https://crbug.com/936211
     has_dchecks = is_debug || dcheck_always_on
-    if (has_dchecks && !is_fuchsia && !is_ios) {
+    if (has_dchecks && !is_fuchsia) {
       cflags += [ "-Wextra-semi" ]
     }
   }
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index eae91a3..33e3372 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-30c5e3c76330052611948b969e6f0ac2a2828a5f
\ No newline at end of file
+bb61479483bd49c8db68b7c6cb72de68866b8b91
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 82e01907..86ff0a24 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-dd4592d4cc8e9767a366112fb3bfaf8bed0ee4f5
\ No newline at end of file
+64758f33cc3d77082e41d5114ecd23d16ae0da84
\ No newline at end of file
diff --git a/build/toolchain/clang_code_coverage_wrapper.py b/build/toolchain/clang_code_coverage_wrapper.py
index eb493bfa..96978056 100755
--- a/build/toolchain/clang_code_coverage_wrapper.py
+++ b/build/toolchain/clang_code_coverage_wrapper.py
@@ -2,11 +2,19 @@
 # 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.
-"""Adds code coverage flags to the invocations of the Clang C/C++ compiler.
+"""Removes code coverage flags from invocations of the Clang C/C++ compiler.
 
-This script is used to instrument a subset of the source files, and the list of
-files to instrument is specified by an input file that is passed to this script
-as a command-line argument.
+If the GN arg `use_clang_coverage=true`, this script will be invoked by default.
+GN will add coverage instrumentation flags to almost all source files.
+
+This script is used to remove instrumentation flags from a subset of the source
+files. By default, it will not remove flags from any files. If the option
+--files-to-instrument is passed, this script will remove flags from all files
+except the ones listed in --files-to-instrument.
+
+This script also contains hard-coded exclusion lists of files to never
+instrument, indexed by target operating system. Files in these lists have their
+flags removed in both modes. The OS can be selected with --target-os.
 
 The path to the coverage instrumentation input file should be relative to the
 root build directory, and the file consists of multiple lines where each line
@@ -37,6 +45,8 @@
 import sys
 
 # Flags used to enable coverage instrumentation.
+# Flags should be listed in the same order that they are added in
+# build/config/coverage/BUILD.gn
 _COVERAGE_FLAGS = [
     '-fprofile-instr-generate', '-fcoverage-mapping',
     # Following experimental flags remove unused header functions from the
@@ -46,6 +56,41 @@
     '-mllvm', '-limited-coverage-experimental=true'
 ]
 
+# Map of exclusion lists indexed by target OS.
+# If no target OS is defined, or one is defined that doesn't have a specific
+# entry, use the 'default' exclusion_list. Anything added to 'default' will
+# apply to all platforms that don't have their own specific list.
+_COVERAGE_EXCLUSION_LIST_MAP = {
+    'default': [],
+    'chromeos': [
+        # These files caused clang to crash while compiling them. They are
+        # excluded pending an investigation into the underlying compiler bug.
+        '../../third_party/webrtc/p2p/base/p2p_transport_channel.cc',
+        '../../third_party/icu/source/common/uts46.cpp',
+        '../../third_party/icu/source/common/ucnvmbcs.cpp',
+        '../../base/android/android_image_reader_compat.cc',
+    ]
+}
+
+
+def _remove_flags_from_command(command):
+  # We need to remove the coverage flags for this file, but we only want to
+  # remove them if we see the exact sequence defined in _COVERAGE_FLAGS.
+  # That ensures that we only remove the flags added by GN when
+  # "use_clang_coverage" is true. Otherwise, we would remove flags set by
+  # other parts of the build system.
+  start_flag = _COVERAGE_FLAGS[0]
+  num_flags = len(_COVERAGE_FLAGS)
+  start_idx = 0
+  try:
+    while True:
+      idx = command.index(start_flag, start_idx)
+      start_idx = idx + 1
+      if command[idx:idx+num_flags] == _COVERAGE_FLAGS:
+        del command[idx:idx+num_flags]
+        break
+  except ValueError:
+    pass
 
 def main():
   # TODO(crbug.com/898695): Make this wrapper work on Windows platform.
@@ -54,16 +99,23 @@
   arg_parser.add_argument(
       '--files-to-instrument',
       type=str,
-      required=True,
       help='Path to a file that contains a list of file names to instrument.')
+  arg_parser.add_argument(
+      '--target-os',
+      required=False,
+      help='The OS to compile for.')
   arg_parser.add_argument('args', nargs=argparse.REMAINDER)
   parsed_args = arg_parser.parse_args()
 
-  if not os.path.isfile(parsed_args.files_to_instrument):
+  if (parsed_args.files_to_instrument and
+      not os.path.isfile(parsed_args.files_to_instrument)):
     raise Exception('Path to the coverage instrumentation file: "%s" doesn\'t '
                     'exist.' % parsed_args.files_to_instrument)
 
   compile_command = parsed_args.args
+  if not any('clang' in s for s in compile_command):
+    return subprocess.call(compile_command)
+
   try:
     # The command is assumed to use Clang as the compiler, and the path to the
     # source file is behind the -c argument, and the path to the source path is
@@ -79,12 +131,19 @@
     raise Exception('Source file to be compiled is missing from the command.')
 
   compile_source_file = compile_command[index_dash_c + 1]
-  with open(parsed_args.files_to_instrument) as f:
-    if compile_source_file + '\n' in f.read():
-      compile_command.extend(_COVERAGE_FLAGS)
+  target_os = parsed_args.target_os
+  if target_os not in _COVERAGE_EXCLUSION_LIST_MAP:
+    target_os = 'default'
+  exclusion_list = _COVERAGE_EXCLUSION_LIST_MAP[target_os]
+
+  if compile_source_file in exclusion_list:
+    _remove_flags_from_command(compile_command)
+  elif parsed_args.files_to_instrument:
+    with open(parsed_args.files_to_instrument) as f:
+      if compile_source_file not in f.read():
+        _remove_flags_from_command(compile_command)
 
   return subprocess.call(compile_command)
 
-
 if __name__ == '__main__':
   sys.exit(main())
diff --git a/buildtools/DEPS b/buildtools/DEPS
index c3c9e92..3e06679d 100644
--- a/buildtools/DEPS
+++ b/buildtools/DEPS
@@ -5,7 +5,7 @@
 
   # When changing these, also update the svn revisions in deps_revisions.gni
   "clang_format_revision": "96636aa0e9f047f17447f2d45a094d0b59ed7917",
-  "libcxx_revision":       "22d3f6dd25e5efc59124ba1c00b8f98b14be4201",
+  "libcxx_revision":       "9ae8fb4a3c5fef4e9d41e97bbd9397a412932ab0",
   "libcxxabi_revision":    "0d529660e32d77d9111912d73f2c74fc5fa2a858",
   "libunwind_revision":    "69d9b84cca8354117b9fe9705a4430d789ee599b",
 }
diff --git a/buildtools/deps_revisions.gni b/buildtools/deps_revisions.gni
index b16d426..0f39b45a 100644
--- a/buildtools/deps_revisions.gni
+++ b/buildtools/deps_revisions.gni
@@ -6,7 +6,7 @@
   # The svn revisions that belong to the git hashes in DEPS. Used to cause full
   # rebuilds on libc++ rolls.
   clang_format_svn_revision = "346566"
-  libcxx_svn_revision = "354212"
+  libcxx_svn_revision = "355550"
   libcxxabi_svn_revision = "354284"
   libunwind_svn_revision = "348981"
 }
diff --git a/cc/layers/layer_impl.cc b/cc/layers/layer_impl.cc
index 12ce2ec9..b036dd80 100644
--- a/cc/layers/layer_impl.cc
+++ b/cc/layers/layer_impl.cc
@@ -137,10 +137,13 @@
 
 void LayerImpl::PopulateSharedQuadState(viz::SharedQuadState* state,
                                         bool contents_opaque) const {
+  EffectNode* effect_node = GetEffectTree().Node(effect_tree_index_);
   state->SetAll(draw_properties_.target_space_transform, gfx::Rect(bounds()),
                 draw_properties_.visible_layer_rect, draw_properties_.clip_rect,
                 draw_properties_.is_clipped, contents_opaque,
-                draw_properties_.opacity, SkBlendMode::kSrcOver,
+                draw_properties_.opacity,
+                effect_node->has_render_surface ? SkBlendMode::kSrcOver
+                                                : effect_node->blend_mode,
                 GetSortingContextId());
 }
 
diff --git a/cc/layers/layer_impl.h b/cc/layers/layer_impl.h
index 158381e..d3f3d6e 100644
--- a/cc/layers/layer_impl.h
+++ b/cc/layers/layer_impl.h
@@ -561,11 +561,11 @@
   friend class TreeSynchronizer;
 
   DrawMode current_draw_mode_;
+  EffectTree& GetEffectTree() const;
 
  private:
   PropertyTrees* GetPropertyTrees() const;
   ClipTree& GetClipTree() const;
-  EffectTree& GetEffectTree() const;
   ScrollTree& GetScrollTree() const;
   TransformTree& GetTransformTree() const;
 
diff --git a/cc/layers/picture_layer_impl.cc b/cc/layers/picture_layer_impl.cc
index d80af73..1babd64 100644
--- a/cc/layers/picture_layer_impl.cc
+++ b/cc/layers/picture_layer_impl.cc
@@ -25,6 +25,7 @@
 #include "cc/paint/display_item_list.h"
 #include "cc/tiles/tile_manager.h"
 #include "cc/tiles/tiling_set_raster_queue_all.h"
+#include "cc/trees/effect_node.h"
 #include "cc/trees/layer_tree_impl.h"
 #include "cc/trees/occlusion.h"
 #include "components/viz/common/frame_sinks/begin_frame_args.h"
@@ -285,11 +286,13 @@
     if (mask_type_ == Layer::LayerMaskType::NOT_MASK) {
       occlusion = draw_properties().occlusion_in_content_space;
     }
+
+    EffectNode* effect_node = GetEffectTree().Node(effect_tree_index());
     SolidColorLayerImpl::AppendSolidQuads(
         render_pass, occlusion, shared_quad_state, scaled_visible_layer_rect,
         raster_source_->GetSolidColor(),
         !layer_tree_impl()->settings().enable_edge_anti_aliasing,
-        append_quads_data);
+        effect_node->blend_mode, append_quads_data);
     return;
   }
 
diff --git a/cc/layers/solid_color_layer_impl.cc b/cc/layers/solid_color_layer_impl.cc
index add28199..48c728c 100644
--- a/cc/layers/solid_color_layer_impl.cc
+++ b/cc/layers/solid_color_layer_impl.cc
@@ -7,6 +7,7 @@
 #include <algorithm>
 
 #include "cc/layers/append_quads_data.h"
+#include "cc/trees/effect_node.h"
 #include "cc/trees/layer_tree_impl.h"
 #include "cc/trees/occlusion.h"
 #include "components/viz/common/quads/solid_color_draw_quad.h"
@@ -31,12 +32,24 @@
     const gfx::Rect& visible_layer_rect,
     SkColor color,
     bool force_anti_aliasing_off,
+    SkBlendMode effect_blend_mode,
     AppendQuadsData* append_quads_data) {
-  float alpha =
-      (SkColorGetA(color) * (1.0f / 255.0f)) * shared_quad_state->opacity;
-  DCHECK_EQ(SkBlendMode::kSrcOver, shared_quad_state->blend_mode);
-  if (alpha < std::numeric_limits<float>::epsilon())
-    return;
+  // Transparent, solid quads can be omitted if the effect blend mode is
+  // kSrcOver. Note that |effect_blend_mode| may be different than
+  // |shared_quad_state->blend_mode|, if the blend is applied by a render
+  // surface. This is because a layer that induces an effect node emits
+  // two quads, one for the layer, and one for the render surface, and in
+  // this situation the blend mode is lifted up to the render surface.
+  // This will work for situations where there is only one layer under the
+  // mask, but will not work in complex blend mode situations. This bug is
+  // tracked in crbug.com/939168.
+  if (effect_blend_mode == SkBlendMode::kSrcOver) {
+    float alpha =
+        (SkColorGetA(color) * (1.0f / 255.0f)) * shared_quad_state->opacity;
+
+    if (alpha < std::numeric_limits<float>::epsilon())
+      return;
+  }
 
   gfx::Rect visible_quad_rect =
       occlusion_in_layer_space.GetUnoccludedContentRect(visible_layer_rect);
@@ -56,10 +69,11 @@
 
   // TODO(hendrikw): We need to pass the visible content rect rather than
   // |bounds()| here.
+  EffectNode* effect_node = GetEffectTree().Node(effect_tree_index());
   AppendSolidQuads(render_pass, draw_properties().occlusion_in_content_space,
                    shared_quad_state, gfx::Rect(bounds()), background_color(),
                    !layer_tree_impl()->settings().enable_edge_anti_aliasing,
-                   append_quads_data);
+                   effect_node->blend_mode, append_quads_data);
 }
 
 const char* SolidColorLayerImpl::LayerTypeAsString() const {
diff --git a/cc/layers/solid_color_layer_impl.h b/cc/layers/solid_color_layer_impl.h
index cb2c25f..78a35a4 100644
--- a/cc/layers/solid_color_layer_impl.h
+++ b/cc/layers/solid_color_layer_impl.h
@@ -27,6 +27,7 @@
                                const gfx::Rect& visible_layer_rect,
                                SkColor color,
                                bool force_anti_aliasing_off,
+                               SkBlendMode effect_blend_mode,
                                AppendQuadsData* append_quads_data);
 
   ~SolidColorLayerImpl() override;
diff --git a/cc/paint/decoded_draw_image.cc b/cc/paint/decoded_draw_image.cc
index 32571f3..2c66554 100644
--- a/cc/paint/decoded_draw_image.cc
+++ b/cc/paint/decoded_draw_image.cc
@@ -38,7 +38,11 @@
                        kNone_SkFilterQuality,
                        true) {}
 
-DecodedDrawImage::DecodedDrawImage(const DecodedDrawImage& other) = default;
+DecodedDrawImage::DecodedDrawImage(const DecodedDrawImage&) = default;
+DecodedDrawImage::DecodedDrawImage(DecodedDrawImage&&) = default;
+DecodedDrawImage& DecodedDrawImage::operator=(const DecodedDrawImage&) =
+    default;
+DecodedDrawImage& DecodedDrawImage::operator=(DecodedDrawImage&&) = default;
 
 DecodedDrawImage::~DecodedDrawImage() = default;
 
diff --git a/cc/paint/decoded_draw_image.h b/cc/paint/decoded_draw_image.h
index afc0773..02b3ad1 100644
--- a/cc/paint/decoded_draw_image.h
+++ b/cc/paint/decoded_draw_image.h
@@ -36,6 +36,10 @@
                    bool needs_mips,
                    bool is_budgeted);
   DecodedDrawImage(const DecodedDrawImage& other);
+  DecodedDrawImage(DecodedDrawImage&& other);
+  DecodedDrawImage& operator=(const DecodedDrawImage&);
+  DecodedDrawImage& operator=(DecodedDrawImage&&);
+
   DecodedDrawImage();
   ~DecodedDrawImage();
 
diff --git a/cc/tiles/gpu_image_decode_cache_unittest.cc b/cc/tiles/gpu_image_decode_cache_unittest.cc
index f78f4f4..e8418bf 100644
--- a/cc/tiles/gpu_image_decode_cache_unittest.cc
+++ b/cc/tiles/gpu_image_decode_cache_unittest.cc
@@ -396,7 +396,7 @@
       return new_draw_image;
     }
 
-    return draw_image;
+    return std::move(draw_image);
   }
 
   sk_sp<SkImage> GetLastTransferredImage() {
diff --git a/cc/trees/layer_tree_host_pixeltest_masks.cc b/cc/trees/layer_tree_host_pixeltest_masks.cc
index 19e3e2a0..dc63c71 100644
--- a/cc/trees/layer_tree_host_pixeltest_masks.cc
+++ b/cc/trees/layer_tree_host_pixeltest_masks.cc
@@ -158,6 +158,215 @@
       &property_trees);
 }
 
+// This tests that a solid color empty layer with mask effect works correctly.
+TEST_P(LayerTreeHostLayerListPixelTest, SolidColorLayerEmptyMaskWithEffect) {
+  PropertyTrees property_trees;
+  scoped_refptr<Layer> root_layer;
+  InitializeForLayerListMode(&root_layer, &property_trees);
+
+  EffectNode isolation_effect;
+  isolation_effect.clip_id = 1;
+  isolation_effect.stable_id = 2;
+  isolation_effect.has_render_surface = true;
+  isolation_effect.transform_id = 1;
+  property_trees.effect_tree.Insert(isolation_effect, 1);
+
+  EffectNode mask_effect;
+  mask_effect.clip_id = 1;
+  mask_effect.stable_id = 3;
+  mask_effect.transform_id = 1;
+  mask_effect.blend_mode = SkBlendMode::kDstIn;
+  property_trees.effect_tree.Insert(mask_effect, 2);
+
+  scoped_refptr<SolidColorLayer> background =
+      CreateSolidColorLayer(gfx::Rect(100, 100), SK_ColorWHITE);
+  background->set_property_tree_sequence_number(property_trees.sequence_number);
+  background->SetClipTreeIndex(1);
+  background->SetEffectTreeIndex(1);
+  background->SetScrollTreeIndex(1);
+  background->SetTransformTreeIndex(1);
+  root_layer->AddChild(background);
+
+  scoped_refptr<SolidColorLayer> green =
+      CreateSolidColorLayer(gfx::Rect(25, 25, 50, 50), kCSSGreen);
+  green->set_property_tree_sequence_number(property_trees.sequence_number);
+  green->SetClipTreeIndex(1);
+  green->SetEffectTreeIndex(2);
+  green->SetScrollTreeIndex(1);
+  green->SetTransformTreeIndex(1);
+
+  root_layer->AddChild(green);
+
+  // Apply a mask that is empty and solid-color. This should result in
+  // the green layer being entirely clipped out.
+  gfx::Size mask_bounds(50, 50);
+  scoped_refptr<SolidColorLayer> mask =
+      CreateSolidColorLayer(gfx::Rect(25, 25, 50, 50), SK_ColorTRANSPARENT);
+  mask->SetOffsetToTransformParent(gfx::Vector2dF(25, 25));
+  mask->set_property_tree_sequence_number(property_trees.sequence_number);
+  mask->SetBounds(mask_bounds);
+  mask->SetIsDrawable(true);
+  mask->SetClipTreeIndex(1);
+  mask->SetEffectTreeIndex(3);
+  mask->SetScrollTreeIndex(1);
+  mask->SetTransformTreeIndex(1);
+  root_layer->AddChild(mask);
+
+  RunPixelResourceTestWithLayerList(
+      root_layer,
+      base::FilePath(
+          FILE_PATH_LITERAL("solid_color_empty_mask_with_effect.png")),
+      &property_trees);
+}
+
+class SolidColorEmptyMaskContentLayerClient : public ContentLayerClient {
+ public:
+  explicit SolidColorEmptyMaskContentLayerClient(const gfx::Size& bounds)
+      : bounds_(bounds) {}
+  ~SolidColorEmptyMaskContentLayerClient() override = default;
+
+  bool FillsBoundsCompletely() const override { return false; }
+  size_t GetApproximateUnsharedMemoryUsage() const override { return 0; }
+
+  gfx::Rect PaintableRegion() override { return gfx::Rect(bounds_); }
+
+  scoped_refptr<DisplayItemList> PaintContentsToDisplayList(
+      PaintingControlSetting picture_control) override {
+    // Intentionally return a solid color, empty mask display list. This
+    // is a situation where all content should be masked out.
+    auto display_list = base::MakeRefCounted<DisplayItemList>();
+    return display_list;
+  }
+
+ private:
+  gfx::Size bounds_;
+};
+
+TEST_P(LayerTreeHostLayerListPixelTest, SolidColorEmptyMaskWithEffect) {
+  PropertyTrees property_trees;
+  scoped_refptr<Layer> root_layer;
+  InitializeForLayerListMode(&root_layer, &property_trees);
+
+  EffectNode isolation_effect;
+  isolation_effect.clip_id = 1;
+  isolation_effect.stable_id = 2;
+  isolation_effect.has_render_surface = true;
+  isolation_effect.transform_id = 1;
+  property_trees.effect_tree.Insert(isolation_effect, 1);
+
+  EffectNode mask_effect;
+  mask_effect.clip_id = 1;
+  mask_effect.stable_id = 3;
+  mask_effect.transform_id = 1;
+  mask_effect.blend_mode = SkBlendMode::kDstIn;
+  property_trees.effect_tree.Insert(mask_effect, 2);
+
+  scoped_refptr<SolidColorLayer> background =
+      CreateSolidColorLayer(gfx::Rect(100, 100), SK_ColorWHITE);
+  background->set_property_tree_sequence_number(property_trees.sequence_number);
+  background->SetClipTreeIndex(1);
+  background->SetEffectTreeIndex(1);
+  background->SetScrollTreeIndex(1);
+  background->SetTransformTreeIndex(1);
+  root_layer->AddChild(background);
+
+  scoped_refptr<SolidColorLayer> green =
+      CreateSolidColorLayer(gfx::Rect(25, 25, 50, 50), kCSSGreen);
+  green->set_property_tree_sequence_number(property_trees.sequence_number);
+  green->SetClipTreeIndex(1);
+  green->SetEffectTreeIndex(2);
+  green->SetScrollTreeIndex(1);
+  green->SetTransformTreeIndex(1);
+
+  root_layer->AddChild(green);
+
+  // Apply a mask that is empty and solid-color. This should result in
+  // the green layer being entirely clipped out.
+  gfx::Size mask_bounds(50, 50);
+  SolidColorEmptyMaskContentLayerClient client(mask_bounds);
+
+  scoped_refptr<PictureLayer> mask = PictureLayer::Create(&client);
+  mask->SetOffsetToTransformParent(gfx::Vector2dF(25, 25));
+  mask->set_property_tree_sequence_number(property_trees.sequence_number);
+  mask->SetBounds(mask_bounds);
+  mask->SetIsDrawable(true);
+  mask->SetClipTreeIndex(1);
+  mask->SetEffectTreeIndex(3);
+  mask->SetScrollTreeIndex(1);
+  mask->SetTransformTreeIndex(1);
+  root_layer->AddChild(mask);
+
+  RunPixelResourceTestWithLayerList(
+      root_layer,
+      base::FilePath(
+          FILE_PATH_LITERAL("solid_color_empty_mask_with_effect.png")),
+      &property_trees);
+}
+
+// same as SolidColorEmptyMaskWithEffect, except the mask has a render surface.
+TEST_P(LayerTreeHostLayerListPixelTest,
+       SolidColorEmptyMaskWithEffectAndRenderSurface) {
+  PropertyTrees property_trees;
+  scoped_refptr<Layer> root_layer;
+  InitializeForLayerListMode(&root_layer, &property_trees);
+
+  EffectNode isolation_effect;
+  isolation_effect.clip_id = 1;
+  isolation_effect.stable_id = 2;
+  isolation_effect.has_render_surface = true;
+  isolation_effect.transform_id = 1;
+  property_trees.effect_tree.Insert(isolation_effect, 1);
+
+  EffectNode mask_effect;
+  mask_effect.clip_id = 1;
+  mask_effect.stable_id = 3;
+  mask_effect.transform_id = 1;
+  mask_effect.blend_mode = SkBlendMode::kDstIn;
+  mask_effect.has_render_surface = true;
+  property_trees.effect_tree.Insert(mask_effect, 2);
+
+  scoped_refptr<SolidColorLayer> background =
+      CreateSolidColorLayer(gfx::Rect(100, 100), SK_ColorWHITE);
+  background->set_property_tree_sequence_number(property_trees.sequence_number);
+  background->SetClipTreeIndex(1);
+  background->SetEffectTreeIndex(1);
+  background->SetScrollTreeIndex(1);
+  background->SetTransformTreeIndex(1);
+  root_layer->AddChild(background);
+
+  scoped_refptr<SolidColorLayer> green =
+      CreateSolidColorLayer(gfx::Rect(25, 25, 50, 50), kCSSGreen);
+  green->set_property_tree_sequence_number(property_trees.sequence_number);
+  green->SetClipTreeIndex(1);
+  green->SetEffectTreeIndex(2);
+  green->SetScrollTreeIndex(1);
+  green->SetTransformTreeIndex(1);
+
+  root_layer->AddChild(green);
+
+  // Apply a mask that is empty and solid-color. This should result in
+  // the green layer being entirely clipped out.
+  gfx::Size mask_bounds(50, 50);
+  SolidColorEmptyMaskContentLayerClient client(mask_bounds);
+
+  scoped_refptr<PictureLayer> mask = PictureLayer::Create(&client);
+  mask->SetOffsetToTransformParent(gfx::Vector2dF(25, 25));
+  mask->set_property_tree_sequence_number(property_trees.sequence_number);
+  mask->SetBounds(mask_bounds);
+  mask->SetIsDrawable(true);
+  mask->SetClipTreeIndex(1);
+  mask->SetEffectTreeIndex(3);
+  mask->SetScrollTreeIndex(1);
+  mask->SetTransformTreeIndex(1);
+  root_layer->AddChild(mask);
+
+  RunPixelResourceTestWithLayerList(
+      root_layer,
+      base::FilePath(
+          FILE_PATH_LITERAL("solid_color_empty_mask_with_effect.png")),
+      &property_trees);
+}
+
 // Tests a situation in which there is no other content in the target
 // render surface that the mask applies to. In this situation, the mask
 // should have no effect on the rendered output.
diff --git a/chrome/VERSION b/chrome/VERSION
index 1b08393..0123f0e 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=74
 MINOR=0
-BUILD=3727
+BUILD=3728
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 44569fc..db5560ca 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -144,14 +144,6 @@
   custom_package = "org.chromium.chrome.download"
 }
 
-android_resources("chrome_autofill_assistant_java_resources") {
-  resource_dirs = [ "//chrome/android/java/res_autofill_assistant" ]
-  deps = [
-    ":chrome_app_java_resources",
-  ]
-  custom_package = "org.chromium.chrome.autofill_assistant"
-}
-
 java_strings_grd("chrome_strings_grd") {
   defines = chrome_grit_defines
   grd_file = "java/strings/android_chrome_strings.grd"
@@ -240,7 +232,6 @@
 android_library("chrome_java") {
   deps = [
     ":chrome_app_java_resources",
-    ":chrome_autofill_assistant_java_resources",
     ":chrome_download_java_resources",
     ":chrome_public_android_manifest",
     ":chrome_public_apk_template_resources",
@@ -442,6 +433,7 @@
 java_group("chrome_all_java") {
   deps = [
     ":chrome_java",
+    "//chrome/android/features/autofill_assistant:java",
     "//chrome/android/features/media_router:java",
   ]
 
@@ -1820,6 +1812,7 @@
   deps = [
     ":chrome_test_apk_template_resources",
     ":chrome_test_java",
+    "//chrome/android/features/autofill_assistant:test_java",
     "//chrome/android/features/media_router:test_java",
     "//chrome/android/features/touchless:touchless_java_test",
     "//chrome/android/webapk/libs/runtime_library:runtime_library_javatests",
@@ -2089,6 +2082,16 @@
 
 generate_jni("jni_headers") {
   sources = [
+    "features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantModel.java",
+    "features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java",
+    "features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetails.java",
+    "features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsModel.java",
+    "features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderDelegate.java",
+    "features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderModel.java",
+    "features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayDelegate.java",
+    "features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayModel.java",
+    "features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestDelegate.java",
+    "features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestModel.java",
     "features/media_router/java/src/org/chromium/chrome/browser/media/router/ChromeMediaRouter.java",
     "features/media_router/java/src/org/chromium/chrome/browser/media/router/ChromeMediaRouterDialogController.java",
     "features/media_router/java/src/org/chromium/chrome/browser/media/router/FlingingControllerBridge.java",
@@ -2126,17 +2129,7 @@
     "java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java",
     "java/src/org/chromium/chrome/browser/autofill/PhoneNumberUtil.java",
     "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingBridge.java",
-    "java/src/org/chromium/chrome/browser/autofill_assistant/AssistantModel.java",
     "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantClient.java",
-    "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java",
-    "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetails.java",
-    "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsModel.java",
-    "java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderDelegate.java",
-    "java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderModel.java",
-    "java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayDelegate.java",
-    "java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayModel.java",
-    "java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestDelegate.java",
-    "java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestModel.java",
     "java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTask.java",
     "java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTaskScheduler.java",
     "java/src/org/chromium/chrome/browser/banners/AppBannerManager.java",
diff --git a/chrome/android/features/autofill_assistant/BUILD.gn b/chrome/android/features/autofill_assistant/BUILD.gn
new file mode 100644
index 0000000..fc7d093
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/BUILD.gn
@@ -0,0 +1,143 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/android/config.gni")
+import("//build/config/android/rules.gni")
+import("//chrome/common/features.gni")
+
+android_library("java") {
+  deps = [
+    ":java_resources",
+    "//base:base_java",
+    "//chrome/android:chrome_java",
+    "//components/url_formatter/android:url_formatter_java",
+    "//content/public/android:content_java",
+    "//third_party/android_deps:android_support_design_java",
+    "//third_party/android_deps:com_android_support_recyclerview_v7_java",
+    "//third_party/android_deps:com_android_support_support_compat_java",
+    "//third_party/android_deps:com_android_support_support_core_ui_java",
+    "//third_party/android_deps:com_android_support_support_core_utils_java",
+    "//third_party/blink/public/mojom:android_mojo_bindings_java",
+    "//ui/android:ui_java",
+  ]
+  java_files = [
+    "java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/AssistantCoordinator.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/AssistantKeyboardCoordinator.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/AssistantModel.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinator.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/AssistantSnackbar.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/EditDistance.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/FeedbackContext.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantCarouselCoordinator.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantCarouselModel.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChip.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChipViewHolder.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetails.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsCoordinator.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsModel.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsViewBinder.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/header/AnimatedProgressBar.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderCoordinator.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderDelegate.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderModel.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderViewBinder.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayCoordinator.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayDelegate.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayModel.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/overlay/TouchEventFilterView.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestCoordinator.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestDelegate.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestModel.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestOptions.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequest.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequestSection.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestBottomBar.java",
+    "java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestUI.java",
+  ]
+}
+
+android_library("test_java") {
+  testonly = true
+
+  java_files = [
+    "javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java",
+    "javatests/src/org/chromium/chrome/browser/autofill_assistant/EditDistanceTest.java",
+  ]
+
+  deps = [
+    ":java",
+    "//base:base_java",
+    "//base:base_java_test_support",
+    "//chrome/android:chrome_java",
+    "//chrome/android:chrome_test_util_java",
+    "//net/android:net_java_test_support",
+    "//third_party/android_deps:com_android_support_recyclerview_v7_java",
+    "//third_party/android_support_test_runner:runner_java",
+    "//third_party/junit",
+    "//third_party/mockito:mockito_java",
+    "//ui/android:ui_full_java",
+  ]
+}
+
+android_resources("java_resources") {
+  resource_dirs = [ "java/res" ]
+  deps = [
+    ":java_strings_grd",
+    "//chrome/android:chrome_app_java_resources",
+  ]
+  custom_package = "org.chromium.chrome.autofill_assistant"
+}
+
+java_strings_grd("java_strings_grd") {
+  defines = chrome_grit_defines
+  grd_file = "java/strings/android_chrome_autofill_assistant_strings.grd"
+  outputs = [
+    "values-am/android_chrome_autofill_assistant_strings.xml",
+    "values-ar/android_chrome_autofill_assistant_strings.xml",
+    "values-bg/android_chrome_autofill_assistant_strings.xml",
+    "values-ca/android_chrome_autofill_assistant_strings.xml",
+    "values-cs/android_chrome_autofill_assistant_strings.xml",
+    "values-da/android_chrome_autofill_assistant_strings.xml",
+    "values-de/android_chrome_autofill_assistant_strings.xml",
+    "values-el/android_chrome_autofill_assistant_strings.xml",
+    "values/android_chrome_autofill_assistant_strings.xml",
+    "values-en-rGB/android_chrome_autofill_assistant_strings.xml",
+    "values-es/android_chrome_autofill_assistant_strings.xml",
+    "values-es-rUS/android_chrome_autofill_assistant_strings.xml",
+    "values-fa/android_chrome_autofill_assistant_strings.xml",
+    "values-fi/android_chrome_autofill_assistant_strings.xml",
+    "values-tl/android_chrome_autofill_assistant_strings.xml",
+    "values-fr/android_chrome_autofill_assistant_strings.xml",
+    "values-hi/android_chrome_autofill_assistant_strings.xml",
+    "values-hr/android_chrome_autofill_assistant_strings.xml",
+    "values-hu/android_chrome_autofill_assistant_strings.xml",
+    "values-in/android_chrome_autofill_assistant_strings.xml",
+    "values-it/android_chrome_autofill_assistant_strings.xml",
+    "values-iw/android_chrome_autofill_assistant_strings.xml",
+    "values-ja/android_chrome_autofill_assistant_strings.xml",
+    "values-ko/android_chrome_autofill_assistant_strings.xml",
+    "values-lt/android_chrome_autofill_assistant_strings.xml",
+    "values-lv/android_chrome_autofill_assistant_strings.xml",
+    "values-nl/android_chrome_autofill_assistant_strings.xml",
+    "values-nb/android_chrome_autofill_assistant_strings.xml",
+    "values-pl/android_chrome_autofill_assistant_strings.xml",
+    "values-pt-rBR/android_chrome_autofill_assistant_strings.xml",
+    "values-pt-rPT/android_chrome_autofill_assistant_strings.xml",
+    "values-ro/android_chrome_autofill_assistant_strings.xml",
+    "values-ru/android_chrome_autofill_assistant_strings.xml",
+    "values-sk/android_chrome_autofill_assistant_strings.xml",
+    "values-sl/android_chrome_autofill_assistant_strings.xml",
+    "values-sr/android_chrome_autofill_assistant_strings.xml",
+    "values-sv/android_chrome_autofill_assistant_strings.xml",
+    "values-sw/android_chrome_autofill_assistant_strings.xml",
+    "values-th/android_chrome_autofill_assistant_strings.xml",
+    "values-tr/android_chrome_autofill_assistant_strings.xml",
+    "values-uk/android_chrome_autofill_assistant_strings.xml",
+    "values-vi/android_chrome_autofill_assistant_strings.xml",
+    "values-zh-rCN/android_chrome_autofill_assistant_strings.xml",
+    "values-zh-rTW/android_chrome_autofill_assistant_strings.xml",
+  ]
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill_assistant/OWNERS b/chrome/android/features/autofill_assistant/OWNERS
similarity index 100%
rename from chrome/android/javatests/src/org/chromium/chrome/browser/autofill_assistant/OWNERS
rename to chrome/android/features/autofill_assistant/OWNERS
diff --git a/chrome/android/features/autofill_assistant/java/DEPS b/chrome/android/features/autofill_assistant/java/DEPS
new file mode 100644
index 0000000..cc628521
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+content/public/android/java/src/org/chromium/content_public/browser",
+]
diff --git a/chrome/android/java/res_autofill_assistant/drawable-hdpi/autofill_assistant_bottombar_bg.9.png b/chrome/android/features/autofill_assistant/java/res/drawable-hdpi/autofill_assistant_bottombar_bg.9.png
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable-hdpi/autofill_assistant_bottombar_bg.9.png
rename to chrome/android/features/autofill_assistant/java/res/drawable-hdpi/autofill_assistant_bottombar_bg.9.png
Binary files differ
diff --git a/chrome/android/java/res_autofill_assistant/drawable-hdpi/ic_feedback_black_24dp.png b/chrome/android/features/autofill_assistant/java/res/drawable-hdpi/ic_feedback_black_24dp.png
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable-hdpi/ic_feedback_black_24dp.png
rename to chrome/android/features/autofill_assistant/java/res/drawable-hdpi/ic_feedback_black_24dp.png
Binary files differ
diff --git a/chrome/android/java/res_autofill_assistant/drawable-mdpi/autofill_assistant_bottombar_bg.9.png b/chrome/android/features/autofill_assistant/java/res/drawable-mdpi/autofill_assistant_bottombar_bg.9.png
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable-mdpi/autofill_assistant_bottombar_bg.9.png
rename to chrome/android/features/autofill_assistant/java/res/drawable-mdpi/autofill_assistant_bottombar_bg.9.png
Binary files differ
diff --git a/chrome/android/java/res_autofill_assistant/drawable-mdpi/ic_feedback_black_24dp.png b/chrome/android/features/autofill_assistant/java/res/drawable-mdpi/ic_feedback_black_24dp.png
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable-mdpi/ic_feedback_black_24dp.png
rename to chrome/android/features/autofill_assistant/java/res/drawable-mdpi/ic_feedback_black_24dp.png
Binary files differ
diff --git a/chrome/android/java/res_autofill_assistant/drawable-mdpi/onboarding_background.png b/chrome/android/features/autofill_assistant/java/res/drawable-mdpi/onboarding_background.png
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable-mdpi/onboarding_background.png
rename to chrome/android/features/autofill_assistant/java/res/drawable-mdpi/onboarding_background.png
Binary files differ
diff --git a/chrome/android/java/res_autofill_assistant/drawable-xhdpi/autofill_assistant_bottombar_bg.9.png b/chrome/android/features/autofill_assistant/java/res/drawable-xhdpi/autofill_assistant_bottombar_bg.9.png
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable-xhdpi/autofill_assistant_bottombar_bg.9.png
rename to chrome/android/features/autofill_assistant/java/res/drawable-xhdpi/autofill_assistant_bottombar_bg.9.png
Binary files differ
diff --git a/chrome/android/java/res_autofill_assistant/drawable-xhdpi/ic_feedback_black_24dp.png b/chrome/android/features/autofill_assistant/java/res/drawable-xhdpi/ic_feedback_black_24dp.png
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable-xhdpi/ic_feedback_black_24dp.png
rename to chrome/android/features/autofill_assistant/java/res/drawable-xhdpi/ic_feedback_black_24dp.png
Binary files differ
diff --git a/chrome/android/java/res_autofill_assistant/drawable-xxhdpi/autofill_assistant_bottombar_bg.9.png b/chrome/android/features/autofill_assistant/java/res/drawable-xxhdpi/autofill_assistant_bottombar_bg.9.png
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable-xxhdpi/autofill_assistant_bottombar_bg.9.png
rename to chrome/android/features/autofill_assistant/java/res/drawable-xxhdpi/autofill_assistant_bottombar_bg.9.png
Binary files differ
diff --git a/chrome/android/java/res_autofill_assistant/drawable-xxhdpi/ic_feedback_black_24dp.png b/chrome/android/features/autofill_assistant/java/res/drawable-xxhdpi/ic_feedback_black_24dp.png
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable-xxhdpi/ic_feedback_black_24dp.png
rename to chrome/android/features/autofill_assistant/java/res/drawable-xxhdpi/ic_feedback_black_24dp.png
Binary files differ
diff --git a/chrome/android/java/res_autofill_assistant/drawable-xxhdpi/onboarding_background.png b/chrome/android/features/autofill_assistant/java/res/drawable-xxhdpi/onboarding_background.png
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable-xxhdpi/onboarding_background.png
rename to chrome/android/features/autofill_assistant/java/res/drawable-xxhdpi/onboarding_background.png
Binary files differ
diff --git a/chrome/android/java/res_autofill_assistant/drawable-xxxhdpi/autofill_assistant_bottombar_bg.9.png b/chrome/android/features/autofill_assistant/java/res/drawable-xxxhdpi/autofill_assistant_bottombar_bg.9.png
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable-xxxhdpi/autofill_assistant_bottombar_bg.9.png
rename to chrome/android/features/autofill_assistant/java/res/drawable-xxxhdpi/autofill_assistant_bottombar_bg.9.png
Binary files differ
diff --git a/chrome/android/java/res_autofill_assistant/drawable-xxxhdpi/ic_feedback_black_24dp.png b/chrome/android/features/autofill_assistant/java/res/drawable-xxxhdpi/ic_feedback_black_24dp.png
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable-xxxhdpi/ic_feedback_black_24dp.png
rename to chrome/android/features/autofill_assistant/java/res/drawable-xxxhdpi/ic_feedback_black_24dp.png
Binary files differ
diff --git a/chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_bottombar_spacer.xml b/chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_bottombar_spacer.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_bottombar_spacer.xml
rename to chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_bottombar_spacer.xml
diff --git a/chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_button_hairline_bg.xml b/chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_button_hairline_bg.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_button_hairline_bg.xml
rename to chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_button_hairline_bg.xml
diff --git a/chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_button_hairline_bg_normal.xml b/chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_button_hairline_bg_normal.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_button_hairline_bg_normal.xml
rename to chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_button_hairline_bg_normal.xml
diff --git a/chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_button_hairline_bg_pressed.xml b/chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_button_hairline_bg_pressed.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_button_hairline_bg_pressed.xml
rename to chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_button_hairline_bg_pressed.xml
diff --git a/chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_chip_assistive_bg.xml b/chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_chip_assistive_bg.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_chip_assistive_bg.xml
rename to chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_chip_assistive_bg.xml
diff --git a/chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_chip_assistive_bg_normal.xml b/chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_chip_assistive_bg_normal.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_chip_assistive_bg_normal.xml
rename to chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_chip_assistive_bg_normal.xml
diff --git a/chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_chip_assistive_bg_pressed.xml b/chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_chip_assistive_bg_pressed.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_chip_assistive_bg_pressed.xml
rename to chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_chip_assistive_bg_pressed.xml
diff --git a/chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_default_details.xml b/chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_default_details.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_default_details.xml
rename to chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_default_details.xml
diff --git a/chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_details_bg.xml b/chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_details_bg.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_details_bg.xml
rename to chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_details_bg.xml
diff --git a/chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_lightblue_rect_bg.xml b/chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_lightblue_rect_bg.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_lightblue_rect_bg.xml
rename to chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_lightblue_rect_bg.xml
diff --git a/chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_swipe_indicator.xml b/chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_swipe_indicator.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable/autofill_assistant_swipe_indicator.xml
rename to chrome/android/features/autofill_assistant/java/res/drawable/autofill_assistant_swipe_indicator.xml
diff --git a/chrome/android/java/res_autofill_assistant/drawable/ic_autofill_assistant_24dp.xml b/chrome/android/features/autofill_assistant/java/res/drawable/ic_autofill_assistant_24dp.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/drawable/ic_autofill_assistant_24dp.xml
rename to chrome/android/features/autofill_assistant/java/res/drawable/ic_autofill_assistant_24dp.xml
diff --git a/chrome/android/java/res_autofill_assistant/layout/autofill_assistant_button_filled.xml b/chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_button_filled.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/layout/autofill_assistant_button_filled.xml
rename to chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_button_filled.xml
diff --git a/chrome/android/java/res_autofill_assistant/layout/autofill_assistant_button_hairline.xml b/chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_button_hairline.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/layout/autofill_assistant_button_hairline.xml
rename to chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_button_hairline.xml
diff --git a/chrome/android/java/res_autofill_assistant/layout/autofill_assistant_chip_assistive.xml b/chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_chip_assistive.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/layout/autofill_assistant_chip_assistive.xml
rename to chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_chip_assistive.xml
diff --git a/chrome/android/java/res_autofill_assistant/layout/autofill_assistant_details.xml b/chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_details.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/layout/autofill_assistant_details.xml
rename to chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_details.xml
diff --git a/chrome/android/java/res_autofill_assistant/layout/autofill_assistant_header.xml b/chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_header.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/layout/autofill_assistant_header.xml
rename to chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_header.xml
diff --git a/chrome/android/java/res_autofill_assistant/layout/autofill_assistant_onboarding.xml b/chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_onboarding.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/layout/autofill_assistant_onboarding.xml
rename to chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_onboarding.xml
diff --git a/chrome/android/java/res_autofill_assistant/layout/autofill_assistant_payment_request.xml b/chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_payment_request.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/layout/autofill_assistant_payment_request.xml
rename to chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_payment_request.xml
diff --git a/chrome/android/java/res_autofill_assistant/layout/autofill_assistant_payment_request_bottom_bar.xml b/chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_payment_request_bottom_bar.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/layout/autofill_assistant_payment_request_bottom_bar.xml
rename to chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_payment_request_bottom_bar.xml
diff --git a/chrome/android/java/res_autofill_assistant/layout/autofill_assistant_sheet.xml b/chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_sheet.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/layout/autofill_assistant_sheet.xml
rename to chrome/android/features/autofill_assistant/java/res/layout/autofill_assistant_sheet.xml
diff --git a/chrome/android/java/res_autofill_assistant/values-v17/colors.xml b/chrome/android/features/autofill_assistant/java/res/values-v17/colors.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/values-v17/colors.xml
rename to chrome/android/features/autofill_assistant/java/res/values-v17/colors.xml
diff --git a/chrome/android/java/res_autofill_assistant/values-v17/dimens.xml b/chrome/android/features/autofill_assistant/java/res/values-v17/dimens.xml
similarity index 84%
rename from chrome/android/java/res_autofill_assistant/values-v17/dimens.xml
rename to chrome/android/features/autofill_assistant/java/res/values-v17/dimens.xml
index 84ff5b2..4caeae3 100644
--- a/chrome/android/java/res_autofill_assistant/values-v17/dimens.xml
+++ b/chrome/android/features/autofill_assistant/java/res/values-v17/dimens.xml
@@ -5,4 +5,5 @@
 <resources>
     <dimen name="autofill_assistant_bottombar_horizontal_spacing">24dp</dimen>
     <dimen name="autofill_assistant_bottombar_vertical_spacing">20dp</dimen>
-</resources>
\ No newline at end of file
+    <dimen name="autofill_assistant_details_image_size">48dp</dimen>
+</resources>
diff --git a/chrome/android/java/res_autofill_assistant/values-v17/styles.xml b/chrome/android/features/autofill_assistant/java/res/values-v17/styles.xml
similarity index 100%
rename from chrome/android/java/res_autofill_assistant/values-v17/styles.xml
rename to chrome/android/features/autofill_assistant/java/res/values-v17/styles.xml
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantCoordinator.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantCoordinator.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantCoordinator.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantKeyboardCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantKeyboardCoordinator.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantKeyboardCoordinator.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantKeyboardCoordinator.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantModel.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantModel.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantModel.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantModel.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinator.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinator.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinator.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantSnackbar.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantSnackbar.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantSnackbar.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantSnackbar.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/EditDistance.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/EditDistance.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/EditDistance.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/EditDistance.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/FeedbackContext.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/FeedbackContext.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/FeedbackContext.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/FeedbackContext.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantCarouselCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantCarouselCoordinator.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantCarouselCoordinator.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantCarouselCoordinator.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantCarouselModel.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantCarouselModel.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantCarouselModel.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantCarouselModel.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChip.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChip.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChip.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChip.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChipViewHolder.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChipViewHolder.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChipViewHolder.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChipViewHolder.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetails.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetails.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetails.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetails.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsCoordinator.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsCoordinator.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsCoordinator.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsModel.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsModel.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsModel.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsModel.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsViewBinder.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsViewBinder.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsViewBinder.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsViewBinder.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/header/AnimatedProgressBar.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AnimatedProgressBar.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/header/AnimatedProgressBar.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AnimatedProgressBar.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderCoordinator.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderCoordinator.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderCoordinator.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderDelegate.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderDelegate.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderDelegate.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderDelegate.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderModel.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderModel.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderModel.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderModel.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderViewBinder.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderViewBinder.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderViewBinder.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderViewBinder.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayCoordinator.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayCoordinator.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayCoordinator.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayDelegate.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayDelegate.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayDelegate.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayDelegate.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayModel.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayModel.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayModel.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayModel.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/TouchEventFilterView.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/TouchEventFilterView.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/TouchEventFilterView.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/TouchEventFilterView.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestCoordinator.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestCoordinator.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestCoordinator.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestCoordinator.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestDelegate.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestDelegate.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestDelegate.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestDelegate.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestModel.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestModel.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestModel.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestModel.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestOptions.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestOptions.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestOptions.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestOptions.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequest.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequest.java
similarity index 99%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequest.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequest.java
index 0a983182..1c2b416 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequest.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequest.java
@@ -12,7 +12,7 @@
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.Callback;
-import org.chromium.chrome.R;
+import org.chromium.chrome.autofill_assistant.R;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.autofill.PersonalDataManager;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequestSection.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequestSection.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequestSection.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequestSection.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestBottomBar.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestBottomBar.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestBottomBar.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestBottomBar.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestUI.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestUI.java
similarity index 100%
rename from chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestUI.java
rename to chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestUI.java
diff --git a/chrome/android/features/autofill_assistant/java/strings/android_chrome_autofill_assistant_strings.grd b/chrome/android/features/autofill_assistant/java/strings/android_chrome_autofill_assistant_strings.grd
new file mode 100644
index 0000000..4e19a82c
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/android_chrome_autofill_assistant_strings.grd
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- android_chrome_strings.grd contains strings for VR DFM of Chrome for Android. -->
+<grit current_release="1" latest_public_release="0" output_all_resource_defines="false">
+  <outputs>
+    <output filename="values-am/android_chrome_autofill_assistant_strings.xml" lang="am" type="android" />
+    <output filename="values-ar/android_chrome_autofill_assistant_strings.xml" lang="ar" type="android" />
+    <output filename="values-bg/android_chrome_autofill_assistant_strings.xml" lang="bg" type="android" />
+    <output filename="values-ca/android_chrome_autofill_assistant_strings.xml" lang="ca" type="android" />
+    <output filename="values-cs/android_chrome_autofill_assistant_strings.xml" lang="cs" type="android" />
+    <output filename="values-da/android_chrome_autofill_assistant_strings.xml" lang="da" type="android" />
+    <output filename="values-de/android_chrome_autofill_assistant_strings.xml" lang="de" type="android" />
+    <output filename="values-el/android_chrome_autofill_assistant_strings.xml" lang="el" type="android" />
+    <output filename="values/android_chrome_autofill_assistant_strings.xml" lang="en" type="android" />
+    <output filename="values-en-rGB/android_chrome_autofill_assistant_strings.xml" lang="en-GB" type="android" />
+    <output filename="values-es/android_chrome_autofill_assistant_strings.xml" lang="es" type="android" />
+    <output filename="values-es-rUS/android_chrome_autofill_assistant_strings.xml" lang="es-419" type="android" />
+    <output filename="values-fa/android_chrome_autofill_assistant_strings.xml" lang="fa" type="android" />
+    <output filename="values-fi/android_chrome_autofill_assistant_strings.xml" lang="fi" type="android" />
+    <output filename="values-tl/android_chrome_autofill_assistant_strings.xml" lang="fil" type="android" />
+    <output filename="values-fr/android_chrome_autofill_assistant_strings.xml" lang="fr" type="android" />
+    <output filename="values-hi/android_chrome_autofill_assistant_strings.xml" lang="hi" type="android" />
+    <output filename="values-hr/android_chrome_autofill_assistant_strings.xml" lang="hr" type="android" />
+    <output filename="values-hu/android_chrome_autofill_assistant_strings.xml" lang="hu" type="android" />
+    <output filename="values-in/android_chrome_autofill_assistant_strings.xml" lang="id" type="android" />
+    <output filename="values-it/android_chrome_autofill_assistant_strings.xml" lang="it" type="android" />
+    <output filename="values-iw/android_chrome_autofill_assistant_strings.xml" lang="iw" type="android" />
+    <output filename="values-ja/android_chrome_autofill_assistant_strings.xml" lang="ja" type="android" />
+    <output filename="values-ko/android_chrome_autofill_assistant_strings.xml" lang="ko" type="android" />
+    <output filename="values-lt/android_chrome_autofill_assistant_strings.xml" lang="lt" type="android" />
+    <output filename="values-lv/android_chrome_autofill_assistant_strings.xml" lang="lv" type="android" />
+    <output filename="values-nl/android_chrome_autofill_assistant_strings.xml" lang="nl" type="android" />
+    <output filename="values-nb/android_chrome_autofill_assistant_strings.xml" lang="no" type="android" />
+    <output filename="values-pl/android_chrome_autofill_assistant_strings.xml" lang="pl" type="android" />
+    <output filename="values-pt-rBR/android_chrome_autofill_assistant_strings.xml" lang="pt-BR" type="android" />
+    <output filename="values-pt-rPT/android_chrome_autofill_assistant_strings.xml" lang="pt-PT" type="android" />
+    <output filename="values-ro/android_chrome_autofill_assistant_strings.xml" lang="ro" type="android" />
+    <output filename="values-ru/android_chrome_autofill_assistant_strings.xml" lang="ru" type="android" />
+    <output filename="values-sk/android_chrome_autofill_assistant_strings.xml" lang="sk" type="android" />
+    <output filename="values-sl/android_chrome_autofill_assistant_strings.xml" lang="sl" type="android" />
+    <output filename="values-sr/android_chrome_autofill_assistant_strings.xml" lang="sr" type="android" />
+    <output filename="values-sv/android_chrome_autofill_assistant_strings.xml" lang="sv" type="android" />
+    <output filename="values-sw/android_chrome_autofill_assistant_strings.xml" lang="sw" type="android" />
+    <output filename="values-th/android_chrome_autofill_assistant_strings.xml" lang="th" type="android" />
+    <output filename="values-tr/android_chrome_autofill_assistant_strings.xml" lang="tr" type="android" />
+    <output filename="values-uk/android_chrome_autofill_assistant_strings.xml" lang="uk" type="android" />
+    <output filename="values-vi/android_chrome_autofill_assistant_strings.xml" lang="vi" type="android" />
+    <output filename="values-zh-rCN/android_chrome_autofill_assistant_strings.xml" lang="zh-CN" type="android" />
+    <output filename="values-zh-rTW/android_chrome_autofill_assistant_strings.xml" lang="zh-TW" type="android" />
+  </outputs>
+  <translations>
+    <file lang="am" path="translations/android_chrome_autofill_assistant_strings_am.xtb" />
+    <file lang="ar" path="translations/android_chrome_autofill_assistant_strings_ar.xtb" />
+    <file lang="bg" path="translations/android_chrome_autofill_assistant_strings_bg.xtb" />
+    <file lang="bn" path="translations/android_chrome_autofill_assistant_strings_bn.xtb" />
+    <file lang="ca" path="translations/android_chrome_autofill_assistant_strings_ca.xtb" />
+    <file lang="cs" path="translations/android_chrome_autofill_assistant_strings_cs.xtb" />
+    <file lang="da" path="translations/android_chrome_autofill_assistant_strings_da.xtb" />
+    <file lang="de" path="translations/android_chrome_autofill_assistant_strings_de.xtb" />
+    <file lang="el" path="translations/android_chrome_autofill_assistant_strings_el.xtb" />
+    <file lang="en-GB" path="translations/android_chrome_autofill_assistant_strings_en-GB.xtb" />
+    <file lang="es" path="translations/android_chrome_autofill_assistant_strings_es.xtb" />
+    <file lang="es-419" path="translations/android_chrome_autofill_assistant_strings_es-419.xtb" />
+    <file lang="et" path="translations/android_chrome_autofill_assistant_strings_et.xtb" />
+    <file lang="fa" path="translations/android_chrome_autofill_assistant_strings_fa.xtb" />
+    <file lang="fi" path="translations/android_chrome_autofill_assistant_strings_fi.xtb" />
+    <file lang="fil" path="translations/android_chrome_autofill_assistant_strings_fil.xtb" />
+    <file lang="fr" path="translations/android_chrome_autofill_assistant_strings_fr.xtb" />
+    <file lang="gu" path="translations/android_chrome_autofill_assistant_strings_gu.xtb" />
+    <file lang="hi" path="translations/android_chrome_autofill_assistant_strings_hi.xtb" />
+    <file lang="hr" path="translations/android_chrome_autofill_assistant_strings_hr.xtb" />
+    <file lang="hu" path="translations/android_chrome_autofill_assistant_strings_hu.xtb" />
+    <file lang="id" path="translations/android_chrome_autofill_assistant_strings_id.xtb" />
+    <file lang="it" path="translations/android_chrome_autofill_assistant_strings_it.xtb" />
+    <file lang="iw" path="translations/android_chrome_autofill_assistant_strings_iw.xtb" />
+    <file lang="ja" path="translations/android_chrome_autofill_assistant_strings_ja.xtb" />
+    <file lang="ko" path="translations/android_chrome_autofill_assistant_strings_ko.xtb" />
+    <file lang="kn" path="translations/android_chrome_autofill_assistant_strings_kn.xtb" />
+    <file lang="lt" path="translations/android_chrome_autofill_assistant_strings_lt.xtb" />
+    <file lang="lv" path="translations/android_chrome_autofill_assistant_strings_lv.xtb" />
+    <file lang="ml" path="translations/android_chrome_autofill_assistant_strings_ml.xtb" />
+    <file lang="mr" path="translations/android_chrome_autofill_assistant_strings_mr.xtb" />
+    <file lang="ms" path="translations/android_chrome_autofill_assistant_strings_ms.xtb" />
+    <file lang="nl" path="translations/android_chrome_autofill_assistant_strings_nl.xtb" />
+    <file lang="no" path="translations/android_chrome_autofill_assistant_strings_no.xtb" />
+    <file lang="pl" path="translations/android_chrome_autofill_assistant_strings_pl.xtb" />
+    <file lang="pt-BR" path="translations/android_chrome_autofill_assistant_strings_pt-BR.xtb" />
+    <file lang="pt-PT" path="translations/android_chrome_autofill_assistant_strings_pt-PT.xtb" />
+    <file lang="ro" path="translations/android_chrome_autofill_assistant_strings_ro.xtb" />
+    <file lang="ru" path="translations/android_chrome_autofill_assistant_strings_ru.xtb" />
+    <file lang="sk" path="translations/android_chrome_autofill_assistant_strings_sk.xtb" />
+    <file lang="sl" path="translations/android_chrome_autofill_assistant_strings_sl.xtb" />
+    <file lang="sr" path="translations/android_chrome_autofill_assistant_strings_sr.xtb" />
+    <file lang="sv" path="translations/android_chrome_autofill_assistant_strings_sv.xtb" />
+    <file lang="sw" path="translations/android_chrome_autofill_assistant_strings_sw.xtb" />
+    <file lang="ta" path="translations/android_chrome_autofill_assistant_strings_ta.xtb" />
+    <file lang="te" path="translations/android_chrome_autofill_assistant_strings_te.xtb" />
+    <file lang="th" path="translations/android_chrome_autofill_assistant_strings_th.xtb" />
+    <file lang="tr" path="translations/android_chrome_autofill_assistant_strings_tr.xtb" />
+    <file lang="uk" path="translations/android_chrome_autofill_assistant_strings_uk.xtb" />
+    <file lang="vi" path="translations/android_chrome_autofill_assistant_strings_vi.xtb" />
+    <file lang="zh-CN" path="translations/android_chrome_autofill_assistant_strings_zh-CN.xtb" />
+    <file lang="zh-TW" path="translations/android_chrome_autofill_assistant_strings_zh-TW.xtb" />
+  </translations>
+  <release allow_pseudo="false" seq="1">
+    <messages fallback_to_english="true">
+       <message name="IDS_AUTOFILL_ASSISTANT_ONBOARDING_TITLE" desc="Onboarding title for the autofill assistant.">
+        Google Assistant\nin Chrome
+      </message>
+      <message name="IDS_INIT_OK" desc="Init screen confirmation text.">
+        I accept
+      </message>
+      <message name="IDS_AUTOFILL_ASSISTANT_INIT_MESSAGE" desc="Onboarding message describing autofill assistant's capability.">
+        Google Assistant saves you time by helping you check out on the web
+      </message>
+      <message name="IDS_AUTOFILL_ASSISTANT_GOOGLE_TERMS_DESCRIPTION" desc="Message linking to the Google terms and conditions for Google Assistant in Chrome.">
+        Chrome will send the site’s URL and content as well as your email and credit card type saved in Chrome to Google. You can turn this off in Chrome settings. <ph name="BEGIN_LINK">&lt;link&gt;</ph>Learn&#xA0;more<ph name="END_LINK">&lt;/link&gt;</ph>
+      </message>
+      <message name="IDS_AUTOFILL_ASSISTANT_GOOGLE_TERMS_URL" desc="URL for Google Autofill Assistant Terms of Service" translateable="false">
+        http://support.google.com/assistant?p=fast_checkout
+      </message>
+      <message name="IDS_AUTOFILL_ASSISTANT_3RD_PARTY_TERMS_ACCEPT" desc="Message that indicates that the user agrees to the terms and conditions of a 3rd party's domain, e.g., 'odeon.co.uk'.">
+        I agree to the terms &amp; conditions, privacy policy, and right of withdrawal of <ph name="BEGIN_BOLD">&lt;b&gt;</ph><ph name="DOMAIN">%1$s<ex>google.com</ex></ph><ph name="END_BOLD">&lt;/b&gt;</ph>
+      </message>
+      <message name="IDS_AUTOFILL_ASSISTANT_3RD_PARTY_TERMS_REVIEW" desc="Message that indicates that the user wants to review the terms and conditions of a 3rd party's domain, e.g., 'odeon.co.uk'.">
+        Read and agree to the terms &amp; conditions on <ph name="BEGIN_BOLD">&lt;b&gt;</ph><ph name="DOMAIN">%1$s<ex>google.com</ex></ph><ph name="END_BOLD">&lt;/b&gt;</ph> later
+      </message>
+      <message name="IDS_AUTOFILL_ASSISTANT_3RD_PARTY_PRIVACY_NOTICE" desc="Privacy notice telling users that autofill assistant will send personal data to a third party’s website.">
+        Chrome will send personal data you selected to <ph name="BEGIN_BOLD">&lt;b&gt;</ph><ph name="DOMAIN">%1$s<ex>google.com</ex></ph><ph name="END_BOLD">&lt;/b&gt;</ph>
+      </message>
+      <message name="IDS_AUTOFILL_ASSISTANT_AVAILABLE_ACCESSIBILITY" desc="Accessibility description of Autofill Assistant is available.">
+        Google Assistant in Chrome is available near bottom of the screen
+      </message>
+      <message name="IDS_AUTOFILL_ASSISTANT_FIRST_RUN_ACCESSIBILITY" desc="Accessibility description of Autofill Assistant first run screen is shown.">
+        Google Assistant in Chrome first run screen is shown
+      </message>
+      <message name="IDS_AUTOFILL_ASSISTANT_PAYMENT_INFO_CONFIRM" desc="Text on the payment request primary button to confirm payment information [CHAR-LIMIT=32]">
+        Continue
+      </message>
+      <message name="IDS_AUTOFILL_ASSISTANT_GIVE_UP"
+               desc="Text label that is shown when autofill assistant cannot help anymore, because of a user action."
+               internal_comment="TODO(wnwen): Remove duplication in components/autofill_assistant_strings.grdp">
+        Looks like you navigated off on your own, I’ll leave you to it. Have a nice day :)
+      </message>
+    </messages>
+  </release>
+</grit>
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_am.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_am.xtb
new file mode 100644
index 0000000..92406ec2
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_am.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="am">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ar.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ar.xtb
new file mode 100644
index 0000000..198ea62
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ar.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ar">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_bg.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_bg.xtb
new file mode 100644
index 0000000..6681995
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_bg.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="bg">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_bn.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_bn.xtb
new file mode 100644
index 0000000..eca68d46
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_bn.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="bn">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ca.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ca.xtb
new file mode 100644
index 0000000..71cdd772
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ca.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ca">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_cs.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_cs.xtb
new file mode 100644
index 0000000..dc153a85
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_cs.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="cs">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_da.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_da.xtb
new file mode 100644
index 0000000..1256832
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_da.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="da">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_de.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_de.xtb
new file mode 100644
index 0000000..43dd909
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_de.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="de">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_el.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_el.xtb
new file mode 100644
index 0000000..1b096642
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_el.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="el">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_en-GB.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_en-GB.xtb
new file mode 100644
index 0000000..12c3fa00
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_en-GB.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="en-GB">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_es-419.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_es-419.xtb
new file mode 100644
index 0000000..b652ed0
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_es-419.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="es-419">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_es.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_es.xtb
new file mode 100644
index 0000000..4d4f400
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_es.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="es">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_et.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_et.xtb
new file mode 100644
index 0000000..ab777bc5
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_et.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="et">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_fa.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_fa.xtb
new file mode 100644
index 0000000..4cff15d
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_fa.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="fa">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_fi.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_fi.xtb
new file mode 100644
index 0000000..60ba9aa
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_fi.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="fi">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_fil.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_fil.xtb
new file mode 100644
index 0000000..8f6a880
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_fil.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="fil">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_fr.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_fr.xtb
new file mode 100644
index 0000000..bf48975a
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_fr.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="fr">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_gu.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_gu.xtb
new file mode 100644
index 0000000..7969d06
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_gu.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="gu">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_hi.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_hi.xtb
new file mode 100644
index 0000000..279503cd
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_hi.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="hi">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_hr.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_hr.xtb
new file mode 100644
index 0000000..9ec62af
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_hr.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="hr">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_hu.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_hu.xtb
new file mode 100644
index 0000000..bdc02ee
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_hu.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="hu">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_id.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_id.xtb
new file mode 100644
index 0000000..5f2882d
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_id.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="id">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_it.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_it.xtb
new file mode 100644
index 0000000..e7df702
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_it.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="it">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_iw.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_iw.xtb
new file mode 100644
index 0000000..a29d4ad
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_iw.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="iw">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ja.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ja.xtb
new file mode 100644
index 0000000..d8a3543
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ja.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ja">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_kn.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_kn.xtb
new file mode 100644
index 0000000..4ecb12ba
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_kn.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="kn">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ko.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ko.xtb
new file mode 100644
index 0000000..558b05b
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ko.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ko">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_lt.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_lt.xtb
new file mode 100644
index 0000000..f20c0fa2
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_lt.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="lt">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_lv.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_lv.xtb
new file mode 100644
index 0000000..6f3afbc
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_lv.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="lv">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ml.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ml.xtb
new file mode 100644
index 0000000..e01197e
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ml.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ml">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_mr.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_mr.xtb
new file mode 100644
index 0000000..b137924e
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_mr.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="mr">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ms.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ms.xtb
new file mode 100644
index 0000000..518685dd
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ms.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ms">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_nl.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_nl.xtb
new file mode 100644
index 0000000..05ab957
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_nl.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="nl">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_no.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_no.xtb
new file mode 100644
index 0000000..ede4de30
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_no.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="no">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_pl.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_pl.xtb
new file mode 100644
index 0000000..1bf17bd5
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_pl.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="pl">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_pt-BR.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_pt-BR.xtb
new file mode 100644
index 0000000..de39dfa
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_pt-BR.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="pt-BR">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_pt-PT.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_pt-PT.xtb
new file mode 100644
index 0000000..0b98ee77
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_pt-PT.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="pt-PT">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ro.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ro.xtb
new file mode 100644
index 0000000..7129eb4
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ro.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ro">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ru.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ru.xtb
new file mode 100644
index 0000000..6dfaa442
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ru.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ru">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_sk.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_sk.xtb
new file mode 100644
index 0000000..202e515a
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_sk.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sk">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_sl.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_sl.xtb
new file mode 100644
index 0000000..31b5a1a
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_sl.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sl">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_sr.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_sr.xtb
new file mode 100644
index 0000000..984d7192
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_sr.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sr">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_sv.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_sv.xtb
new file mode 100644
index 0000000..9a787b8
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_sv.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sv">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_sw.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_sw.xtb
new file mode 100644
index 0000000..9aa61cb
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_sw.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="sw">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ta.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ta.xtb
new file mode 100644
index 0000000..c983c75f2
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_ta.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="ta">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_te.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_te.xtb
new file mode 100644
index 0000000..7affb9a
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_te.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="te">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_th.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_th.xtb
new file mode 100644
index 0000000..dbe6a601
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_th.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="th">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_tr.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_tr.xtb
new file mode 100644
index 0000000..d99480c0
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_tr.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="tr">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_uk.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_uk.xtb
new file mode 100644
index 0000000..6e80099d
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_uk.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="uk">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_vi.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_vi.xtb
new file mode 100644
index 0000000..8a42ab1
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_vi.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="vi">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_zh-CN.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_zh-CN.xtb
new file mode 100644
index 0000000..c7d76e8
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_zh-CN.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="zh-CN">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_zh-TW.xtb b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_zh-TW.xtb
new file mode 100644
index 0000000..3e0c306
--- /dev/null
+++ b/chrome/android/features/autofill_assistant/java/strings/translations/android_chrome_autofill_assistant_strings_zh-TW.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>
+<!DOCTYPE translationbundle>
+<translationbundle lang="zh-TW">
+</translationbundle>
\ No newline at end of file
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java
similarity index 100%
rename from chrome/android/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java
rename to chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill_assistant/EditDistanceTest.java b/chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/EditDistanceTest.java
similarity index 100%
rename from chrome/android/javatests/src/org/chromium/chrome/browser/autofill_assistant/EditDistanceTest.java
rename to chrome/android/features/autofill_assistant/javatests/src/org/chromium/chrome/browser/autofill_assistant/EditDistanceTest.java
diff --git a/chrome/android/features/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java b/chrome/android/features/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
index aa51a9b..6edd39c 100644
--- a/chrome/android/features/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
+++ b/chrome/android/features/touchless/java/src/org/chromium/chrome/browser/touchless/NoTouchActivity.java
@@ -17,10 +17,9 @@
 import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBuilder;
 import org.chromium.chrome.browser.tab.TabRedirectHandler;
 import org.chromium.chrome.browser.tab.TabState;
-import org.chromium.chrome.browser.tab.TabUma.TabCreationState;
-import org.chromium.chrome.browser.tabmodel.TabLaunchType;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.common.Referrer;
 import org.chromium.ui.base.PageTransition;
@@ -168,8 +167,10 @@
         TabState tabState = TabState.restoreTabState(savedInstanceState);
         assert tabState != null;
 
-        return new Tab(tabId, Tab.INVALID_TAB_ID, false, getWindowAndroid(),
-                TabLaunchType.FROM_RESTORE, TabCreationState.FROZEN_ON_RESTORE, tabState);
+        return TabBuilder.createFromFrozenState(tabState)
+                .setId(tabId)
+                .setWindow(getWindowAndroid())
+                .build();
     }
 
     @Override
diff --git a/chrome/android/java/res/layout/bottom_control_container.xml b/chrome/android/java/res/layout/bottom_control_container.xml
new file mode 100644
index 0000000..49d44956
--- /dev/null
+++ b/chrome/android/java/res/layout/bottom_control_container.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2019 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<org.chromium.chrome.browser.toolbar.bottom.ScrollingBottomViewResourceFrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content" >
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/bottom_toolbar_height_with_shadow" >
+
+        <ImageView
+            android:id="@+id/bottom_container_top_shadow"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/toolbar_shadow_height"
+            android:src="@drawable/modern_toolbar_shadow"
+            android:scaleType="fitXY"
+            android:scaleY="-1"
+            tools:ignore="ContentDescription" />
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:id="@+id/bottom_container_slot">
+
+            <ViewStub
+                android:id="@+id/bottom_toolbar_stub"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="start|bottom"
+                android:inflatedId="@+id/bottom_toolbar"
+                android:layout="@layout/bottom_toolbar" />
+
+        </FrameLayout>
+
+    </LinearLayout>
+
+</org.chromium.chrome.browser.toolbar.bottom.ScrollingBottomViewResourceFrameLayout>
diff --git a/chrome/android/java/res/layout/bottom_tab_grid_toolbar.xml b/chrome/android/java/res/layout/bottom_tab_grid_toolbar.xml
index 6f5bf508..9a6c369 100644
--- a/chrome/android/java/res/layout/bottom_tab_grid_toolbar.xml
+++ b/chrome/android/java/res/layout/bottom_tab_grid_toolbar.xml
@@ -3,10 +3,9 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<org.chromium.chrome.browser.tasks.tab_list_ui.BottomTabToolbarView
+<org.chromium.chrome.browser.tasks.tab_list_ui.BottomTabListToolbarView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content" >
     <LinearLayout
@@ -16,14 +15,12 @@
         android:orientation="horizontal"
         android:gravity="center_vertical"
         android:background="@color/modern_primary_color">
-        <org.chromium.chrome.browser.widget.ListMenuButton
+        <org.chromium.ui.widget.ChromeImageView
             android:id="@+id/toolbar_left_button"
-            android:layout_width="@dimen/bottom_tab_grid_toolbar_icon_size"
-            android:layout_height="match_parent"
+            style="@style/BottomToolbarButton"
             android:src="@drawable/ic_expand_more_black_24dp"
             app:tint="@color/standard_mode_tint"
-            android:background="?attr/selectableItemBackground"
-            android:contentDescription="@string/accessibility_collapse_section_header" />
+            android:contentDescription="@string/accessibility_bottom_tab_grid_close_tab_sheet" />
          <TextView
              android:id="@+id/title"
              android:layout_height="wrap_content"
@@ -32,15 +29,12 @@
              android:singleLine="true"
              android:ellipsize="end"
              android:textAppearance="@style/TextAppearance.BlackTitle1"
-             android:gravity="center"
-             android:focusableInTouchMode="true" />
-        <org.chromium.chrome.browser.widget.ListMenuButton
+             android:gravity="center"/>
+        <org.chromium.ui.widget.ChromeImageView
             android:id="@+id/toolbar_right_button"
-            android:layout_width="@dimen/bottom_tab_grid_toolbar_icon_size"
-            android:layout_height="match_parent"
+            style="@style/BottomToolbarButton"
             android:src="@drawable/plus"
             app:tint="@color/standard_mode_tint"
-            android:background="?attr/selectableItemBackground"
             android:contentDescription="@string/bottom_tab_grid_new_tab" />
    </LinearLayout>
-</org.chromium.chrome.browser.tasks.tab_list_ui.BottomTabToolbarView>
+</org.chromium.chrome.browser.tasks.tab_list_ui.BottomTabListToolbarView>
diff --git a/chrome/android/java/res/layout/bottom_tab_strip_toolbar.xml b/chrome/android/java/res/layout/bottom_tab_strip_toolbar.xml
index ff84919..f682754 100644
--- a/chrome/android/java/res/layout/bottom_tab_strip_toolbar.xml
+++ b/chrome/android/java/res/layout/bottom_tab_strip_toolbar.xml
@@ -3,12 +3,11 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<org.chromium.chrome.browser.tasks.tab_list_ui.BottomTabToolbarView
+<org.chromium.chrome.browser.tasks.tab_list_ui.BottomTabListToolbarView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content" >
+    android:layout_height="match_parent" >
     <LinearLayout
         android:id="@+id/main_content"
         android:layout_width="match_parent"
@@ -16,30 +15,22 @@
         android:orientation="horizontal"
         android:gravity="center_vertical"
         android:background="@color/modern_primary_color">
-        <org.chromium.chrome.browser.widget.ListMenuButton
+        <org.chromium.ui.widget.ChromeImageView
             android:id="@+id/toolbar_left_button"
-            android:layout_width="@dimen/bottom_tab_grid_toolbar_icon_size"
-            android:layout_height="match_parent"
+            style="@style/BottomToolbarButton"
             android:src="@drawable/ic_expand_less_black_24dp"
             app:tint="@color/standard_mode_tint"
-            android:background="?attr/selectableItemBackground"
-            android:contentDescription="@string/accessibility_expand_section_header" />
-
+            android:contentDescription="@string/accessibility_bottom_tab_strip_expand_tab_sheet" />
         <FrameLayout
             android:id="@+id/toolbar_container_view"
             android:layout_width="0dp"
             android:layout_height="match_parent"
-            android:layout_weight="1"
-            android:background="@android:color/holo_orange_dark"
-            android:orientation="horizontal" />
-
-        <org.chromium.chrome.browser.widget.ListMenuButton
+            android:layout_weight="1"/>
+        <org.chromium.ui.widget.ChromeImageView
             android:id="@+id/toolbar_right_button"
-            android:layout_width="@dimen/bottom_tab_grid_toolbar_icon_size"
-            android:layout_height="match_parent"
-            android:src="@drawable/plus"
+            style="@style/BottomToolbarButton"
             app:tint="@color/standard_mode_tint"
-            android:background="?attr/selectableItemBackground"
+            android:src="@drawable/plus"
             android:contentDescription="@string/bottom_tab_grid_new_tab" />
    </LinearLayout>
-</org.chromium.chrome.browser.tasks.tab_list_ui.BottomTabToolbarView>
+</org.chromium.chrome.browser.tasks.tab_list_ui.BottomTabListToolbarView>
diff --git a/chrome/android/java/res/layout/bottom_toolbar.xml b/chrome/android/java/res/layout/bottom_toolbar.xml
index cf50148..475e391 100644
--- a/chrome/android/java/res/layout/bottom_toolbar.xml
+++ b/chrome/android/java/res/layout/bottom_toolbar.xml
@@ -6,14 +6,14 @@
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content" >
+    android:layout_height="@dimen/bottom_toolbar_height" >
 
   <include layout="@layout/bottom_toolbar_browsing" />
 
   <ViewStub
       android:id="@+id/bottom_toolbar_tab_switcher_mode_stub"
       android:layout_width="match_parent"
-      android:layout_height="@dimen/bottom_toolbar_height_with_shadow"
+      android:layout_height="wrap_content"
       android:inflatedId="@+id/bottom_toolbar_tab_switcher_mode"
       android:layout="@layout/bottom_toolbar_tab_switcher" />
 
diff --git a/chrome/android/java/res/layout/bottom_toolbar_browsing.xml b/chrome/android/java/res/layout/bottom_toolbar_browsing.xml
index a068f3aa..896fe0d 100644
--- a/chrome/android/java/res/layout/bottom_toolbar_browsing.xml
+++ b/chrome/android/java/res/layout/bottom_toolbar_browsing.xml
@@ -3,80 +3,57 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<org.chromium.chrome.browser.toolbar.bottom.ScrollingBottomViewResourceFrameLayout
+
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:id="@+id/bottom_toolbar_control_container"
+    android:id="@+id/bottom_toolbar_browsing"
+    android:orientation="horizontal"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:minHeight="@dimen/bottom_toolbar_height" >
+    android:minHeight="@dimen/bottom_toolbar_height"
+    android:background="@color/modern_primary_color"
+    android:layout_gravity="top|center_horizontal"
+    android:paddingStart="@dimen/bottom_toolbar_start_padding"
+    android:paddingEnd="@dimen/bottom_toolbar_end_padding" >
 
-    <ImageView
-        android:id="@+id/bottom_toolbar_top_shadow"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/toolbar_shadow_height"
-        android:src="@drawable/modern_toolbar_shadow"
-        android:scaleType="fitXY"
-        android:scaleY="-1"
-        tools:ignore="ContentDescription" />
+    <org.chromium.chrome.browser.toolbar.HomeButton
+        android:id="@+id/home_button"
+        app:tint="@color/standard_mode_tint"
+        style="@style/BottomToolbarButton"
+        android:contentDescription="@string/accessibility_toolbar_btn_home" />
 
-    <org.chromium.chrome.browser.widget.bottomsheet.TouchRestrictingFrameLayout
-        android:id="@+id/bottom_toolbar_container"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/bottom_toolbar_height"
-        android:layout_marginTop="@dimen/toolbar_shadow_height" >
+    <include layout="@layout/toolbar_space" />
 
-        <LinearLayout
-            android:id="@+id/bottom_toolbar_buttons"
-            android:orientation="horizontal"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:background="@color/modern_primary_color"
-            android:layout_gravity="top|center_horizontal"
-            android:paddingStart="@dimen/bottom_toolbar_start_padding"
-            android:paddingEnd="@dimen/bottom_toolbar_end_padding" >
+    <org.chromium.chrome.browser.toolbar.bottom.ShareButton
+        android:id="@+id/share_button"
+        style="@style/BottomToolbarButton"
+        android:src="@drawable/ic_share_white_24dp"
+        app:tint="@color/standard_mode_tint"
+        android:contentDescription="@string/share" />
 
-            <org.chromium.chrome.browser.toolbar.HomeButton
-                android:id="@+id/home_button"
-                app:tint="@color/standard_mode_tint"
-                style="@style/BottomToolbarButton"
-                android:contentDescription="@string/accessibility_toolbar_btn_home" />
+    <include layout="@layout/toolbar_space" />
 
-            <include layout="@layout/toolbar_space" />
+    <org.chromium.chrome.browser.toolbar.bottom.SearchAccelerator
+        android:id="@+id/search_accelerator"
+        android:layout_width="@dimen/search_accelerator_width"
+        android:layout_height="@dimen/search_accelerator_height"
+        android:layout_gravity="center"
+        android:paddingTop="@dimen/search_accelerator_height_padding"
+        android:paddingBottom="@dimen/search_accelerator_height_padding"
+        android:src="@drawable/ic_search"
+        android:contentDescription="@string/accessibility_toolbar_btn_search_accelerator" />
 
-            <org.chromium.chrome.browser.toolbar.bottom.ShareButton
-                android:id="@+id/share_button"
-                style="@style/BottomToolbarButton"
-                android:src="@drawable/ic_share_white_24dp"
-                app:tint="@color/standard_mode_tint"
-                android:contentDescription="@string/share" />
+    <include layout="@layout/toolbar_space" />
 
-            <include layout="@layout/toolbar_space" />
+    <org.chromium.chrome.browser.toolbar.TabSwitcherButtonView
+        android:id="@+id/tab_switcher_button"
+        style="@style/BottomToolbarButton"
+        android:contentDescription="@string/accessibility_toolbar_btn_tabswitcher_toggle_default" />
 
-            <org.chromium.chrome.browser.toolbar.bottom.SearchAccelerator
-                android:id="@+id/search_accelerator"
-                android:layout_width="@dimen/search_accelerator_width"
-                android:layout_height="@dimen/search_accelerator_height"
-                android:layout_gravity="center"
-                android:paddingTop="@dimen/search_accelerator_height_padding"
-                android:paddingBottom="@dimen/search_accelerator_height_padding"
-                android:src="@drawable/ic_search"
-                android:contentDescription="@string/accessibility_toolbar_btn_search_accelerator" />
+    <include layout="@layout/toolbar_space" />
 
-            <include layout="@layout/toolbar_space" />
+    <include layout="@layout/menu_button" />
 
-            <org.chromium.chrome.browser.toolbar.TabSwitcherButtonView
-                android:id="@+id/tab_switcher_button"
-                style="@style/BottomToolbarButton"
-                android:contentDescription="@string/accessibility_toolbar_btn_tabswitcher_toggle_default" />
+</LinearLayout>
 
-            <include layout="@layout/toolbar_space" />
-
-            <include layout="@layout/menu_button" />
-
-        </LinearLayout>
-
-    </org.chromium.chrome.browser.widget.bottomsheet.TouchRestrictingFrameLayout>
-
-</org.chromium.chrome.browser.toolbar.bottom.ScrollingBottomViewResourceFrameLayout>
diff --git a/chrome/android/java/res/layout/bottom_toolbar_tab_switcher.xml b/chrome/android/java/res/layout/bottom_toolbar_tab_switcher.xml
index 1298d66..8f70ec69 100644
--- a/chrome/android/java/res/layout/bottom_toolbar_tab_switcher.xml
+++ b/chrome/android/java/res/layout/bottom_toolbar_tab_switcher.xml
@@ -14,15 +14,6 @@
     android:visibility="gone"
     android:clickable="true" >
 
-    <ImageView
-        android:id="@+id/bottom_toolbar_top_shadow"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/toolbar_shadow_height"
-        android:src="@drawable/modern_toolbar_shadow"
-        android:scaleType="fitXY"
-        android:scaleY="-1"
-        tools:ignore="ContentDescription" />
-
     <LinearLayout
         android:id="@+id/bottom_toolbar_buttons"
         android:orientation="horizontal"
diff --git a/chrome/android/java/res/layout/confirm_import_sync_data.xml b/chrome/android/java/res/layout/confirm_import_sync_data.xml
index 37338bc..a3e10426 100644
--- a/chrome/android/java/res/layout/confirm_import_sync_data.xml
+++ b/chrome/android/java/res/layout/confirm_import_sync_data.xml
@@ -29,6 +29,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="16dp"
+            android:background="?android:attr/selectableItemBackground"
             app:titleText="@string/sync_import_existing_data" />
 
         <org.chromium.chrome.browser.widget.RadioButtonWithDescription
@@ -36,6 +37,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="16dp"
+            android:background="?android:attr/selectableItemBackground"
             app:titleText="@string/sync_keep_existing_data_separate" />
 
     </LinearLayout>
diff --git a/chrome/android/java/res/layout/main.xml b/chrome/android/java/res/layout/main.xml
index 3c55737..833ea76 100644
--- a/chrome/android/java/res/layout/main.xml
+++ b/chrome/android/java/res/layout/main.xml
@@ -84,12 +84,12 @@
             android:visibility="gone" />
 
         <ViewStub
-            android:id="@+id/bottom_toolbar_stub"
+            android:id="@+id/bottom_controls_stub"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_gravity="start|bottom"
-            android:inflatedId="@+id/bottom_toolbar"
-            android:layout="@layout/bottom_toolbar" />
+            android:inflatedId="@+id/bottom_controls"
+            android:layout="@layout/bottom_control_container" />
 
         <ViewStub
             android:id="@+id/control_container_stub"
diff --git a/chrome/android/java/res/layout/radio_button_with_description.xml b/chrome/android/java/res/layout/radio_button_with_description.xml
index d4b6f0d..5878855 100644
--- a/chrome/android/java/res/layout/radio_button_with_description.xml
+++ b/chrome/android/java/res/layout/radio_button_with_description.xml
@@ -12,6 +12,9 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_centerVertical="true"
+        android:clickable="false"
+        android:focusable="false"
+        android:background="@null"
         android:paddingEnd="16dp" />
 
     <TextView
diff --git a/chrome/android/java/res/layout/tri_state_site_settings_preference.xml b/chrome/android/java/res/layout/tri_state_site_settings_preference.xml
index a1e4e480..fff1771 100644
--- a/chrome/android/java/res/layout/tri_state_site_settings_preference.xml
+++ b/chrome/android/java/res/layout/tri_state_site_settings_preference.xml
@@ -8,9 +8,9 @@
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
-    style="@style/PreferenceLayout"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:focusable="false"
     android:orientation="vertical">
 
     <org.chromium.chrome.browser.widget.RadioButtonWithDescription
@@ -18,6 +18,8 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginBottom="16dp"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+        android:background="?android:attr/selectableItemBackground"
         app:titleText="@string/website_settings_category_allowed" />
 
     <org.chromium.chrome.browser.widget.RadioButtonWithDescription
@@ -25,11 +27,15 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginBottom="16dp"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+        android:background="?android:attr/selectableItemBackground"
         app:titleText="@string/website_settings_category_ask" />
 
     <org.chromium.chrome.browser.widget.RadioButtonWithDescription
         android:id="@+id/blocked"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+        android:background="?android:attr/selectableItemBackground"
         app:titleText="@string/website_settings_category_blocked" />
 </LinearLayout>
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index 38e61a16..5ff9556b 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -590,12 +590,8 @@
     <!-- Explicit Language Ask Prompt dimensions -->
     <dimen name="explicit_ask_checkbox_end_padding">4dp</dimen>
 
-    <!-- Autofill Assistant dimensions -->
-    <dimen name="autofill_assistant_details_image_size">48dp</dimen>
-
     <!-- Tab List dimensions -->
     <dimen name="tab_grid_favicon_size">32dp</dimen>
     <dimen name="tab_list_selected_inset">6dp</dimen>
-    <dimen name="bottom_tab_grid_toolbar_icon_size">48dp</dimen>
 
 </resources>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
index 038ca65..ef8c1bd6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
@@ -18,6 +18,7 @@
 import android.os.SystemClock;
 import android.provider.Browser;
 import android.provider.MediaStore;
+import android.provider.Settings;
 import android.speech.RecognizerResultsIntent;
 import android.support.annotation.IntDef;
 import android.support.annotation.Nullable;
@@ -1006,8 +1007,7 @@
         if (isChromeToken(token)) {
             return true;
         }
-        if (ExternalAuthUtils.getInstance().isGoogleSigned(
-                    ApiCompatibilityUtils.getCreatorPackage(token))) {
+        if (ExternalAuthUtils.getInstance().isGoogleSigned(token.getCreatorPackage())) {
             return true;
         }
         return false;
@@ -1019,11 +1019,18 @@
         // i.e. the user will see what is going on.
         Context appContext = ContextUtils.getApplicationContext();
         if (!ApiCompatibilityUtils.isInteractive(appContext)) return false;
-        if (!ApiCompatibilityUtils.isDeviceProvisioned(appContext)) return true;
+        if (!isDeviceProvisioned(appContext)) return true;
         return !((KeyguardManager) appContext.getSystemService(Context.KEYGUARD_SERVICE))
                 .inKeyguardRestrictedInputMode();
     }
 
+    private static boolean isDeviceProvisioned(Context context) {
+        if (context == null || context.getContentResolver() == null) return true;
+        return Settings.Global.getInt(
+                       context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0)
+                != 0;
+    }
+
     /*
      * The default behavior here is to open in a new tab.  If this is changed, ensure
      * intents with action NDEF_DISCOVERED (links beamed over NFC) are handled properly.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/SingleTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/SingleTabActivity.java
index cec95e6c..bfe6add 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/SingleTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/SingleTabActivity.java
@@ -12,6 +12,7 @@
 import android.view.KeyEvent;
 
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBuilder;
 import org.chromium.chrome.browser.tab.TabDelegateFactory;
 import org.chromium.chrome.browser.tabmodel.SingleTabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabLaunchType;
@@ -89,8 +90,10 @@
         }
 
         if (tab == null) {
-            tab = new Tab(Tab.INVALID_TAB_ID, Tab.INVALID_TAB_ID, false, getWindowAndroid(),
-                    TabLaunchType.FROM_CHROME_UI, null, null);
+            tab = new TabBuilder()
+                          .setWindow(getWindowAndroid())
+                          .setLaunchType(TabLaunchType.FROM_CHROME_UI)
+                          .build();
         }
 
         tab.initialize(null, getTabContentManager(), createTabDelegateFactory(), false, unfreeze);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenu.java b/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenu.java
index 13c2d71..d83df99 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenu.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenu.java
@@ -356,7 +356,7 @@
                 if (!mIsByPermanentButton) offsets[1] += padding.bottom;
             }
 
-            if (!ApiCompatibilityUtils.isLayoutRtl(anchorView.getRootView())) {
+            if (anchorView.getRootView().getLayoutDirection() != View.LAYOUT_DIRECTION_RTL) {
                 offsets[0] = anchorView.getWidth() - popupWidth;
             }
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetView.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetView.java
index 7778ed0e..b240d68 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetView.java
@@ -14,8 +14,6 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
-import org.chromium.base.ApiCompatibilityUtils;
-
 /**
  * Displays the data provided by the {@link AccessorySheetViewBinder}.
  */
@@ -37,8 +35,8 @@
         mTopShadow = findViewById(org.chromium.chrome.R.id.accessory_sheet_shadow);
 
         // Ensure that sub components of the sheet use the RTL direction:
-        ApiCompatibilityUtils.setLayoutDirection(
-                mViewPager, isLayoutRtl() ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
+        int layoutDirection = isLayoutRtl() ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR;
+        mViewPager.setLayoutDirection(layoutDirection);
     }
 
     void setAdapter(PagerAdapter adapter) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryView.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryView.java
index 868e07d..850bc3d5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryView.java
@@ -16,7 +16,6 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.LinearLayout;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeFeatureList;
 
@@ -67,10 +66,9 @@
         initializeHorizontalRecyclerView(mBarItemsView);
 
         // Apply RTL layout changes to the view's children:
-        ApiCompatibilityUtils.setLayoutDirection(findViewById(R.id.accessory_bar_contents),
-                isLayoutRtl() ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
-        ApiCompatibilityUtils.setLayoutDirection(mBarItemsView,
-                isLayoutRtl() ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
+        int layoutDirection = isLayoutRtl() ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR;
+        findViewById(R.id.accessory_bar_contents).setLayoutDirection(layoutDirection);
+        mBarItemsView.setLayoutDirection(layoutDirection);
 
         // Set listener's to touch/click events so they are not propagated to the page below.
         setOnTouchListener((view, motionEvent) -> {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewBinder.java
index 572e01c0..51e1a0b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewBinder.java
@@ -20,7 +20,6 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetTabModel.AccessorySheetDataPiece;
 import org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetTabViewBinder.ElementViewHolder;
@@ -148,7 +147,7 @@
                 icon.setBounds(0, 0, mIconSize, mIconSize);
             }
             text.setCompoundDrawablePadding(mPadding);
-            ApiCompatibilityUtils.setCompoundDrawablesRelative(text, icon, null, null, null);
+            text.setCompoundDrawablesRelative(icon, null, null, null);
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
index 05137cf..0a3470da 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
@@ -9,7 +9,6 @@
 import android.os.Bundle;
 import android.support.annotation.Nullable;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.Callback;
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.ChromeActivity;
@@ -167,7 +166,7 @@
         if (pendingIntent == null) {
             return false;
         }
-        String packageName = ApiCompatibilityUtils.getCreatorPackage(pendingIntent);
+        String packageName = pendingIntent.getCreatorPackage();
         for (String whitelistedPackage : whitelist) {
             if (whitelistedPackage.equals(packageName)) {
                 return true;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browseractions/BrowserActionsTabCreatorManager.java b/chrome/android/java/src/org/chromium/chrome/browser/browseractions/BrowserActionsTabCreatorManager.java
index bda4ceb..2888b7e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browseractions/BrowserActionsTabCreatorManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browseractions/BrowserActionsTabCreatorManager.java
@@ -8,6 +8,7 @@
 
 import org.chromium.base.ContextUtils;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBuilder;
 import org.chromium.chrome.browser.tab.TabDelegateFactory;
 import org.chromium.chrome.browser.tab.TabState;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
@@ -47,8 +48,10 @@
                 : "tab launch type should be FROM_BROWSER_ACTIONS or FROM_RESTORE";
             Context context = ContextUtils.getApplicationContext();
             WindowAndroid windowAndroid = new WindowAndroid(context);
-            Tab tab = Tab.createTabForLazyLoad(
-                    false, windowAndroid, type, Tab.INVALID_TAB_ID, loadUrlParams);
+            Tab tab = TabBuilder.createForLazyLoad(loadUrlParams)
+                              .setWindow(windowAndroid)
+                              .setLaunchType(type)
+                              .build();
             tab.initialize(null, null, new TabDelegateFactory(), true, false);
             mTabModel.addTab(tab, -1, type);
             return tab;
@@ -58,8 +61,10 @@
         public Tab createFrozenTab(TabState state, int id, int index) {
             Context context = ContextUtils.getApplicationContext();
             WindowAndroid windowAndroid = new WindowAndroid(context);
-            Tab tab = Tab.createFrozenTabFromState(
-                    id, false, windowAndroid, Tab.INVALID_TAB_ID, state);
+            Tab tab = TabBuilder.createFromFrozenState(state)
+                              .setId(id)
+                              .setWindow(windowAndroid)
+                              .build();
             tab.initialize(null, null, new TabDelegateFactory(), true, false);
             mTabModel.addTab(tab, index, TabLaunchType.FROM_RESTORE);
             return tab;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/LayerTitleCache.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/LayerTitleCache.java
index 3c8e8c0f..c4ecc74 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/LayerTitleCache.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/LayerTitleCache.java
@@ -23,6 +23,7 @@
 import org.chromium.chrome.browser.tab.TabFavicon;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.ui.base.DeviceFormFactor;
+import org.chromium.ui.base.LocalizationUtils;
 import org.chromium.ui.resources.ResourceManager;
 import org.chromium.ui.resources.dynamics.BitmapDynamicResource;
 import org.chromium.ui.resources.dynamics.DynamicResourceLoader;
@@ -132,7 +133,6 @@
                     mContext, tab.getUrl(), !isDarkTheme);
         }
 
-        boolean isRtl = tab.isTitleDirectionRtl();
         TitleBitmapFactory titleBitmapFactory =
                 isDarkTheme ? mDarkTitleBitmapFactory : mStandardTitleBitmapFactory;
 
@@ -147,6 +147,10 @@
                 titleBitmapFactory.getFaviconBitmap(originalFavicon), fetchFaviconFromHistory);
 
         if (mNativeLayerTitleCache != 0) {
+            String tabTitle = tab.getTitle();
+            boolean isRtl = tabTitle != null
+                    && LocalizationUtils.getFirstStrongCharacterDirection(tabTitle)
+                            == LocalizationUtils.RIGHT_TO_LEFT;
             nativeUpdateLayer(mNativeLayerTitleCache, tabId, title.getTitleResId(),
                     title.getFaviconResId(), isDarkTheme, isRtl);
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/HiddenTabHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/HiddenTabHolder.java
index 0510c2b..92114da0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/HiddenTabHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/HiddenTabHolder.java
@@ -4,23 +4,29 @@
 
 package org.chromium.chrome.browser.customtabs;
 
+import android.content.Context;
 import android.content.Intent;
+import android.graphics.Rect;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
 import android.support.customtabs.CustomTabsSessionToken;
 import android.text.TextUtils;
 
+import org.chromium.base.ContextUtils;
 import org.chromium.base.TraceEvent;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.browser.IntentHandler;
+import org.chromium.chrome.browser.prerender.ExternalPrerenderHandler;
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBuilder;
 import org.chromium.chrome.browser.tab.TabObserver;
+import org.chromium.chrome.browser.tabmodel.TabLaunchType;
 import org.chromium.chrome.browser.util.UrlUtilities;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.common.Referrer;
 import org.chromium.network.mojom.ReferrerPolicy;
-
+import org.chromium.ui.base.WindowAndroid;
 
 /**
  * Holds a hidden tab which may be used to preload pages before a CustomTabActivity is launched.
@@ -75,7 +81,7 @@
         // Ensures no Browser.EXTRA_HEADERS were in the Intent.
         if (IntentHandler.getExtraHeadersFromIntent(extrasIntent) != null) return;
 
-        Tab tab = Tab.createDetached(CustomTabDelegateFactory.createDummy());
+        Tab tab = buildDetachedTab();
 
         HiddenTabObserver observer = new HiddenTabObserver();
         tab.addObserver(observer);
@@ -98,6 +104,31 @@
     }
 
     /**
+     * Creates an instance of a {@link Tab} that is fully detached from any activity.
+     * Also performs general tab initialization as well as detached specifics.
+     *
+     * The current application context must allow the creation of a WindowAndroid.
+     *
+     * @return The newly created and initialized tab.
+     */
+    private static Tab buildDetachedTab() {
+        Context context = ContextUtils.getApplicationContext();
+        Tab tab = new TabBuilder()
+                          .setWindow(new WindowAndroid(context))
+                          .setLaunchType(TabLaunchType.FROM_SPECULATIVE_BACKGROUND_CREATION)
+                          .build();
+        tab.initialize(null, null, CustomTabDelegateFactory.createDummy(), true, false);
+
+        // Resize the webContent to avoid expensive post load resize when attaching the tab.
+        Rect bounds = ExternalPrerenderHandler.estimateContentSize(context, false);
+        int width = bounds.right - bounds.left;
+        int height = bounds.bottom - bounds.top;
+        tab.getWebContents().setSize(width, height);
+        tab.detach();
+        return tab;
+    }
+
+    /**
      * Returns the preloaded {@link Tab} if it matches the given |url| and |referrer|. Null if no
      * such {@link Tab}. If a {@link Tab} is preloaded but it does not match, it is discarded.
      *
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabFactory.java
index 919e908..258d0995 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabFactory.java
@@ -15,6 +15,7 @@
 import org.chromium.chrome.browser.customtabs.CustomTabTabPersistencePolicy;
 import org.chromium.chrome.browser.dependency_injection.ActivityScope;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBuilder;
 import org.chromium.chrome.browser.tab.TabDelegateFactory;
 import org.chromium.chrome.browser.tabmodel.ChromeTabCreator;
 import org.chromium.chrome.browser.tabmodel.TabLaunchType;
@@ -92,8 +93,13 @@
                 IntentUtils.safeGetIntExtra(intent, IntentHandler.EXTRA_TAB_ID, Tab.INVALID_TAB_ID);
         int parentTabId = IntentUtils.safeGetIntExtra(
                 intent, IntentHandler.EXTRA_PARENT_TAB_ID, Tab.INVALID_TAB_ID);
-        return new Tab(assignedTabId, parentTabId, mIntentDataProvider.isIncognito(),
-                mActivityWindowAndroid.get(), TabLaunchType.FROM_EXTERNAL_APP, null, null);
+        return new TabBuilder()
+                .setId(assignedTabId)
+                .setParentId(parentTabId)
+                .setIncognito(mIntentDataProvider.isIncognito())
+                .setWindow(mActivityWindowAndroid.get())
+                .setLaunchType(TabLaunchType.FROM_EXTERNAL_APP)
+                .build();
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarControlLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarControlLayout.java
index 2132266ff..1391fde5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarControlLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarControlLayout.java
@@ -261,7 +261,7 @@
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         int width = right - left;
-        boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(this);
+        boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
 
         // Child positions were already determined during the measurement pass.
         for (int childIndex = 0; childIndex < getChildCount(); childIndex++) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarLayout.java
index b6de63eb..d1be867 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarLayout.java
@@ -23,7 +23,6 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.widget.DualControlLayout;
 import org.chromium.ui.UiUtils;
@@ -305,7 +304,7 @@
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         // Place all the views in the positions already determined during onMeasure().
         int width = right - left;
-        boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(this);
+        boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
 
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/translate/TranslateMenuHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/translate/TranslateMenuHelper.java
index ff1573b5..fdc948c3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/infobar/translate/TranslateMenuHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/translate/TranslateMenuHelper.java
@@ -20,7 +20,6 @@
 import android.widget.PopupWindow;
 import android.widget.TextView;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.infobar.TranslateOptions;
 
@@ -135,7 +134,7 @@
 
         // When layout is RTL, set the horizontal offset to align the menu with the left side of the
         // screen.
-        if (ApiCompatibilityUtils.isLayoutRtl(mAnchorView)) {
+        if (mAnchorView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
             int[] tempLocation = new int[2];
             mAnchorView.getLocationOnScreen(tempLocation);
             mPopup.setHorizontalOffset(-tempLocation[0]);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/translate/TranslateTabLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/translate/TranslateTabLayout.java
index e56ed157..c1342ed 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/infobar/translate/TranslateTabLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/translate/TranslateTabLayout.java
@@ -17,7 +17,6 @@
 import android.view.MotionEvent;
 import android.view.animation.DecelerateInterpolator;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
 
 /**
@@ -212,8 +211,8 @@
         // The steps of the scrolling animation:
         //   1. wait for START_POSITION_WAIT_DURATION_MS.
         //   2. scroll to the end in SCROLL_DURATION_MS.
-        mScrollToEndAnimator = ObjectAnimator.ofInt(
-                this, "scrollX", ApiCompatibilityUtils.isLayoutRtl(this) ? 0 : maxScrollDistance);
+        mScrollToEndAnimator = ObjectAnimator.ofInt(this, "scrollX",
+                getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : maxScrollDistance);
         mScrollToEndAnimator.setStartDelay(START_POSITION_WAIT_DURATION_MS);
         mScrollToEndAnimator.setDuration(SCROLL_DURATION_MS);
         mScrollToEndAnimator.setInterpolator(new DecelerateInterpolator());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarTablet.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarTablet.java
index 3d51233..c9f7732 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarTablet.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarTablet.java
@@ -15,7 +15,6 @@
 import android.view.View;
 import android.view.WindowManager;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.download.DownloadUtils;
 import org.chromium.chrome.browser.ntp.NewTabPage;
@@ -498,7 +497,7 @@
      * @param deleteOffset The additional offset to use for the delete button.
      */
     private void setChildTranslationsForWidthChangeAnimation(int offset, int deleteOffset) {
-        if (!ApiCompatibilityUtils.isLayoutRtl(this)) {
+        if (getLayoutDirection() != LAYOUT_DIRECTION_RTL) {
             // When the location bar layout direction is LTR, the buttons at the end (left side)
             // of the location bar need to stick to the left edge.
             if (mSaveOfflineButton.getVisibility() == View.VISIBLE) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
index 699e138..cb76fc27 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
@@ -379,12 +379,12 @@
         // insertion point when an RTL user enters RTL text). Also render text normally when the
         // text field is empty (because then it displays an instruction that is not a URL).
         if (mFocused || length() == 0 || !mUrlBarDelegate.shouldForceLTR()) {
-            ApiCompatibilityUtils.setTextDirection(this, TEXT_DIRECTION_INHERIT);
+            setTextDirection(TEXT_DIRECTION_INHERIT);
         } else {
-            ApiCompatibilityUtils.setTextDirection(this, TEXT_DIRECTION_LTR);
+            setTextDirection(TEXT_DIRECTION_LTR);
         }
         // Always align to the same as the paragraph direction (LTR = left, RTL = right).
-        ApiCompatibilityUtils.setTextAlignment(this, TEXT_ALIGNMENT_TEXT_START);
+        setTextAlignment(TEXT_ALIGNMENT_TEXT_START);
     }
 
     @Override
@@ -717,7 +717,7 @@
         setSelection(0);
 
         float currentTextSize = getTextSize();
-        boolean currentIsRtl = ApiCompatibilityUtils.isLayoutRtl(this);
+        boolean currentIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
 
         int measuredWidth = getMeasuredWidth() - (getPaddingLeft() + getPaddingRight());
         if (scrollType == mPreviousScrollType && TextUtils.equals(text, mPreviousScrollText)
@@ -757,7 +757,7 @@
         Editable text = getText();
         float scrollPos = 0f;
         if (TextUtils.isEmpty(text)) {
-            if (ApiCompatibilityUtils.isLayoutRtl(this)
+            if (getLayoutDirection() == LAYOUT_DIRECTION_RTL
                     && BidiFormatter.getInstance().isRtl(getHint())) {
                 // Compared to below that uses getPrimaryHorizontal(1) due to 0 returning an
                 // invalid value, if the text is empty, getPrimaryHorizontal(0) returns the actual
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionView.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionView.java
index 8ccdf1ae..0ae1809b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionView.java
@@ -155,7 +155,7 @@
         if (getMeasuredWidth() == 0) return;
 
         boolean refineVisible = mRefineView.getVisibility() == VISIBLE;
-        boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(this);
+        boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
         int contentsViewOffsetX = isRtl && refineVisible ? mRefineWidth : 0;
         mContentsView.layout(contentsViewOffsetX, 0,
                 contentsViewOffsetX + mContentsView.getMeasuredWidth(),
@@ -378,7 +378,7 @@
         SuggestionContentsContainer(Context context, Drawable backgroundDrawable) {
             super(context);
 
-            ApiCompatibilityUtils.setLayoutDirection(this, View.LAYOUT_DIRECTION_INHERIT);
+            setLayoutDirection(View.LAYOUT_DIRECTION_INHERIT);
 
             setBackground(backgroundDrawable);
             setClickable(true);
@@ -405,7 +405,7 @@
             mTextLine1.setLayoutParams(
                     new LayoutParams(LayoutParams.WRAP_CONTENT, mSuggestionHeight));
             mTextLine1.setSingleLine();
-            ApiCompatibilityUtils.setTextAlignment(mTextLine1, TEXT_ALIGNMENT_VIEW_START);
+            mTextLine1.setTextAlignment(TEXT_ALIGNMENT_VIEW_START);
             addView(mTextLine1);
 
             mTextLine2 = new TextView(context);
@@ -413,7 +413,7 @@
                     new LayoutParams(LayoutParams.WRAP_CONTENT, mSuggestionHeight));
             mTextLine2.setSingleLine();
             mTextLine2.setVisibility(INVISIBLE);
-            ApiCompatibilityUtils.setTextAlignment(mTextLine2, TEXT_ALIGNMENT_VIEW_START);
+            mTextLine2.setTextAlignment(TEXT_ALIGNMENT_VIEW_START);
             addView(mTextLine2);
 
             mAnswerImage = new ImageView(context);
@@ -430,7 +430,7 @@
                 canvas.save();
                 float suggestionIconLeft =
                         (mSuggestionIconWidthPx - mSuggestionIcon.getIntrinsicWidth()) / 2f;
-                if (ApiCompatibilityUtils.isLayoutRtl(this)) {
+                if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
                     suggestionIconLeft += getMeasuredWidth() - mSuggestionIconWidthPx;
                 }
                 float suggestionIconTop =
@@ -500,7 +500,7 @@
                     mSuggestionDelegate.getAdditionalTextLine1StartPadding(
                             mTextLine1, r - l - mSuggestionStartOffsetPx);
 
-            if (ApiCompatibilityUtils.isLayoutRtl(this)) {
+            if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
                 int rightStartPos = r - l - mSuggestionStartOffsetPx;
                 mTextLine1.layout(
                         0, line1Top, rightStartPos - line1AdditionalStartPadding, line1Bottom);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionViewViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionViewViewBinder.java
index baf34470..0d1ae937 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionViewViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/basic/SuggestionViewViewBinder.java
@@ -100,7 +100,7 @@
             view.getTextLine1().setTextColor(
                     model.get(SuggestionViewProperties.TEXT_LINE_1_TEXT_COLOR));
         } else if (SuggestionViewProperties.TEXT_LINE_1_TEXT_DIRECTION.equals(propertyKey)) {
-            ApiCompatibilityUtils.setTextDirection(view.getTextLine1(),
+            view.getTextLine1().setTextDirection(
                     model.get(SuggestionViewProperties.TEXT_LINE_1_TEXT_DIRECTION));
         } else if (SuggestionViewProperties.TEXT_LINE_1_TEXT.equals(propertyKey)) {
             view.getTextLine1().setText(model.get(SuggestionViewProperties.TEXT_LINE_1_TEXT).text);
@@ -115,7 +115,7 @@
             view.getTextLine2().setTextColor(
                     model.get(SuggestionViewProperties.TEXT_LINE_2_TEXT_COLOR));
         } else if (SuggestionViewProperties.TEXT_LINE_2_TEXT_DIRECTION.equals(propertyKey)) {
-            ApiCompatibilityUtils.setTextDirection(view.getTextLine2(),
+            view.getTextLine2().setTextDirection(
                     model.get(SuggestionViewProperties.TEXT_LINE_2_TEXT_DIRECTION));
         } else if (SuggestionViewProperties.TEXT_LINE_2_TEXT.equals(propertyKey)) {
             Spannable line2Text = model.get(SuggestionViewProperties.TEXT_LINE_2_TEXT).text;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/page_info/CertificateViewer.java b/chrome/android/java/src/org/chromium/chrome/browser/page_info/CertificateViewer.java
index 3754bd5..b43dac6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/page_info/CertificateViewer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/page_info/CertificateViewer.java
@@ -94,14 +94,14 @@
 
         TextView title = new TextView(mContext);
         title.setText(R.string.certtitle);
-        ApiCompatibilityUtils.setTextAlignment(title, View.TEXT_ALIGNMENT_VIEW_START);
+        title.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
         ApiCompatibilityUtils.setTextAppearance(title, android.R.style.TextAppearance_Large);
         title.setTypeface(title.getTypeface(), Typeface.BOLD);
         title.setPadding(mPadding, mPadding, mPadding, mPadding / 2);
         dialogContainer.addView(title);
 
         Spinner spinner = new Spinner(mContext);
-        ApiCompatibilityUtils.setTextAlignment(spinner, View.TEXT_ALIGNMENT_VIEW_START);
+        spinner.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
         spinner.setAdapter(arrayAdapter);
         spinner.setOnItemSelectedListener(this);
         spinner.setDropDownWidth(ViewGroup.LayoutParams.MATCH_PARENT);
@@ -207,7 +207,7 @@
 
     private TextView addLabel(LinearLayout certificateView, String label) {
         TextView t = new TextView(mContext);
-        ApiCompatibilityUtils.setTextAlignment(t, View.TEXT_ALIGNMENT_VIEW_START);
+        t.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
         t.setPadding(mPadding, mPadding / 2, mPadding, 0);
         t.setText(label);
         t.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
@@ -219,7 +219,7 @@
 
     private void addValue(LinearLayout certificateView, String value) {
         TextView t = new TextView(mContext);
-        ApiCompatibilityUtils.setTextAlignment(t, View.TEXT_ALIGNMENT_VIEW_START);
+        t.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
         t.setText(value);
         t.setPadding(mPadding, 0, mPadding, mPadding / 2);
         t.setTextColor(ApiCompatibilityUtils.getColor(mContext.getResources(),
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/password_manager/AccountChooserDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/password_manager/AccountChooserDialog.java
index 9e47aaf..5bce64e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/password_manager/AccountChooserDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/password_manager/AccountChooserDialog.java
@@ -28,7 +28,6 @@
 import android.widget.ListView;
 import android.widget.TextView;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.signin.ProfileDataCache;
@@ -238,7 +237,7 @@
 
         final int width = view.getWidth();
 
-        final int xOffset = ApiCompatibilityUtils.isLayoutRtl(view)
+        final int xOffset = view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
                 ? screenPos[0]
                 : screenPos[0] + width - text.getMeasuredWidth();
 
@@ -256,7 +255,8 @@
 
         // The xOffset is with regard to the left edge of the screen. Gravity.LEFT is deprecated,
         // which is why the following line is necessary.
-        final int xGravity = ApiCompatibilityUtils.isLayoutRtl(view) ? Gravity.END : Gravity.START;
+        final int xGravity = view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? Gravity.END
+                                                                                    : Gravity.START;
 
         Toast toast = new Toast(context);
         toast.setGravity(Gravity.TOP | xGravity, xOffset, yOffset);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
index c96298a..26d4038e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
@@ -48,6 +48,7 @@
 import org.chromium.chrome.browser.tabmodel.TabModelObserver;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
+import org.chromium.chrome.browser.tabmodel.TabModelUtils;
 import org.chromium.chrome.browser.tabmodel.TabSelectionType;
 import org.chromium.chrome.browser.widget.prefeditor.Completable;
 import org.chromium.chrome.browser.widget.prefeditor.EditableOption;
@@ -628,6 +629,15 @@
         mObservedTabModelSelector.addObserver(mSelectorObserver);
         mObservedTabModel.addObserver(mTabModelObserver);
 
+        // Only the currently selected tab is allowed to show the payment UI.
+        if (TabModelUtils.getCurrentWebContents(mObservedTabModel) != mWebContents) {
+            mJourneyLogger.setNotShown(NotShownReason.OTHER);
+            disconnectFromClientWithDebugMessage(
+                    "Background tab is not allowed to show PaymentRequest UI");
+            if (sObserverForTest != null) sObserverForTest.onPaymentRequestServiceShowFailed();
+            return;
+        }
+
         // Catch any time the user enters the overview mode and dismiss the payment UI.
         if (chromeActivity instanceof ChromeTabbedActivity) {
             mOverviewModeBehavior =
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestBottomBar.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestBottomBar.java
index b75dc64..33dcdbe3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestBottomBar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestBottomBar.java
@@ -7,11 +7,8 @@
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.View;
-import android.view.View.MeasureSpec;
 import android.view.ViewGroup;
-import android.view.ViewGroup.MarginLayoutParams;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
 
 /** This class represents a bar to display at the bottom of the payment request UI. */
@@ -107,7 +104,7 @@
 
         int childLeft;
         int childRight;
-        boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(this);
+        boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
         if (isRtl) {
             childRight = viewWidth - rightSpace;
             childLeft = childRight;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestHeader.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestHeader.java
index 6056902..8333ba8e0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestHeader.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestHeader.java
@@ -13,7 +13,6 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.UrlConstants;
 import org.chromium.chrome.browser.omnibox.OmniboxUrlEmphasizer;
@@ -58,7 +57,7 @@
 
         if (origin.startsWith(UrlConstants.HTTPS_URL_PREFIX)) {
             // Add a lock icon.
-            ApiCompatibilityUtils.setCompoundDrawablesRelativeWithIntrinsicBounds(hostName,
+            hostName.setCompoundDrawablesRelativeWithIntrinsicBounds(
                     TintedDrawable.constructTintedDrawable(
                             mContext, R.drawable.omnibox_https_valid, R.color.google_green_700),
                     null, null, null);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestSection.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestSection.java
index 7ce7c9c..d8fa461 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestSection.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestSection.java
@@ -374,7 +374,7 @@
         mSummaryRightTextView = new TextView(getContext());
         ApiCompatibilityUtils.setTextAppearance(
                 mSummaryRightTextView, R.style.TextAppearance_BlackTitle1);
-        ApiCompatibilityUtils.setTextAlignment(mSummaryRightTextView, TEXT_ALIGNMENT_TEXT_END);
+        mSummaryRightTextView.setTextAlignment(TEXT_ALIGNMENT_TEXT_END);
 
         // The main TextView sucks up all the available space.
         LinearLayout.LayoutParams leftLayoutParams = new LinearLayout.LayoutParams(
@@ -602,7 +602,7 @@
                     mUpdatedView, R.style.TextAppearance_BlackTitle1);
             LinearLayout.LayoutParams updatedLayoutParams = new LinearLayout.LayoutParams(
                     LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
-            ApiCompatibilityUtils.setTextAlignment(mUpdatedView, TEXT_ALIGNMENT_TEXT_END);
+            mUpdatedView.setTextAlignment(TEXT_ALIGNMENT_TEXT_END);
             mUpdatedView.setTextColor(ApiCompatibilityUtils.getColor(
                     context.getResources(), R.color.google_green_700));
             MarginLayoutParamsCompat.setMarginStart(updatedLayoutParams,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceManager.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceManager.java
index a9f225b0..761d73c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceManager.java
@@ -285,6 +285,12 @@
             "twa_dialog_number_of_dismissals_on_clear_data";
 
     /**
+     * Whether or not the tab group is enabled.
+     * Default value is false.
+     */
+    public static final String TAB_GROUPS_ANDROID_ENABLED_KEY = "tab_group_android_enabled";
+
+    /**
      * Deprecated keys for Chrome Home.
      */
     private static final String CHROME_HOME_USER_ENABLED_KEY = "chrome_home_user_enabled";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/PreferencesLauncher.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/PreferencesLauncher.java
index b8cd5bf..1ae947a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/PreferencesLauncher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/PreferencesLauncher.java
@@ -195,8 +195,6 @@
 
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return false;
 
-        if (!ChromeFeatureList.isEnabled(GOOGLE_ACCOUNT_PWM_UI)) return false;
-
         int minGooglePlayServicesVersion = ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
                 GOOGLE_ACCOUNT_PWM_UI, MIN_GOOGLE_PLAY_SERVICES_VERSION_PARAM,
                 DEFAULT_MIN_GOOGLE_PLAY_SERVICES_APK_VERSION);
@@ -204,6 +202,8 @@
                 != ConnectionResult.SUCCESS)
             return false;
 
+        if (!ChromeFeatureList.isEnabled(GOOGLE_ACCOUNT_PWM_UI)) return false;
+
         return googlePasswordManagerUIProvider.showGooglePasswordManager(activity);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/languages/LanguageListPreference.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/languages/LanguageListPreference.java
index ebce0b9..315a37b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/languages/LanguageListPreference.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/languages/LanguageListPreference.java
@@ -15,7 +15,6 @@
 import android.view.accessibility.AccessibilityManager;
 import android.widget.TextView;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.preferences.PrefServiceBridge;
 import org.chromium.chrome.browser.util.AccessibilityUtil;
@@ -144,7 +143,7 @@
         mView = super.onCreateView(parent);
 
         mAddLanguageButton = (TextView) mView.findViewById(R.id.add_language);
-        ApiCompatibilityUtils.setCompoundDrawablesRelativeWithIntrinsicBounds(mAddLanguageButton,
+        mAddLanguageButton.setCompoundDrawablesRelativeWithIntrinsicBounds(
                 TintedDrawable.constructTintedDrawable(
                         getContext(), R.drawable.plus, R.color.pref_accent_color),
                 null, null, null);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/website/TriStateSiteSettingsPreference.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/website/TriStateSiteSettingsPreference.java
index 4260f15..9093127 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/website/TriStateSiteSettingsPreference.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/website/TriStateSiteSettingsPreference.java
@@ -29,6 +29,11 @@
 
     public TriStateSiteSettingsPreference(Context context, AttributeSet attrs) {
         super(context, attrs);
+        // Make unselectable, otherwise TriStateSiteSettingsPreference is treated as one
+        // selectable Preference, instead of three selectable radio buttons.
+        // Allows radio buttons to be selected via Bluetooth keyboard (key events).
+        // See: crbug.com/936143
+        setSelectable(false);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java
index 351d30c..696311b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/searchwidget/SearchActivity.java
@@ -32,8 +32,8 @@
 import org.chromium.chrome.browser.snackbar.SnackbarManager;
 import org.chromium.chrome.browser.snackbar.SnackbarManager.SnackbarManageable;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBuilder;
 import org.chromium.chrome.browser.tab.TabDelegateFactory;
-import org.chromium.chrome.browser.tab.TabIdManager;
 import org.chromium.chrome.browser.tabmodel.TabLaunchType;
 import org.chromium.chrome.browser.util.IntentUtils;
 import org.chromium.components.url_formatter.UrlFormatter;
@@ -167,9 +167,10 @@
     public void finishNativeInitialization() {
         super.finishNativeInitialization();
 
-        mTab = new Tab(TabIdManager.getInstance().generateValidId(Tab.INVALID_TAB_ID),
-                Tab.INVALID_TAB_ID, false, getWindowAndroid(), TabLaunchType.FROM_EXTERNAL_APP,
-                null, null);
+        mTab = new TabBuilder()
+                       .setWindow(getWindowAndroid())
+                       .setLaunchType(TabLaunchType.FROM_EXTERNAL_APP)
+                       .build();
         mTab.initialize(WebContentsFactory.createWebContents(false, false), null,
                 new TabDelegateFactory(), false, false);
         mTab.loadUrl(new LoadUrlParams(ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL));
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/share/ShareHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/share/ShareHelper.java
index 447687d6..d3a301c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/share/ShareHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/share/ShareHelper.java
@@ -38,6 +38,7 @@
 import org.chromium.base.ApplicationState;
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.Callback;
+import org.chromium.base.ContentUriUtils;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.base.StreamUtil;
@@ -322,7 +323,7 @@
                         fOut.write(jpegImageData);
                         fOut.flush();
 
-                        return ApiCompatibilityUtils.getUriForImageCaptureFile(saveFile);
+                        return ContentUriUtils.getContentUriFromFile(saveFile);
                     } else {
                         Log.w(TAG, "Share failed -- Unable to create share image directory.");
                     }
@@ -377,7 +378,7 @@
             new AsyncTask<Uri>() {
                 @Override
                 protected Uri doInBackground() {
-                    return ApiCompatibilityUtils.getUriForImageCaptureFile(new File(path));
+                    return ContentUriUtils.getContentUriFromFile(new File(path));
                 }
 
                 @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsBinder.java
index a7a1c94..80ce003f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/SuggestionsBinder.java
@@ -318,8 +318,7 @@
 
     private void setFaviconOnView(Drawable drawable, int faviconSizePx) {
         drawable.setBounds(0, 0, faviconSizePx, faviconSizePx);
-        ApiCompatibilityUtils.setCompoundDrawablesRelative(
-                mPublisherTextView, drawable, null, null, null);
+        mPublisherTextView.setCompoundDrawablesRelative(drawable, null, null, null);
         mPublisherTextView.setVisibility(View.VISIBLE);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGridLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGridLayout.java
index 3f82060..752d973 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGridLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/TileGridLayout.java
@@ -12,7 +12,6 @@
 import android.view.View;
 import android.widget.FrameLayout;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.util.MathUtils;
@@ -97,7 +96,7 @@
         // Arrange the visible children in a grid.
         int numRows = (visibleChildCount + numColumns - 1) / numColumns;
         int paddingTop = getPaddingTop();
-        boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(this);
+        boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
 
         for (int i = 0; i < visibleChildCount; i++) {
             View child = getChildAt(i);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
index ee3de8d6..bdfe652 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
@@ -88,7 +88,6 @@
 import org.chromium.content_public.common.BrowserControlsState;
 import org.chromium.content_public.common.Referrer;
 import org.chromium.content_public.common.ResourceRequestBody;
-import org.chromium.ui.base.LocalizationUtils;
 import org.chromium.ui.base.PageTransition;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.mojom.WindowOpenDisposition;
@@ -264,17 +263,11 @@
     private long mTimestampMillis = INVALID_TIMESTAMP;
 
     /**
-     * Title of the ContentViews webpage.Always update mTitle through updateTitle() so that it also
-     * updates mIsTitleDirectionRtl correctly.
+     * Title of the ContentViews webpage.
      */
     private String mTitle;
 
     /**
-     * Indicates if mTitle should be displayed from right to left.
-     */
-    private boolean mIsTitleDirectionRtl;
-
-    /**
      * The mInterceptNavigationDelegate will be consulted for top-level frame navigations. This
      * allows presenting the intent picker to the user so that a native Android application can be
      * used if available.
@@ -296,8 +289,6 @@
     /** The current browser controls constraints. -1 if not set. */
     private @BrowserControlsState int mBrowserConstrolsConstraints = -1;
 
-    private final TabObserver mFullscreenHandler = new TabFullscreenHandler();
-
     private TabDelegateFactory mDelegateFactory;
 
     private BrowserControlsVisibilityDelegate mBrowserControlsVisibilityDelegate;
@@ -328,35 +319,25 @@
     /**
      * Creates an instance of a {@link Tab}.
      *
-     * This constructor may be called before the native library has been loaded, so any additions
-     * must be vetted for library calls.
-     *
-     * @param id        The id this tab should be identified with.
-     * @param incognito Whether or not this tab is incognito.
-     * @param window    An instance of a {@link WindowAndroid}.
-     */
-    public Tab(int id, boolean incognito, WindowAndroid window) {
-        this(id, INVALID_TAB_ID, incognito, window, null, null, null);
-    }
-
-    /**
-     * Creates an instance of a {@link Tab}.
-     *
      * This constructor can be called before the native library has been loaded, so any additions
      * must be vetted for library calls.
      *
-     * @param id          The id this tab should be identified with.
-     * @param parentId    The id id of the tab that caused this tab to be opened.
-     * @param incognito   Whether or not this tab is incognito.
-     * @param window      An instance of a {@link WindowAndroid}.
-     * @param creationState State in which the tab is created, needed to initialize TabUma
-     *                      accounting. When null, TabUma will not be initialized.
-     * @param frozenState State containing information about this Tab, if it was persisted.
+     * Package-private. Use {@link TabBuilder} to create an instance.
+     *
+     * @param id            The id this tab should be identified with.
+     * @param parentId      The id id of the tab that caused this tab to be opened.
+     * @param incognito     Whether or not this tab is incognito.
+     * @param window        An instance of a {@link WindowAndroid}.
+     * @param launchType    Type indicating how this tab was launched.
+     * @param creationState State in which the tab is created.
+     * @param frozenState   State containing information about this Tab, if it was persisted.
+     * @param loadUrlParams Parameters used for a lazily loaded Tab.
      */
     @SuppressLint("HandlerLeak")
-    public Tab(int id, int parentId, boolean incognito, WindowAndroid window,
-            @Nullable @TabLaunchType Integer type,
-            @Nullable @TabCreationState Integer creationState, TabState frozenState) {
+    Tab(int id, int parentId, boolean incognito, WindowAndroid window,
+            @Nullable @TabLaunchType Integer launchType,
+            @Nullable @TabCreationState Integer creationState, TabState frozenState,
+            LoadUrlParams loadUrlParams) {
         mId = TabIdManager.getInstance().generateValidId(id);
         mParentId = parentId;
         mIncognito = incognito;
@@ -369,20 +350,24 @@
         ContextThemeWrapper themeWrapper = new ContextThemeWrapper(
                 ContextUtils.getApplicationContext(), ChromeActivity.getThemeId());
         Configuration config = new Configuration();
+        // Pre-Android O, fontScale gets initialized to 1 in the constructor. Set it to 0 so
+        // that applyOverrideConfiguration() does not interpret it as an overridden value.
+        config.fontScale = 0;
         config.uiMode = Configuration.UI_MODE_NIGHT_NO
                 | (config.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
         themeWrapper.applyOverrideConfiguration(config);
         mThemedApplicationContext = themeWrapper;
 
         mWindowAndroid = window;
-        mLaunchType = type;
-        mLaunchTypeAtCreation = type;
+        mLaunchType = launchType;
+        mLaunchTypeAtCreation = launchType;
+        mPendingLoadParams = loadUrlParams;
+        if (loadUrlParams != null) mUrl = loadUrlParams.getUrl();
 
         TabThemeColorHelper.createForTab(this);
 
         // Restore data from the TabState, if it existed.
         if (frozenState != null) {
-            assert type == TabLaunchType.FROM_RESTORE;
             restoreFieldsFromState(frozenState);
         } else {
             if (mParentId == INVALID_TAB_ID || getTabModelSelector() == null
@@ -393,7 +378,7 @@
             }
         }
 
-        addObserver(mFullscreenHandler);
+        TabFullscreenHandler.createForTab(this);
 
         if (incognito) {
             CipherFactory.getInstance().triggerKeyGeneration();
@@ -402,15 +387,7 @@
         ContextualSearchTabHelper.createForTab(this);
         MediaSessionTabHelper.createForTab(this);
 
-        if (creationState != null) {
-            TabUma.create(this, creationState);
-            if (frozenState == null) {
-                assert creationState != TabCreationState.FROZEN_ON_RESTORE;
-            } else {
-                assert type == TabLaunchType.FROM_RESTORE
-                        && creationState == TabCreationState.FROZEN_ON_RESTORE;
-            }
-        }
+        if (creationState != null) TabUma.create(this, creationState);
 
         mAttachStateChangeListener = new OnAttachStateChangeListener() {
             @Override
@@ -441,9 +418,6 @@
 
         TabThemeColorHelper.get(this).updateFromTabState(state);
         mTitle = state.getDisplayTitleFromState();
-        mIsTitleDirectionRtl = mTitle != null
-                && LocalizationUtils.getFirstStrongCharacterDirection(mTitle)
-                        == LocalizationUtils.RIGHT_TO_LEFT;
         mLaunchTypeAtCreation = state.tabLaunchTypeAtCreation;
         mRootId = state.rootId == Tab.INVALID_TAB_ID ? mId : state.rootId;
     }
@@ -695,7 +669,7 @@
     }
 
     /** @return WebContentsState representing the state of the WebContents (navigations, etc.) */
-    public WebContentsState getFrozenContentsState() {
+    private WebContentsState getFrozenContentsState() {
         return mFrozenContentsState;
     }
 
@@ -1144,7 +1118,7 @@
      * - Removes the tab from its current {@link TabModelSelector}, effectively severing
      *   the {@link Activity} to {@link Tab} link.
      */
-    private void detach() {
+    public void detach() {
         // TODO(yusufo): We can't call updateWindowAndroid here and set mWindowAndroid to null
         // because many code paths (including navigation) expect the tab to always be associated
         // with an activity, and will crash. crbug.com/657007
@@ -1319,7 +1293,6 @@
     protected void didFinishPageLoad(String url) {
         mIsTabStateDirty = true;
         updateTitle();
-        updateFullscreenEnabledState();
 
         for (TabObserver observer : mObservers) observer.onPageLoadFinished(this, url);
         mIsBeingRestored = false;
@@ -1610,8 +1583,6 @@
 
         mIsTabStateDirty = true;
         mTitle = title;
-        mIsTitleDirectionRtl = LocalizationUtils.getFirstStrongCharacterDirection(title)
-                == LocalizationUtils.RIGHT_TO_LEFT;
         notifyPageTitleChanged();
     }
 
@@ -1638,13 +1609,6 @@
     }
 
     /**
-     * @return True if the tab title should be displayed from right to left.
-     */
-    public boolean isTitleDirectionRtl() {
-        return mIsTitleDirectionRtl;
-    }
-
-    /**
      * Loads the tab if it's not loaded (e.g. because it was killed in background).
      * This will trigger a regular load for tabs with pending lazy first load (tabs opened in
      * background on low-memory devices).
@@ -1896,10 +1860,6 @@
         return mNativeTabAndroid;
     }
 
-    private static Rect getEstimatedContentSize(Context context) {
-        return ExternalPrerenderHandler.estimateContentSize(context, false);
-    }
-
     /** This is currently called when committing a pre-rendered page. */
     @VisibleForTesting
     public void swapWebContents(
@@ -1914,7 +1874,7 @@
 
         Rect bounds = new Rect();
         if (originalWidth == 0 && originalHeight == 0) {
-            bounds = getEstimatedContentSize(getApplicationContext());
+            bounds = ExternalPrerenderHandler.estimateContentSize(getApplicationContext(), false);
             originalWidth = bounds.right - bounds.left;
             originalHeight = bounds.bottom - bounds.top;
         }
@@ -1996,14 +1956,6 @@
     }
 
     /**
-     * @param params Parameters that should be used for a lazily loaded Tab.
-     */
-    private void setPendingLoadParams(LoadUrlParams params) {
-        mPendingLoadParams = params;
-        mUrl = params.getUrl();
-    }
-
-    /**
      * @see #setAppAssociatedWith(String) for more information.
      * TODO(aurimas): investigate reducing the visibility of this method after TabModel refactoring.
      *
@@ -2365,18 +2317,6 @@
     }
 
     /**
-     * Creates a new, "frozen" tab from a saved state. This can be used for background tabs restored
-     * on cold start that should be loaded when switched to. initialize() needs to be called
-     * afterwards to complete the second level initialization.
-     */
-    public static Tab createFrozenTabFromState(
-            int id, boolean incognito, WindowAndroid nativeWindow, int parentId, TabState state) {
-        assert state != null;
-        return new Tab(id, parentId, incognito, nativeWindow, TabLaunchType.FROM_RESTORE,
-                TabCreationState.FROZEN_ON_RESTORE, state);
-    }
-
-    /**
      * Update whether or not the current native tab and/or web contents are
      * currently visible (from an accessibility perspective), or whether
      * they're obscured by another view.
@@ -2407,57 +2347,6 @@
         return activity != null && activity.isViewObscuringAllTabs();
     }
 
-    /**
-     * Creates a new tab to be loaded lazily. This can be used for tabs opened in the background
-     * that should be loaded when switched to. initialize() needs to be called afterwards to
-     * complete the second level initialization.
-     */
-    public static Tab createTabForLazyLoad(boolean incognito, WindowAndroid nativeWindow,
-            @TabLaunchType int type, int parentId, LoadUrlParams loadUrlParams) {
-        Tab tab = new Tab(INVALID_TAB_ID, parentId, incognito, nativeWindow, type,
-                TabCreationState.FROZEN_FOR_LAZY_LOAD, null);
-        tab.setPendingLoadParams(loadUrlParams);
-        return tab;
-    }
-
-    /**
-     * Creates a fresh tab. initialize() needs to be called afterwards to complete the second level
-     * initialization.
-     * @param initiallyHidden true iff the tab being created is initially in background
-     */
-    public static Tab createLiveTab(int id, boolean incognito, WindowAndroid nativeWindow,
-            @TabLaunchType int type, int parentId, boolean initiallyHidden) {
-        return new Tab(id, parentId, incognito, nativeWindow, type,
-                initiallyHidden ? TabCreationState.LIVE_IN_BACKGROUND
-                                : TabCreationState.LIVE_IN_FOREGROUND,
-                null);
-    }
-
-    /**
-     * Creates an instance of a {@link Tab} that is fully detached from any activity.
-     * Also performs general tab initialization as well as detached specifics.
-     *
-     * The current application context must allow the creation of a WindowAndroid.
-     *
-     * @return The newly created and initialized tab.
-     */
-    public static Tab createDetached(TabDelegateFactory delegateFactory) {
-        Context context = ContextUtils.getApplicationContext();
-        WindowAndroid windowAndroid = new WindowAndroid(context);
-        Tab tab = new Tab(INVALID_TAB_ID, INVALID_TAB_ID, false, windowAndroid,
-                TabLaunchType.FROM_SPECULATIVE_BACKGROUND_CREATION, null, null);
-        tab.initialize(null, null, delegateFactory, true, false);
-
-        // Resize the webContent to avoid expensive post load resize when attaching the tab.
-        Rect bounds = getEstimatedContentSize(context);
-        int width = bounds.right - bounds.left;
-        int height = bounds.bottom - bounds.top;
-        tab.getWebContents().setSize(width, height);
-
-        tab.detach();
-        return tab;
-    }
-
     void handleRendererResponsiveStateChanged(boolean isResponsive) {
         mIsRendererUnresponsive = !isResponsive;
         for (TabObserver observer : mObservers) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabBuilder.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabBuilder.java
new file mode 100644
index 0000000..3e16e24
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabBuilder.java
@@ -0,0 +1,144 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.tab;
+
+import org.chromium.chrome.browser.tab.TabUma.TabCreationState;
+import org.chromium.chrome.browser.tabmodel.TabLaunchType;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.ui.base.WindowAndroid;
+
+/**
+ * Builds {@link Tab} using builder pattern. All Tab classes should be instantiated
+ * through this builder.
+ */
+public class TabBuilder {
+    private int mId = Tab.INVALID_TAB_ID;
+    private int mParentId = Tab.INVALID_TAB_ID;
+    private boolean mIncognito;
+    private WindowAndroid mWindow;
+    private Integer mLaunchType;
+    private Integer mCreationType;
+    private TabState mFrozenState;
+    private LoadUrlParams mLoadUrlParams;
+
+    /**
+     * Sets the id with which the Tab to create should be identified.
+     * @param id The id of the Tab.
+     * @return {@link TabBuilder} creating the Tab.
+     */
+    public TabBuilder setId(int id) {
+        mId = id;
+        return this;
+    }
+
+    /**
+     * Sets the id of the tab from which the new one is opened.
+     * @param parentId The id of the parent Tab.
+     * @return {@link TabBuilder} creating the Tab.
+     */
+    public TabBuilder setParentId(int parentId) {
+        mParentId = parentId;
+        return this;
+    }
+
+    /**
+     * Sets incognito mode.
+     * @param incognito {@code true} if the tab will be in incognito mode.
+     * @return {@link TabBuilder} creating the Tab.
+     */
+    public TabBuilder setIncognito(boolean incognito) {
+        mIncognito = incognito;
+        return this;
+    }
+
+    /**
+     * Sets window which the Tab will be attached to.
+     * @param window An instance of a {@link WindowAndroid}.
+     * @return {@link TabBuilder} creating the Tab.
+     */
+    public TabBuilder setWindow(WindowAndroid window) {
+        mWindow = window;
+        return this;
+    }
+
+    /**
+     * Sets a flag indicating how this tab is launched (from a link, external app, etc).
+     * @param type Launch type.
+     * @return {@link TabBuilder} creating the Tab.
+     */
+    public TabBuilder setLaunchType(@TabLaunchType int type) {
+        mLaunchType = type;
+        return this;
+    }
+
+    public Tab build() {
+        // Pre-condition check
+        if (mCreationType != null) {
+            if (mFrozenState == null) {
+                assert mCreationType != TabCreationState.FROZEN_ON_RESTORE;
+            } else {
+                assert mLaunchType == TabLaunchType.FROM_RESTORE
+                        && mCreationType == TabCreationState.FROZEN_ON_RESTORE;
+            }
+        } else {
+            if (mFrozenState != null) assert mLaunchType == TabLaunchType.FROM_RESTORE;
+        }
+
+        return new Tab(mId, mParentId, mIncognito, mWindow, mLaunchType, mCreationType,
+                mFrozenState, mLoadUrlParams);
+    }
+
+    private TabBuilder setCreationType(@TabCreationState int type) {
+        mCreationType = type;
+        return this;
+    }
+
+    private TabBuilder setFrozenState(TabState frozenState) {
+        mFrozenState = frozenState;
+        return this;
+    }
+
+    private TabBuilder setLoadUrlParams(LoadUrlParams loadUrlParams) {
+        mLoadUrlParams = loadUrlParams;
+        return this;
+    }
+
+    /**
+     * Creates a TabBuilder for a new, "frozen" tab from a saved state. This can be used for
+     * background tabs restored on cold start that should be loaded when switched to. initialize()
+     * needs to be called afterwards to complete the second level initialization.
+     * @param state Frozen state from which the tab will be created.
+     */
+    public static TabBuilder createFromFrozenState(TabState state) {
+        assert state != null;
+        return new TabBuilder()
+                .setLaunchType(TabLaunchType.FROM_RESTORE)
+                .setCreationType(TabCreationState.FROZEN_ON_RESTORE)
+                .setFrozenState(state);
+    }
+
+    /**
+     * Creates a TabBuilder for a new tab to be loaded lazily. This can be used for tabs opened
+     * in the background that should be loaded when switched to. initialize() needs to be called
+     * afterwards to complete the second level initialization.
+     * @param loadUrlParams Params specifying the conditions for loading url.
+     */
+    public static TabBuilder createForLazyLoad(LoadUrlParams loadUrlParams) {
+        return new TabBuilder()
+                .setLoadUrlParams(loadUrlParams)
+                .setCreationType(TabCreationState.FROZEN_FOR_LAZY_LOAD);
+    }
+
+    /**
+     * Creates a TabBuilder for a fresh tab. initialize() needs to be called afterwards to
+     * complete the second level initialization.
+     * @param initiallyHidden true iff the tab being created is initially in background
+     */
+    public static TabBuilder createLiveTab(boolean initiallyHidden) {
+        return new TabBuilder().setCreationType(initiallyHidden
+                        ? TabCreationState.LIVE_IN_BACKGROUND
+                        : TabCreationState.LIVE_IN_FOREGROUND);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabFullscreenHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabFullscreenHandler.java
index 17ea187..34e1fc1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabFullscreenHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabFullscreenHandler.java
@@ -4,6 +4,8 @@
 
 package org.chromium.chrome.browser.tab;
 
+import org.chromium.base.UserData;
+import org.chromium.base.UserDataHost;
 import org.chromium.chrome.browser.fullscreen.FullscreenOptions;
 import org.chromium.content_public.browser.SelectionPopupController;
 import org.chromium.content_public.common.BrowserControlsState;
@@ -11,10 +13,32 @@
 /**
  * {@link TabObserver} for basic fullscreen operations for {@link Tab}.
  */
-public final class TabFullscreenHandler extends EmptyTabObserver {
+public final class TabFullscreenHandler extends EmptyTabObserver implements UserData {
+    private static final Class<TabFullscreenHandler> USER_DATA_KEY = TabFullscreenHandler.class;
+
+    private final Tab mTab;
+
     /** A runnable to delay the enabling of fullscreen mode if necessary. */
     private Runnable mEnterFullscreenRunnable;
 
+    public static void createForTab(Tab tab) {
+        UserDataHost host = tab.getUserDataHost();
+        assert host.getUserData(USER_DATA_KEY) == null;
+        host.setUserData(USER_DATA_KEY, new TabFullscreenHandler(tab));
+    }
+
+    private TabFullscreenHandler(Tab tab) {
+        mTab = tab;
+        mTab.addObserver(this);
+    }
+
+    // UserData
+
+    @Override
+    public void destroy() {
+        mTab.removeObserver(this);
+    }
+
     @Override
     public void onSSLStateUpdated(Tab tab) {
         tab.updateFullscreenEnabledState();
@@ -81,4 +105,9 @@
             tab.updateBrowserControlsState(BrowserControlsState.SHOWN, false);
         }
     }
+
+    @Override
+    public void onPageLoadFinished(Tab tab, String url) {
+        tab.updateFullscreenEnabledState();
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
index 37054d9..d5a1da6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/ChromeTabCreator.java
@@ -15,6 +15,7 @@
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
 import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBuilder;
 import org.chromium.chrome.browser.tab.TabDelegateFactory;
 import org.chromium.chrome.browser.tab.TabParentIntent;
 import org.chromium.chrome.browser.tab.TabRedirectHandler;
@@ -129,8 +130,13 @@
 
                 assert TabModelUtils.getTabIndexById(mTabModel, assignedTabId)
                         == TabModel.INVALID_TAB_INDEX;
-                tab = Tab.createLiveTab(assignedTabId, mIncognito, mNativeWindow, type, parentId,
-                        !openInForeground);
+                tab = TabBuilder.createLiveTab(!openInForeground)
+                              .setId(assignedTabId)
+                              .setParentId(parentId)
+                              .setIncognito(mIncognito)
+                              .setWindow(mNativeWindow)
+                              .setLaunchType(type)
+                              .build();
                 tab.initialize(
                         webContents, mTabContentManager, delegateFactory, !openInForeground, false);
                 TabParentIntent.from(tab).set(parentIntent);
@@ -139,12 +145,21 @@
                 // On low memory devices the tabs opened in background are not loaded automatically
                 // to preserve resources (cpu, memory, strong renderer binding) for the foreground
                 // tab.
-                tab = Tab.createTabForLazyLoad(
-                        mIncognito, mNativeWindow, type, parentId, loadUrlParams);
+                tab = TabBuilder.createForLazyLoad(loadUrlParams)
+                              .setParentId(parentId)
+                              .setIncognito(mIncognito)
+                              .setWindow(mNativeWindow)
+                              .setLaunchType(type)
+                              .build();
                 tab.initialize(null, mTabContentManager, delegateFactory, !openInForeground, false);
             } else {
-                tab = Tab.createLiveTab(Tab.INVALID_TAB_ID, mIncognito, mNativeWindow, type,
-                        parentId, !openInForeground);
+                tab = TabBuilder.createLiveTab(!openInForeground)
+                              .setParentId(parentId)
+                              .setIncognito(mIncognito)
+                              .setWindow(mNativeWindow)
+                              .setLaunchType(type)
+                              .build();
+
                 tab.initialize(null, mTabContentManager, delegateFactory, !openInForeground, false);
                 tab.loadUrl(loadUrlParams);
             }
@@ -176,8 +191,12 @@
         boolean openInForeground = mOrderController.willOpenInForeground(type, mIncognito);
         TabDelegateFactory delegateFactory = parent == null ? createDefaultTabDelegateFactory()
                 : parent.getDelegateFactory();
-        Tab tab = Tab.createLiveTab(
-                Tab.INVALID_TAB_ID, mIncognito, mNativeWindow, type, parentId, !openInForeground);
+        Tab tab = TabBuilder.createLiveTab(!openInForeground)
+                          .setParentId(parentId)
+                          .setIncognito(mIncognito)
+                          .setWindow(mNativeWindow)
+                          .setLaunchType(type)
+                          .build();
         tab.initialize(webContents, mTabContentManager, delegateFactory, !openInForeground, false);
         mTabModel.addTab(tab, position, type);
         return true;
@@ -274,8 +293,12 @@
 
     @Override
     public Tab createFrozenTab(TabState state, int id, int index) {
-        Tab tab = Tab.createFrozenTabFromState(
-                id, state.isIncognito(), mNativeWindow, state.parentId, state);
+        Tab tab = TabBuilder.createFromFrozenState(state)
+                          .setId(id)
+                          .setParentId(state.parentId)
+                          .setIncognito(state.isIncognito())
+                          .setWindow(mNativeWindow)
+                          .build();
         boolean selectTab = mOrderController.willOpenInForeground(TabLaunchType.FROM_RESTORE,
                 state.isIncognito());
         tab.initialize(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/DocumentTabModelImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/DocumentTabModelImpl.java
index a2628e2b..178cac9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/DocumentTabModelImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/DocumentTabModelImpl.java
@@ -13,6 +13,7 @@
 import org.chromium.chrome.browser.document.DocumentActivity;
 import org.chromium.chrome.browser.document.DocumentUtils;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBuilder;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
 import org.chromium.chrome.browser.tabmodel.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabList;
@@ -236,7 +237,8 @@
 
         // Create a placeholder Tab that just has the ID.
         if (entry.placeholderTab == null) {
-            entry.placeholderTab = new Tab(tabId, isIncognito(), null);
+            entry.placeholderTab =
+                    new TabBuilder().setId(tabId).setIncognito(isIncognito()).build();
         }
 
         return entry.placeholderTab;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/TabDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/TabDelegate.java
index 5190eae..34d92d8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/TabDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/TabDelegate.java
@@ -18,6 +18,7 @@
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
 import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBuilder;
 import org.chromium.chrome.browser.tab.TabIdManager;
 import org.chromium.chrome.browser.tab.TabState;
 import org.chromium.chrome.browser.tabmodel.AsyncTabParamsManager;
@@ -53,8 +54,10 @@
      */
     @Override
     public Tab createFrozenTab(TabState state, int id, int index) {
-        return Tab.createFrozenTabFromState(
-                id, state.isIncognito(), null, Tab.INVALID_TAB_ID, state);
+        return TabBuilder.createFromFrozenState(state)
+                .setId(id)
+                .setIncognito(state.isIncognito())
+                .build();
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupUtils.java
index e437d83..418eebb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupUtils.java
@@ -32,15 +32,21 @@
 
         @StringRes
         int textId;
+        @StringRes
+        int accessibilityTextId;
         switch (featureName) {
             case FeatureConstants.TAB_GROUPS_QUICKLY_COMPARE_PAGES_FEATURE:
                 textId = R.string.iph_tab_groups_quickly_compare_pages_text;
+                accessibilityTextId = textId;
                 break;
             case FeatureConstants.TAB_GROUPS_TAP_TO_SEE_ANOTHER_TAB_FEATURE:
                 textId = R.string.iph_tab_groups_tap_to_see_another_tab_text;
+                accessibilityTextId =
+                        R.string.iph_tab_groups_tap_to_see_another_tab_accessibility_text;
                 break;
             case FeatureConstants.TAB_GROUPS_YOUR_TABS_ARE_TOGETHER_FEATURE:
                 textId = R.string.iph_tab_groups_your_tabs_together_text;
+                accessibilityTextId = textId;
                 break;
             default:
                 assert false;
@@ -53,8 +59,8 @@
 
         ViewRectProvider rectProvider = new ViewRectProvider(view);
 
-        TextBubble textBubble =
-                new TextBubble(view.getContext(), view, textId, textId, true, rectProvider);
+        TextBubble textBubble = new TextBubble(
+                view.getContext(), view, textId, accessibilityTextId, true, rectProvider);
         textBubble.setDismissOnTouchInteraction(true);
         textBubble.addOnDismissListener(() -> tracker.dismissed(featureName));
         textBubble.show();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridCoordinator.java
index 256580b..24d0539 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridCoordinator.java
@@ -4,14 +4,18 @@
 
 package org.chromium.chrome.browser.tasks.tab_list_ui;
 
+import android.app.Activity;
 import android.content.Context;
 
+import org.chromium.base.ApplicationStatus;
+import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
 import org.chromium.chrome.browser.dependency_injection.ActivityScope;
 import org.chromium.chrome.browser.lifecycle.Destroyable;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.tasks.tab_groups.TabGroupUtils;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -34,7 +38,7 @@
             TabCreatorManager tabCreatorManager) {
         mContext = context;
 
-        mToolbarPropertyModel = new PropertyModel(BottomTabGridSheetToolbarProperties.ALL_KEYS);
+        mToolbarPropertyModel = new PropertyModel(BottomTabGridSheetProperties.ALL_KEYS);
 
         mTabGridCoordinator = new TabListCoordinator(TabListCoordinator.TabListMode.GRID, context,
                 tabModelSelector, tabContentManager, bottomSheetController.getBottomSheet(), false);
@@ -42,6 +46,7 @@
         mMediator =
                 new BottomTabGridMediator(mContext, bottomSheetController, this::resetWithTabModel,
                         mToolbarPropertyModel, tabModelSelector, tabCreatorManager);
+        startObservingForCreationIPH();
     }
 
     /**
@@ -77,7 +82,7 @@
             mToolbarCoordinator = new BottomTabGridSheetToolbarCoordinator(
                     mContext, mTabGridCoordinator.getContainerView(), mToolbarPropertyModel);
             mBottomSheetContent = new BottomTabGridSheetContent(
-                    mTabGridCoordinator.getContainerView(), mToolbarCoordinator);
+                    mTabGridCoordinator.getContainerView(), mToolbarCoordinator.getView());
         } else {
             if (mBottomSheetContent != null) {
                 mBottomSheetContent.destroy();
@@ -89,4 +94,12 @@
             }
         }
     }
+
+    private void startObservingForCreationIPH() {
+        Activity activity = ApplicationStatus.getLastTrackedFocusedActivity();
+        if (!(activity instanceof ChromeTabbedActivity)) return;
+
+        TabGroupUtils.startObservingForTabGroupsIPH(
+                ((ChromeTabbedActivity) activity).getTabModelSelector());
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridMediator.java
index 969fbab2..5e8029f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridMediator.java
@@ -16,6 +16,7 @@
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabModelObserver;
+import org.chromium.chrome.browser.tabmodel.TabSelectionType;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.BottomSheetContent;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.StateChangeReason;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
@@ -45,17 +46,17 @@
     private final Context mContext;
     private final BottomSheetController mBottomSheetController;
     private final BottomSheetObserver mSheetObserver;
-    private final PropertyModel mToolbarPropertyModel;
+    private final PropertyModel mModel;
     private final TabModelSelector mTabModelSelector;
     private final TabModelSelectorTabModelObserver mTabModelObserver;
     private final TabCreatorManager mTabCreatorManager;
 
     BottomTabGridMediator(Context context, BottomSheetController bottomSheetController,
-            ResetHandler resetHandler, PropertyModel toolbarPropertyModel,
-            TabModelSelector tabModelSelector, TabCreatorManager tabCreatorManager) {
+            ResetHandler resetHandler, PropertyModel model, TabModelSelector tabModelSelector,
+            TabCreatorManager tabCreatorManager) {
         mContext = context;
         mBottomSheetController = bottomSheetController;
-        mToolbarPropertyModel = toolbarPropertyModel;
+        mModel = model;
         mTabModelSelector = tabModelSelector;
         mTabCreatorManager = tabCreatorManager;
 
@@ -71,18 +72,23 @@
         mTabModelObserver = new TabModelSelectorTabModelObserver(tabModelSelector) {
             @Override
             public void didCloseTab(int tabId, boolean incognito) {
-                updateBottomSheetTitle();
+                updateBottomSheetTitleAndMargin();
             }
 
             @Override
             public void didAddTab(Tab tab, @TabLaunchType int type) {
-                updateBottomSheetTitle();
+                updateBottomSheetTitleAndMargin();
+            }
+
+            @Override
+            public void didSelectTab(Tab tab, int type, int lastId) {
+                if (type == TabSelectionType.FROM_USER) resetHandler.resetWithTabModel(null);
             }
         };
 
         // setup toolbar property model
         setupToolbarClickHandlers();
-        updateBottomSheetTitle();
+        updateBottomSheetTitleAndMargin();
     }
 
     /**
@@ -125,18 +131,19 @@
                 : null;
     }
 
-    private void updateBottomSheetTitle() {
+    private void updateBottomSheetTitleAndMargin() {
         int tabsCount = mTabModelSelector.getCurrentModel().getCount();
-        mToolbarPropertyModel.set(BottomTabGridSheetToolbarProperties.HEADER_TITLE,
+        mModel.set(BottomTabGridSheetProperties.HEADER_TITLE,
                 mContext.getResources().getQuantityString(
                         R.plurals.bottom_tab_grid_title_placeholder, tabsCount, tabsCount));
+        mModel.set(BottomTabGridSheetProperties.CONTENT_TOP_MARGIN,
+                (int) mContext.getResources().getDimension(R.dimen.control_container_height));
     }
 
     private void setupToolbarClickHandlers() {
-        mToolbarPropertyModel.set(BottomTabGridSheetToolbarProperties.COLLAPSE_CLICK_LISTENER,
+        mModel.set(BottomTabGridSheetProperties.COLLAPSE_CLICK_LISTENER,
                 getCollapseButtonClickListener());
-        mToolbarPropertyModel.set(BottomTabGridSheetToolbarProperties.ADD_CLICK_LISTENER,
-                getAddButtonClickListener());
+        mModel.set(BottomTabGridSheetProperties.ADD_CLICK_LISTENER, getAddButtonClickListener());
     }
 
     private OnClickListener getCollapseButtonClickListener() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetContent.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetContent.java
index 7e44b77b..8e503da2a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetContent.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetContent.java
@@ -10,23 +10,19 @@
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.BottomSheetContent;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.ContentPriority;
 
-import javax.inject.Inject;
-
 /** A {@link BottomSheetContent} that displays tab grid. **/
 public class BottomTabGridSheetContent implements BottomSheetContent {
     private final TabListRecyclerView mRecyclerView;
-    private final BottomTabGridSheetToolbarCoordinator mToolbarCoordinator;
+    private final View mToolbarView;
 
     /**
-     * Construct a new {@link ContextualSuggestionsBottomSheetContent}.
+     * Construct a new {@link BottomTabGridSheetContent}.
      * @param recyclerView The {@link TabListRecyclerView} holding the tab grid.
-     * @param toolbarCoordinator instance of {@link BottomTabGridSheetToolbarCoordinator}
+     * @param toolbarView The toolbar {@link View} to use.}
      */
-    @Inject
-    BottomTabGridSheetContent(TabListRecyclerView recyclerView,
-            BottomTabGridSheetToolbarCoordinator toolbarCoordinator) {
+    BottomTabGridSheetContent(TabListRecyclerView recyclerView, View toolbarView) {
         mRecyclerView = recyclerView;
-        mToolbarCoordinator = toolbarCoordinator;
+        mToolbarView = toolbarView;
     }
 
     @Override
@@ -36,7 +32,7 @@
 
     @Override
     public View getToolbarView() {
-        return mToolbarCoordinator.getView();
+        return mToolbarView;
     }
 
     @Override
@@ -78,6 +74,11 @@
     }
 
     @Override
+    public boolean hasCustomLifecycle() {
+        return true;
+    }
+
+    @Override
     public int getSheetClosedAccessibilityStringId() {
         return R.string.bottom_tab_grid_closed;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetToolbarProperties.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetProperties.java
similarity index 72%
rename from chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetToolbarProperties.java
rename to chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetProperties.java
index 1224b59..fbb0496 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetToolbarProperties.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetProperties.java
@@ -9,7 +9,7 @@
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
 
-class BottomTabGridSheetToolbarProperties {
+class BottomTabGridSheetProperties {
     public static final PropertyModel
             .WritableObjectPropertyKey<OnClickListener> COLLAPSE_CLICK_LISTENER =
             new PropertyModel.WritableObjectPropertyKey<OnClickListener>();
@@ -18,7 +18,9 @@
             new PropertyModel.WritableObjectPropertyKey<OnClickListener>();
     public static final PropertyModel.WritableObjectPropertyKey<String> HEADER_TITLE =
             new PropertyModel.WritableObjectPropertyKey<String>();
+    public static final PropertyModel.WritableIntPropertyKey CONTENT_TOP_MARGIN =
+            new PropertyModel.WritableIntPropertyKey();
 
-    public static final PropertyKey[] ALL_KEYS =
-            new PropertyKey[] {COLLAPSE_CLICK_LISTENER, ADD_CLICK_LISTENER, HEADER_TITLE};
+    public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {
+            COLLAPSE_CLICK_LISTENER, ADD_CLICK_LISTENER, HEADER_TITLE, CONTENT_TOP_MARGIN};
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetToolbarCoordinator.java
index 6b1e5596..36e4a1c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetToolbarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetToolbarCoordinator.java
@@ -26,17 +26,18 @@
      * Construct a new {@link BottomTabGridSheetToolbarCoordinator}.
      *
      * @param context              The {@link Context} used to retrieve resources.
-     * @param parentView           The parent {@link View} to which the content will
+     * @param contentView          The {@link View} to which the content will
      *                             eventually be attached.
      * @param toolbarPropertyModel The {@link PropertyModel} instance representing
      *                             the toolbar.
      */
     BottomTabGridSheetToolbarCoordinator(
-            Context context, ViewGroup parentView, PropertyModel toolbarPropertyModel) {
+            Context context, ViewGroup contentView, PropertyModel toolbarPropertyModel) {
         mToolbarView = (BottomTabListToolbarView) LayoutInflater.from(context).inflate(
-                R.layout.bottom_tab_grid_toolbar, parentView, false);
-        mModelChangeProcessor = PropertyModelChangeProcessor.create(
-                toolbarPropertyModel, mToolbarView, BottomTabGridSheetToolbarViewBinder::bind);
+                R.layout.bottom_tab_grid_toolbar, contentView, false);
+        mModelChangeProcessor = PropertyModelChangeProcessor.create(toolbarPropertyModel,
+                new BottomTabGridSheetViewBinder.ViewHolder(mToolbarView, contentView),
+                BottomTabGridSheetViewBinder::bind);
     }
 
     /** @return The content {@link View}. */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetToolbarViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetToolbarViewBinder.java
deleted file mode 100644
index 46c77e4..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetToolbarViewBinder.java
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.tasks.tab_list_ui;
-
-import static org.chromium.chrome.browser.tasks.tab_list_ui.BottomTabGridSheetToolbarProperties.ADD_CLICK_LISTENER;
-import static org.chromium.chrome.browser.tasks.tab_list_ui.BottomTabGridSheetToolbarProperties.COLLAPSE_CLICK_LISTENER;
-import static org.chromium.chrome.browser.tasks.tab_list_ui.BottomTabGridSheetToolbarProperties.HEADER_TITLE;
-
-import org.chromium.ui.modelutil.PropertyKey;
-import org.chromium.ui.modelutil.PropertyModel;
-
-/**
- * ViewBinder for BottomTabGridSheetToolbar.
- */
-class BottomTabGridSheetToolbarViewBinder {
-    /**
-     * Binds the given model to the given view, updating the payload in propertyKey.
-     * @param model The model to use.
-     * @param view The View to use.
-     * @param propertyKey The key for the property to update for.
-     */
-    public static void bind(
-            PropertyModel model, BottomTabListToolbarView view, PropertyKey propertyKey) {
-        if (COLLAPSE_CLICK_LISTENER == propertyKey) {
-            view.setLeftButtonOnClickListener(model.get(COLLAPSE_CLICK_LISTENER));
-        } else if (ADD_CLICK_LISTENER == propertyKey) {
-            view.setRightButtonOnClickListener(model.get(ADD_CLICK_LISTENER));
-        } else if (HEADER_TITLE == propertyKey) {
-            view.setTitle(model.get(HEADER_TITLE));
-        }
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetViewBinder.java
new file mode 100644
index 0000000..1a2ddf7
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetViewBinder.java
@@ -0,0 +1,54 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.tasks.tab_list_ui;
+
+import static org.chromium.chrome.browser.tasks.tab_list_ui.BottomTabGridSheetProperties.ADD_CLICK_LISTENER;
+import static org.chromium.chrome.browser.tasks.tab_list_ui.BottomTabGridSheetProperties.COLLAPSE_CLICK_LISTENER;
+import static org.chromium.chrome.browser.tasks.tab_list_ui.BottomTabGridSheetProperties.CONTENT_TOP_MARGIN;
+import static org.chromium.chrome.browser.tasks.tab_list_ui.BottomTabGridSheetProperties.HEADER_TITLE;
+
+import android.view.View;
+import android.widget.FrameLayout;
+
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/**
+ * ViewBinder for BottomTabGridSheet.
+ */
+class BottomTabGridSheetViewBinder {
+    /**
+     * ViewHolder class to get access to all {@link View}s inside the BottomTabGrid.
+     */
+    public static class ViewHolder {
+        public final BottomTabListToolbarView toolbarView;
+        public final View contentView;
+
+        ViewHolder(BottomTabListToolbarView toolbarView, View contentView) {
+            this.toolbarView = toolbarView;
+            this.contentView = contentView;
+        }
+    }
+
+    /**
+     * Binds the given model to the given view, updating the payload in propertyKey.
+     * @param model The model to use.
+     * @param viewHolder The ViewHolder to use.
+     * @param propertyKey The key for the property to update for.
+     */
+    public static void bind(PropertyModel model, ViewHolder viewHolder, PropertyKey propertyKey) {
+        if (COLLAPSE_CLICK_LISTENER == propertyKey) {
+            viewHolder.toolbarView.setLeftButtonOnClickListener(model.get(COLLAPSE_CLICK_LISTENER));
+        } else if (ADD_CLICK_LISTENER == propertyKey) {
+            viewHolder.toolbarView.setRightButtonOnClickListener(model.get(ADD_CLICK_LISTENER));
+        } else if (HEADER_TITLE == propertyKey) {
+            viewHolder.toolbarView.setTitle(model.get(HEADER_TITLE));
+        } else if (CONTENT_TOP_MARGIN == propertyKey) {
+            ((FrameLayout.LayoutParams) viewHolder.contentView.getLayoutParams()).topMargin =
+                    model.get(CONTENT_TOP_MARGIN);
+            viewHolder.contentView.requestLayout();
+        }
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabListToolbarView.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabListToolbarView.java
index 11c6fc38..1c483d4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabListToolbarView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabListToolbarView.java
@@ -12,15 +12,15 @@
 import android.widget.TextView;
 
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.widget.ListMenuButton;
+import org.chromium.ui.widget.ChromeImageView;
 
 /**
  * Represents a generic toolbar used in the bottom strip/grid component.
  * {@link BottomTabGridSheetToolbarCoordinator}
  */
 public class BottomTabListToolbarView extends FrameLayout {
-    private ListMenuButton mRightButton;
-    private ListMenuButton mLeftButton;
+    private ChromeImageView mRightButton;
+    private ChromeImageView mLeftButton;
     private ViewGroup mContainerView;
     private TextView mTitleTextView;
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripBottomToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripBottomToolbarCoordinator.java
index 41c1a60..bd77276 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripBottomToolbarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripBottomToolbarCoordinator.java
@@ -4,17 +4,10 @@
 
 package org.chromium.chrome.browser.tasks.tab_list_ui;
 
-import static org.chromium.chrome.browser.dependency_injection.ChromeCommonQualifiers.ACTIVITY_CONTEXT;
-
 import android.content.Context;
 import android.view.ViewGroup;
-import android.view.ViewStub;
 
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
-import org.chromium.chrome.browser.dependency_injection.ActivityScope;
-import org.chromium.chrome.browser.init.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.lifecycle.Destroyable;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
 import org.chromium.chrome.browser.tabmodel.TabModel;
@@ -22,44 +15,29 @@
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
 import org.chromium.ui.modelutil.PropertyModel;
 
-import javax.inject.Inject;
-import javax.inject.Named;
-
 /**
  * A coordinator for BottomTabStrip component. Manages the communication with
  * {@link TabListCoordinator} & @{link BottomTabGridCoordinator} as well as the
  * life-cycle of shared component objects.
  */
-@ActivityScope
 public class TabStripBottomToolbarCoordinator
         implements Destroyable, TabStripBottomToolbarMediator.ResetHandler {
-    private final ActivityLifecycleDispatcher mLifecycleDispatcher;
     private final Context mContext;
     private final PropertyModel mTabStripToolbarModel;
-    private BottomSheetController mBottomSheetController;
     private BottomTabGridCoordinator mBottomTabGridCoordinator;
-    private TabContentManager mTabContentManager;
-    private TabCreatorManager mTabCreatorManager;
     private TabListCoordinator mTabStripCoordinator;
-    private TabModelSelector mTabModelSelector;
     private TabStripBottomToolbarMediator mMediator;
     private TabStripToolbarCoordinator mTabStripToolbarCoordinator;
 
     /**
      * Creates a new {@link TabStripBottomToolbarCoordinator}
      */
-    @Inject
-    public TabStripBottomToolbarCoordinator(@Named(ACTIVITY_CONTEXT) Context context,
-            ChromeActivity activity, ActivityLifecycleDispatcher lifecycleDispatcher) {
+    public TabStripBottomToolbarCoordinator(Context context, ViewGroup parentView) {
         mContext = context;
-        mLifecycleDispatcher = lifecycleDispatcher;
         mTabStripToolbarModel = new PropertyModel(TabStripToolbarViewProperties.ALL_KEYS);
 
-        ViewStub stub = activity.findViewById(R.id.bottom_toolbar_stub);
-        mTabStripToolbarCoordinator = new TabStripToolbarCoordinator(
-                mContext, (ViewGroup) stub.inflate(), mTabStripToolbarModel);
-
-        mLifecycleDispatcher.register(this);
+        mTabStripToolbarCoordinator =
+                new TabStripToolbarCoordinator(mContext, parentView, mTabStripToolbarModel);
     }
 
     /**
@@ -68,20 +46,15 @@
     public void initializeWithNative(TabModelSelector tabModelSelector,
             TabContentManager tabContentManager, TabCreatorManager tabCreatorManager,
             BottomSheetController bottomSheetController) {
-        mTabModelSelector = tabModelSelector;
-        mTabContentManager = tabContentManager;
-        mTabCreatorManager = tabCreatorManager;
-        mBottomSheetController = bottomSheetController;
-
         mTabStripCoordinator = new TabListCoordinator(TabListCoordinator.TabListMode.STRIP,
-                mContext, mTabModelSelector, mTabContentManager,
+                mContext, tabModelSelector, tabContentManager,
                 mTabStripToolbarCoordinator.getTabListContainerView(), true);
 
-        mBottomTabGridCoordinator = new BottomTabGridCoordinator(mContext, mBottomSheetController,
-                mTabModelSelector, mTabContentManager, mTabCreatorManager);
+        mBottomTabGridCoordinator = new BottomTabGridCoordinator(mContext, bottomSheetController,
+                tabModelSelector, tabContentManager, tabCreatorManager);
 
         mMediator = new TabStripBottomToolbarMediator(
-                this, mTabStripToolbarModel, mTabModelSelector, mTabCreatorManager);
+                this, mTabStripToolbarModel, tabModelSelector, tabCreatorManager);
     }
 
     /**
@@ -93,7 +66,6 @@
     @Override
     public void resetStripWithTabModel(TabModel tabModel) {
         mTabStripCoordinator.resetWithTabModel(tabModel);
-        mMediator.resetWithTabModel(tabModel);
     }
 
     /**
@@ -115,6 +87,5 @@
         mTabStripCoordinator.destroy();
         mBottomTabGridCoordinator.destroy();
         mMediator.destroy();
-        mLifecycleDispatcher.unregister(this);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripBottomToolbarMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripBottomToolbarMediator.java
index bece6415..444890d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripBottomToolbarMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripBottomToolbarMediator.java
@@ -4,8 +4,6 @@
 
 package org.chromium.chrome.browser.tasks.tab_list_ui;
 
-import android.view.View.OnClickListener;
-
 import org.chromium.chrome.browser.UrlConstants;
 import org.chromium.chrome.browser.lifecycle.Destroyable;
 import org.chromium.chrome.browser.tab.Tab;
@@ -71,11 +69,8 @@
         };
 
         setupToolbarClickHandlers();
-        mResetHandler.resetStripWithTabModel(tabModelSelector.getCurrentModel());
-    }
-
-    void resetWithTabModel(TabModel tabModel) {
         mToolbarPropertyModel.set(TabStripToolbarViewProperties.IS_MAIN_CONTENT_VISIBLE, true);
+        mResetHandler.resetStripWithTabModel(tabModelSelector.getCurrentModel());
     }
 
     private void setupToolbarClickHandlers() {
@@ -90,21 +85,6 @@
         });
     }
 
-    private OnClickListener getExpandButtonClickListener() {
-        return view -> {
-            mResetHandler.resetSheetWithTabModel(mTabModelSelector.getCurrentModel());
-        };
-    }
-
-    private OnClickListener getAddButtonClickListener() {
-        return view -> {
-            Tab currentTab = mTabModelSelector.getCurrentTab();
-            mTabCreatorManager.getTabCreator(currentTab.isIncognito())
-                    .createNewTab(new LoadUrlParams(UrlConstants.NTP_URL), TabLaunchType.FROM_LINK,
-                            currentTab);
-        };
-    }
-
     @Override
     public void destroy() {
         mTabModelObserver.destroy();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripViewBinder.java
index 64d2581..b0104e5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripViewBinder.java
@@ -41,14 +41,19 @@
                                             R.drawable.selected_tab_background,
                                             holder.itemView.getContext().getTheme())
                                     : null);
+            String title = item.get(TabProperties.TITLE);
             if (item.get(TabProperties.IS_SELECTED)) {
                 holder.button.setOnClickListener(view -> {
                     item.get(TabProperties.TAB_CLOSED_LISTENER).run(holder.getTabId());
                 });
+                holder.button.setContentDescription(holder.itemView.getContext().getString(
+                        R.string.accessibility_tabstrip_btn_close_tab, title));
             } else {
                 holder.button.setOnClickListener(view -> {
                     item.get(TabProperties.TAB_SELECTED_LISTENER).run(holder.getTabId());
                 });
+                holder.button.setContentDescription(holder.itemView.getContext().getString(
+                        R.string.accessibility_tabstrip_tab, title));
             }
         } else if (TabProperties.FAVICON == propertyKey) {
             Bitmap favicon = item.get(TabProperties.FAVICON);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index 9590abb4..f1b21971 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -84,7 +84,7 @@
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
 import org.chromium.chrome.browser.tabmodel.TabSelectionType;
-import org.chromium.chrome.browser.toolbar.bottom.BottomToolbarCoordinator;
+import org.chromium.chrome.browser.toolbar.bottom.BottomControlsCoordinator;
 import org.chromium.chrome.browser.toolbar.top.ActionModeController;
 import org.chromium.chrome.browser.toolbar.top.ActionModeController.ActionBarDelegate;
 import org.chromium.chrome.browser.toolbar.top.Toolbar;
@@ -177,7 +177,7 @@
     private TopToolbarCoordinator mToolbar;
     private final ToolbarControlContainer mControlContainer;
 
-    private BottomToolbarCoordinator mBottomToolbarCoordinator;
+    private BottomControlsCoordinator mBottomControlsCoordinator;
     private TabModelSelector mTabModelSelector;
     private TabModelSelectorObserver mTabModelSelectorObserver;
     private TabModelObserver mTabModelObserver;
@@ -782,11 +782,12 @@
             mActivity.onShareMenuItemSelected(false, isIncognito);
         };
 
-        mBottomToolbarCoordinator = new BottomToolbarCoordinator(mActivity.getFullscreenManager(),
-                mActivity.findViewById(R.id.bottom_toolbar_stub),
+        mBottomControlsCoordinator = new BottomControlsCoordinator(mActivity.getFullscreenManager(),
+                mActivity.findViewById(R.id.bottom_controls_stub),
                 mActivity.getActivityTabProvider(), homeButtonListener, searchAcceleratorListener,
                 shareButtonListener);
-        mBottomToolbarCoordinator.setBottomToolbarVisible(mIsBottomToolbarVisible);
+        mBottomControlsCoordinator.setBottomControlsVisible(mIsBottomToolbarVisible);
+
         Toast.setGlobalExtraYOffset(
                 mActivity.getResources().getDimensionPixelSize(R.dimen.bottom_toolbar_height));
     }
@@ -826,8 +827,8 @@
     /**
      * @return The coordinator for the bottom toolbar if it exists.
      */
-    public BottomToolbarCoordinator getBottomToolbarCoordinator() {
-        return mBottomToolbarCoordinator;
+    public BottomControlsCoordinator getBottomToolbarCoordinator() {
+        return mBottomControlsCoordinator;
     }
 
     private void showMenuIPHTextBubble(ChromeActivity activity, Tracker tracker, String featureName,
@@ -964,7 +965,7 @@
                 mLayoutManager.addSceneChangeObserver(mSceneChangeObserver);
             }
 
-            if (mBottomToolbarCoordinator != null) {
+            if (mBottomControlsCoordinator != null) {
                 final OnClickListener closeTabsClickListener = v -> {
                     recordBottomToolbarUseForIPH();
                     final boolean isIncognito = mTabModelSelector.isIncognitoSelected();
@@ -977,14 +978,14 @@
                     mTabModelSelector.getModel(isIncognito).closeAllTabs();
                 };
                 mAppMenuButtonHelper.setOnClickRunnable(() -> recordBottomToolbarUseForIPH());
-                mBottomToolbarCoordinator.initializeWithNative(
+                mBottomControlsCoordinator.initializeWithNative(
                         mActivity.getCompositorViewHolder().getResourceManager(),
                         mActivity.getCompositorViewHolder().getLayoutManager(),
                         wrapBottomToolbarClickListenerForIPH(tabSwitcherClickHandler),
                         wrapBottomToolbarClickListenerForIPH(newTabClickHandler),
-                        closeTabsClickListener, mAppMenuButtonHelper, mTabModelSelector,
-                        mOverviewModeBehavior, mActivity.getWindowAndroid(), mTabCountProvider,
-                        mIncognitoStateProvider, mActivity.findViewById(R.id.control_container));
+                        closeTabsClickListener, mAppMenuButtonHelper, mOverviewModeBehavior,
+                        mActivity.getWindowAndroid(), mTabCountProvider, mIncognitoStateProvider,
+                        mActivity.findViewById(R.id.control_container));
 
                 // Allow the bottom toolbar to be focused in accessibility after the top toolbar.
                 ApiCompatibilityUtils.setAccessibilityTraversalBefore(
@@ -1007,8 +1008,8 @@
      */
     public void showAppMenuUpdateBadge() {
         mToolbar.showAppMenuUpdateBadge();
-        if (mBottomToolbarCoordinator != null) {
-            mBottomToolbarCoordinator.showAppMenuUpdateBadge();
+        if (mBottomControlsCoordinator != null) {
+            mBottomControlsCoordinator.showAppMenuUpdateBadge();
         }
     }
 
@@ -1018,8 +1019,8 @@
      */
     public void removeAppMenuUpdateBadge(boolean animate) {
         mToolbar.removeAppMenuUpdateBadge(animate);
-        if (mBottomToolbarCoordinator != null) {
-            mBottomToolbarCoordinator.removeAppMenuUpdateBadge();
+        if (mBottomControlsCoordinator != null) {
+            mBottomControlsCoordinator.removeAppMenuUpdateBadge();
         }
     }
 
@@ -1028,8 +1029,8 @@
      * TODO(amaralp): Only the top or bottom menu should be visible.
      */
     public boolean isShowingAppMenuUpdateBadge() {
-        if (mBottomToolbarCoordinator != null
-                && mBottomToolbarCoordinator.isShowingAppMenuUpdateBadge()) {
+        if (mBottomControlsCoordinator != null
+                && mBottomControlsCoordinator.isShowingAppMenuUpdateBadge()) {
             return true;
         }
         return mToolbar.isShowingAppMenuUpdateBadge();
@@ -1093,8 +1094,8 @@
      * @return The view containing the pop up menu button.
      */
     public @Nullable View getMenuButton() {
-        if (mBottomToolbarCoordinator != null && isMenuButtonInBottomToolbar()) {
-            return mBottomToolbarCoordinator.getMenuButtonWrapper().getImageButton();
+        if (mBottomControlsCoordinator != null && isMenuButtonInBottomToolbar()) {
+            return mBottomControlsCoordinator.getMenuButtonWrapper().getImageButton();
         }
         return mToolbar.getMenuButton();
     }
@@ -1164,9 +1165,9 @@
             mLayoutManager = null;
         }
 
-        if (mBottomToolbarCoordinator != null) {
-            mBottomToolbarCoordinator.destroy();
-            mBottomToolbarCoordinator = null;
+        if (mBottomControlsCoordinator != null) {
+            mBottomControlsCoordinator.destroy();
+            mBottomControlsCoordinator = null;
         }
 
         if (mOmniboxStartupMetrics != null) {
@@ -1206,11 +1207,11 @@
     public void onOrientationChange() {
         if (mActionModeController != null) mActionModeController.showControlsOnOrientationChange();
 
-        if (mBottomToolbarCoordinator != null && FeatureUtilities.isAdaptiveToolbarEnabled()) {
+        if (mBottomControlsCoordinator != null && FeatureUtilities.isAdaptiveToolbarEnabled()) {
             mIsBottomToolbarVisible = mActivity.getResources().getConfiguration().orientation
                     != Configuration.ORIENTATION_LANDSCAPE;
             mToolbar.onBottomToolbarVisibilityChanged(mIsBottomToolbarVisible);
-            mBottomToolbarCoordinator.setBottomToolbarVisible(mIsBottomToolbarVisible);
+            mBottomControlsCoordinator.setBottomControlsVisible(mIsBottomToolbarVisible);
             if (mAppMenuButtonHelper != null) {
                 mAppMenuButtonHelper.setMenuShowsFromBottom(mIsBottomToolbarVisible);
             }
@@ -1345,8 +1346,8 @@
 
     @Nullable
     private MenuButton getMenuButtonWrapper() {
-        if (mBottomToolbarCoordinator != null) {
-            return mBottomToolbarCoordinator.getMenuButtonWrapper();
+        if (mBottomControlsCoordinator != null) {
+            return mBottomControlsCoordinator.getMenuButtonWrapper();
         }
 
         return mToolbar.getMenuButtonWrapper();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java
new file mode 100644
index 0000000..284d42d
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java
@@ -0,0 +1,159 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.toolbar.bottom;
+
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ActivityTabProvider;
+import org.chromium.chrome.browser.appmenu.AppMenuButtonHelper;
+import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
+import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
+import org.chromium.chrome.browser.compositor.layouts.ToolbarSwipeLayout;
+import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
+import org.chromium.chrome.browser.toolbar.IncognitoStateProvider;
+import org.chromium.chrome.browser.toolbar.MenuButton;
+import org.chromium.chrome.browser.toolbar.TabCountProvider;
+import org.chromium.chrome.browser.toolbar.bottom.BottomControlsViewBinder.ViewHolder;
+import org.chromium.ui.base.WindowAndroid;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
+import org.chromium.ui.resources.ResourceManager;
+
+/**
+ * The root coordinator for the bottom controls component. This component is intended for use with
+ * bottom UI that re-sizes the web contents, scrolls off-screen, and hides when the keyboard is
+ * shown. This class has two primary components, an Android view and a composited texture that draws
+ * when the controls are being scrolled off-screen. The Android version does not draw unless the
+ * controls offset is 0.
+ */
+public class BottomControlsCoordinator {
+    /** The mediator that handles events from outside the bottom controls. */
+    private final BottomControlsMediator mMediator;
+
+    /** The coordinator for the split toolbar's bottom toolbar component. */
+    private final BottomToolbarCoordinator mBottomToolbarCoordinator;
+
+    /**
+     * Build the coordinator that manages the bottom controls.
+     * @param fullscreenManager A {@link ChromeFullscreenManager} to update the bottom controls
+     *                          height for the renderer.
+     * @param stub The bottom controls {@link ViewStub} to inflate.
+     * @param tabProvider The {@link ActivityTabProvider} used in the bottom toolbar.
+     * @param homeButtonListener The {@link OnClickListener} for the bottom toolbar's home button.
+     * @param searchAcceleratorListener The {@link OnClickListener} for the bottom toolbar's
+     *                                  search accelerator.
+     * @param shareButtonListener The {@link OnClickListener} for the bottom toolbar's share button.
+     */
+    public BottomControlsCoordinator(ChromeFullscreenManager fullscreenManager, ViewStub stub,
+            ActivityTabProvider tabProvider, OnClickListener homeButtonListener,
+            OnClickListener searchAcceleratorListener, OnClickListener shareButtonListener) {
+        final ScrollingBottomViewResourceFrameLayout root =
+                (ScrollingBottomViewResourceFrameLayout) stub.inflate();
+
+        PropertyModel model = new PropertyModel(BottomControlsProperties.ALL_KEYS);
+
+        PropertyModelChangeProcessor.create(
+                model, new ViewHolder(root), BottomControlsViewBinder::bind);
+
+        mMediator = new BottomControlsMediator(model, fullscreenManager,
+                root.getResources().getDimensionPixelOffset(R.dimen.bottom_toolbar_height));
+
+        mBottomToolbarCoordinator = new BottomToolbarCoordinator(
+                root.findViewById(R.id.bottom_toolbar_stub), tabProvider, homeButtonListener,
+                searchAcceleratorListener, shareButtonListener);
+    }
+
+    /**
+     * Initialize the bottom controls with the components that had native initialization
+     * dependencies.
+     * <p>
+     * Calling this must occur after the native library have completely loaded.
+     * @param resourceManager A {@link ResourceManager} for loading textures into the compositor.
+     * @param layoutManager A {@link LayoutManager} to attach overlays to.
+     * @param tabSwitcherListener An {@link OnClickListener} that is triggered when the
+     *                            bottom toolbar's tab switcher button is clicked.
+     * @param newTabClickListener An {@link OnClickListener} that is triggered when the
+     *                            bottom toolbar's new tab button is clicked.
+     * @param menuButtonHelper An {@link AppMenuButtonHelper} that is triggered when the
+     *                         bottom toolbar's menu button is clicked.
+     * @param overviewModeBehavior The overview mode manager.
+     * @param windowAndroid A {@link WindowAndroid} for watching keyboard visibility events.
+     * @param tabCountProvider Updates the tab count number in the tab switcher button and in the
+     *                         incognito toggle tab layout.
+     * @param incognitoStateProvider Notifies components when incognito mode is entered or exited.
+     * @param topToolbarRoot The root {@link ViewGroup} of the top toolbar.
+     */
+    public void initializeWithNative(ResourceManager resourceManager, LayoutManager layoutManager,
+            OnClickListener tabSwitcherListener, OnClickListener newTabClickListener,
+            OnClickListener closeTabsClickListener, AppMenuButtonHelper menuButtonHelper,
+            OverviewModeBehavior overviewModeBehavior, WindowAndroid windowAndroid,
+            TabCountProvider tabCountProvider, IncognitoStateProvider incognitoStateProvider,
+            ViewGroup topToolbarRoot) {
+        mMediator.setLayoutManager(layoutManager);
+        mMediator.setResourceManager(resourceManager);
+        mMediator.setToolbarSwipeHandler(layoutManager.getToolbarSwipeHandler());
+        mMediator.setWindowAndroid(windowAndroid);
+
+        mBottomToolbarCoordinator.initializeWithNative(tabSwitcherListener, newTabClickListener,
+                closeTabsClickListener, menuButtonHelper, overviewModeBehavior, tabCountProvider,
+                incognitoStateProvider, topToolbarRoot);
+    }
+
+    /**
+     * @param isVisible Whether the bottom control is visible.
+     */
+    public void setBottomControlsVisible(boolean isVisible) {
+        mMediator.setBottomControlsVisible(isVisible);
+        mBottomToolbarCoordinator.setBottomToolbarVisible(isVisible);
+    }
+
+    /**
+     * Show the update badge over the bottom toolbar's app menu.
+     */
+    public void showAppMenuUpdateBadge() {
+        mBottomToolbarCoordinator.showAppMenuUpdateBadge();
+    }
+
+    /**
+     * Remove the update badge.
+     */
+    public void removeAppMenuUpdateBadge() {
+        mBottomToolbarCoordinator.removeAppMenuUpdateBadge();
+    }
+
+    /**
+     * @return Whether the update badge is showing.
+     */
+    public boolean isShowingAppMenuUpdateBadge() {
+        return mBottomToolbarCoordinator.isShowingAppMenuUpdateBadge();
+    }
+
+    /**
+     * @return The wrapper for the browsing mode toolbar's app menu button.
+     */
+    public MenuButton getMenuButtonWrapper() {
+        return mBottomToolbarCoordinator.getMenuButtonWrapper();
+    }
+
+    /**
+     * @param layout The {@link ToolbarSwipeLayout} that the bottom controls will hook into. This
+     *               allows the bottom controls to provide the layout with scene layers with the
+     *               bottom controls' texture.
+     */
+    public void setToolbarSwipeLayout(ToolbarSwipeLayout layout) {
+        mMediator.setToolbarSwipeLayout(layout);
+    }
+
+    /**
+     * Clean up any state when the bottom controls component is destroyed.
+     */
+    public void destroy() {
+        mBottomToolbarCoordinator.destroy();
+        mMediator.destroy();
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsMediator.java
new file mode 100644
index 0000000..cc788bc
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsMediator.java
@@ -0,0 +1,190 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.toolbar.bottom;
+
+import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelManager;
+import org.chromium.chrome.browser.compositor.layouts.Layout;
+import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
+import org.chromium.chrome.browser.compositor.layouts.SceneChangeObserver;
+import org.chromium.chrome.browser.compositor.layouts.ToolbarSwipeLayout;
+import org.chromium.chrome.browser.compositor.layouts.eventfilter.EdgeSwipeHandler;
+import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
+import org.chromium.ui.KeyboardVisibilityDelegate;
+import org.chromium.ui.base.WindowAndroid;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.resources.ResourceManager;
+
+/**
+ * This class is responsible for reacting to events from the outside world, interacting with other
+ * coordinators, running most of the business logic associated with the bottom controls component,
+ * and updating the model accordingly.
+ */
+class BottomControlsMediator implements ChromeFullscreenManager.FullscreenListener,
+                                        KeyboardVisibilityDelegate.KeyboardVisibilityListener,
+                                        SceneChangeObserver,
+                                        OverlayPanelManager.OverlayPanelManagerObserver {
+    /** The model for the bottom controls component that holds all of its view state. */
+    private final PropertyModel mModel;
+
+    /** The fullscreen manager to observe browser controls events. */
+    private final ChromeFullscreenManager mFullscreenManager;
+
+    /** The height of the bottom bar in pixels */
+    private final int mBottomControlContainerHeight;
+
+    /** A {@link WindowAndroid} for watching keyboard visibility events. */
+    private WindowAndroid mWindowAndroid;
+
+    /** The bottom controls visibility. */
+    private boolean mIsBottomControlsVisible;
+
+    /** Whether any overlay panel is showing. */
+    private boolean mIsOverlayPanelShowing;
+
+    /** Whether the swipe layout is currently active. */
+    private boolean mIsInSwipeLayout;
+
+    /** Whether the soft keyboard is visible. */
+    private boolean mIsKeyboardVisible;
+
+    /**
+     * Build a new mediator that handles events from outside the bottom controls component.
+     * @param model The {@link BottomControlsProperties} that holds all the view state for the
+     *         bottom
+     *              controls component.
+     * @param fullscreenManager A {@link ChromeFullscreenManager} for events related to the browser
+     *                          controls.
+     * @param bottomControlContainerHeight The height of the bottom bar in pixels.
+     */
+    BottomControlsMediator(PropertyModel model, ChromeFullscreenManager fullscreenManager,
+            int bottomControlContainerHeight) {
+        mModel = model;
+
+        mFullscreenManager = fullscreenManager;
+        mFullscreenManager.addListener(this);
+
+        mBottomControlContainerHeight = bottomControlContainerHeight;
+    }
+
+    /**
+     * @param swipeHandler The handler that controls the bottom toolbar's swipe behavior.
+     */
+    void setToolbarSwipeHandler(EdgeSwipeHandler swipeHandler) {
+        mModel.set(BottomControlsProperties.TOOLBAR_SWIPE_HANDLER, swipeHandler);
+    }
+
+    void setResourceManager(ResourceManager resourceManager) {
+        mModel.set(BottomControlsProperties.RESOURCE_MANAGER, resourceManager);
+    }
+
+    void setToolbarSwipeLayout(ToolbarSwipeLayout layout) {
+        mModel.set(BottomControlsProperties.TOOLBAR_SWIPE_LAYOUT, layout);
+    }
+
+    void setWindowAndroid(WindowAndroid windowAndroid) {
+        assert mWindowAndroid == null : "#setWindowAndroid should only be called once per toolbar.";
+        // Watch for keyboard events so we can hide the bottom toolbar when the keyboard is showing.
+        mWindowAndroid = windowAndroid;
+        mWindowAndroid.getKeyboardDelegate().addKeyboardVisibilityListener(this);
+    }
+
+    void setLayoutManager(LayoutManager layoutManager) {
+        mModel.set(BottomControlsProperties.LAYOUT_MANAGER, layoutManager);
+        layoutManager.addSceneChangeObserver(this);
+        layoutManager.getOverlayPanelManager().addObserver(this);
+    }
+
+    void setBottomControlsVisible(boolean visible) {
+        mIsBottomControlsVisible = visible;
+        updateCompositedViewVisibility();
+        updateAndroidViewVisibility();
+    }
+
+    /**
+     * Clean up anything that needs to be when the bottom controls component is destroyed.
+     */
+    void destroy() {
+        mFullscreenManager.removeListener(this);
+        if (mWindowAndroid != null) {
+            mWindowAndroid.getKeyboardDelegate().removeKeyboardVisibilityListener(this);
+            mWindowAndroid = null;
+        }
+        if (mModel.get(BottomControlsProperties.LAYOUT_MANAGER) != null) {
+            LayoutManager manager = mModel.get(BottomControlsProperties.LAYOUT_MANAGER);
+            manager.getOverlayPanelManager().removeObserver(this);
+            manager.removeSceneChangeObserver(this);
+        }
+    }
+
+    @Override
+    public void onContentOffsetChanged(int offset) {}
+
+    @Override
+    public void onControlsOffsetChanged(int topOffset, int bottomOffset, boolean needsAnimate) {
+        mModel.set(BottomControlsProperties.Y_OFFSET, bottomOffset);
+        updateAndroidViewVisibility();
+    }
+
+    @Override
+    public void onToggleOverlayVideoMode(boolean enabled) {}
+
+    @Override
+    public void onBottomControlsHeightChanged(int bottomControlsHeight) {}
+
+    @Override
+    public void onOverlayPanelShown() {
+        mIsOverlayPanelShowing = true;
+        updateAndroidViewVisibility();
+    }
+
+    @Override
+    public void onOverlayPanelHidden() {
+        mIsOverlayPanelShowing = false;
+        updateAndroidViewVisibility();
+    }
+
+    @Override
+    public void keyboardVisibilityChanged(boolean isShowing) {
+        mIsKeyboardVisible = isShowing;
+        updateCompositedViewVisibility();
+        updateAndroidViewVisibility();
+    }
+
+    @Override
+    public void onTabSelectionHinted(int tabId) {}
+
+    @Override
+    public void onSceneChange(Layout layout) {
+        mIsInSwipeLayout = layout instanceof ToolbarSwipeLayout;
+        updateAndroidViewVisibility();
+    }
+
+    /**
+     * The composited view is the composited version of the Android View. It is used to be able to
+     * scroll the bottom controls off-screen synchronously. Since the bottom controls live below
+     * the webcontents we re-size the webcontents through
+     * {@link ChromeFullscreenManager#setBottomControlsHeight(int)} whenever the composited view
+     * visibility changes.
+     */
+    private void updateCompositedViewVisibility() {
+        final boolean isCompositedViewVisible = mIsBottomControlsVisible && !mIsKeyboardVisible;
+        mModel.set(BottomControlsProperties.COMPOSITED_VIEW_VISIBLE, isCompositedViewVisible);
+        mFullscreenManager.setBottomControlsHeight(
+                isCompositedViewVisible ? mBottomControlContainerHeight : 0);
+    }
+
+    /**
+     * The Android View is the interactive view. The composited view should always be behind the
+     * Android view which means we hide the Android view whenever the composited view is hidden.
+     * We also hide the Android view as we are scrolling the bottom controls off screen this is
+     * done by checking if {@link ChromeFullscreenManager#getBottomControlOffset()} is
+     * non-zero.
+     */
+    private void updateAndroidViewVisibility() {
+        mModel.set(BottomControlsProperties.ANDROID_VIEW_VISIBLE,
+                mIsBottomControlsVisible && !mIsKeyboardVisible && !mIsOverlayPanelShowing
+                        && !mIsInSwipeLayout && mFullscreenManager.getBottomControlOffset() == 0);
+    }
+}
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsProperties.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsProperties.java
new file mode 100644
index 0000000..7817bfb
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsProperties.java
@@ -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.
+
+package org.chromium.chrome.browser.toolbar.bottom;
+
+import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
+import org.chromium.chrome.browser.compositor.layouts.ToolbarSwipeLayout;
+import org.chromium.chrome.browser.compositor.layouts.eventfilter.EdgeSwipeHandler;
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey;
+import org.chromium.ui.modelutil.PropertyModel.WritableIntPropertyKey;
+import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
+import org.chromium.ui.resources.ResourceManager;
+
+class BottomControlsProperties {
+    /** The Y offset of the view in px. */
+    static final WritableIntPropertyKey Y_OFFSET = new WritableIntPropertyKey();
+
+    /** Whether the Android view version of the bottom controls component is visible. */
+    static final WritableBooleanPropertyKey ANDROID_VIEW_VISIBLE = new WritableBooleanPropertyKey();
+
+    /** Whether the composited version of the bottom controls component is visible. */
+    static final WritableBooleanPropertyKey COMPOSITED_VIEW_VISIBLE =
+            new WritableBooleanPropertyKey();
+
+    /** A {@link LayoutManager} to attach overlays to. */
+    static final WritableObjectPropertyKey<LayoutManager> LAYOUT_MANAGER =
+            new WritableObjectPropertyKey<>();
+
+    /** The browser's {@link ToolbarSwipeLayout}. */
+    static final WritableObjectPropertyKey<ToolbarSwipeLayout> TOOLBAR_SWIPE_LAYOUT =
+            new WritableObjectPropertyKey<>();
+
+    /** A {@link ResourceManager} for loading textures into the compositor. */
+    static final WritableObjectPropertyKey<ResourceManager> RESOURCE_MANAGER =
+            new WritableObjectPropertyKey<>();
+
+    /** A handler for swipe events on the toolbar. */
+    static final WritableObjectPropertyKey<EdgeSwipeHandler> TOOLBAR_SWIPE_HANDLER =
+            new WritableObjectPropertyKey<>();
+
+    static final PropertyKey[] ALL_KEYS =
+            new PropertyKey[] {Y_OFFSET, ANDROID_VIEW_VISIBLE, COMPOSITED_VIEW_VISIBLE,
+                    LAYOUT_MANAGER, TOOLBAR_SWIPE_LAYOUT, RESOURCE_MANAGER, TOOLBAR_SWIPE_HANDLER};
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsViewBinder.java
new file mode 100644
index 0000000..be4df4d
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsViewBinder.java
@@ -0,0 +1,74 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.toolbar.bottom;
+
+import android.view.View;
+
+import org.chromium.chrome.browser.compositor.scene_layer.ScrollingBottomViewSceneLayer;
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel;
+
+class BottomControlsViewBinder {
+    /**
+     * A wrapper class that holds a {@link ScrollingBottomViewResourceFrameLayout}
+     * and a composited layer to be used with the {@link BottomControlsViewBinder}.
+     */
+    static class ViewHolder {
+        /** A handle to the Android View based version of the bottom controls. */
+        public final ScrollingBottomViewResourceFrameLayout root;
+
+        /** A handle to the composited bottom controls layer. */
+        public ScrollingBottomViewSceneLayer sceneLayer;
+
+        /**
+         * @param bottomControlsRootView The Android View based bottom controls.
+         */
+        public ViewHolder(ScrollingBottomViewResourceFrameLayout bottomControlsRootView) {
+            root = bottomControlsRootView;
+        }
+    }
+
+    static void bind(PropertyModel model, ViewHolder view, PropertyKey propertyKey) {
+        if (BottomControlsProperties.Y_OFFSET == propertyKey) {
+            // Native may not have completely initialized by the time this is set.
+            if (view.sceneLayer == null) return;
+            view.sceneLayer.setYOffset(model.get(BottomControlsProperties.Y_OFFSET));
+        } else if (BottomControlsProperties.ANDROID_VIEW_VISIBLE == propertyKey) {
+            view.root.setVisibility(model.get(BottomControlsProperties.ANDROID_VIEW_VISIBLE)
+                            ? View.VISIBLE
+                            : View.INVISIBLE);
+        } else if (BottomControlsProperties.COMPOSITED_VIEW_VISIBLE == propertyKey) {
+            if (view.sceneLayer == null) return;
+            final boolean showCompositedView =
+                    model.get(BottomControlsProperties.COMPOSITED_VIEW_VISIBLE);
+            view.sceneLayer.setIsVisible(showCompositedView);
+            model.get(BottomControlsProperties.TOOLBAR_SWIPE_LAYOUT)
+                    .setBottomToolbarSceneLayersVisibility(showCompositedView);
+            model.get(BottomControlsProperties.LAYOUT_MANAGER).requestUpdate();
+        } else if (BottomControlsProperties.LAYOUT_MANAGER == propertyKey) {
+            assert view.sceneLayer == null;
+            view.sceneLayer =
+                    new ScrollingBottomViewSceneLayer(view.root, view.root.getTopShadowHeight());
+            view.sceneLayer.setIsVisible(
+                    model.get(BottomControlsProperties.COMPOSITED_VIEW_VISIBLE));
+            model.get(BottomControlsProperties.LAYOUT_MANAGER)
+                    .addSceneOverlayToBack(view.sceneLayer);
+        } else if (BottomControlsProperties.TOOLBAR_SWIPE_LAYOUT == propertyKey) {
+            assert view.sceneLayer != null;
+            model.get(BottomControlsProperties.TOOLBAR_SWIPE_LAYOUT)
+                    .setBottomToolbarSceneLayers(new ScrollingBottomViewSceneLayer(view.sceneLayer),
+                            new ScrollingBottomViewSceneLayer(view.sceneLayer),
+                            model.get(BottomControlsProperties.COMPOSITED_VIEW_VISIBLE));
+        } else if (BottomControlsProperties.RESOURCE_MANAGER == propertyKey) {
+            model.get(BottomControlsProperties.RESOURCE_MANAGER)
+                    .getDynamicResourceLoader()
+                    .registerResource(view.root.getId(), view.root.getResourceAdapter());
+        } else if (BottomControlsProperties.TOOLBAR_SWIPE_HANDLER == propertyKey) {
+            view.root.setSwipeDetector(model.get(BottomControlsProperties.TOOLBAR_SWIPE_HANDLER));
+        } else {
+            assert false : "Unhandled property detected in BottomControlsViewBinder!";
+        }
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarCoordinator.java
index 8fc1aa0..a41b37c8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarCoordinator.java
@@ -12,22 +12,16 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.appmenu.AppMenuButtonHelper;
-import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
-import org.chromium.chrome.browser.compositor.layouts.ToolbarSwipeLayout;
-import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
-import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.toolbar.IncognitoStateProvider;
 import org.chromium.chrome.browser.toolbar.MenuButton;
 import org.chromium.chrome.browser.toolbar.TabCountProvider;
-import org.chromium.ui.base.WindowAndroid;
-import org.chromium.ui.resources.ResourceManager;
 
 /**
- * The root coordinator for the bottom toolbar. It has two subcomponents. The browing mode bottom
+ * The root coordinator for the bottom toolbar. It has two sub-components: the browsing mode bottom
  * toolbar and the tab switcher mode bottom toolbar.
  */
-public class BottomToolbarCoordinator {
+class BottomToolbarCoordinator {
     /** The browsing mode bottom toolbar component */
     private final BrowsingModeBottomToolbarCoordinator mBrowsingModeCoordinator;
 
@@ -40,31 +34,25 @@
     /** A provider that notifies components when the theme color changes.*/
     private final BottomToolbarThemeColorProvider mBottomToolbarThemeColorProvider;
 
-    /** The root view of the bottom toolbar. */
-    private final View mRoot;
-
     /**
      * Build the coordinator that manages the bottom toolbar.
-     * @param fullscreenManager A {@link ChromeFullscreenManager} to update the bottom controls
-     *                          height for the renderer.
      * @param stub The bottom toolbar {@link ViewStub} to inflate.
      * @param tabProvider The {@link ActivityTabProvider} used for making the IPH.
      * @param homeButtonListener The {@link OnClickListener} for the home button.
      * @param searchAcceleratorListener The {@link OnClickListener} for the search accelerator.
      * @param shareButtonListener The {@link OnClickListener} for the share button.
      */
-    public BottomToolbarCoordinator(ChromeFullscreenManager fullscreenManager, ViewStub stub,
-            ActivityTabProvider tabProvider, OnClickListener homeButtonListener,
-            OnClickListener searchAcceleratorListener, OnClickListener shareButtonListener) {
-        mRoot = stub.inflate();
+    BottomToolbarCoordinator(ViewStub stub, ActivityTabProvider tabProvider,
+            OnClickListener homeButtonListener, OnClickListener searchAcceleratorListener,
+            OnClickListener shareButtonListener) {
+        View root = stub.inflate();
 
-        mBrowsingModeCoordinator =
-                new BrowsingModeBottomToolbarCoordinator(mRoot, fullscreenManager, tabProvider,
-                        homeButtonListener, searchAcceleratorListener, shareButtonListener);
+        mBrowsingModeCoordinator = new BrowsingModeBottomToolbarCoordinator(root, tabProvider,
+                homeButtonListener, searchAcceleratorListener, shareButtonListener);
 
-        mTabSwitcherModeStub = mRoot.findViewById(R.id.bottom_toolbar_tab_switcher_mode_stub);
+        mTabSwitcherModeStub = root.findViewById(R.id.bottom_toolbar_tab_switcher_mode_stub);
 
-        mBottomToolbarThemeColorProvider = new BottomToolbarThemeColorProvider(mRoot.getContext());
+        mBottomToolbarThemeColorProvider = new BottomToolbarThemeColorProvider(root.getContext());
     }
 
     /**
@@ -72,50 +60,38 @@
      * dependencies.
      * <p>
      * Calling this must occur after the native library have completely loaded.
-     * @param resourceManager A {@link ResourceManager} for loading textures into the compositor.
-     * @param layoutManager A {@link LayoutManager} to attach overlays to.
      * @param tabSwitcherListener An {@link OnClickListener} that is triggered when the
      *                            tab switcher button is clicked.
      * @param newTabClickListener An {@link OnClickListener} that is triggered when the
      *                            new tab button is clicked.
      * @param menuButtonHelper An {@link AppMenuButtonHelper} that is triggered when the
      *                         menu button is clicked.
-     * @param tabModelSelector A {@link TabModelSelector} that incognito toggle tab layout uses to
-                               switch between normal and incognito tabs.
      * @param overviewModeBehavior The overview mode manager.
-     * @param windowAndroid A {@link WindowAndroid} for watching keyboard visibility events.
      * @param tabCountProvider Updates the tab count number in the tab switcher button and in the
      *                         incognito toggle tab layout.
      * @param incognitoStateProvider Notifies components when incognito mode is entered or exited.
      * @param topToolbarRoot The root {@link ViewGroup} of the top toolbar.
      */
-    public void initializeWithNative(ResourceManager resourceManager, LayoutManager layoutManager,
-            OnClickListener tabSwitcherListener, OnClickListener newTabClickListener,
-            OnClickListener closeTabsClickListener, AppMenuButtonHelper menuButtonHelper,
-            TabModelSelector tabModelSelector, OverviewModeBehavior overviewModeBehavior,
-            WindowAndroid windowAndroid, TabCountProvider tabCountProvider,
-            IncognitoStateProvider incognitoStateProvider, ViewGroup topToolbarRoot) {
+    void initializeWithNative(OnClickListener tabSwitcherListener,
+            OnClickListener newTabClickListener, OnClickListener closeTabsClickListener,
+            AppMenuButtonHelper menuButtonHelper, OverviewModeBehavior overviewModeBehavior,
+            TabCountProvider tabCountProvider, IncognitoStateProvider incognitoStateProvider,
+            ViewGroup topToolbarRoot) {
         mBottomToolbarThemeColorProvider.setIncognitoStateProvider(incognitoStateProvider);
         mBottomToolbarThemeColorProvider.setOverviewModeBehavior(overviewModeBehavior);
 
-        mBrowsingModeCoordinator.initializeWithNative(resourceManager, layoutManager,
-                tabSwitcherListener, menuButtonHelper, overviewModeBehavior, windowAndroid,
-                tabCountProvider, mBottomToolbarThemeColorProvider, tabModelSelector);
+        mBrowsingModeCoordinator.initializeWithNative(tabSwitcherListener, menuButtonHelper,
+                overviewModeBehavior, tabCountProvider, mBottomToolbarThemeColorProvider);
         mTabSwitcherModeCoordinator = new TabSwitcherBottomToolbarCoordinator(mTabSwitcherModeStub,
                 topToolbarRoot, incognitoStateProvider, mBottomToolbarThemeColorProvider,
-                newTabClickListener, closeTabsClickListener, menuButtonHelper, tabModelSelector,
-                overviewModeBehavior, tabCountProvider);
+                newTabClickListener, closeTabsClickListener, menuButtonHelper, overviewModeBehavior,
+                tabCountProvider);
     }
 
     /**
      * @param isVisible Whether the bottom toolbar is visible.
      */
-    public void setBottomToolbarVisible(boolean isVisible) {
-        // TODO (amaralp): The coordinator should not have direct access to the view.
-        mRoot.setVisibility(isVisible ? View.VISIBLE : View.GONE);
-
-        mBrowsingModeCoordinator.setVisible(isVisible);
-
+    void setBottomToolbarVisible(boolean isVisible) {
         if (mTabSwitcherModeCoordinator != null) {
             mTabSwitcherModeCoordinator.showToolbarOnTop(!isVisible);
         }
@@ -124,44 +100,35 @@
     /**
      * Show the update badge over the bottom toolbar's app menu.
      */
-    public void showAppMenuUpdateBadge() {
+    void showAppMenuUpdateBadge() {
         mBrowsingModeCoordinator.showAppMenuUpdateBadge();
     }
 
     /**
      * Remove the update badge.
      */
-    public void removeAppMenuUpdateBadge() {
+    void removeAppMenuUpdateBadge() {
         mBrowsingModeCoordinator.removeAppMenuUpdateBadge();
     }
 
     /**
      * @return Whether the update badge is showing.
      */
-    public boolean isShowingAppMenuUpdateBadge() {
+    boolean isShowingAppMenuUpdateBadge() {
         return mBrowsingModeCoordinator.isShowingAppMenuUpdateBadge();
     }
 
     /**
-     * @param layout The {@link ToolbarSwipeLayout} that the bottom toolbar will hook into. This
-     *               allows the bottom toolbar to provide the layout with scene layers with the
-     *               bottom toolbar's texture.
-     */
-    public void setToolbarSwipeLayout(ToolbarSwipeLayout layout) {
-        mBrowsingModeCoordinator.setToolbarSwipeLayout(layout);
-    }
-
-    /**
      * @return The wrapper for the browsing mode toolbar's app menu button.
      */
-    public MenuButton getMenuButtonWrapper() {
+    MenuButton getMenuButtonWrapper() {
         return mBrowsingModeCoordinator.getMenuButton();
     }
 
     /**
      * Clean up any state when the bottom toolbar is destroyed.
      */
-    public void destroy() {
+    void destroy() {
         mBrowsingModeCoordinator.destroy();
         if (mTabSwitcherModeCoordinator != null) {
             mTabSwitcherModeCoordinator.destroy();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarCoordinator.java
index 0c1ca9f..a7208f4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarCoordinator.java
@@ -6,28 +6,22 @@
 
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.view.ViewGroup;
 
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.ActivityTabProvider.HintlessActivityTabObserver;
 import org.chromium.chrome.browser.ThemeColorProvider;
 import org.chromium.chrome.browser.appmenu.AppMenuButtonHelper;
-import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
-import org.chromium.chrome.browser.compositor.layouts.ToolbarSwipeLayout;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
-import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
 import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.toolbar.HomeButton;
 import org.chromium.chrome.browser.toolbar.MenuButton;
 import org.chromium.chrome.browser.toolbar.TabCountProvider;
 import org.chromium.chrome.browser.toolbar.TabSwitcherButtonCoordinator;
-import org.chromium.chrome.browser.toolbar.bottom.BrowsingModeBottomToolbarViewBinder.ViewHolder;
 import org.chromium.components.feature_engagement.Tracker;
-import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
-import org.chromium.ui.resources.ResourceManager;
 
 /**
  * The coordinator for the browsing mode bottom toolbar. This class has two primary components,
@@ -44,7 +38,7 @@
     /** The share button that lives in the bottom toolbar. */
     private final ShareButton mShareButton;
 
-    /** The search acceleartor that lives in the bottom toolbar. */
+    /** The search accelerator that lives in the bottom toolbar. */
     private final SearchAccelerator mSearchAccelerator;
 
     /** The tab switcher button component that lives in the bottom toolbar. */
@@ -56,32 +50,22 @@
     /**
      * Build the coordinator that manages the browsing mode bottom toolbar.
      * @param root The root {@link View} for locating the views to inflate.
-     * @param fullscreenManager A {@link ChromeFullscreenManager} to update the bottom controls
-     *                          height for the renderer.
      * @param tabProvider The {@link ActivityTabProvider} used for making the IPH.
      * @param homeButtonListener The {@link OnClickListener} for the home button.
      * @param searchAcceleratorListener The {@link OnClickListener} for the search accelerator.
      * @param shareButtonListener The {@link OnClickListener} for the share button.
      */
-    public BrowsingModeBottomToolbarCoordinator(View root,
-            ChromeFullscreenManager fullscreenManager, ActivityTabProvider tabProvider,
+    BrowsingModeBottomToolbarCoordinator(View root, ActivityTabProvider tabProvider,
             OnClickListener homeButtonListener, OnClickListener searchAcceleratorListener,
             OnClickListener shareButtonListener) {
         BrowsingModeBottomToolbarModel model = new BrowsingModeBottomToolbarModel();
 
-        final ScrollingBottomViewResourceFrameLayout toolbarRoot =
-                (ScrollingBottomViewResourceFrameLayout) root.findViewById(
-                        R.id.bottom_toolbar_control_container);
-
-        final int shadowHeight =
-                toolbarRoot.getResources().getDimensionPixelOffset(R.dimen.toolbar_shadow_height);
-        toolbarRoot.setTopShadowHeight(shadowHeight);
+        final ViewGroup toolbarRoot = root.findViewById(R.id.bottom_toolbar_browsing);
 
         PropertyModelChangeProcessor.create(
-                model, new ViewHolder(toolbarRoot), new BrowsingModeBottomToolbarViewBinder());
+                model, toolbarRoot, new BrowsingModeBottomToolbarViewBinder());
 
-        mMediator = new BrowsingModeBottomToolbarMediator(
-                model, fullscreenManager, toolbarRoot.getResources());
+        mMediator = new BrowsingModeBottomToolbarMediator(model);
 
         mHomeButton = toolbarRoot.findViewById(R.id.home_button);
         mHomeButton.setOnClickListener(homeButtonListener);
@@ -98,14 +82,13 @@
 
         mMenuButton = toolbarRoot.findViewById(R.id.menu_button_wrapper);
 
-        final View iphAnchor = toolbarRoot.findViewById(R.id.search_accelerator);
         tabProvider.addObserverAndTrigger(new HintlessActivityTabObserver() {
             @Override
             public void onActivityTabChanged(Tab tab) {
                 if (tab == null) return;
                 final Tracker tracker = TrackerFactory.getTrackerForProfile(tab.getProfile());
-                tracker.addOnInitializedCallback(
-                        (ready) -> mMediator.showIPH(tab.getActivity(), iphAnchor, tracker));
+                tracker.addOnInitializedCallback((ready) -> mMediator.showIPH(tab.getActivity(),
+                        mSearchAccelerator, tracker));
                 tabProvider.removeObserver(this);
             }
         });
@@ -116,28 +99,17 @@
      * dependencies.
      * <p>
      * Calling this must occur after the native library have completely loaded.
-     * @param resourceManager A {@link ResourceManager} for loading textures into the compositor.
-     * @param layoutManager A {@link LayoutManager} to attach overlays to.
      * @param tabSwitcherListener An {@link OnClickListener} that is triggered when the
      *                            tab switcher button is clicked.
      * @param menuButtonHelper An {@link AppMenuButtonHelper} that is triggered when the
      *                         menu button is clicked.
      * @param overviewModeBehavior The overview mode manager.
-     * @param windowAndroid A {@link WindowAndroid} for watching keyboard visibility events.
      * @param tabCountProvider Updates the tab count number in the tab switcher button.
      * @param themeColorProvider Notifies components when theme color changes.
-     * @param tabModelSelector A {@link TabModelSelector} that the share button uses to know whether
-     *                         or not to be enabled.
      */
-    public void initializeWithNative(ResourceManager resourceManager, LayoutManager layoutManager,
-            OnClickListener tabSwitcherListener, AppMenuButtonHelper menuButtonHelper,
-            OverviewModeBehavior overviewModeBehavior, WindowAndroid windowAndroid,
-            TabCountProvider tabCountProvider, ThemeColorProvider themeColorProvider,
-            TabModelSelector tabModelSelector) {
-        mMediator.setLayoutManager(layoutManager);
-        mMediator.setResourceManager(resourceManager);
-        mMediator.setToolbarSwipeHandler(layoutManager.getToolbarSwipeHandler());
-        mMediator.setWindowAndroid(windowAndroid);
+    void initializeWithNative(OnClickListener tabSwitcherListener,
+            AppMenuButtonHelper menuButtonHelper, OverviewModeBehavior overviewModeBehavior,
+            TabCountProvider tabCountProvider, ThemeColorProvider themeColorProvider) {
         mMediator.setOverviewModeBehavior(overviewModeBehavior);
         mMediator.setThemeColorProvider(themeColorProvider);
 
@@ -154,57 +126,34 @@
     }
 
     /**
-     * @param isVisible Whether the browsing mode bottom toolbar is visible.
-     */
-    public void setVisible(boolean isVisible) {
-        mMediator.setVisible(isVisible);
-    }
-
-    /**
      * Show the update badge over the bottom toolbar's app menu.
      */
-    public void showAppMenuUpdateBadge() {
+    void showAppMenuUpdateBadge() {
         mMenuButton.showAppMenuUpdateBadgeIfAvailable(true);
     }
 
     /**
      * Remove the update badge.
      */
-    public void removeAppMenuUpdateBadge() {
+    void removeAppMenuUpdateBadge() {
         mMenuButton.removeAppMenuUpdateBadge(true);
     }
 
     /**
      * @return Whether the update badge is showing.
      */
-    public boolean isShowingAppMenuUpdateBadge() {
+    boolean isShowingAppMenuUpdateBadge() {
         return mMenuButton.isShowingAppMenuUpdateBadge();
     }
 
     /**
-     * @param layout The {@link ToolbarSwipeLayout} that the bottom toolbar will hook into. This
-     *               allows the bottom toolbar to provide the layout with scene layers with the
-     *               bottom toolbar's texture.
-     */
-    public void setToolbarSwipeLayout(ToolbarSwipeLayout layout) {
-        mMediator.setToolbarSwipeLayout(layout);
-    }
-
-    /**
      * @return The browsing mode bottom toolbar's menu button.
      */
-    public MenuButton getMenuButton() {
+    MenuButton getMenuButton() {
         return mMenuButton;
     }
 
     /**
-     * @return Whether the browsing mode toolbar is visible.
-     */
-    public boolean isVisible() {
-        return mMediator.isVisible();
-    }
-
-    /**
      * Clean up any state when the browsing mode bottom toolbar is destroyed.
      */
     public void destroy() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarMediator.java
index 398c6e1..dfe0fa4f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarMediator.java
@@ -4,7 +4,6 @@
 
 package org.chromium.chrome.browser.toolbar.bottom;
 
-import android.content.res.Resources;
 import android.view.View;
 
 import org.chromium.base.ApiCompatibilityUtils;
@@ -12,32 +11,18 @@
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ThemeColorProvider;
 import org.chromium.chrome.browser.ThemeColorProvider.ThemeColorObserver;
-import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelManager.OverlayPanelManagerObserver;
-import org.chromium.chrome.browser.compositor.layouts.Layout;
-import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior.OverviewModeObserver;
-import org.chromium.chrome.browser.compositor.layouts.SceneChangeObserver;
-import org.chromium.chrome.browser.compositor.layouts.ToolbarSwipeLayout;
-import org.chromium.chrome.browser.compositor.layouts.eventfilter.EdgeSwipeHandler;
-import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
-import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager.FullscreenListener;
 import org.chromium.chrome.browser.widget.FeatureHighlightProvider;
 import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.components.feature_engagement.Tracker;
-import org.chromium.ui.KeyboardVisibilityDelegate;
-import org.chromium.ui.base.WindowAndroid;
-import org.chromium.ui.resources.ResourceManager;
 
 /**
  * This class is responsible for reacting to events from the outside world, interacting with other
  * coordinators, running most of the business logic associated with the browsing mode bottom
  * toolbar, and updating the model accordingly.
  */
-class BrowsingModeBottomToolbarMediator
-        implements FullscreenListener, KeyboardVisibilityDelegate.KeyboardVisibilityListener,
-                   OverlayPanelManagerObserver, OverviewModeObserver, SceneChangeObserver,
-                   ThemeColorObserver {
+class BrowsingModeBottomToolbarMediator implements OverviewModeObserver, ThemeColorObserver {
     /** The amount of time to show the Duet help bubble for. */
     private static final int DUET_IPH_BUBBLE_SHOW_DURATION_MS = 10000;
 
@@ -47,51 +32,19 @@
     /** The model for the browsing mode bottom toolbar that holds all of its state. */
     private BrowsingModeBottomToolbarModel mModel;
 
-    /** Whether the swipe layout is currently active. */
-    private boolean mIsInSwipeLayout;
-
-    /** The fullscreen manager to observe browser controls events. */
-    private final ChromeFullscreenManager mFullscreenManager;
-
     /** The overview mode manager. */
     private OverviewModeBehavior mOverviewModeBehavior;
 
-    /** A {@link WindowAndroid} for watching keyboard visibility events. */
-    private WindowAndroid mWindowAndroid;
-
     /** A provider that notifies components when the theme color changes.*/
     private ThemeColorProvider mThemeColorProvider;
 
-    /** A state set to {@code true} while any overlay panel is showing. */
-    private boolean mIsOverlayPanelShowing;
-
-    /** Whether the bottom toolbar is hidden because we are in adaptive toolbar mode. */
-    private boolean mIsHiddenForAdaptiveToolbar;
-
-    /** The height of the bottom bar in pixels */
-    private final int mBottomToolbarHeight;
-
     /**
      * Build a new mediator that handles events from outside the bottom toolbar.
      * @param model The {@link BrowsingModeBottomToolbarModel} that holds all the state for the
      *              browsing mode  bottom toolbar.
-     * @param fullscreenManager A {@link ChromeFullscreenManager} for events related to the browser
-     *                          controls.
-     * @param resources Android {@link Resources} to pull dimensions from.
      */
-    BrowsingModeBottomToolbarMediator(BrowsingModeBottomToolbarModel model,
-            ChromeFullscreenManager fullscreenManager, Resources resources) {
+    BrowsingModeBottomToolbarMediator(BrowsingModeBottomToolbarModel model) {
         mModel = model;
-        mFullscreenManager = fullscreenManager;
-        mFullscreenManager.addListener(this);
-        mBottomToolbarHeight = resources.getDimensionPixelOffset(R.dimen.bottom_toolbar_height);
-    }
-
-    /**
-     * @param swipeHandler The handler that controls the toolbar swipe behavior.
-     */
-    void setToolbarSwipeHandler(EdgeSwipeHandler swipeHandler) {
-        mModel.set(BrowsingModeBottomToolbarModel.TOOLBAR_SWIPE_HANDLER, swipeHandler);
     }
 
     void setThemeColorProvider(ThemeColorProvider themeColorProvider) {
@@ -99,10 +52,6 @@
         mThemeColorProvider.addThemeColorObserver(this);
     }
 
-    void setResourceManager(ResourceManager resourceManager) {
-        mModel.set(BrowsingModeBottomToolbarModel.RESOURCE_MANAGER, resourceManager);
-    }
-
     void setOverviewModeBehavior(OverviewModeBehavior overviewModeBehavior) {
         if (mOverviewModeBehavior != null) {
             mOverviewModeBehavior.removeOverviewModeObserver(this);
@@ -111,23 +60,6 @@
         mOverviewModeBehavior.addOverviewModeObserver(this);
     }
 
-    void setToolbarSwipeLayout(ToolbarSwipeLayout layout) {
-        mModel.set(BrowsingModeBottomToolbarModel.TOOLBAR_SWIPE_LAYOUT, layout);
-    }
-
-    void setWindowAndroid(WindowAndroid windowAndroid) {
-        assert mWindowAndroid == null : "#setWindowAndroid should only be called once per toolbar.";
-        // Watch for keyboard events so we can hide the bottom toolbar when the keyboard is showing.
-        mWindowAndroid = windowAndroid;
-        mWindowAndroid.getKeyboardDelegate().addKeyboardVisibilityListener(this);
-    }
-
-    void setLayoutManager(LayoutManager layoutManager) {
-        mModel.set(BrowsingModeBottomToolbarModel.LAYOUT_MANAGER, layoutManager);
-        layoutManager.addSceneChangeObserver(this);
-        layoutManager.getOverlayPanelManager().addObserver(this);
-    }
-
     /**
      * Maybe show the IPH bubble for Chrome Duet.
      * @param activity An activity to attach the IPH to.
@@ -153,28 +85,14 @@
                 DUET_IPH_BUBBLE_SHOW_DURATION_MS);
     }
 
-    boolean isVisible() {
-        return mModel.get(BrowsingModeBottomToolbarModel.IS_VISIBLE);
-    }
-
     /**
      * Clean up anything that needs to be when the bottom toolbar is destroyed.
      */
     void destroy() {
-        mFullscreenManager.removeListener(this);
         if (mOverviewModeBehavior != null) {
             mOverviewModeBehavior.removeOverviewModeObserver(this);
             mOverviewModeBehavior = null;
         }
-        if (mWindowAndroid != null) {
-            mWindowAndroid.getKeyboardDelegate().removeKeyboardVisibilityListener(this);
-            mWindowAndroid = null;
-        }
-        if (mModel.get(BrowsingModeBottomToolbarModel.LAYOUT_MANAGER) != null) {
-            LayoutManager manager = mModel.get(BrowsingModeBottomToolbarModel.LAYOUT_MANAGER);
-            manager.getOverlayPanelManager().removeObserver(this);
-            manager.removeSceneChangeObserver(this);
-        }
         if (mThemeColorProvider != null) {
             mThemeColorProvider.removeThemeColorObserver(this);
             mThemeColorProvider = null;
@@ -182,31 +100,8 @@
     }
 
     @Override
-    public void onContentOffsetChanged(int offset) {}
-
-    @Override
-    public void onControlsOffsetChanged(int topOffset, int bottomOffset, boolean needsAnimate) {
-        mModel.set(BrowsingModeBottomToolbarModel.Y_OFFSET, bottomOffset);
-        if (bottomOffset > 0 || mFullscreenManager.getBottomControlsHeight() == 0) {
-            mModel.set(BrowsingModeBottomToolbarModel.ANDROID_VIEW_VISIBLE, false);
-        } else {
-            tryShowingAndroidView();
-        }
-    }
-
-    @Override
-    public void onToggleOverlayVideoMode(boolean enabled) {}
-
-    @Override
-    public void onBottomControlsHeightChanged(int bottomControlsHeight) {
-        mModel.set(
-                BrowsingModeBottomToolbarModel.COMPOSITED_VIEW_VISIBLE, bottomControlsHeight != 0);
-    }
-
-    @Override
     public void onOverviewModeStartedShowing(boolean showToolbar) {
         mModel.set(BrowsingModeBottomToolbarModel.IS_VISIBLE, false);
-        mModel.set(BrowsingModeBottomToolbarModel.ANDROID_VIEW_VISIBLE, false);
     }
 
     @Override
@@ -215,77 +110,13 @@
     @Override
     public void onOverviewModeStartedHiding(boolean showToolbar, boolean delayAnimation) {
         mModel.set(BrowsingModeBottomToolbarModel.IS_VISIBLE, true);
-        mModel.set(BrowsingModeBottomToolbarModel.ANDROID_VIEW_VISIBLE, true);
     }
 
     @Override
     public void onOverviewModeFinishedHiding() {}
 
     @Override
-    public void onOverlayPanelShown() {
-        mIsOverlayPanelShowing = true;
-        mModel.set(BrowsingModeBottomToolbarModel.ANDROID_VIEW_VISIBLE, false);
-    }
-
-    @Override
-    public void onOverlayPanelHidden() {
-        mIsOverlayPanelShowing = false;
-        tryShowingAndroidView();
-    }
-
-    @Override
-    public void keyboardVisibilityChanged(boolean isShowing) {
-        // The toolbars are force shown when the keyboard is visible, so we can blindly set
-        // the bottom toolbar view to visible or invisible regardless of the previous state.
-        if (isShowing) {
-            mModel.set(BrowsingModeBottomToolbarModel.ANDROID_VIEW_VISIBLE, false);
-            mFullscreenManager.setBottomControlsHeight(0);
-        } else {
-            mFullscreenManager.setBottomControlsHeight(
-                    mIsHiddenForAdaptiveToolbar ? 0 : mBottomToolbarHeight);
-            tryShowingAndroidView();
-            mModel.set(BrowsingModeBottomToolbarModel.Y_OFFSET,
-                    (int) mFullscreenManager.getBottomControlOffset());
-        }
-    }
-
-    @Override
-    public void onTabSelectionHinted(int tabId) {}
-
-    @Override
-    public void onSceneChange(Layout layout) {
-        if (layout instanceof ToolbarSwipeLayout) {
-            mIsInSwipeLayout = true;
-            mModel.set(BrowsingModeBottomToolbarModel.ANDROID_VIEW_VISIBLE, false);
-        } else if (mIsInSwipeLayout) {
-            // Only change to visible if leaving the swipe layout.
-            mIsInSwipeLayout = false;
-            mModel.set(BrowsingModeBottomToolbarModel.ANDROID_VIEW_VISIBLE, true);
-        }
-    }
-
-    @Override
     public void onThemeColorChanged(int primaryColor, boolean shouldAnimate) {
         mModel.set(BrowsingModeBottomToolbarModel.PRIMARY_COLOR, primaryColor);
     }
-
-    /**
-     * Try showing the toolbar's Android view after it has been hidden. This accounts for cases
-     * where a browser signal would ordinarily re-show the view, but others still require it to be
-     * hidden.
-     */
-    private void tryShowingAndroidView() {
-        if (mIsHiddenForAdaptiveToolbar) return;
-        if (mFullscreenManager.getBottomControlOffset() > 0) return;
-        if (mIsOverlayPanelShowing) return;
-        if (mModel.get(BrowsingModeBottomToolbarModel.Y_OFFSET) != 0) return;
-        mModel.set(BrowsingModeBottomToolbarModel.ANDROID_VIEW_VISIBLE, true);
-    }
-
-    public void setVisible(boolean isVisible) {
-        mIsHiddenForAdaptiveToolbar = !isVisible;
-        mFullscreenManager.setBottomControlsHeight(
-                mIsHiddenForAdaptiveToolbar ? 0 : mBottomToolbarHeight);
-        mFullscreenManager.updateViewportSize();
-    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarModel.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarModel.java
index 6b30f63..0d86662 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarModel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarModel.java
@@ -4,55 +4,22 @@
 
 package org.chromium.chrome.browser.toolbar.bottom;
 
-import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
-import org.chromium.chrome.browser.compositor.layouts.ToolbarSwipeLayout;
-import org.chromium.chrome.browser.compositor.layouts.eventfilter.EdgeSwipeHandler;
 import org.chromium.ui.modelutil.PropertyModel;
-import org.chromium.ui.resources.ResourceManager;
 
 /**
  * All of the state for the bottom toolbar, updated by the {@link
  * BrowsingModeBottomToolbarCoordinator}.
  */
 public class BrowsingModeBottomToolbarModel extends PropertyModel {
-    /** The Y offset of the view in px. */
-    public static final WritableIntPropertyKey Y_OFFSET = new WritableIntPropertyKey();
-
-    /** Whether the Android view version of the toolbar is visible. */
-    public static final WritableBooleanPropertyKey ANDROID_VIEW_VISIBLE =
-            new WritableBooleanPropertyKey();
-
-    /** Whether the composited version of the toolbar is visible. */
-    public static final WritableBooleanPropertyKey COMPOSITED_VIEW_VISIBLE =
-            new WritableBooleanPropertyKey();
-
-    /** A {@link LayoutManager} to attach overlays to. */
-    public static final WritableObjectPropertyKey<LayoutManager> LAYOUT_MANAGER =
-            new WritableObjectPropertyKey<>();
-
-    /** The browser's {@link ToolbarSwipeLayout}. */
-    public static final WritableObjectPropertyKey<ToolbarSwipeLayout> TOOLBAR_SWIPE_LAYOUT =
-            new WritableObjectPropertyKey<>();
-
-    /** A {@link ResourceManager} for loading textures into the compositor. */
-    public static final WritableObjectPropertyKey<ResourceManager> RESOURCE_MANAGER =
-            new WritableObjectPropertyKey<>();
-
-    /** A handler for swipe events on the toolbar. */
-    public static final WritableObjectPropertyKey<EdgeSwipeHandler> TOOLBAR_SWIPE_HANDLER =
-            new WritableObjectPropertyKey<>();
-
     /** Primary color of bottom toolbar. */
-    public static final WritableIntPropertyKey PRIMARY_COLOR = new WritableIntPropertyKey();
+    static final WritableIntPropertyKey PRIMARY_COLOR = new WritableIntPropertyKey();
 
     /** Whether the browsing mode bottom toolbar is visible */
-    public static final WritableBooleanPropertyKey IS_VISIBLE = new WritableBooleanPropertyKey();
+    static final WritableBooleanPropertyKey IS_VISIBLE = new WritableBooleanPropertyKey();
 
     /** Default constructor. */
-    public BrowsingModeBottomToolbarModel() {
-        super(Y_OFFSET, ANDROID_VIEW_VISIBLE, COMPOSITED_VIEW_VISIBLE, LAYOUT_MANAGER,
-                TOOLBAR_SWIPE_LAYOUT, RESOURCE_MANAGER, TOOLBAR_SWIPE_HANDLER, IS_VISIBLE,
-                PRIMARY_COLOR);
+    BrowsingModeBottomToolbarModel() {
+        super(IS_VISIBLE, PRIMARY_COLOR);
         set(IS_VISIBLE, true);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarViewBinder.java
index 40eb7dd..74c1f61 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarViewBinder.java
@@ -5,10 +5,7 @@
 package org.chromium.chrome.browser.toolbar.bottom;
 
 import android.view.View;
-import android.view.ViewGroup;
 
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.compositor.scene_layer.ScrollingBottomViewSceneLayer;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
 
@@ -18,81 +15,22 @@
  * {@link BrowsingModeBottomToolbarModel} when a notification of an update is received.
  */
 public class BrowsingModeBottomToolbarViewBinder
-        implements PropertyModelChangeProcessor.ViewBinder<BrowsingModeBottomToolbarModel,
-                BrowsingModeBottomToolbarViewBinder.ViewHolder, PropertyKey> {
-    /**
-     * A wrapper class that holds a {@link ViewGroup} (the toolbar view) and a composited layer to
-     * be used with the {@link BrowsingModeBottomToolbarViewBinder}.
-     */
-    public static class ViewHolder {
-        /** A handle to the Android View based version of the toolbar. */
-        public final ScrollingBottomViewResourceFrameLayout toolbarRoot;
-
-        /** A handle to the composited bottom toolbar layer. */
-        public ScrollingBottomViewSceneLayer sceneLayer;
-
-        /**
-         * @param toolbarRootView The Android View based toolbar.
-         */
-        public ViewHolder(ScrollingBottomViewResourceFrameLayout toolbarRootView) {
-            toolbarRoot = toolbarRootView;
-        }
-    }
-
+        implements PropertyModelChangeProcessor
+                           .ViewBinder<BrowsingModeBottomToolbarModel, View, PropertyKey> {
     /**
      * Build a binder that handles interaction between the model and the views that make up the
      * browsing mode bottom toolbar.
      */
-    public BrowsingModeBottomToolbarViewBinder() {}
+    BrowsingModeBottomToolbarViewBinder() {}
 
     @Override
     public final void bind(
-            BrowsingModeBottomToolbarModel model, ViewHolder view, PropertyKey propertyKey) {
-        if (BrowsingModeBottomToolbarModel.Y_OFFSET == propertyKey) {
-            // Native may not have completely initialized by the time this is set.
-            if (view.sceneLayer == null) return;
-            view.sceneLayer.setYOffset(model.get(BrowsingModeBottomToolbarModel.Y_OFFSET));
-        } else if (BrowsingModeBottomToolbarModel.ANDROID_VIEW_VISIBLE == propertyKey) {
-            view.toolbarRoot.setVisibility(
-                    model.get(BrowsingModeBottomToolbarModel.ANDROID_VIEW_VISIBLE)
-                            ? View.VISIBLE
-                            : View.INVISIBLE);
-        } else if (BrowsingModeBottomToolbarModel.COMPOSITED_VIEW_VISIBLE == propertyKey) {
-            if (view.sceneLayer == null) return;
-            final boolean showCompositedView =
-                    model.get(BrowsingModeBottomToolbarModel.COMPOSITED_VIEW_VISIBLE);
-            view.sceneLayer.setIsVisible(showCompositedView);
-            model.get(BrowsingModeBottomToolbarModel.TOOLBAR_SWIPE_LAYOUT)
-                    .setBottomToolbarSceneLayersVisibility(showCompositedView);
-            model.get(BrowsingModeBottomToolbarModel.LAYOUT_MANAGER).requestUpdate();
-        } else if (BrowsingModeBottomToolbarModel.LAYOUT_MANAGER == propertyKey) {
-            assert view.sceneLayer == null;
-            view.sceneLayer = new ScrollingBottomViewSceneLayer(
-                    view.toolbarRoot, view.toolbarRoot.getTopShadowHeight());
-            view.sceneLayer.setIsVisible(
-                    model.get(BrowsingModeBottomToolbarModel.COMPOSITED_VIEW_VISIBLE));
-            model.get(BrowsingModeBottomToolbarModel.LAYOUT_MANAGER)
-                    .addSceneOverlayToBack(view.sceneLayer);
-        } else if (BrowsingModeBottomToolbarModel.TOOLBAR_SWIPE_LAYOUT == propertyKey) {
-            assert view.sceneLayer != null;
-            model.get(BrowsingModeBottomToolbarModel.TOOLBAR_SWIPE_LAYOUT)
-                    .setBottomToolbarSceneLayers(new ScrollingBottomViewSceneLayer(view.sceneLayer),
-                            new ScrollingBottomViewSceneLayer(view.sceneLayer),
-                            model.get(BrowsingModeBottomToolbarModel.COMPOSITED_VIEW_VISIBLE));
-        } else if (BrowsingModeBottomToolbarModel.RESOURCE_MANAGER == propertyKey) {
-            model.get(BrowsingModeBottomToolbarModel.RESOURCE_MANAGER)
-                    .getDynamicResourceLoader()
-                    .registerResource(
-                            view.toolbarRoot.getId(), view.toolbarRoot.getResourceAdapter());
-        } else if (BrowsingModeBottomToolbarModel.TOOLBAR_SWIPE_HANDLER == propertyKey) {
-            view.toolbarRoot.setSwipeDetector(
-                    model.get(BrowsingModeBottomToolbarModel.TOOLBAR_SWIPE_HANDLER));
-        } else if (BrowsingModeBottomToolbarModel.PRIMARY_COLOR == propertyKey) {
-            view.toolbarRoot.findViewById(R.id.bottom_toolbar_buttons)
-                    .setBackgroundColor(model.get(BrowsingModeBottomToolbarModel.PRIMARY_COLOR));
+            BrowsingModeBottomToolbarModel model, View view, PropertyKey propertyKey) {
+        if (BrowsingModeBottomToolbarModel.PRIMARY_COLOR == propertyKey) {
+            view.setBackgroundColor(model.get(BrowsingModeBottomToolbarModel.PRIMARY_COLOR));
         } else if (BrowsingModeBottomToolbarModel.IS_VISIBLE == propertyKey) {
-            final boolean isVisible = model.get(BrowsingModeBottomToolbarModel.IS_VISIBLE);
-            view.toolbarRoot.setVisibility(isVisible ? View.VISIBLE : View.GONE);
+            view.setVisibility(model.get(BrowsingModeBottomToolbarModel.IS_VISIBLE) ? View.VISIBLE
+                                                                                    : View.GONE);
         } else {
             assert false : "Unhandled property detected in BrowsingModeBottomToolbarViewBinder!";
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/ScrollingBottomViewResourceFrameLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/ScrollingBottomViewResourceFrameLayout.java
index 0200eb7..4c3c4b7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/ScrollingBottomViewResourceFrameLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/ScrollingBottomViewResourceFrameLayout.java
@@ -11,6 +11,7 @@
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 
+import org.chromium.chrome.R;
 import org.chromium.chrome.browser.compositor.layouts.eventfilter.EdgeSwipeHandler;
 import org.chromium.chrome.browser.contextualsearch.SwipeRecognizer;
 import org.chromium.chrome.browser.widget.ViewResourceFrameLayout;
@@ -25,13 +26,14 @@
     private final Rect mCachedRect = new Rect();
 
     /** The height of the shadow sitting above the bottom view in px. */
-    private int mTopShadowHeightPx;
+    private final int mTopShadowHeightPx;
 
     /** A swipe recognizer for handling swipe gestures. */
     private SwipeRecognizer mSwipeRecognizer;
 
     public ScrollingBottomViewResourceFrameLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mTopShadowHeightPx = getResources().getDimensionPixelOffset(R.dimen.toolbar_shadow_height);
     }
 
     /**
@@ -89,13 +91,6 @@
     }
 
     /**
-     * @param height The height of the view's top shadow in px.
-     */
-    public void setTopShadowHeight(int height) {
-        mTopShadowHeightPx = height;
-    }
-
-    /**
      * @return The height of the view's top shadow in px.
      */
     public int getTopShadowHeight() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarCoordinator.java
index 7cc9487..b978070 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarCoordinator.java
@@ -13,7 +13,6 @@
 import org.chromium.chrome.browser.ThemeColorProvider;
 import org.chromium.chrome.browser.appmenu.AppMenuButtonHelper;
 import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
-import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.toolbar.IncognitoStateProvider;
 import org.chromium.chrome.browser.toolbar.MenuButton;
 import org.chromium.chrome.browser.toolbar.TabCountProvider;
@@ -48,17 +47,15 @@
      *                               close all tabs button is clicked.
      * @param menuButtonHelper An {@link AppMenuButtonHelper} that is triggered when the
      *                         menu button is clicked.
-     * @param tabModelSelector A {@link TabModelSelector} that incognito toggle tab layout uses to
-     *                         switch between normal and incognito tabs.
      * @param overviewModeBehavior The overview mode manager.
      * @param tabCountProvider Updates the tab count number in the tab switcher button and in the
      *                         incognito toggle tab layout.
      */
-    public TabSwitcherBottomToolbarCoordinator(ViewStub stub, ViewGroup topToolbarRoot,
+    TabSwitcherBottomToolbarCoordinator(ViewStub stub, ViewGroup topToolbarRoot,
             IncognitoStateProvider incognitoStateProvider, ThemeColorProvider themeColorProvider,
             OnClickListener newTabClickListener, OnClickListener closeTabsClickListener,
-            AppMenuButtonHelper menuButtonHelper, TabModelSelector tabModelSelector,
-            OverviewModeBehavior overviewModeBehavior, TabCountProvider tabCountProvider) {
+            AppMenuButtonHelper menuButtonHelper, OverviewModeBehavior overviewModeBehavior,
+            TabCountProvider tabCountProvider) {
         final View root = stub.inflate();
 
         TabSwitcherBottomToolbarModel model = new TabSwitcherBottomToolbarModel();
@@ -90,7 +87,7 @@
     /**
      * @param showOnTop Whether to show the tab switcher bottom toolbar on the top of the screen.
      */
-    public void showToolbarOnTop(boolean showOnTop) {
+    void showToolbarOnTop(boolean showOnTop) {
         mMediator.showToolbarOnTop(showOnTop);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarViewBinder.java
index 7fe4bbe..14a52d1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/bottom/TabSwitcherBottomToolbarViewBinder.java
@@ -27,7 +27,7 @@
      * Build a binder that handles interaction between the model and the tab switcher bottom toolbar
      * view.
      */
-    public TabSwitcherBottomToolbarViewBinder(ViewGroup topRoot, ViewGroup bottomRoot) {
+    TabSwitcherBottomToolbarViewBinder(ViewGroup topRoot, ViewGroup bottomRoot) {
         mTopRoot = topRoot;
         mBottomRoot = bottomRoot;
     }
@@ -43,8 +43,6 @@
                     .setBackgroundColor(model.get(TabSwitcherBottomToolbarModel.PRIMARY_COLOR));
         } else if (TabSwitcherBottomToolbarModel.SHOW_ON_TOP == propertyKey) {
             final boolean showOnTop = model.get(TabSwitcherBottomToolbarModel.SHOW_ON_TOP);
-            view.findViewById(R.id.bottom_toolbar_top_shadow)
-                    .setVisibility(showOnTop ? View.GONE : View.VISIBLE);
             view.findViewById(R.id.bottom_toolbar_bottom_shadow)
                     .setVisibility(showOnTop ? View.VISIBLE : View.GONE);
             reparentView(view, showOnTop ? mTopRoot : mBottomRoot);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
index 841e6b0..ef4cd55 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
@@ -611,7 +611,7 @@
 
         if (mLayoutLocationBarWithoutExtraButton) {
             float offset = getLocationBarWidthOffsetForExperimentalButton();
-            if (ApiCompatibilityUtils.isLayoutRtl(this)) leftMargin -= (int) offset;
+            if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) leftMargin -= (int) offset;
             width += (int) offset;
         }
 
@@ -644,7 +644,7 @@
      */
     private int getFocusedLocationBarLeftMargin(int priorVisibleWidth) {
         int baseMargin = mToolbarSidePadding;
-        if (ApiCompatibilityUtils.isLayoutRtl(mLocationBar)) {
+        if (mLocationBar.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
             return baseMargin;
         } else {
             return baseMargin - priorVisibleWidth;
@@ -661,7 +661,7 @@
         // and the layout values have not yet been set.
         if (visualState == VisualState.NEW_TAB_NORMAL && mTabSwitcherState == STATIC_TAB) {
             return mToolbarSidePadding;
-        } else if (ApiCompatibilityUtils.isLayoutRtl(this)) {
+        } else if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
             return getBoundsAfterAccountingForRightButtons();
         } else {
             return getBoundsAfterAccountingForLeftButton();
@@ -689,7 +689,7 @@
         // and the layout values have not yet been set.
         if (visualState == VisualState.NEW_TAB_NORMAL && mTabSwitcherState == STATIC_TAB) {
             return getMeasuredWidth() - mToolbarSidePadding;
-        } else if (ApiCompatibilityUtils.isLayoutRtl(this)) {
+        } else if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
             return getMeasuredWidth() - getBoundsAfterAccountingForLeftButton();
         } else {
             return getMeasuredWidth() - getBoundsAfterAccountingForRightButtons();
@@ -830,7 +830,7 @@
                 (int) MathUtils.interpolate(getViewBoundsLeftOfLocationBar(visualState),
                         getFocusedLeftPositionOfLocationBarBackground(), expansion);
 
-        if (mExperimentalButtonAnimationRunning && ApiCompatibilityUtils.isLayoutRtl(this)) {
+        if (mExperimentalButtonAnimationRunning && getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
             leftViewPosition -= getLocationBarBackgroundOffsetForExperimentalButton();
         }
 
@@ -855,7 +855,8 @@
                 (int) MathUtils.interpolate(getViewBoundsRightOfLocationBar(visualState),
                         getFocusedRightPositionOfLocationBarBackground(), expansion);
 
-        if (mExperimentalButtonAnimationRunning && !ApiCompatibilityUtils.isLayoutRtl(this)) {
+        if (mExperimentalButtonAnimationRunning
+                && !(getLayoutDirection() == LAYOUT_DIRECTION_RTL)) {
             rightViewPosition += getLocationBarBackgroundOffsetForExperimentalButton();
         }
 
@@ -972,7 +973,7 @@
                     getViewBoundsLeftOfLocationBar(mVisualState) - mUnfocusedLocationBarLayoutLeft;
         }
 
-        boolean isLocationBarRtl = ApiCompatibilityUtils.isLayoutRtl(mLocationBar);
+        boolean isLocationBarRtl = mLocationBar.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
         if (isLocationBarRtl) {
             locationBarBaseTranslationX += mUnfocusedLocationBarLayoutWidth - currentWidth;
         }
@@ -1047,7 +1048,7 @@
      */
     private float getUrlActionsTranslationXForExpansionAnimation(
             boolean isLocationBarRtl, float locationBarBaseTranslationX) {
-        boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(this);
+        boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
         float urlActionsTranslationX = 0;
         if (!isLocationBarRtl || isRtl) {
             // Negate the location bar translation to keep the URL action container in the same
@@ -1444,7 +1445,7 @@
                 // When the defocus animation is running, the location bar padding needs to be
                 // subtracted from the clip bounds so that the location bar text width in the last
                 // frame of the animation matches the text width of the unfocused location bar.
-                if (ApiCompatibilityUtils.isLayoutRtl(mLocationBar)) {
+                if (mLocationBar.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
                     locationBarClipLeft +=
                             ViewCompat.getPaddingStart(mLocationBar) * inversePercent;
                 } else {
@@ -1452,7 +1453,7 @@
                 }
             }
             if (mExperimentalButtonAnimationRunning) {
-                if (ApiCompatibilityUtils.isLayoutRtl(mLocationBar)) {
+                if (mLocationBar.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
                     locationBarClipLeft += ViewCompat.getPaddingStart(mLocationBar);
                 } else {
                     locationBarClipRight -= ViewCompat.getPaddingEnd(mLocationBar);
@@ -1874,7 +1875,7 @@
             @Override
             public void set(TextView view, Integer scrollX) {
                 if (mRtlStateInvalid) return;
-                boolean rtl = ApiCompatibilityUtils.isLayoutRtl(containerView);
+                boolean rtl = containerView.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
                 if (rtl != isContainerRtl) {
                     mRtlStateInvalid = true;
                     if (!rtl || mUrlBar.getLayout() != null) {
@@ -1906,7 +1907,7 @@
         }
 
         float density = getContext().getResources().getDisplayMetrics().density;
-        boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(this);
+        boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
         float toolbarButtonTranslationX =
                 MathUtils.flipSignIf(URL_FOCUS_TOOLBAR_BUTTONS_TRANSLATION_X_DP, isRtl) * density;
 
@@ -2457,7 +2458,7 @@
             if (!isMenuButtonPresent()) mExperimentalButton.setPadding(0, 0, 0, 0);
             mExperimentalButtonTranslation = getResources().getDimensionPixelSize(
                     R.dimen.toolbar_optional_button_animation_translation);
-            if (ApiCompatibilityUtils.isLayoutRtl(this)) mExperimentalButtonTranslation *= -1;
+            if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) mExperimentalButtonTranslation *= -1;
         } else {
             if (mExperimentalButtonAnimationRunning) {
                 mExperimentalButtonAnimator.end();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java b/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java
index 321069a8..17912b7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java
@@ -79,6 +79,7 @@
     private static Boolean sShouldInflateToolbarOnBackgroundThread;
     private static Boolean sIsNightModeAvailable;
     private static Boolean sShouldPrioritizeBootstrapTasks;
+    private static Boolean sIsTabGroupsAndroidEnabled;
 
     private static Boolean sDownloadAutoResumptionEnabledInNative;
 
@@ -198,6 +199,8 @@
         cacheDownloadAutoResumptionEnabledInNative();
         cachePrioritizeBootstrapTasks();
 
+        if (isDeviceEligibleForTabGroups()) cacheTabGroupsAndroidEnabled();
+
         // Propagate DONT_PREFETCH_LIBRARIES and REACHED_CODE_PROFILER feature values to
         // LibraryLoader. This can't be done in LibraryLoader itself because it lives in //base and
         // can't depend on ChromeFeatureList.
@@ -495,14 +498,33 @@
                 && ChromeFeatureList.isEnabled(ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID);
     }
 
+    private static void cacheTabGroupsAndroidEnabled() {
+        ChromePreferenceManager.getInstance().writeBoolean(
+                ChromePreferenceManager.TAB_GROUPS_ANDROID_ENABLED_KEY,
+                ChromeFeatureList.isEnabled(ChromeFeatureList.TAB_GROUPS_ANDROID));
+    }
+
     /**
-     * @param activityContext The context for the containing {@link android.app.Activity}.
      * @return Whether the tab group feature is enabled and available for use.
      */
-    public static boolean isTabGroupsEnabled(Context activityContext) {
-        return !DeviceFormFactor.isNonMultiDisplayContextOnTablet(activityContext)
-                && !SysUtils.isLowEndDevice() && !DeviceClassManager.enableAccessibilityLayout()
-                && ChromeFeatureList.isEnabled(ChromeFeatureList.TAB_GROUPS_ANDROID);
+    public static boolean isTabGroupsAndroidEnabled() {
+        if (!isDeviceEligibleForTabGroups()) return false;
+
+        if (sIsTabGroupsAndroidEnabled == null) {
+            ChromePreferenceManager preferenceManager = ChromePreferenceManager.getInstance();
+
+            sIsTabGroupsAndroidEnabled = preferenceManager.readBoolean(
+                    ChromePreferenceManager.TAB_GROUPS_ANDROID_ENABLED_KEY, false);
+        }
+
+        return sIsTabGroupsAndroidEnabled;
+    }
+
+    private static boolean isDeviceEligibleForTabGroups() {
+        return !SysUtils.isLowEndDevice()
+                && !DeviceFormFactor.isNonMultiDisplayContextOnTablet(
+                        ContextUtils.getApplicationContext())
+                && !DeviceClassManager.enableAccessibilityLayout();
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
index bfdd9e4..6e3b34c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
@@ -46,11 +46,10 @@
 import org.chromium.chrome.browser.fullscreen.FullscreenOptions;
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBuilder;
 import org.chromium.chrome.browser.tab.TabDelegateFactory;
 import org.chromium.chrome.browser.tab.TabObserver;
 import org.chromium.chrome.browser.tab.TabState;
-import org.chromium.chrome.browser.tab.TabUma.TabCreationState;
-import org.chromium.chrome.browser.tabmodel.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.document.TabDelegate;
 import org.chromium.chrome.browser.toolbar.top.ToolbarControlContainer;
 import org.chromium.chrome.browser.util.ColorUtils;
@@ -389,8 +388,10 @@
         TabState tabState = TabState.restoreTabState(getActivityDirectory(), tabId);
         if (tabState == null) return null;
 
-        return new Tab(tabId, Tab.INVALID_TAB_ID, false, getWindowAndroid(),
-                TabLaunchType.FROM_RESTORE, TabCreationState.FROZEN_ON_RESTORE, tabState);
+        return TabBuilder.createFromFrozenState(tabState)
+                .setId(tabId)
+                .setWindow(getWindowAndroid())
+                .build();
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/CompatibilityTextInputLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/CompatibilityTextInputLayout.java
index 4a9bc32a..64b4406 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/CompatibilityTextInputLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/CompatibilityTextInputLayout.java
@@ -12,8 +12,6 @@
 import android.view.ViewGroup;
 import android.widget.EditText;
 
-import org.chromium.base.ApiCompatibilityUtils;
-
 import java.util.ArrayList;
 
 /**
@@ -47,7 +45,7 @@
         ArrayList<EditText> views = new ArrayList<>();
         findEditTextChildren(this, views);
         if (views.size() == 1) {
-            ApiCompatibilityUtils.setLabelFor(this, views.get(0).getId());
+            setLabelFor(views.get(0).getId());
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/DualControlLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/DualControlLayout.java
index d6d946a3..da67141 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/DualControlLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/DualControlLayout.java
@@ -202,7 +202,7 @@
         int rightPadding = getPaddingRight();
 
         int width = right - left;
-        boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(this);
+        boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
         boolean isPrimaryOnRight = (isRtl && mAlignment == DualControlLayoutAlignment.START)
                 || (!isRtl
                            && (mAlignment == DualControlLayoutAlignment.APART
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/ListMenuButton.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/ListMenuButton.java
index 1da0f140..4c1e064 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/ListMenuButton.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/ListMenuButton.java
@@ -197,8 +197,9 @@
 
                 // Set the compound drawable at the end for items with a valid endIconId,
                 // otherwise clear the compound drawable if the endIconId is 0.
-                ApiCompatibilityUtils.setCompoundDrawablesRelativeWithIntrinsicBounds(
-                        (TextView) view, 0, 0, items[position].getEndIconId(), 0);
+                ((TextView) view)
+                        .setCompoundDrawablesRelativeWithIntrinsicBounds(
+                                0, 0, items[position].getEndIconId(), 0);
 
                 return view;
             }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/RadioButtonWithDescription.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/RadioButtonWithDescription.java
index 011d4a4a..2b212c8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/RadioButtonWithDescription.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/RadioButtonWithDescription.java
@@ -55,8 +55,10 @@
         if (attrs != null) applyAttributes(attrs);
 
         // We want RadioButtonWithDescription to handle the clicks itself.
-        mRadioButton.setClickable(false);
         setOnClickListener(this);
+        // Make it focusable for navigation via key events (tab/up/down keys)
+        // with Bluetooth keyboard. See: crbug.com/936143
+        setFocusable(true);
     }
 
     private void applyAttributes(AttributeSet attrs) {
@@ -111,6 +113,10 @@
      */
     public void setChecked(boolean checked) {
         mRadioButton.setChecked(checked);
+        // Retain focus on RadioButtonWithDescription after radio button is checked.
+        // Otherwise focus is lost. This is required for Bluetooth keyboard navigation.
+        // See: crbug.com/936143
+        if (checked) requestFocus();
     }
 
     public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/TextViewWithCompoundDrawables.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/TextViewWithCompoundDrawables.java
index 7d36a8d..7f6c97c3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/TextViewWithCompoundDrawables.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/TextViewWithCompoundDrawables.java
@@ -12,8 +12,8 @@
 import android.graphics.drawable.Drawable;
 import android.support.v7.widget.AppCompatTextView;
 import android.util.AttributeSet;
+import android.widget.TextView;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.R;
 
 /**
@@ -45,7 +45,7 @@
         super.drawableStateChanged();
 
         if (mDrawableTint != null) {
-            setDrawableTint(ApiCompatibilityUtils.getCompoundDrawablesRelative(this));
+            setDrawableTint(getCompoundDrawablesRelative());
         }
     }
 
@@ -64,7 +64,7 @@
 
         if (mDrawableWidth <= 0 && mDrawableHeight <= 0 && mDrawableTint == null) return;
 
-        Drawable[] drawables = ApiCompatibilityUtils.getCompoundDrawablesRelative(this);
+        Drawable[] drawables = getCompoundDrawablesRelative();
         for (Drawable drawable : drawables) {
             if (drawable == null) continue;
 
@@ -82,8 +82,7 @@
 
         if (mDrawableTint != null) setDrawableTint(drawables);
 
-        ApiCompatibilityUtils.setCompoundDrawablesRelative(
-                this, drawables[0], drawables[1], drawables[2], drawables[3]);
+        setCompoundDrawablesRelative(drawables[0], drawables[1], drawables[2], drawables[3]);
     }
 
     private void setDrawableTint(Drawable[] drawables) {
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index 5769619..2d51c8a6 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -3877,13 +3877,16 @@
       <message name="IDS_IPH_PREVIEWS_OMNIBOX_UI_ACCESSIBILITY_TEXT" desc="The in-product-help text informing the user that the displayed page was modified to make it load faster or use less data. Prompts the user to tap the message and load the original, unaltered, page if they would like. The 'Lite page provided by Google.' sentence should match TC ID 373879247902731825">
         Lite page provided by Google. Tap the load original button to load the original page.
       </message>
-      <message name="IDS_IPH_TAB_GROUPS_QUICKLY_COMPARE_PAGES_TEXT" desc="In-product help for prompting the user on pages with links to long press and open links in different tabs.">
+      <message name="IDS_IPH_TAB_GROUPS_QUICKLY_COMPARE_PAGES_TEXT" desc="When Chrome detects that the user is searching or comparing multiple pages (e.g. while comparison shopping), this in-product help text appears at the bottom of the screen. The text informs the user that they can longpress on a link to open it as a new tab within a tab group.">
         Quickly compare pages by making a group. To start, touch &amp; hold a link.
       </message>
-      <message name="IDS_IPH_TAB_GROUPS_TAP_TO_SEE_ANOTHER_TAB_TEXT" desc="In-product help for when the tab strip containing the tabs in a tab group are shown.">
+      <message name="IDS_IPH_TAB_GROUPS_TAP_TO_SEE_ANOTHER_TAB_ACCESSIBILITY_TEXT" desc="This in-product help accessibility text points to the strip of favicons at the bottom of the page. The favicons indicate all the open tabs within the tab group. The text informs the user that they can tap on any favicon to switch to that tab.">
+        Switch between tabs in your tab group near bottom of screen
+      </message>
+      <message name="IDS_IPH_TAB_GROUPS_TAP_TO_SEE_ANOTHER_TAB_TEXT" desc="This in-product help text points to the strip of favicons at the bottom of the page. The favicons indicate all the open tabs within the tab group. The text informs the user that they can tap on any favicon to switch to that tab.">
         Tap to see another tab
       </message>
-      <message name="IDS_IPH_TAB_GROUPS_YOUR_TABS_TOGETHER_TEXT" desc="In-product help for when a tab switcher card with multiple thumbnails are shown.">
+      <message name="IDS_IPH_TAB_GROUPS_YOUR_TABS_TOGETHER_TEXT" desc="This in-product help text points to a group of tabs within the Tab Switcher. The text informs the user that they can find existing tab groups in the Tab Switcher.">
         Your tabs are grouped together here
       </message>
       <message name="IDS_IPH_TRANSLATE_MENU_BUTTON_TEXT" desc="The in-product-help message after a successful navigation prompting user to translate current page.">
@@ -4014,16 +4017,6 @@
          Close other incognito tabs
       </message>
 
-      <!-- Autofill Assistant -->
-       <message name="IDS_AUTOFILL_ASSISTANT_ONBOARDING_TITLE" desc="Onboarding title for the autofill assistant.">
-        Google Assistant\nin Chrome
-      </message>
-      <message name="IDS_INIT_OK" desc="Init screen confirmation text.">
-        I accept
-      </message>
-      <message name="IDS_AUTOFILL_ASSISTANT_INIT_MESSAGE" desc="Onboarding message describing autofill assistant's capability.">
-        Google Assistant saves you time by helping you check out on the web
-      </message>
       <!-- Autofill Assistant preferences -->
       <message name="IDS_PREFS_AUTOFILL_ASSISTANT_TITLE" desc="Title for the Autofill Assistant preferences screen. [CHAR-LIMIT=32]">
         Google Assistant in Chrome
@@ -4031,21 +4024,6 @@
       <message name="IDS_PREFS_AUTOFILL_ASSISTANT_SWITCH" desc="Title for the switch toggling whether Autofill Assistant is enabled. [CHAR-LIMIT=32]">
         Assistant Triggered Checkout
       </message>
-      <message name="IDS_AUTOFILL_ASSISTANT_GOOGLE_TERMS_DESCRIPTION" desc="Message linking to the Google terms and conditions for Google Assistant in Chrome.">
-        Chrome will send the site’s URL and content as well as your email and credit card type saved in Chrome to Google. You can turn this off in Chrome settings. <ph name="BEGIN_LINK">&lt;link&gt;</ph>Learn&#xA0;more<ph name="END_LINK">&lt;/link&gt;</ph>
-      </message>
-      <message name="IDS_AUTOFILL_ASSISTANT_GOOGLE_TERMS_URL" desc="URL for Google Autofill Assistant Terms of Service" translateable="false">
-        http://support.google.com/assistant?p=fast_checkout
-      </message>
-      <message name="IDS_AUTOFILL_ASSISTANT_3RD_PARTY_TERMS_ACCEPT" desc="Message that indicates that the user agrees to the terms and conditions of a 3rd party's domain, e.g., 'odeon.co.uk'.">
-        I agree to the terms &amp; conditions, privacy policy, and right of withdrawal of <ph name="BEGIN_BOLD">&lt;b&gt;</ph><ph name="DOMAIN">%1$s<ex>google.com</ex></ph><ph name="END_BOLD">&lt;/b&gt;</ph>
-      </message>
-      <message name="IDS_AUTOFILL_ASSISTANT_3RD_PARTY_TERMS_REVIEW" desc="Message that indicates that the user wants to review the terms and conditions of a 3rd party's domain, e.g., 'odeon.co.uk'.">
-        Read and agree to the terms &amp; conditions on <ph name="BEGIN_BOLD">&lt;b&gt;</ph><ph name="DOMAIN">%1$s<ex>google.com</ex></ph><ph name="END_BOLD">&lt;/b&gt;</ph> later
-      </message>
-      <message name="IDS_AUTOFILL_ASSISTANT_3RD_PARTY_PRIVACY_NOTICE" desc="Privacy notice telling users that autofill assistant will send personal data to a third party’s website.">
-        Chrome will send personal data you selected to <ph name="BEGIN_BOLD">&lt;b&gt;</ph><ph name="DOMAIN">%1$s<ex>google.com</ex></ph><ph name="END_BOLD">&lt;/b&gt;</ph>
-      </message>
       <!-- Usage Stats strings -->
       <message name="IDS_USAGE_STATS_CONSENT_TITLE" desc="Title for activity authorizing Digital Wellbeing to access Chrome usage data">
         Show your Chrome activity in Digital Wellbeing?
@@ -4095,7 +4073,16 @@
       </message>
 
       <message name="IDS_BOTTOM_TAB_GRID_NEW_TAB" desc="Accessibility string for BottomTabGridToolbar option 'create new tab in group' indicated visually by the '+' sign.">
-        Create new tab in group
+        Add new tab to group
+      </message>
+
+      <message name="IDS_ACCESSIBILITY_BOTTOM_TAB_GRID_CLOSE_TAB_SHEET" desc="Accessibility string for BottomTabGridToolbar button indicated visually by the 'v' sign.">
+        Hide fullscreen grid
+      </message>
+
+      <!-- Bottom Tab Strip strings -->
+      <message name="IDS_ACCESSIBILITY_BOTTOM_TAB_STRIP_EXPAND_TAB_SHEET" desc="Accessibility string for BottomTabStripToolbar button indicated visually by the '^' sign.">
+        Show group's tabs in fullscreen grid
       </message>
     </messages>
   </release>
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index ebe7218..9525278 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -138,44 +138,10 @@
   "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetMediator.java",
   "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewBinder.java",
   "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetModernViewBinder.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomBarCoordinator.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/AssistantCoordinator.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/AssistantKeyboardCoordinator.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/AssistantModel.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/AssistantOnboardingCoordinator.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/AssistantSnackbar.java",
   "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantClient.java",
   "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java",
   "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantMetrics.java",
   "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantPreferencesUtil.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiController.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/EditDistance.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/FeedbackContext.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantCarouselCoordinator.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantCarouselModel.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChip.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/carousel/AssistantChipViewHolder.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetails.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsCoordinator.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsModel.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/details/AssistantDetailsViewBinder.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/header/AnimatedProgressBar.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderCoordinator.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderDelegate.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderModel.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/header/AssistantHeaderViewBinder.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayCoordinator.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayDelegate.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayModel.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/overlay/TouchEventFilterView.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestCoordinator.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestDelegate.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestModel.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestOptions.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequest.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequestSection.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestBottomBar.java",
-  "java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestUI.java",
   "java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTaskScheduler.java",
   "java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTask.java",
   "java/src/org/chromium/chrome/browser/background_task_scheduler/NativeBackgroundTask.java",
@@ -1524,6 +1490,7 @@
   "java/src/org/chromium/chrome/browser/tab/TabAttributeKeys.java",
   "java/src/org/chromium/chrome/browser/tab/TabAttributes.java",
   "java/src/org/chromium/chrome/browser/tab/TabBrowserControlsOffsetHelper.java",
+  "java/src/org/chromium/chrome/browser/tab/TabBuilder.java",
   "java/src/org/chromium/chrome/browser/tab/TabContextMenuItemDelegate.java",
   "java/src/org/chromium/chrome/browser/tab/TabContextMenuPopulator.java",
   "java/src/org/chromium/chrome/browser/tab/TabDelegateFactory.java",
@@ -1598,9 +1565,9 @@
   "java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripBottomToolbarMediator.java",
   "java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripToolbarCoordinator.java",
   "java/src/org/chromium/chrome/browser/tasks/tab_list_ui/TabStripBottomToolbarCoordinator.java",
-  "java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetToolbarProperties.java",
-  "java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetToolbarViewBinder.java",
+  "java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetProperties.java",
   "java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetToolbarCoordinator.java",
+  "java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetViewBinder.java",
   "java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridCoordinator.java",
   "java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridMediator.java",
   "java/src/org/chromium/chrome/browser/tasks/tab_list_ui/BottomTabGridSheetContent.java",
@@ -1634,6 +1601,10 @@
   "java/src/org/chromium/chrome/browser/toolbar/ToolbarDataProvider.java",
   "java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java",
   "java/src/org/chromium/chrome/browser/toolbar/ToolbarTabController.java",
+  "java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsCoordinator.java",
+  "java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsMediator.java",
+  "java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsProperties.java",
+  "java/src/org/chromium/chrome/browser/toolbar/bottom/BottomControlsViewBinder.java",
   "java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarNewTabButton.java",
   "java/src/org/chromium/chrome/browser/toolbar/bottom/BottomToolbarCoordinator.java",
   "java/src/org/chromium/chrome/browser/toolbar/bottom/BrowsingModeBottomToolbarCoordinator.java",
@@ -1940,8 +1911,6 @@
   "javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessoryIntegrationTest.java",
   "javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewTest.java",
   "javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetModernViewTest.java",
-  "javatests/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantUiTest.java",
-  "javatests/src/org/chromium/chrome/browser/autofill_assistant/EditDistanceTest.java",
   "javatests/src/org/chromium/chrome/browser/banners/AppBannerManagerTest.java",
   "javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkBridgeTest.java",
   "javatests/src/org/chromium/chrome/browser/bookmarks/BookmarkModelTest.java",
@@ -2303,6 +2272,7 @@
   "javatests/src/org/chromium/chrome/browser/sync/ui/PassphraseActivityTest.java",
   "javatests/src/org/chromium/chrome/browser/sync/ui/PassphraseTypeDialogFragmentTest.java",
   "javatests/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateTest.java",
+  "javatests/src/org/chromium/chrome/browser/tab/MockTab.java",
   "javatests/src/org/chromium/chrome/browser/tab/RepostFormWarningTest.java",
   "javatests/src/org/chromium/chrome/browser/tab/SadTabTest.java",
   "javatests/src/org/chromium/chrome/browser/tab/TabIdManagerTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/JavaScriptEvalChromeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/JavaScriptEvalChromeTest.java
index 74969d5..a07e71b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/JavaScriptEvalChromeTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/JavaScriptEvalChromeTest.java
@@ -13,6 +13,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.RetryOnFailure;
@@ -74,16 +75,20 @@
         for (int i = 1; i <= 30; ++i) {
             for (int j = 0; j < 5; ++j) {
                 // Start evaluation of a JavaScript script -- we don't need a result.
-                tab1.getWebContents().evaluateJavaScriptForTests("foobar();", null);
-                tab2.getWebContents().evaluateJavaScriptForTests("foobar();", null);
+                ThreadUtils.runOnUiThreadBlocking(() -> {
+                    tab1.getWebContents().evaluateJavaScriptForTests("foobar();", null);
+                    tab2.getWebContents().evaluateJavaScriptForTests("foobar();", null);
+                });
             }
             Assert.assertEquals("Incorrect JavaScript evaluation result on tab1", i * 2,
                     Integer.parseInt(JavaScriptUtils.executeJavaScriptAndWaitForResult(
                             tab1.getWebContents(), "add2()")));
             for (int j = 0; j < 5; ++j) {
                 // Start evaluation of a JavaScript script -- we don't need a result.
-                tab1.getWebContents().evaluateJavaScriptForTests("foobar();", null);
-                tab2.getWebContents().evaluateJavaScriptForTests("foobar();", null);
+                ThreadUtils.runOnUiThreadBlocking(() -> {
+                    tab1.getWebContents().evaluateJavaScriptForTests("foobar();", null);
+                    tab2.getWebContents().evaluateJavaScriptForTests("foobar();", null);
+                });
             }
             Assert.assertEquals("Incorrect JavaScript evaluation result on tab2", i * 2 + 1,
                     Integer.parseInt(JavaScriptUtils.executeJavaScriptAndWaitForResult(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java
index 78c8e91..09c96ca 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/LayoutManagerTest.java
@@ -36,6 +36,7 @@
 import org.chromium.chrome.browser.compositor.layouts.phone.stack.StackTab;
 import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBuilder;
 import org.chromium.chrome.browser.tabmodel.TabModel;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelUtils;
@@ -562,6 +563,6 @@
 
     @Override
     public Tab createTab(int id, boolean incognito) {
-        return new Tab(id, incognito, null);
+        return new TabBuilder().setId(id).setIncognito(incognito).build();
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabTabPersistencePolicyTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabTabPersistencePolicyTest.java
index 9ed52d4..93ba731d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabTabPersistencePolicyTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabTabPersistencePolicyTest.java
@@ -31,6 +31,7 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
+import org.chromium.chrome.browser.tab.MockTab;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabState;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
@@ -439,7 +440,7 @@
                 new MockTabModel.MockTabModelDelegate() {
                     @Override
                     public Tab createTab(int id, boolean incognito) {
-                        return new Tab(id, incognito, null) {
+                        return new MockTab(id, incognito) {
                             @Override
                             public String getUrl() {
                                 return "https://www.google.com";
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarVoiceRecognitionHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarVoiceRecognitionHandlerTest.java
index d8ebb43..23f44a6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarVoiceRecognitionHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarVoiceRecognitionHandlerTest.java
@@ -32,6 +32,7 @@
 import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestion;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBuilder;
 import org.chromium.chrome.browser.toolbar.ToolbarDataProvider;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@@ -412,7 +413,7 @@
         ThreadUtils.runOnUiThreadBlocking(() -> {
             mWindowAndroid = new TestWindowAndroid(mActivityTestRule.getActivity());
             mWindowAndroid.setAndroidPermissionDelegate(mPermissionDelegate);
-            mTab = new Tab(0, false /* incognito */, mWindowAndroid);
+            mTab = new TabBuilder().setId(0).setWindow(mWindowAndroid).build();
         });
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareMenuActionHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareMenuActionHandlerTest.java
index d3cf48aa..e75ac74 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareMenuActionHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareMenuActionHandlerTest.java
@@ -13,7 +13,7 @@
 
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
-import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.MockTab;
 import org.chromium.chrome.browser.test.ChromeBrowserTestRule;
 import org.chromium.chrome.test.util.SadTabRule;
 import org.chromium.content_public.browser.WebContents;
@@ -36,7 +36,7 @@
     @Test
     @SmallTest
     public void testShouldFetchCanonicalUrl() throws ExecutionException {
-        MockTab mockTab = ThreadUtils.runOnUiThreadBlocking(() -> { return new MockTab(); });
+        MockUrlTab mockTab = ThreadUtils.runOnUiThreadBlocking(() -> { return new MockUrlTab(); });
         MockWebContents mockWebContents = new MockWebContents();
         MockRenderFrameHost mockRenderFrameHost = new MockRenderFrameHost();
         mSadTabRule.setTab(mockTab);
@@ -99,14 +99,14 @@
         Assert.assertEquals(httpsUrl, ShareMenuActionHandler.getUrlToShare(httpsUrl, contentUrl));
     }
 
-    private static class MockTab extends Tab {
+    private static class MockUrlTab extends MockTab {
         public WebContents webContents;
         public String url;
         public boolean isShowingErrorPage;
         public boolean isShowingInterstitialPage;
 
-        public MockTab() {
-            super(INVALID_TAB_ID, false, null);
+        public MockUrlTab() {
+            super(INVALID_TAB_ID, false);
         }
 
         @Override
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/MockTab.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/MockTab.java
new file mode 100644
index 0000000..6f855669
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/MockTab.java
@@ -0,0 +1,18 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.tab;
+
+/**
+ * Tab used for various testing purposes.
+ */
+public class MockTab extends Tab {
+    /**
+     * Constructor for id and incognito atrribute. Tests often need to initialize
+     * these two fields only.
+     */
+    public MockTab(int id, boolean incognito) {
+        super(id, Tab.INVALID_TAB_ID, incognito, null, null, null, null, null);
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabUmaTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabUmaTest.java
index 8bc0ec7c1..c2d938e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabUmaTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/TabUmaTest.java
@@ -70,10 +70,10 @@
         final Tab tab = ThreadUtils.runOnUiThreadBlocking(new Callable<Tab>() {
             @Override
             public Tab call() {
-                Tab bgTab = Tab.createTabForLazyLoad(false,
-                        mActivityTestRule.getActivity().getWindowAndroid(),
-                        TabLaunchType.FROM_LONGPRESS_BACKGROUND, Tab.INVALID_TAB_ID,
-                        new LoadUrlParams(mTestUrl));
+                Tab bgTab = TabBuilder.createForLazyLoad(new LoadUrlParams(mTestUrl))
+                                    .setWindow(mActivityTestRule.getActivity().getWindowAndroid())
+                                    .setLaunchType(TabLaunchType.FROM_LONGPRESS_BACKGROUND)
+                                    .build();
                 bgTab.initialize(null, null, new TabDelegateFactory(), true, false);
                 return bgTab;
             }
@@ -125,9 +125,10 @@
         final Tab liveBgTab = ThreadUtils.runOnUiThreadBlocking(new Callable<Tab>() {
             @Override
             public Tab call() {
-                Tab bgTab = Tab.createLiveTab(Tab.INVALID_TAB_ID, false,
-                        mActivityTestRule.getActivity().getWindowAndroid(),
-                        TabLaunchType.FROM_LONGPRESS_BACKGROUND, Tab.INVALID_TAB_ID, true);
+                Tab bgTab = TabBuilder.createLiveTab(true)
+                                    .setWindow(mActivityTestRule.getActivity().getWindowAndroid())
+                                    .setLaunchType(TabLaunchType.FROM_LONGPRESS_BACKGROUND)
+                                    .build();
                 bgTab.initialize(null, null, new TabDelegateFactory(), true, false);
                 bgTab.loadUrl(new LoadUrlParams(mTestUrl));
                 bgTab.show(TabSelectionType.FROM_USER);
@@ -142,9 +143,10 @@
         final Tab killedBgTab = ThreadUtils.runOnUiThreadBlocking(new Callable<Tab>() {
             @Override
             public Tab call() {
-                Tab bgTab = Tab.createLiveTab(Tab.INVALID_TAB_ID, false,
-                        mActivityTestRule.getActivity().getWindowAndroid(),
-                        TabLaunchType.FROM_LONGPRESS_BACKGROUND, Tab.INVALID_TAB_ID, true);
+                Tab bgTab = TabBuilder.createLiveTab(true)
+                                    .setWindow(mActivityTestRule.getActivity().getWindowAndroid())
+                                    .setLaunchType(TabLaunchType.FROM_LONGPRESS_BACKGROUND)
+                                    .build();
                 bgTab.initialize(null, null, new TabDelegateFactory(), true, false);
                 bgTab.loadUrl(new LoadUrlParams(mTestUrl));
                 // Simulate the renderer being killed by the OS.
@@ -161,10 +163,10 @@
         final Tab frozenBgTab = ThreadUtils.runOnUiThreadBlocking(new Callable<Tab>() {
             @Override
             public Tab call() {
-                Tab bgTab = Tab.createTabForLazyLoad(false,
-                        mActivityTestRule.getActivity().getWindowAndroid(),
-                        TabLaunchType.FROM_LONGPRESS_BACKGROUND, Tab.INVALID_TAB_ID,
-                        new LoadUrlParams(mTestUrl));
+                Tab bgTab = TabBuilder.createForLazyLoad(new LoadUrlParams(mTestUrl))
+                                    .setWindow(mActivityTestRule.getActivity().getWindowAndroid())
+                                    .setLaunchType(TabLaunchType.FROM_LONGPRESS_BACKGROUND)
+                                    .build();
                 bgTab.initialize(null, null, new TabDelegateFactory(), true, false);
                 bgTab.show(TabSelectionType.FROM_USER);
                 return bgTab;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/DocumentModeAssassinTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/DocumentModeAssassinTest.java
index b64d756..a14631c7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/DocumentModeAssassinTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/DocumentModeAssassinTest.java
@@ -28,6 +28,7 @@
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.RetryOnFailure;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBuilder;
 import org.chromium.chrome.browser.tab.TabState;
 import org.chromium.chrome.browser.tabmodel.DocumentModeAssassin.DocumentModeAssassinForTesting;
 import org.chromium.chrome.browser.tabmodel.DocumentModeAssassin.DocumentModeAssassinObserver;
@@ -107,7 +108,7 @@
 
             @Override
             public Tab getTabAt(int index) {
-                return new Tab(TAB_STATE_INFO[index].tabId, false, null);
+                return new TabBuilder().setId(TAB_STATE_INFO[index].tabId).build();
             }
 
             @Override
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorTabObserverTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorTabObserverTest.java
index f2cbad5..ed4e7187 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorTabObserverTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorTabObserverTest.java
@@ -17,6 +17,7 @@
 import org.chromium.base.ObserverList;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBuilder;
 import org.chromium.chrome.browser.tab.TabObserver;
 import org.chromium.chrome.browser.tab.TabTestUtils;
 import org.chromium.content_public.browser.LoadUrlParams;
@@ -137,7 +138,10 @@
     }
 
     private Tab createTestTab(boolean incognito) {
-        Tab testTab = new Tab(Tab.INVALID_TAB_ID, incognito, mTestRule.getWindowAndroid());
+        Tab testTab = new TabBuilder()
+                              .setIncognito(incognito)
+                              .setWindow(mTestRule.getWindowAndroid())
+                              .build();
         testTab.initializeNative();
         return testTab;
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabPersistentStoreTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabPersistentStoreTest.java
index e1b6188..f0dcc32 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabPersistentStoreTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabPersistentStoreTest.java
@@ -33,6 +33,7 @@
 import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
 import org.chromium.chrome.browser.snackbar.undo.UndoBarController;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBuilder;
 import org.chromium.chrome.browser.tab.TabState;
 import org.chromium.chrome.browser.tabmodel.TabCreatorManager.TabCreator;
 import org.chromium.chrome.browser.tabmodel.TabPersistentStore.TabModelSelectorMetadata;
@@ -99,8 +100,10 @@
 
         @Override
         public Tab createNewTab(LoadUrlParams loadUrlParams, @TabLaunchType int type, Tab parent) {
-            Tab tab = Tab.createTabForLazyLoad(
-                    mIsIncognito, null, TabLaunchType.FROM_LINK, Tab.INVALID_TAB_ID, loadUrlParams);
+            Tab tab = TabBuilder.createForLazyLoad(loadUrlParams)
+                              .setIncognito(mIsIncognito)
+                              .setLaunchType(TabLaunchType.FROM_LINK)
+                              .build();
             mSelector.getModel(mIsIncognito).addTab(tab, TabModel.INVALID_TAB_INDEX, type);
             storeTabInfo(null, tab.getId());
             return tab;
@@ -108,8 +111,11 @@
 
         @Override
         public Tab createFrozenTab(TabState state, int id, int index) {
-            Tab tab = Tab.createFrozenTabFromState(
-                    id, state.isIncognito(), null, state.parentId, state);
+            Tab tab = TabBuilder.createFromFrozenState(state)
+                              .setId(id)
+                              .setParentId(state.parentId)
+                              .setIncognito(state.isIncognito())
+                              .build();
             mSelector.getModel(mIsIncognito).addTab(tab, index, TabLaunchType.FROM_RESTORE);
             storeTabInfo(state, id);
             return tab;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabWindowManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabWindowManagerTest.java
index af5e4757..d29eb16 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabWindowManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabWindowManagerTest.java
@@ -21,6 +21,7 @@
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.customtabs.CustomTabActivity;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBuilder;
 import org.chromium.chrome.browser.tabmodel.TabWindowManager.TabModelSelectorFactory;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.browser.tabmodel.MockTabModelSelector;
@@ -294,7 +295,7 @@
         AsyncTabParamsManager.getAsyncTabParams().clear();
         final int asyncTabId = 123;
         final TabReparentingParams dummyParams =
-                new TabReparentingParams(new Tab(0, false, null), null, null);
+                new TabReparentingParams(new TabBuilder().setId(0).build(), null, null);
         Assert.assertFalse(manager.tabExistsInAnySelector(asyncTabId));
         AsyncTabParamsManager.add(asyncTabId, dummyParams);
         try {
@@ -329,7 +330,7 @@
         AsyncTabParamsManager.getAsyncTabParams().clear();
         final int asyncTabId = 123;
         final TabReparentingParams dummyParams =
-                new TabReparentingParams(new Tab(0, false, null), null, null);
+                new TabReparentingParams(new TabBuilder().setId(0).build(), null, null);
         Assert.assertNull(manager.getTabById(asyncTabId));
         AsyncTabParamsManager.add(asyncTabId, dummyParams);
         try {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/LocationBarModelTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/LocationBarModelTest.java
index 1797f2d8..4ec8782 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/LocationBarModelTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/LocationBarModelTest.java
@@ -24,6 +24,7 @@
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.UrlConstants;
 import org.chromium.chrome.browser.omnibox.UrlBarData;
+import org.chromium.chrome.browser.tab.MockTab;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.toolbar.top.ToolbarLayout;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@@ -122,7 +123,7 @@
             super(ContextUtils.getApplicationContext());
             initializeWithNative();
 
-            Tab tab = new Tab(0, false, null) {
+            Tab tab = new MockTab(0, false) {
                 @Override
                 public boolean isInitialized() {
                     return true;
diff --git a/chrome/app/chrome_main_delegate.cc b/chrome/app/chrome_main_delegate.cc
index 4f07270..92acdc8 100644
--- a/chrome/app/chrome_main_delegate.cc
+++ b/chrome/app/chrome_main_delegate.cc
@@ -37,7 +37,6 @@
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/crash_keys.h"
 #include "chrome/common/logging_chrome.h"
-#include "chrome/common/trace_event_args_whitelist.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/gpu/chrome_content_gpu_client.h"
 #include "chrome/renderer/chrome_content_renderer_client.h"
@@ -598,9 +597,6 @@
 
   content::Profiling::ProcessStarted();
 
-  base::trace_event::TraceLog::GetInstance()->SetArgumentFilterPredicate(
-      base::Bind(&IsTraceEventArgsWhitelisted));
-
   // Setup tracing sampler profiler as early as possible at startup if needed.
   tracing_sampler_profiler_ =
       tracing::TracingSamplerProfiler::CreateOnMainThread();
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 07a8688..176ee5c 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -3207,11 +3207,11 @@
   <message name="IDS_SETTINGS_SITE_SETTINGS_SITE_RESET_CONFIRMATION" desc="Text for the dialog that warns about resetting all permissions for a site.">
     Reset site permissions for <ph name="SITE">$1<ex>www.example.com</ex></ph>?
   </message>
-  <message name="IDS_SETTINGS_SITE_SETTINGS_SITE_CLEAR_STORAGE_DIALOG_TITLE" desc="Title of the dialog that warns about clearing storage used by a site (excluding cookies).">
+  <message name="IDS_SETTINGS_SITE_SETTINGS_SITE_CLEAR_STORAGE_DIALOG_TITLE" desc="Title of the dialog that warns about clearing storage used by a site.">
     Clear site data?
   </message>
-  <message name="IDS_SETTINGS_SITE_SETTINGS_SITE_CLEAR_STORAGE_CONFIRMATION" desc="Text for the dialog that warns about clearing storage used by a site (excluding cookies).">
-    All data stored by <ph name="SITE">$1<ex>www.example.com</ex></ph> will be deleted, except for cookies.
+  <message name="IDS_SETTINGS_SITE_SETTINGS_SITE_CLEAR_STORAGE_CONFIRMATION" desc="Text for the dialog that warns about clearing storage used by a site.">
+    All data stored by <ph name="SITE">$1<ex>www.example.com</ex></ph> will be deleted.
   </message>
   <message name="IDS_SETTINGS_SITE_SETTINGS_SITE_GROUP_RESET_DIALOG_TITLE" desc="Title of the dialog that warns about resetting all permissions for a group of sites.">
     Reset site permissions?
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 3457784..953ca53 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3507,6 +3507,10 @@
      flag_descriptions::kEnableDragTabsInTabletModeName,
      flag_descriptions::kEnableDragTabsInTabletModeDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kDragTabsInTabletMode)},
+
+    {"enable-app-grid-ghost", flag_descriptions::kEnableAppGridGhostName,
+     flag_descriptions::kEnableAppGridGhostDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(app_list_features::kEnableAppGridGhost)},
 #endif  // OS_CHROMEOS
 
     {"enable-accessibility-object-model",
diff --git a/chrome/browser/accessibility/accessibility_extension_api.cc b/chrome/browser/accessibility/accessibility_extension_api.cc
index 954fe70e..6970785 100644
--- a/chrome/browser/accessibility/accessibility_extension_api.cc
+++ b/chrome/browser/accessibility/accessibility_extension_api.cc
@@ -82,47 +82,49 @@
 }
 
 ExtensionFunction::ResponseAction
-AccessibilityPrivateSetFocusRingFunction::Run() {
+AccessibilityPrivateSetFocusRingsFunction::Run() {
 #if defined(OS_CHROMEOS)
 
-  std::unique_ptr<extensions::api::accessibility_private::SetFocusRing::Params>
-      params(
-          extensions::api::accessibility_private::SetFocusRing::Params::Create(
-              *args_));
+  std::unique_ptr<accessibility_private::SetFocusRings::Params> params(
+      accessibility_private::SetFocusRings::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params);
 
-  std::vector<gfx::Rect> rects;
-  for (const extensions::api::accessibility_private::ScreenRect& rect :
-       params->rects) {
-    rects.push_back(gfx::Rect(rect.left, rect.top, rect.width, rect.height));
-  }
-
   auto* accessibility_manager = chromeos::AccessibilityManager::Get();
-  if (params->color) {
+
+  for (const accessibility_private::FocusRingInfo& focus_ring :
+       params->focus_rings) {
+    // Convert the given rects into gfx::Rect objects.
+    std::vector<gfx::Rect> rects;
+    for (const accessibility_private::ScreenRect& rect : focus_ring.rects) {
+      rects.push_back(gfx::Rect(rect.left, rect.top, rect.width, rect.height));
+    }
+
+    const std::string id = accessibility_manager->GetFocusRingId(
+        extension_id(), focus_ring.id ? *(focus_ring.id) : "");
+
     SkColor color;
-    if (!extensions::image_util::ParseHexColorString(*(params->color), &color))
+    if (!extensions::image_util::ParseHexColorString(focus_ring.color, &color))
       return RespondNow(Error("Could not parse hex color"));
-    accessibility_manager->SetFocusRingColor(color, extension_id());
-  } else {
-    accessibility_manager->ResetFocusRingColor(extension_id());
-  }
+    accessibility_manager->SetFocusRingColor(color, id);
 
-  // Move the visible focus ring to cover all of these rects.
-  accessibility_manager->SetFocusRing(
-      rects, ash::mojom::FocusRingBehavior::PERSIST_FOCUS_RING, extension_id());
+    // Move the visible focus ring to cover all of these rects.
+    accessibility_manager->SetFocusRing(
+        rects, ash::mojom::FocusRingBehavior::PERSIST_FOCUS_RING, id);
 
-  // Also update the touch exploration controller so that synthesized
-  // touch events are anchored within the focused object.
-  if (!rects.empty()) {
-    chromeos::AccessibilityManager* manager =
-        chromeos::AccessibilityManager::Get();
-    manager->SetTouchAccessibilityAnchorPoint(rects[0].CenterPoint());
+    // Also update the touch exploration controller so that synthesized
+    // touch events are anchored within the focused object.
+    // NOTE: The final anchor point will be determined by the first rect of the
+    // final focus ring.
+    if (!rects.empty()) {
+      accessibility_manager->SetTouchAccessibilityAnchorPoint(
+          rects[0].CenterPoint());
+    }
   }
 
   return RespondNow(NoArguments());
-#endif  // defined(OS_CHROMEOS)
-
+#else
   return RespondNow(Error(kErrorNotSupported));
+#endif  // defined(OS_CHROMEOS)
 }
 
 ExtensionFunction::ResponseAction
diff --git a/chrome/browser/accessibility/accessibility_extension_api.h b/chrome/browser/accessibility/accessibility_extension_api.h
index 1615d94..db273888c 100644
--- a/chrome/browser/accessibility/accessibility_extension_api.h
+++ b/chrome/browser/accessibility/accessibility_extension_api.h
@@ -27,11 +27,11 @@
 };
 
 // API function that sets the location of the accessibility focus ring.
-class AccessibilityPrivateSetFocusRingFunction
+class AccessibilityPrivateSetFocusRingsFunction
     : public UIThreadExtensionFunction {
-  ~AccessibilityPrivateSetFocusRingFunction() override {}
+  ~AccessibilityPrivateSetFocusRingsFunction() override {}
   ResponseAction Run() override;
-  DECLARE_EXTENSION_FUNCTION("accessibilityPrivate.setFocusRing",
+  DECLARE_EXTENSION_FUNCTION("accessibilityPrivate.setFocusRings",
                              ACCESSIBILITY_PRIVATE_SETFOCUSRING)
 };
 
diff --git a/chrome/browser/apps/app_service/app_icon_factory.cc b/chrome/browser/apps/app_service/app_icon_factory.cc
index 15b26863..f312412 100644
--- a/chrome/browser/apps/app_service/app_icon_factory.cc
+++ b/chrome/browser/apps/app_service/app_icon_factory.cc
@@ -16,25 +16,42 @@
 #include "chrome/browser/extensions/chrome_app_icon.h"
 #include "chrome/browser/extensions/chrome_app_icon_loader.h"
 #include "chrome/browser/extensions/extension_service.h"
-#include "chrome/browser/extensions/extension_util.h"
 #include "content/public/common/service_manager_connection.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/image_loader.h"
 #include "extensions/common/manifest_handlers/icons_handler.h"
 #include "services/data_decoder/public/cpp/decode_image.h"
-#include "skia/ext/image_operations.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/gfx/image/image.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/image/image_skia_operations.h"
 
 #if defined(OS_CHROMEOS)
-#include "chrome/browser/chromeos/extensions/gfx_utils.h"
 #include "chrome/browser/ui/app_list/md_icon_normalizer.h"
 #endif
 
 namespace {
 
+void ApplyIconEffects(apps::IconEffects icon_effects,
+                      int size_hint_in_dip,
+                      gfx::ImageSkia* image_skia) {
+  extensions::ChromeAppIcon::ResizeFunction resize_function;
+#if defined(OS_CHROMEOS)
+  if (icon_effects & apps::IconEffects::kResizeAndPad) {
+    resize_function =
+        base::BindRepeating(&app_list::MaybeResizeAndPadIconForMd);
+  }
+#endif
+
+  bool apply_chrome_badge = icon_effects & apps::IconEffects::kBadge;
+  bool app_launchable = !(icon_effects & apps::IconEffects::kGray);
+  bool from_bookmark = icon_effects & apps::IconEffects::kRoundCorners;
+
+  extensions::ChromeAppIcon::ApplyEffects(size_hint_in_dip, resize_function,
+                                          apply_chrome_badge, app_launchable,
+                                          from_bookmark, image_skia);
+}
+
 std::vector<uint8_t> ReadFileAsCompressedData(const base::FilePath path) {
   std::string data;
   base::ReadFileToString(path, &data);
@@ -49,7 +66,9 @@
 // Runs |callback| passing an IconValuePtr with a compressed image: a
 // std::vector<uint8_t>.
 void RunCallbackWithCompressedData(
+    int size_hint_in_dip,
     bool is_placeholder_icon,
+    apps::IconEffects icon_effects,
     apps::mojom::Publisher::LoadIconCallback callback,
     std::vector<uint8_t> data) {
   apps::mojom::IconValuePtr iv = apps::mojom::IconValue::New();
@@ -58,13 +77,24 @@
                              : apps::mojom::IconCompression::kCompressed;
   iv->compressed = std::move(data);
   iv->is_placeholder_icon = is_placeholder_icon;
+  if (icon_effects) {
+    // TODO(crbug.com/826982): decompress the image, apply the icon_effects,
+    // and recompress the post-processed image.
+    //
+    // Even if there are no icon_effects, we might also want to do this if the
+    // size_hint_in_dip doesn't match the compressed image's pixel size. This
+    // isn't trivial, though, as determining the compressed image's pixel size
+    // might involve a sandboxed decoder process.
+  }
   std::move(callback).Run(std::move(iv));
 }
 
 // Like RunCallbackWithCompressedData, but calls "fallback(callback)" if the
 // data is empty.
 void RunCallbackWithCompressedDataWithFallback(
+    int size_hint_in_dip,
     bool is_placeholder_icon,
+    apps::IconEffects icon_effects,
     apps::mojom::Publisher::LoadIconCallback callback,
     base::OnceCallback<void(apps::mojom::Publisher::LoadIconCallback)> fallback,
     std::vector<uint8_t> data) {
@@ -72,20 +102,26 @@
     std::move(fallback).Run(std::move(callback));
     return;
   }
-  RunCallbackWithCompressedData(is_placeholder_icon, std::move(callback),
+  RunCallbackWithCompressedData(size_hint_in_dip, is_placeholder_icon,
+                                icon_effects, std::move(callback),
                                 std::move(data));
 }
 
 // Runs |callback| passing an IconValuePtr with an uncompressed image: a
 // SkBitmap.
 void RunCallbackWithUncompressedSkBitmap(
+    int size_hint_in_dip,
     bool is_placeholder_icon,
+    apps::IconEffects icon_effects,
     apps::mojom::Publisher::LoadIconCallback callback,
     const SkBitmap& bitmap) {
   apps::mojom::IconValuePtr iv = apps::mojom::IconValue::New();
   iv->icon_compression = apps::mojom::IconCompression::kUncompressed;
   iv->uncompressed = gfx::ImageSkia(gfx::ImageSkiaRep(bitmap, 0.0f));
   iv->is_placeholder_icon = is_placeholder_icon;
+  if (icon_effects && !iv->uncompressed.isNull()) {
+    ApplyIconEffects(icon_effects, size_hint_in_dip, &iv->uncompressed);
+  }
   std::move(callback).Run(std::move(iv));
 }
 
@@ -93,7 +129,9 @@
 // std::vector<uint8_t> to a SkBitmap. It calls "fallback(callback)" if the
 // data is empty.
 void RunCallbackWithCompressedDataToUncompressWithFallback(
+    int size_hint_in_dip,
     bool is_placeholder_icon,
+    apps::IconEffects icon_effects,
     apps::mojom::Publisher::LoadIconCallback callback,
     base::OnceCallback<void(apps::mojom::Publisher::LoadIconCallback)> fallback,
     std::vector<uint8_t> data) {
@@ -105,49 +143,39 @@
       content::ServiceManagerConnection::GetForProcess()->GetConnector(), data,
       data_decoder::mojom::ImageCodec::DEFAULT, false,
       data_decoder::kDefaultMaxSizeInBytes, gfx::Size(),
-      base::BindOnce(&RunCallbackWithUncompressedSkBitmap, is_placeholder_icon,
-                     std::move(callback)));
+      base::BindOnce(&RunCallbackWithUncompressedSkBitmap, size_hint_in_dip,
+                     is_placeholder_icon, icon_effects, std::move(callback)));
 }
 
 // Runs |callback| passing an IconValuePtr with an uncompressed image: an
 // ImageSkia.
 void RunCallbackWithUncompressedImageSkia(
+    int size_hint_in_dip,
     bool is_placeholder_icon,
+    apps::IconEffects icon_effects,
     apps::mojom::Publisher::LoadIconCallback callback,
     const gfx::ImageSkia image) {
   apps::mojom::IconValuePtr iv = apps::mojom::IconValue::New();
   iv->icon_compression = apps::mojom::IconCompression::kUncompressed;
   iv->uncompressed = image;
   iv->is_placeholder_icon = is_placeholder_icon;
+  if (icon_effects && !iv->uncompressed.isNull()) {
+    ApplyIconEffects(icon_effects, size_hint_in_dip, &iv->uncompressed);
+  }
   std::move(callback).Run(std::move(iv));
 }
 
-// Runs |callback| passing an IconValuePtr with a filtered, uncompressed image:
-// an Image.
+// Runs |callback| passing an IconValuePtr with an uncompressed image: an
+// Image.
 void RunCallbackWithUncompressedImage(
-    base::OnceCallback<void(gfx::ImageSkia*)> image_filter,
+    int size_hint_in_dip,
     bool is_placeholder_icon,
+    apps::IconEffects icon_effects,
     apps::mojom::Publisher::LoadIconCallback callback,
     const gfx::Image& image) {
-  gfx::ImageSkia image_skia = image.AsImageSkia();
-  std::move(image_filter).Run(&image_skia);
-  RunCallbackWithUncompressedImageSkia(is_placeholder_icon, std::move(callback),
-                                       image_skia);
-}
-
-// Forwards to extensions::ChromeAppIcon::ApplyEffects, with subtle differences
-// in argument types. For example, resize_function is a "ResizeFunction" here,
-// but a "const ResizeFunction&" in extensions::ChromeAppIcon::ApplyEffects.
-void ChromeAppIconApplyEffects(
-    int resource_size_in_dip,
-    extensions::ChromeAppIconLoader::ResizeFunction resize_function,
-    bool apply_chrome_badge,
-    bool app_launchable,
-    bool from_bookmark,
-    gfx::ImageSkia* image_skia) {
-  extensions::ChromeAppIcon::ApplyEffects(resource_size_in_dip, resize_function,
-                                          apply_chrome_badge, app_launchable,
-                                          from_bookmark, image_skia);
+  RunCallbackWithUncompressedImageSkia(size_hint_in_dip, is_placeholder_icon,
+                                       icon_effects, std::move(callback),
+                                       image.AsImageSkia());
 }
 
 }  // namespace
@@ -158,6 +186,7 @@
                            int size_hint_in_dip,
                            content::BrowserContext* context,
                            const std::string& extension_id,
+                           IconEffects icon_effects,
                            apps::mojom::Publisher::LoadIconCallback callback) {
   constexpr bool is_placeholder_icon = false;
   int size_hint_in_px = apps_util::ConvertDipToPx(size_hint_in_dip);
@@ -176,37 +205,21 @@
         break;
 
       case apps::mojom::IconCompression::kUncompressed: {
-        extensions::ChromeAppIconLoader::ResizeFunction resize_function;
-        bool apply_chrome_badge = false;
-#if defined(OS_CHROMEOS)
-        resize_function =
-            base::BindRepeating(&app_list::MaybeResizeAndPadIconForMd),
-        apply_chrome_badge =
-            extensions::util::ShouldApplyChromeBadge(context, extension_id);
-#endif
-
         extensions::ImageLoader::Get(context)->LoadImageAsync(
             extension, std::move(ext_resource),
             gfx::Size(size_hint_in_px, size_hint_in_px),
-            base::BindOnce(
-                &RunCallbackWithUncompressedImage,
-                base::BindOnce(
-                    &ChromeAppIconApplyEffects, size_hint_in_dip,
-                    resize_function, apply_chrome_badge,
-                    extensions::util::IsAppLaunchable(extension_id, context),
-                    extension->from_bookmark()),
-                is_placeholder_icon, std::move(callback)));
+            base::BindOnce(&RunCallbackWithUncompressedImage, size_hint_in_dip,
+                           is_placeholder_icon, icon_effects,
+                           std::move(callback)));
         return;
       }
 
       case apps::mojom::IconCompression::kCompressed: {
-        // TODO(crbug.com/826982): do we also want to apply the
-        // ChromeAppIconApplyEffects image filter here? This will require
-        // decoding from and re-encoding to PNG before and after the filter.
         base::PostTaskWithTraitsAndReplyWithResult(
             FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
             base::BindOnce(&CompressedDataFromResource, ext_resource),
-            base::BindOnce(&RunCallbackWithCompressedData, is_placeholder_icon,
+            base::BindOnce(&RunCallbackWithCompressedData, size_hint_in_dip,
+                           is_placeholder_icon, icon_effects,
                            std::move(callback)));
         return;
       }
@@ -220,13 +233,11 @@
     apps::mojom::IconCompression icon_compression,
     int size_hint_in_dip,
     const base::FilePath& path,
+    IconEffects icon_effects,
     apps::mojom::Publisher::LoadIconCallback callback,
     base::OnceCallback<void(apps::mojom::Publisher::LoadIconCallback)>
         fallback) {
   constexpr bool is_placeholder_icon = false;
-  // TODO(crbug.com/826982): pass size_hint_in_dip (or _in_px) on to the
-  // callbacks, and re-size the decoded-from-file image)?
-
   switch (icon_compression) {
     case apps::mojom::IconCompression::kUnknown:
       break;
@@ -236,8 +247,8 @@
           FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
           base::BindOnce(&ReadFileAsCompressedData, path),
           base::BindOnce(&RunCallbackWithCompressedDataToUncompressWithFallback,
-                         is_placeholder_icon, std::move(callback),
-                         std::move(fallback)));
+                         size_hint_in_dip, is_placeholder_icon, icon_effects,
+                         std::move(callback), std::move(fallback)));
 
       return;
     }
@@ -247,8 +258,8 @@
           FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
           base::BindOnce(&ReadFileAsCompressedData, path),
           base::BindOnce(&RunCallbackWithCompressedDataWithFallback,
-                         is_placeholder_icon, std::move(callback),
-                         std::move(fallback)));
+                         size_hint_in_dip, is_placeholder_icon, icon_effects,
+                         std::move(callback), std::move(fallback)));
       return;
     }
   }
@@ -260,6 +271,7 @@
                           int size_hint_in_dip,
                           int resource_id,
                           bool is_placeholder_icon,
+                          IconEffects icon_effects,
                           apps::mojom::Publisher::LoadIconCallback callback) {
   if (resource_id != 0) {
     switch (icon_compression) {
@@ -271,7 +283,8 @@
             ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
                 resource_id);
         RunCallbackWithUncompressedImageSkia(
-            is_placeholder_icon, std::move(callback),
+            size_hint_in_dip, is_placeholder_icon, icon_effects,
+            std::move(callback),
             gfx::ImageSkiaOperations::CreateResizedImage(
                 *unscaled, skia::ImageOperations::RESIZE_BEST,
                 gfx::Size(size_hint_in_dip, size_hint_in_dip)));
@@ -283,7 +296,8 @@
             ui::ResourceBundle::GetSharedInstance().GetRawDataResource(
                 resource_id);
         RunCallbackWithCompressedData(
-            is_placeholder_icon, std::move(callback),
+            size_hint_in_dip, is_placeholder_icon, icon_effects,
+            std::move(callback),
             std::vector<uint8_t>(data.begin(), data.end()));
         return;
       }
diff --git a/chrome/browser/apps/app_service/app_icon_factory.h b/chrome/browser/apps/app_service/app_icon_factory.h
index 86e0970..64e8901 100644
--- a/chrome/browser/apps/app_service/app_icon_factory.h
+++ b/chrome/browser/apps/app_service/app_icon_factory.h
@@ -19,10 +19,27 @@
 
 namespace apps {
 
+// A bitwise-or of icon post-processing effects.
+//
+// It derives from a uint32_t because it needs to be the same size as the
+// uint32_t IconKey.icon_effects field.
+enum IconEffects : uint32_t {
+  kNone = 0x00,
+
+  // The icon effects are applied in numerical order, low to high. It is always
+  // resize-and-then-badge and never badge-and-then-resize, which can matter if
+  // the badge has a fixed size.
+  kResizeAndPad = 0x01,  // Resize and Pad per Material Design style.
+  kBadge = 0x02,         // Another (Android) app has the same name.
+  kGray = 0x04,          // Disabled apps are grayed out.
+  kRoundCorners = 0x08,  // Bookmark apps get round corners.
+};
+
 void LoadIconFromExtension(apps::mojom::IconCompression icon_compression,
                            int size_hint_in_dip,
                            content::BrowserContext* context,
                            const std::string& extension_id,
+                           IconEffects icon_effects,
                            apps::mojom::Publisher::LoadIconCallback callback);
 
 // The file named by |path| might be empty, not found or otherwise unreadable.
@@ -32,6 +49,7 @@
     apps::mojom::IconCompression icon_compression,
     int size_hint_in_dip,
     const base::FilePath& path,
+    IconEffects icon_effects,
     apps::mojom::Publisher::LoadIconCallback callback,
     base::OnceCallback<void(apps::mojom::Publisher::LoadIconCallback)>
         fallback);
@@ -40,6 +58,7 @@
                           int size_hint_in_dip,
                           int resource_id,
                           bool is_placeholder_icon,
+                          IconEffects icon_effects,
                           apps::mojom::Publisher::LoadIconCallback callback);
 
 }  // namespace apps
diff --git a/chrome/browser/apps/app_service/arc_apps.cc b/chrome/browser/apps/app_service/arc_apps.cc
index 131d440..b79ac919 100644
--- a/chrome/browser/apps/app_service/arc_apps.cc
+++ b/chrome/browser/apps/app_service/arc_apps.cc
@@ -10,7 +10,6 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/containers/flat_map.h"
-#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/arc_apps_factory.h"
 #include "chrome/browser/apps/app_service/dip_px_util.h"
@@ -183,6 +182,7 @@
     // Android app and before bringing up an Android VM for the first time.
     if (icon_key->s_key == arc::kPlayStoreAppId) {
       LoadPlayStoreIcon(icon_compression, size_hint_in_dip,
+                        static_cast<IconEffects>(icon_key->icon_effects),
                         std::move(callback));
       return;
     }
@@ -192,10 +192,11 @@
     LoadIconFromFileWithFallback(
         icon_compression, size_hint_in_dip,
         GetCachedIconFilePath(icon_key->s_key, size_hint_in_dip),
-        std::move(callback),
+        static_cast<IconEffects>(icon_key->icon_effects), std::move(callback),
         base::BindOnce(&ArcApps::LoadIconFromVM, weak_ptr_factory_.GetWeakPtr(),
                        icon_key->s_key, icon_compression, size_hint_in_dip,
-                       allow_placeholder_icon));
+                       allow_placeholder_icon,
+                       static_cast<IconEffects>(icon_key->icon_effects)));
     return;
   }
 
@@ -327,11 +328,12 @@
 
 void ArcApps::OnAppIconUpdated(const std::string& app_id,
                                const ArcAppIconDescriptor& descriptor) {
+  static constexpr uint32_t icon_effects = 0;
   apps::mojom::AppPtr app = apps::mojom::App::New();
   app->app_type = apps::mojom::AppType::kArc;
   app->app_id = app_id;
-  app->icon_key =
-      icon_key_factory_.MakeIconKey(apps::mojom::AppType::kArc, app_id);
+  app->icon_key = icon_key_factory_.MakeIconKey(apps::mojom::AppType::kArc,
+                                                app_id, icon_effects);
   Publish(std::move(app));
 }
 
@@ -389,12 +391,13 @@
                              apps::mojom::IconCompression icon_compression,
                              int32_t size_hint_in_dip,
                              bool allow_placeholder_icon,
+                             IconEffects icon_effects,
                              LoadIconCallback callback) {
   if (allow_placeholder_icon) {
     constexpr bool is_placeholder_icon = true;
     LoadIconFromResource(icon_compression, size_hint_in_dip,
                          IDR_APP_DEFAULT_ICON, is_placeholder_icon,
-                         std::move(callback));
+                         icon_effects, std::move(callback));
     return;
   }
 
@@ -423,6 +426,7 @@
 
 void ArcApps::LoadPlayStoreIcon(apps::mojom::IconCompression icon_compression,
                                 int32_t size_hint_in_dip,
+                                IconEffects icon_effects,
                                 LoadIconCallback callback) {
   // Use overloaded Chrome icon for Play Store that is adapted to Chrome style.
   int size_hint_in_px = apps_util::ConvertDipToPx(size_hint_in_dip);
@@ -430,7 +434,7 @@
                                             : IDR_ARC_SUPPORT_ICON_192;
   constexpr bool is_placeholder_icon = false;
   LoadIconFromResource(icon_compression, size_hint_in_dip, resource_id,
-                       is_placeholder_icon, std::move(callback));
+                       is_placeholder_icon, icon_effects, std::move(callback));
 }
 
 apps::mojom::AppPtr ArcApps::Convert(const std::string& app_id,
@@ -445,8 +449,9 @@
   app->name = app_info.name;
   app->short_name = app->name;
 
-  app->icon_key =
-      icon_key_factory_.MakeIconKey(apps::mojom::AppType::kArc, app_id);
+  static constexpr uint32_t icon_effects = 0;
+  app->icon_key = icon_key_factory_.MakeIconKey(apps::mojom::AppType::kArc,
+                                                app_id, icon_effects);
 
   app->last_launch_time = app_info.last_launch_time;
   app->install_time = app_info.install_time;
diff --git a/chrome/browser/apps/app_service/arc_apps.h b/chrome/browser/apps/app_service/arc_apps.h
index 2e40e4e..d07e204ae 100644
--- a/chrome/browser/apps/app_service/arc_apps.h
+++ b/chrome/browser/apps/app_service/arc_apps.h
@@ -12,6 +12,7 @@
 #include "base/files/file_path.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/apps/app_service/app_icon_factory.h"
 #include "chrome/browser/apps/app_service/icon_key_util.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
 #include "chrome/services/app_service/public/mojom/app_service.mojom.h"
@@ -85,9 +86,11 @@
                       apps::mojom::IconCompression icon_compression,
                       int32_t size_hint_in_dip,
                       bool allow_placeholder_icon,
+                      IconEffects icon_effects,
                       LoadIconCallback callback);
   void LoadPlayStoreIcon(apps::mojom::IconCompression icon_compression,
                          int32_t size_hint_in_dip,
+                         IconEffects icon_effects,
                          LoadIconCallback callback);
 
   apps::mojom::AppPtr Convert(const std::string& app_id,
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 35deb570..e0e27fe7 100644
--- a/chrome/browser/apps/app_service/built_in_chromeos_apps.cc
+++ b/chrome/browser/apps/app_service/built_in_chromeos_apps.cc
@@ -40,7 +40,8 @@
 
   app->icon_key = apps::mojom::IconKey::New(
       apps::mojom::AppType::kBuiltIn,
-      static_cast<uint64_t>(internal_app.icon_resource_id), std::string());
+      static_cast<uint64_t>(internal_app.icon_resource_id), std::string(),
+      apps::IconEffects::kNone);
 
   app->last_launch_time = base::Time();
   app->install_time = base::Time();
@@ -120,8 +121,9 @@
   if (!icon_key.is_null() && (icon_key->u_key != 0) &&
       (icon_key->u_key <= INT_MAX)) {
     int resource_id = static_cast<int>(icon_key->u_key);
-    LoadIconFromResource(icon_compression, size_hint_in_dip, resource_id,
-                         is_placeholder_icon, std::move(callback));
+    LoadIconFromResource(
+        icon_compression, size_hint_in_dip, resource_id, is_placeholder_icon,
+        static_cast<IconEffects>(icon_key->icon_effects), std::move(callback));
     return;
   }
   // On failure, we still run the callback, with the zero IconValue.
diff --git a/chrome/browser/apps/app_service/crostini_apps.cc b/chrome/browser/apps/app_service/crostini_apps.cc
index 7d1e885..7555ae9 100644
--- a/chrome/browser/apps/app_service/crostini_apps.cc
+++ b/chrome/browser/apps/app_service/crostini_apps.cc
@@ -6,7 +6,6 @@
 
 #include <utility>
 
-#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"
@@ -80,18 +79,21 @@
       LoadIconFromFileWithFallback(
           icon_compression, size_hint_in_dip,
           registry_->GetIconPath(icon_key->s_key, scale_factor),
-          std::move(callback),
+          static_cast<IconEffects>(icon_key->icon_effects), std::move(callback),
           base::BindOnce(&CrostiniApps::LoadIconFromVM,
                          weak_ptr_factory_.GetWeakPtr(), icon_key->s_key,
                          icon_compression, size_hint_in_dip,
-                         allow_placeholder_icon, scale_factor));
+                         allow_placeholder_icon, scale_factor,
+                         static_cast<IconEffects>(icon_key->icon_effects)));
       return;
 
     } else if ((icon_key->u_key != 0) && (icon_key->u_key <= INT_MAX)) {
       int resource_id = static_cast<int>(icon_key->u_key);
       constexpr bool is_placeholder_icon = false;
       LoadIconFromResource(icon_compression, size_hint_in_dip, resource_id,
-                           is_placeholder_icon, std::move(callback));
+                           is_placeholder_icon,
+                           static_cast<IconEffects>(icon_key->icon_effects),
+                           std::move(callback));
       return;
     }
   }
@@ -150,6 +152,7 @@
                                   int32_t size_hint_in_dip,
                                   bool allow_placeholder_icon,
                                   ui::ScaleFactor scale_factor,
+                                  IconEffects icon_effects,
                                   LoadIconCallback callback) {
   if (!allow_placeholder_icon) {
     // Treat this as failure. We still run the callback, with the zero
@@ -162,7 +165,7 @@
   constexpr bool is_placeholder_icon = true;
   LoadIconFromResource(icon_compression, size_hint_in_dip,
                        IDR_LOGO_CROSTINI_DEFAULT_192, is_placeholder_icon,
-                       std::move(callback));
+                       icon_effects, std::move(callback));
 
   // Ask the VM to load the icon (and write a cached copy to the file system).
   // The "Maybe" is because multiple requests for the same icon will be merged,
@@ -221,6 +224,8 @@
 apps::mojom::IconKeyPtr CrostiniApps::NewIconKey(const std::string& app_id) {
   DCHECK(!app_id.empty());
 
+  static constexpr uint32_t icon_effects = 0;
+
   // Treat the Crostini Terminal as a special case, loading an icon defined by
   // a resource instead of asking the Crostini VM (or the cache of previous
   // responses from the Crostini VM). Presumably this is for bootstrapping: the
@@ -229,10 +234,12 @@
   // app and before bringing up an Crostini VM for the first time.
   if (app_id == crostini::kCrostiniTerminalId) {
     return apps::mojom::IconKey::New(apps::mojom::AppType::kCrostini,
-                                     IDR_LOGO_CROSTINI_TERMINAL, std::string());
+                                     IDR_LOGO_CROSTINI_TERMINAL, std::string(),
+                                     icon_effects);
   }
 
-  return icon_key_factory_.MakeIconKey(apps::mojom::AppType::kCrostini, app_id);
+  return icon_key_factory_.MakeIconKey(apps::mojom::AppType::kCrostini, app_id,
+                                       icon_effects);
 }
 
 void CrostiniApps::PublishAppID(const std::string& app_id,
diff --git a/chrome/browser/apps/app_service/crostini_apps.h b/chrome/browser/apps/app_service/crostini_apps.h
index b7ef820..64ef99c 100644
--- a/chrome/browser/apps/app_service/crostini_apps.h
+++ b/chrome/browser/apps/app_service/crostini_apps.h
@@ -10,6 +10,7 @@
 
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/apps/app_service/app_icon_factory.h"
 #include "chrome/browser/apps/app_service/icon_key_util.h"
 #include "chrome/browser/chromeos/crostini/crostini_registry_service.h"
 #include "chrome/services/app_service/public/mojom/app_service.mojom.h"
@@ -72,6 +73,7 @@
                       int32_t size_hint_in_dip,
                       bool allow_placeholder_icon,
                       ui::ScaleFactor scale_factor,
+                      IconEffects icon_effects,
                       LoadIconCallback callback);
 
   apps::mojom::AppPtr Convert(
diff --git a/chrome/browser/apps/app_service/extension_apps.cc b/chrome/browser/apps/app_service/extension_apps.cc
index 89fca9a..b85daf2 100644
--- a/chrome/browser/apps/app_service/extension_apps.cc
+++ b/chrome/browser/apps/app_service/extension_apps.cc
@@ -11,6 +11,7 @@
 #include "base/strings/string16.h"
 #include "chrome/browser/apps/app_service/app_icon_factory.h"
 #include "chrome/browser/apps/app_service/launch_util.h"
+#include "chrome/browser/chromeos/extensions/gfx_utils.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_util.h"
@@ -38,10 +39,6 @@
 // This might involve using an extensions::InstallTracker. It might also need
 // the equivalent of a LauncherExtensionAppUpdater.
 
-// TODO(crbug.com/826982): ExtensionAppItem's can be 'badged', which means that
-// it's an extension app that has its Android analog installed. We should cater
-// for that here.
-
 // TODO(crbug.com/826982): do we also need to watch prefs, the same as
 // ExtensionAppModelBuilder?
 
@@ -129,8 +126,9 @@
                              bool allow_placeholder_icon,
                              LoadIconCallback callback) {
   if (!icon_key.is_null() && !icon_key->s_key.empty()) {
-    LoadIconFromExtension(icon_compression, size_hint_in_dip, profile_,
-                          icon_key->s_key, std::move(callback));
+    LoadIconFromExtension(
+        icon_compression, size_hint_in_dip, profile_, icon_key->s_key,
+        static_cast<IconEffects>(icon_key->icon_effects), std::move(callback));
     return;
   }
   // On failure, we still run the callback, with the zero IconValue.
@@ -423,7 +421,23 @@
   app->name = extension->name();
   app->short_name = extension->short_name();
 
-  app->icon_key = icon_key_factory_.MakeIconKey(app_type_, extension->id());
+  IconEffects icon_effects = IconEffects::kNone;
+#if defined(OS_CHROMEOS)
+  icon_effects =
+      static_cast<IconEffects>(icon_effects | IconEffects::kResizeAndPad);
+  if (extensions::util::ShouldApplyChromeBadge(profile_, extension->id())) {
+    icon_effects = static_cast<IconEffects>(icon_effects | IconEffects::kBadge);
+  }
+#endif
+  if (!extensions::util::IsAppLaunchable(extension->id(), profile_)) {
+    icon_effects = static_cast<IconEffects>(icon_effects | IconEffects::kGray);
+  }
+  if (extension->from_bookmark()) {
+    icon_effects =
+        static_cast<IconEffects>(icon_effects | IconEffects::kRoundCorners);
+  }
+  app->icon_key =
+      icon_key_factory_.MakeIconKey(app_type_, extension->id(), icon_effects);
 
   if (profile_) {
     auto* prefs = extensions::ExtensionPrefs::Get(profile_);
diff --git a/chrome/browser/apps/app_service/icon_key_util.cc b/chrome/browser/apps/app_service/icon_key_util.cc
index 0a24c3f6..285bb2e 100644
--- a/chrome/browser/apps/app_service/icon_key_util.cc
+++ b/chrome/browser/apps/app_service/icon_key_util.cc
@@ -11,12 +11,8 @@
 apps::mojom::IconKeyPtr IncrementingIconKeyFactory::MakeIconKey(
     apps::mojom::AppType app_type,
     const std::string& s_key,
-    uint8_t flags) {
-  // The flags occupy the low 8 bits.
-  u_key_ += 1 << 8;
-
-  return apps::mojom::IconKey::New(
-      app_type, u_key_ | static_cast<uint64_t>(flags), s_key);
+    uint32_t icon_effects) {
+  return apps::mojom::IconKey::New(app_type, ++u_key_, s_key, icon_effects);
 }
 
 }  // namespace apps_util
diff --git a/chrome/browser/apps/app_service/icon_key_util.h b/chrome/browser/apps/app_service/icon_key_util.h
index 4d2b434..b7ea0d6 100644
--- a/chrome/browser/apps/app_service/icon_key_util.h
+++ b/chrome/browser/apps/app_service/icon_key_util.h
@@ -22,17 +22,13 @@
 // publish such IconKeys whenever an app's icon changes, even though the App ID
 // itself doesn't change, and App Service app subscribers will notice (and
 // reload) the new icon from the new (changed) icon key.
-//
-// The low 8 bits (a uint8_t) of the resultant IconKey's u_key are reserved for
-// caller-specific flags. For example, colorful/gray icons for enabled/disabled
-// states of the same app can be distinguished in one of those bits.
 class IncrementingIconKeyFactory {
  public:
   IncrementingIconKeyFactory();
 
   apps::mojom::IconKeyPtr MakeIconKey(apps::mojom::AppType app_type,
                                       const std::string& s_key,
-                                      uint8_t flags = 0);
+                                      uint32_t icon_effects);
 
  private:
   uint64_t u_key_;
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index eab5f82..099ca237 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -559,7 +559,7 @@
         <include name="IDR_HELP_MANIFEST" file="resources\help_app\manifest.json" type="BINDATA" />
         <include name="IDR_QUICKOFFICE_MANIFEST" file="resources\chromeos\quickoffice\manifest.json" type="BINDATA" />
         <include name="IDR_PRODUCT_CHROMEOS_SYNC_CONSENT_SCREEN_ICONS" file="internal\resources\chromeos-sync-consent-icons.html" type="BINDATA" />
-        <include name="IDR_CONTAINED_HOME_MANIFEST" file="resources\chromeos\contained_home\manifest.json" type="BINDATA" />
+        <include name="IDR_KIOSK_NEXT_HOME_MANIFEST" file="resources\chromeos\kiosk_next_home\manifest.json" type="BINDATA" />
       </if>
       <if expr="is_win">
         <include name="IDR_SET_AS_DEFAULT_BROWSER_JS" file="resources\set_as_default_browser.js" flattenhtml="true" type="BINDATA" />
diff --git a/chrome/browser/browsing_data/cookies_tree_model.cc b/chrome/browser/browsing_data/cookies_tree_model.cc
index 1256508..9dd8cb6 100644
--- a/chrome/browser/browsing_data/cookies_tree_model.cc
+++ b/chrome/browser/browsing_data/cookies_tree_model.cc
@@ -1957,16 +1957,14 @@
 }
 // static
 std::unique_ptr<CookiesTreeModel> CookiesTreeModel::CreateForProfile(
-    Profile* profile,
-    bool omit_cookies) {
+    Profile* profile) {
   auto* storage_partition =
       content::BrowserContext::GetDefaultStoragePartition(profile);
   auto* file_system_context = storage_partition->GetFileSystemContext();
-  auto* cookie_helper =
-      omit_cookies ? nullptr : new BrowsingDataCookieHelper(storage_partition);
 
   auto container = std::make_unique<LocalDataContainer>(
-      cookie_helper, new BrowsingDataDatabaseHelper(profile),
+      new BrowsingDataCookieHelper(storage_partition),
+      new BrowsingDataDatabaseHelper(profile),
       new BrowsingDataLocalStorageHelper(profile),
       /*session_storage_helper=*/nullptr,
       new BrowsingDataAppCacheHelper(profile),
diff --git a/chrome/browser/browsing_data/cookies_tree_model.h b/chrome/browser/browsing_data/cookies_tree_model.h
index d085479..96824f3 100644
--- a/chrome/browser/browsing_data/cookies_tree_model.h
+++ b/chrome/browser/browsing_data/cookies_tree_model.h
@@ -372,9 +372,7 @@
   void SetBatchExpectation(int batches_expected, bool reset);
 
   // Create CookiesTreeModel by profile info.
-  static std::unique_ptr<CookiesTreeModel> CreateForProfile(
-      Profile* profile,
-      bool omit_cookies = false);
+  static std::unique_ptr<CookiesTreeModel> CreateForProfile(Profile* profile);
 
  private:
   enum CookieIconIndex { COOKIE = 0, DATABASE = 1 };
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index fb5d0748..8bfd708e 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -159,6 +159,8 @@
 #include "chrome/browser/ui/webui/log_web_ui_url.h"
 #include "chrome/browser/usb/usb_tab_helper.h"
 #include "chrome/browser/vr/vr_tab_helper.h"
+#include "chrome/browser/web_applications/components/app_registrar.h"
+#include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/webauthn/authenticator_request_scheduler.h"
 #include "chrome/browser/webauthn/chrome_authenticator_request_delegate.h"
 #include "chrome/common/buildflags.h"
@@ -501,8 +503,6 @@
 #include "chrome/browser/extensions/bookmark_app_navigation_throttle.h"
 #include "chrome/browser/extensions/chrome_content_browser_client_extensions_part.h"
 #include "chrome/browser/extensions/chrome_extension_web_contents_observer.h"
-#include "chrome/browser/extensions/convert_web_app.h"
-#include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/extensions/user_script_listener.h"
 #include "chrome/browser/media/cast_transport_host_filter.h"
 #include "chrome/browser/speech/extension_api/tts_engine_extension_api.h"
@@ -3174,24 +3174,27 @@
       web_prefs->web_app_scope = tab_android->GetWebappManifestScope();
 #elif BUILDFLAG(ENABLE_EXTENSIONS)
     {
-      const extensions::Extension* extension = nullptr;
+      web_prefs->web_app_scope = GURL();
+      // Set |web_app_scope| based on the app associated with the app window if
+      // any. Note that the app associated with the window never changes, even
+      // if the app navigates off scope. This is not a problem because we still
+      // want to use the scope of the app associated with the window, not the
+      // WebContents.
       Browser* browser = chrome::FindBrowserWithWebContents(contents);
       if (base::FeatureList::IsEnabled(features::kDesktopPWAWindowing) &&
           browser && browser->hosted_app_controller() &&
           browser->hosted_app_controller()->created_for_installed_pwa()) {
-        // When a new window is created to host a PWA, this method will return
-        // the scope of the given PWA. It will stay the same for the PWA as
-        // scopes never change after the window was created. It is not
-        // guaranteed that this method will be called on every navigation but
-        // this is not required for things to work, we only need it to be called
-        // at the window creation time.
-        extension = extensions::util::GetInstalledPwaForUrl(
-            contents->GetBrowserContext(), contents->GetLastCommittedURL());
+        // HostedApps that are PWAs are always created through WebAppProvider
+        // or BookmarkAppHelper for profiles that support them, so we should
+        // always be able to retrieve a WebAppProvider from the Profile.
+        //
+        // Similarly, if a Hosted Apps is a PWA, it will always have a scope
+        // so there is no need to test for HasScope().
+        web_prefs->web_app_scope =
+            web_app::WebAppProvider::Get(profile)
+                ->registrar()
+                .GetScopeUrlForApp(browser->hosted_app_controller()->app_id());
       }
-
-      web_prefs->web_app_scope =
-          extension ? extensions::GetScopeURLFromBookmarkApp(extension)
-                    : GURL();
     }
 #endif
 
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index a3813fd..a439d19 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -1277,8 +1277,6 @@
     "login/screens/reset_screen.cc",
     "login/screens/reset_screen.h",
     "login/screens/reset_view.h",
-    "login/screens/screen_exit_code.cc",
-    "login/screens/screen_exit_code.h",
     "login/screens/supervision_transition_screen.cc",
     "login/screens/supervision_transition_screen.h",
     "login/screens/supervision_transition_screen_view.h",
diff --git a/chrome/browser/chromeos/accessibility/accessibility_manager.cc b/chrome/browser/chromeos/accessibility/accessibility_manager.cc
index 27dbedc..55b84d9 100644
--- a/chrome/browser/chromeos/accessibility/accessibility_manager.cc
+++ b/chrome/browser/chromeos/accessibility/accessibility_manager.cc
@@ -476,8 +476,9 @@
 
   if (enabled) {
     chromevox_loader_->SetProfile(
-        profile_, base::Bind(&AccessibilityManager::PostSwitchChromeVoxProfile,
-                             weak_ptr_factory_.GetWeakPtr()));
+        profile_,
+        base::BindRepeating(&AccessibilityManager::PostSwitchChromeVoxProfile,
+                            weak_ptr_factory_.GetWeakPtr()));
   }
 
   if (spoken_feedback_enabled_ == enabled)
@@ -490,9 +491,9 @@
   NotifyAccessibilityStatusChanged(details);
 
   if (enabled) {
-    chromevox_loader_->Load(profile_,
-                            base::Bind(&AccessibilityManager::PostLoadChromeVox,
-                                       weak_ptr_factory_.GetWeakPtr()));
+    chromevox_loader_->Load(
+        profile_, base::BindRepeating(&AccessibilityManager::PostLoadChromeVox,
+                                      weak_ptr_factory_.GetWeakPtr()));
   } else {
     chromevox_loader_->Unload();
   }
@@ -835,7 +836,10 @@
   NotifyAccessibilityStatusChanged(details);
 
   if (enabled) {
-    select_to_speak_loader_->Load(profile_, base::Closure() /* done_cb */);
+    select_to_speak_loader_->Load(
+        profile_,
+        base::BindRepeating(&AccessibilityManager::PostLoadSelectToSpeak,
+                            weak_ptr_factory_.GetWeakPtr()));
     // Construct a delegate to connect SelectToSpeak and its EventHandler in
     // ash.
     select_to_speak_event_handler_delegate_ =
@@ -1314,14 +1318,15 @@
   extensions::EventRouter* event_router =
       extensions::EventRouter::Get(profile_);
 
+  const std::string& extension_id = extension_misc::kChromeVoxExtensionId;
+
   std::unique_ptr<base::ListValue> event_args =
       std::make_unique<base::ListValue>();
   std::unique_ptr<extensions::Event> event(new extensions::Event(
       extensions::events::ACCESSIBILITY_PRIVATE_ON_INTRODUCE_CHROME_VOX,
       extensions::api::accessibility_private::OnIntroduceChromeVox::kEventName,
       std::move(event_args)));
-  event_router->DispatchEventWithLazyListener(
-      extension_misc::kChromeVoxExtensionId, std::move(event));
+  event_router->DispatchEventWithLazyListener(extension_id, std::move(event));
 
   if (!chromevox_panel_) {
     chromevox_panel_ = new ChromeVoxPanel(profile_);
@@ -1333,6 +1338,8 @@
 
   audio_focus_manager_ptr_->SetEnforcementMode(
       media_session::mojom::EnforcementMode::kSingleSession);
+
+  InitializeFocusRings(extension_id);
 }
 
 void AccessibilityManager::PostUnloadChromeVox() {
@@ -1343,10 +1350,7 @@
 
   PlayEarcon(SOUND_SPOKEN_FEEDBACK_DISABLED, PlaySoundOption::ALWAYS);
 
-  // Clear the accessibility focus ring.
-  SetFocusRing(std::vector<gfx::Rect>(),
-               ash::mojom::FocusRingBehavior::PERSIST_FOCUS_RING,
-               extension_misc::kChromeVoxExtensionId);
+  RemoveFocusRings(extension_misc::kChromeVoxExtensionId);
 
   if (chromevox_panel_) {
     chromevox_panel_->Close();
@@ -1380,12 +1384,16 @@
   chromevox_panel_ = nullptr;
 }
 
+void AccessibilityManager::PostLoadSelectToSpeak() {
+  InitializeFocusRings(extension_misc::kSelectToSpeakExtensionId);
+}
+
 void AccessibilityManager::PostUnloadSelectToSpeak() {
   // Do any teardown work needed immediately after Select-to-Speak actually
   // unloads.
 
   // Clear the accessibility focus ring and highlight.
-  HideFocusRing(extension_misc::kSelectToSpeakExtensionId);
+  RemoveFocusRings(extension_misc::kSelectToSpeakExtensionId);
   HideHighlights();
 
   // Stop speech.
@@ -1401,6 +1409,8 @@
             base::BindOnce(&AccessibilityManager::OnSwitchAccessPanelDestroying,
                            base::Unretained(this))));
   }
+
+  InitializeFocusRings(extension_misc::kSwitchAccessExtensionId);
 }
 
 void AccessibilityManager::PostUnloadSwitchAccess() {
@@ -1408,7 +1418,7 @@
   // unloads.
 
   // Clear the accessibility focus ring.
-  HideFocusRing(extension_misc::kSwitchAccessExtensionId);
+  RemoveFocusRings(extension_misc::kSwitchAccessExtensionId);
 
   // Close the context menu
   if (switch_access_panel_) {
@@ -1453,27 +1463,55 @@
   return dictation_->OnToggleDictation();
 }
 
-void AccessibilityManager::SetFocusRingColor(SkColor color,
-                                             std::string caller_id) {
-  accessibility_focus_ring_controller_->SetFocusRingColor(color, caller_id);
+const std::string AccessibilityManager::GetFocusRingId(
+    const std::string& extension_id,
+    const std::string& focus_ring_name) {
+  // Add the focus ring name to the list of focus rings for the extension.
+  focus_ring_names_for_extension_id_.find(extension_id)
+      ->second.insert(focus_ring_name);
+  return extension_id + '-' + focus_ring_name;
 }
 
-void AccessibilityManager::ResetFocusRingColor(std::string caller_id) {
-  accessibility_focus_ring_controller_->ResetFocusRingColor(caller_id);
+void AccessibilityManager::InitializeFocusRings(
+    const std::string& extension_id) {
+  if (focus_ring_names_for_extension_id_.count(extension_id) == 0) {
+    focus_ring_names_for_extension_id_.emplace(extension_id,
+                                               std::set<std::string>());
+  }
+}
+
+void AccessibilityManager::RemoveFocusRings(const std::string& extension_id) {
+  if (focus_ring_names_for_extension_id_.count(extension_id) != 0) {
+    const std::set<std::string>& focus_ring_names =
+        focus_ring_names_for_extension_id_.find(extension_id)->second;
+
+    for (const std::string& focus_ring_name : focus_ring_names)
+      HideFocusRing(GetFocusRingId(extension_id, focus_ring_name));
+  }
+  focus_ring_names_for_extension_id_.erase(extension_id);
+}
+
+void AccessibilityManager::SetFocusRingColor(SkColor color,
+                                             std::string focus_ring_id) {
+  accessibility_focus_ring_controller_->SetFocusRingColor(color, focus_ring_id);
+}
+
+void AccessibilityManager::ResetFocusRingColor(std::string focus_ring_id) {
+  accessibility_focus_ring_controller_->ResetFocusRingColor(focus_ring_id);
 }
 
 void AccessibilityManager::SetFocusRing(
     const std::vector<gfx::Rect>& rects_in_screen,
     ash::mojom::FocusRingBehavior focus_ring_behavior,
-    std::string caller_id) {
+    std::string focus_ring_id) {
   accessibility_focus_ring_controller_->SetFocusRing(
-      rects_in_screen, focus_ring_behavior, caller_id);
+      rects_in_screen, focus_ring_behavior, focus_ring_id);
   if (focus_ring_observer_for_test_)
     focus_ring_observer_for_test_.Run();
 }
 
-void AccessibilityManager::HideFocusRing(std::string caller_id) {
-  accessibility_focus_ring_controller_->HideFocusRing(caller_id);
+void AccessibilityManager::HideFocusRing(std::string focus_ring_id) {
+  accessibility_focus_ring_controller_->HideFocusRing(focus_ring_id);
   if (focus_ring_observer_for_test_)
     focus_ring_observer_for_test_.Run();
 }
@@ -1505,8 +1543,8 @@
   if (user_list.empty())
     return false;
 
-  // |user_list| is sorted by last log in date. Take the most recent user to log
-  // in.
+  // |user_list| is sorted by last log in date. Take the most recent user to
+  // log in.
   bool val;
   return user_manager::known_user::GetBooleanPref(
              user_list[0]->GetAccountId(), kUserStartupSoundEnabled, &val) &&
@@ -1529,8 +1567,8 @@
   if (user_list.empty())
     return std::string();
 
-  // |user_list| is sorted by last log in date. Take the most recent user to log
-  // in.
+  // |user_list| is sorted by last log in date. Take the most recent user to
+  // log in.
   std::string val;
   return user_manager::known_user::GetStringPref(
              user_list[0]->GetAccountId(), kUserBluetoothBrailleDisplayAddress,
diff --git a/chrome/browser/chromeos/accessibility/accessibility_manager.h b/chrome/browser/chromeos/accessibility/accessibility_manager.h
index e39206d..591839c18 100644
--- a/chrome/browser/chromeos/accessibility/accessibility_manager.h
+++ b/chrome/browser/chromeos/accessibility/accessibility_manager.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_CHROMEOS_ACCESSIBILITY_ACCESSIBILITY_MANAGER_H_
 #define CHROME_BROWSER_CHROMEOS_ACCESSIBILITY_ACCESSIBILITY_MANAGER_H_
 
+#include <map>
 #include <memory>
 #include <set>
 #include <string>
@@ -304,6 +305,13 @@
   // Hides focus ring on screen.
   void HideFocusRing(std::string caller_id);
 
+  // Initializes the focus rings when an extension loads.
+  void InitializeFocusRings(const std::string& extension_id);
+
+  // Hides all focus rings for the extension, and removes that extension from
+  // |focus_ring_names_for_extension_id_|.
+  void RemoveFocusRings(const std::string& extension_id);
+
   // Draws a highlight at the given rects in screen coordinates. Rects may be
   // overlapping and will be merged into one layer. This looks similar to
   // selecting a region with the cursor, except it is drawn in the foreground
@@ -329,6 +337,10 @@
   // Sets the bluetooth braille display device address for the current user.
   void UpdateBluetoothBrailleDisplayAddress(const std::string& address);
 
+  // Create a focus ring ID from the extension ID and the name of the ring.
+  const std::string GetFocusRingId(const std::string& extension_id,
+                                   const std::string& focus_ring_name);
+
   // Test helpers:
   void SetProfileForTest(Profile* profile);
   static void SetBrailleControllerForTest(
@@ -349,7 +361,9 @@
   void PostUnloadChromeVox();
   void PostSwitchChromeVoxProfile();
 
+  void PostLoadSelectToSpeak();
   void PostUnloadSelectToSpeak();
+
   void PostLoadSwitchAccess();
   void PostUnloadSwitchAccess();
 
@@ -462,6 +476,9 @@
   ash::mojom::AccessibilityFocusRingControllerPtr
       accessibility_focus_ring_controller_;
 
+  std::map<std::string, std::set<std::string>>
+      focus_ring_names_for_extension_id_;
+
   bool app_terminating_ = false;
 
   std::unique_ptr<DictationChromeos> dictation_;
diff --git a/chrome/browser/chromeos/accessibility/select_to_speak_browsertest.cc b/chrome/browser/chromeos/accessibility/select_to_speak_browsertest.cc
index 69ae8ea..dcb02bc 100644
--- a/chrome/browser/chromeos/accessibility/select_to_speak_browsertest.cc
+++ b/chrome/browser/chromeos/accessibility/select_to_speak_browsertest.cc
@@ -287,12 +287,15 @@
       base::BindRepeating(&SelectToSpeakTest::OnFocusRingChanged, GetWeakPtr());
   chromeos::AccessibilityManager::Get()->SetFocusRingObserverForTest(callback);
 
+  std::string focus_ring_id =
+      chromeos::AccessibilityManager::Get()->GetFocusRingId(
+          extension_misc::kSelectToSpeakExtensionId, "");
+
   ash::AccessibilityFocusRingController* controller =
       ash::Shell::Get()->accessibility_focus_ring_controller();
   controller->SetNoFadeForTesting();
   const ash::AccessibilityFocusRingGroup* focus_ring_group =
-      controller->GetFocusRingGroupForTesting(
-          extension_misc::kSelectToSpeakExtensionId);
+      controller->GetFocusRingGroupForTesting(focus_ring_id);
   // No focus rings to start.
   EXPECT_EQ(nullptr, focus_ring_group);
 
@@ -306,8 +309,7 @@
 
   // Expect a focus ring to have been drawn.
   WaitForFocusRingChanged();
-  focus_ring_group = controller->GetFocusRingGroupForTesting(
-      extension_misc::kSelectToSpeakExtensionId);
+  focus_ring_group = controller->GetFocusRingGroupForTesting(focus_ring_id);
   ASSERT_NE(nullptr, focus_ring_group);
   std::vector<std::unique_ptr<ash::AccessibilityFocusRingLayer>> const&
       focus_rings = focus_ring_group->focus_layers_for_testing();
diff --git a/chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service.cc b/chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service.cc
index 0b3b809f..6af3748 100644
--- a/chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service.cc
+++ b/chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service.cc
@@ -111,7 +111,9 @@
 
   // input_method::InputMethodEngineBase::Observer overrides:
   void OnActivate(const std::string& engine_id) override {
-    owner_->OnArcImeActivated();
+    owner_->is_arc_ime_active_ = true;
+    // TODO(yhanada): Remove this line after we migrate to SPM completely.
+    owner_->OnInputContextHandlerChanged();
   }
   void OnFocus(
       const ui::IMEEngineHandlerInterface::InputContext& context) override {
@@ -133,7 +135,9 @@
   }
   void OnReset(const std::string& engine_id) override {}
   void OnDeactivated(const std::string& engine_id) override {
-    owner_->OnArcImeDeactivated();
+    owner_->is_arc_ime_active_ = false;
+    // TODO(yhanada): Remove this line after we migrate to SPM completely.
+    owner_->OnInputContextHandlerChanged();
   }
   void OnCompositionBoundsChanged(
       const std::vector<gfx::Rect>& bounds) override {}
@@ -175,7 +179,9 @@
   void OnBlur() override {}
   void OnCaretBoundsChanged(const ui::TextInputClient* client) override {}
   void OnTextInputStateChanged(const ui::TextInputClient* client) override {}
-  void OnInputMethodDestroyed(const ui::InputMethod* input_method) override {}
+  void OnInputMethodDestroyed(const ui::InputMethod* input_method) override {
+    owner_->input_method_ = nullptr;
+  }
   void OnShowVirtualKeyboardIfEnabled() override {
     owner_->SendShowVirtualKeyboard();
   }
@@ -262,6 +268,9 @@
             &ArcInputMethodManagerService::OnAccessibilityStatusChanged,
             base::Unretained(this)));
   }
+
+  DCHECK(ui::IMEBridge::Get());
+  ui::IMEBridge::Get()->AddObserver(this);
 }
 
 ArcInputMethodManagerService::~ArcInputMethodManagerService() = default;
@@ -286,6 +295,14 @@
   RemoveArcIMEFromPrefs();
   profile_->GetPrefs()->CommitPendingWrite();
 
+  if (input_method_) {
+    input_method_->RemoveObserver(input_method_observer_.get());
+    input_method_ = nullptr;
+  }
+
+  if (ui::IMEBridge::Get())
+    ui::IMEBridge::Get()->RemoveObserver(this);
+
   if (TabletModeClient::Get())
     TabletModeClient::Get()->RemoveObserver(tablet_mode_observer_.get());
 
@@ -481,6 +498,22 @@
   }
 }
 
+void ArcInputMethodManagerService::OnInputContextHandlerChanged() {
+  if (ui::IMEBridge::Get()->GetInputContextHandler() == nullptr) {
+    if (input_method_)
+      input_method_->RemoveObserver(input_method_observer_.get());
+    input_method_ = nullptr;
+    return;
+  }
+
+  if (input_method_)
+    input_method_->RemoveObserver(input_method_observer_.get());
+  input_method_ =
+      ui::IMEBridge::Get()->GetInputContextHandler()->GetInputMethod();
+  if (input_method_)
+    input_method_->AddObserver(input_method_observer_.get());
+}
+
 void ArcInputMethodManagerService::OnAccessibilityStatusChanged(
     const chromeos::AccessibilityStatusEventDetails& event_details) {
   if (event_details.notification_type !=
@@ -527,6 +560,9 @@
 }
 
 void ArcInputMethodManagerService::Focus(int context_id) {
+  if (!is_arc_ime_active_)
+    return;
+
   DCHECK(!active_connection_);
   active_connection_ = std::make_unique<InputConnectionImpl>(
       proxy_ime_engine_.get(), imm_bridge_.get(), context_id);
@@ -543,7 +579,7 @@
 }
 
 void ArcInputMethodManagerService::UpdateTextInputState() {
-  if (!active_connection_)
+  if (!is_arc_ime_active_ || !active_connection_)
     return;
   active_connection_->UpdateTextInputState(
       false /* is_input_state_update_requested */);
@@ -697,25 +733,14 @@
     manager->NotifyInputMethodExtensionAdded(proxy_ime_extension_id_);
 }
 
-void ArcInputMethodManagerService::OnArcImeActivated() {
-  ui::InputMethod* input_method =
-      ui::IMEBridge::Get()->GetInputContextHandler()->GetInputMethod();
-  if (input_method)
-    input_method->AddObserver(input_method_observer_.get());
-}
-
-void ArcInputMethodManagerService::OnArcImeDeactivated() {
-  ui::InputMethod* input_method =
-      ui::IMEBridge::Get()->GetInputContextHandler()->GetInputMethod();
-  if (input_method)
-    input_method->RemoveObserver(input_method_observer_.get());
-}
-
 bool ArcInputMethodManagerService::IsVirtualKeyboardShown() const {
   return is_virtual_keyboard_shown_;
 }
 
 void ArcInputMethodManagerService::SendShowVirtualKeyboard() {
+  if (!is_arc_ime_active_)
+    return;
+
   imm_bridge_->SendShowVirtualKeyboard();
   // TODO(yhanada): Should observe IME window size changes.
   is_virtual_keyboard_shown_ = true;
@@ -724,6 +749,9 @@
 }
 
 void ArcInputMethodManagerService::SendHideVirtualKeyboard() {
+  if (!is_arc_ime_active_)
+    return;
+
   imm_bridge_->SendHideVirtualKeyboard();
   // TODO(yhanada): Should observe IME window size changes.
   is_virtual_keyboard_shown_ = false;
diff --git a/chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service.h b/chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service.h
index 8ae044d..67c4a9d 100644
--- a/chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service.h
+++ b/chrome/browser/chromeos/arc/input_method_manager/arc_input_method_manager_service.h
@@ -20,6 +20,7 @@
 #include "components/keyed_service/content/browser_context_keyed_service_factory.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "ui/base/ime/chromeos/input_method_manager.h"
+#include "ui/base/ime/ime_bridge_observer.h"
 
 namespace content {
 class BrowserContext;
@@ -33,7 +34,8 @@
     : public KeyedService,
       public ArcInputMethodManagerBridge::Delegate,
       public chromeos::input_method::InputMethodManager::ImeMenuObserver,
-      public chromeos::input_method::InputMethodManager::Observer {
+      public chromeos::input_method::InputMethodManager::Observer,
+      public ui::IMEBridgeObserver {
  public:
   class Observer : public base::CheckedObserver {
    public:
@@ -83,6 +85,10 @@
                           Profile* profile,
                           bool show_message) override;
 
+  // ui::IMEBridgeObserver overrides:
+  void OnRequestSwitchEngine() override {}
+  void OnInputContextHandlerChanged() override;
+
   // Called when a11y keyboard option changed and disables ARC IME while a11y
   // keyboard option is enabled.
   void OnAccessibilityStatusChanged(
@@ -121,10 +127,6 @@
   // Notifies InputMethodManager's observers of possible ARC IME state changes.
   void NotifyInputMethodManagerObservers(bool is_tablet_mode);
 
-  // Called by InputMethodEngineObserver.
-  void OnArcImeActivated();
-  void OnArcImeDeactivated();
-
   bool IsVirtualKeyboardShown() const;
   void SendShowVirtualKeyboard();
   void SendHideVirtualKeyboard();
@@ -143,6 +145,11 @@
   const std::string proxy_ime_extension_id_;
   std::unique_ptr<chromeos::InputMethodEngine> proxy_ime_engine_;
 
+  // The currently active input method, observed for
+  // OnShowVirtualKeyboardIfEnabled.
+  ui::InputMethod* input_method_ = nullptr;
+  bool is_arc_ime_active_ = false;
+
   std::unique_ptr<InputConnectionImpl> active_connection_;
 
   std::unique_ptr<TabletModeObserver> tablet_mode_observer_;
diff --git a/chrome/browser/chromeos/child_accounts/event_based_status_reporting_service_unittest.cc b/chrome/browser/chromeos/child_accounts/event_based_status_reporting_service_unittest.cc
index 2c693c3..0f4af8fd 100644
--- a/chrome/browser/chromeos/child_accounts/event_based_status_reporting_service_unittest.cc
+++ b/chrome/browser/chromeos/child_accounts/event_based_status_reporting_service_unittest.cc
@@ -124,6 +124,7 @@
     service_->Shutdown();
     arc_test_.TearDown();
     profile_.reset();
+    SystemClockClient::Shutdown();
     PowerManagerClient::Shutdown();
   }
 
diff --git a/chrome/browser/chromeos/child_accounts/screen_time_controller.cc b/chrome/browser/chromeos/child_accounts/screen_time_controller.cc
index 68165b7..3975957e 100644
--- a/chrome/browser/chromeos/child_accounts/screen_time_controller.cc
+++ b/chrome/browser/chromeos/child_accounts/screen_time_controller.cc
@@ -18,6 +18,7 @@
 #include "chrome/browser/chromeos/child_accounts/consumer_status_reporting_service.h"
 #include "chrome/browser/chromeos/child_accounts/consumer_status_reporting_service_factory.h"
 #include "chrome/browser/chromeos/child_accounts/parent_access_code/policy_config_source.h"
+#include "chrome/browser/chromeos/child_accounts/time_limit_override.h"
 #include "chrome/browser/chromeos/login/lock/screen_locker.h"
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
 #include "chrome/browser/profiles/profile.h"
@@ -54,6 +55,7 @@
 void ScreenTimeController::RegisterProfilePrefs(PrefRegistrySimple* registry) {
   registry->RegisterDictionaryPref(prefs::kScreenTimeLastState);
   registry->RegisterDictionaryPref(prefs::kUsageTimeLimit);
+  registry->RegisterDictionaryPref(prefs::kTimeLimitLocalOverride);
 }
 
 ScreenTimeController::ScreenTimeController(content::BrowserContext* context)
@@ -120,7 +122,16 @@
   if (!session_manager::SessionManager::Get()->IsScreenLocked())
     return;
 
-  UpdateLockScreenState(false /*blocked*/, base::Time());
+  usage_time_limit::TimeLimitOverride local_override(
+      usage_time_limit::TimeLimitOverride::Action::kUnlock, clock_->Now(),
+      base::nullopt);
+  // Replace previous local override stored in pref, because PAC can only be
+  // entered if previous override is not active anymore.
+  pref_service_->Set(prefs::kTimeLimitLocalOverride,
+                     local_override.ToDictionary());
+  pref_service_->CommitPendingWrite();
+
+  CheckTimeLimit("OnAccessCodeValidation");
 }
 
 void ScreenTimeController::SetClocksForTesting(
@@ -154,10 +165,13 @@
   base::Optional<usage_time_limit::State> last_state = GetLastStateFromPref();
   const base::DictionaryValue* time_limit =
       pref_service_->GetDictionary(prefs::kUsageTimeLimit);
+  const base::DictionaryValue* local_override =
+      pref_service_->GetDictionary(prefs::kTimeLimitLocalOverride);
 
+  // TODO(agawronska): Usage timestamp should be passed instead of second |now|.
   usage_time_limit::State state = usage_time_limit::GetState(
-      time_limit->CreateDeepCopy(), GetScreenTimeDuration(), now, now,
-      &time_zone, last_state);
+      time_limit->CreateDeepCopy(), local_override, GetScreenTimeDuration(),
+      now, now, &time_zone, last_state);
   SaveCurrentStateToPref(state);
 
   // Show/hide time limits message based on the policy enforcement.
@@ -206,10 +220,12 @@
       ScheduleUsageTimeLimitWarning(state);
   }
 
-  base::Time next_get_state_time =
-      std::min(state.next_state_change_time,
-               usage_time_limit::GetExpectedResetTime(
-                   time_limit->CreateDeepCopy(), now, &time_zone));
+  // TODO(agawronska): We are creating UsageTimeLimitProcessor second time in
+  // this method. Could expected reset time be returned as a part of the state?
+  base::Time next_get_state_time = std::min(
+      state.next_state_change_time,
+      usage_time_limit::GetExpectedResetTime(time_limit->CreateDeepCopy(),
+                                             local_override, now, &time_zone));
   if (!next_get_state_time.is_null()) {
     VLOG(1) << "Scheduling state change timer in " << next_get_state_time - now;
     next_state_timer_->Start(
@@ -416,11 +432,13 @@
       system::TimezoneSettings::GetInstance()->GetTimezone();
   const base::DictionaryValue* time_limit =
       pref_service_->GetDictionary(prefs::kUsageTimeLimit);
+  const base::DictionaryValue* local_override =
+      pref_service_->GetDictionary(prefs::kTimeLimitLocalOverride);
 
   base::Optional<base::TimeDelta> remaining_usage =
-      usage_time_limit::GetRemainingTimeUsage(time_limit->CreateDeepCopy(), now,
-                                              GetScreenTimeDuration(),
-                                              &time_zone);
+      usage_time_limit::GetRemainingTimeUsage(
+          time_limit->CreateDeepCopy(), local_override, now,
+          GetScreenTimeDuration(), &time_zone);
 
   // Remaining time usage can be bigger than |kUsageTimeLimitWarningTime|
   // because it is counted in another class so the timers might be called with
diff --git a/chrome/browser/chromeos/child_accounts/time_limit_test_utils.cc b/chrome/browser/chromeos/child_accounts/time_limit_test_utils.cc
index 35d8b17f..3dcafa73 100644
--- a/chrome/browser/chromeos/child_accounts/time_limit_test_utils.cc
+++ b/chrome/browser/chromeos/child_accounts/time_limit_test_utils.cc
@@ -162,6 +162,7 @@
         policy->SetKey(usage_time_limit::TimeLimitOverride::kOverridesDictKey,
                        base::Value(base::Value::Type::LIST));
   }
+
   usage_time_limit::TimeLimitOverride new_override(action, created_at,
                                                    base::nullopt);
   overrides->GetList().push_back(new_override.ToDictionary());
diff --git a/chrome/browser/chromeos/child_accounts/usage_time_limit_processor.cc b/chrome/browser/chromeos/child_accounts/usage_time_limit_processor.cc
index 84ab19d..74a50b4 100644
--- a/chrome/browser/chromeos/child_accounts/usage_time_limit_processor.cc
+++ b/chrome/browser/chromeos/child_accounts/usage_time_limit_processor.cc
@@ -76,6 +76,7 @@
       base::Optional<internal::TimeWindowLimit> time_window_limit,
       base::Optional<internal::TimeUsageLimit> time_usage_limit,
       base::Optional<TimeLimitOverride> time_limit_override,
+      base::Optional<TimeLimitOverride> local_time_limit_override,
       const base::TimeDelta& used_time,
       const base::Time& usage_timestamp,
       const base::Time& current_time,
@@ -192,6 +193,9 @@
   // The policy override object.
   base::Optional<TimeLimitOverride> time_limit_override_;
 
+  // The local override object.
+  base::Optional<TimeLimitOverride> local_time_limit_override_;
+
   // How long the user has used the device.
   const base::TimeDelta used_time_;
 
@@ -239,6 +243,7 @@
     base::Optional<internal::TimeWindowLimit> time_window_limit,
     base::Optional<internal::TimeUsageLimit> time_usage_limit,
     base::Optional<TimeLimitOverride> time_limit_override,
+    base::Optional<TimeLimitOverride> local_time_limit_override,
     const base::TimeDelta& used_time,
     const base::Time& usage_timestamp,
     const base::Time& current_time,
@@ -246,7 +251,6 @@
     const base::Optional<State>& previous_state)
     : time_window_limit_(std::move(time_window_limit)),
       time_usage_limit_(std::move(time_usage_limit)),
-      time_limit_override_(std::move(time_limit_override)),
       used_time_(used_time),
       usage_timestamp_(usage_timestamp),
       current_time_(current_time),
@@ -254,6 +258,18 @@
       current_weekday_(GetCurrentWeekday()),
       previous_state_(previous_state),
       enabled_time_usage_limit_(GetEnabledTimeUsageLimit()) {
+  // Use local override if it is newer than policy override, otherwise ignore
+  // local override.
+  // Note: |time_limit_override_| needs to be set before calculating
+  // |active_time_window_limit_| and |active_time_usage_limit_|.
+  bool should_use_local_override = local_time_limit_override.has_value() &&
+                                   (!time_limit_override.has_value() ||
+                                    local_time_limit_override->created_at() >
+                                        time_limit_override->created_at());
+  time_limit_override_ = should_use_local_override
+                             ? std::move(local_time_limit_override)
+                             : std::move(time_limit_override);
+
   // This will also set overridden_window_limit_ to true if applicable.
   // TODO: refactor GetActiveTimeWindowLimit to stop updating the state on a
   // getter method.
@@ -645,7 +661,6 @@
   }
 
   bool has_valid_lock_override =
-      time_limit_override_->IsLock() &&
       time_limit_override_->created_at() > last_reset_time &&
       !override_cancelled_by_window_limit;
 
@@ -1181,6 +1196,7 @@
 }
 
 State GetState(const std::unique_ptr<base::DictionaryValue>& time_limit,
+               const base::Value* local_override,
                const base::TimeDelta& used_time,
                const base::Time& usage_timestamp,
                const base::Time& current_time,
@@ -1192,15 +1208,20 @@
       TimeUsageLimitFromPolicy(time_limit);
   base::Optional<TimeLimitOverride> time_limit_override =
       OverrideFromPolicy(time_limit);
+  base::Optional<TimeLimitOverride> local_time_limit_override =
+      TimeLimitOverride::FromDictionary(local_override);
+  // TODO(agawronska): Pass |usage_timestamp| instead of second |current_time|.
   return internal::UsageTimeLimitProcessor(
              std::move(time_window_limit), std::move(time_usage_limit),
-             std::move(time_limit_override), used_time, current_time,
+             std::move(time_limit_override),
+             std::move(local_time_limit_override), used_time, current_time,
              current_time, time_zone, previous_state)
       .GetState();
 }
 
 base::Time GetExpectedResetTime(
     const std::unique_ptr<base::DictionaryValue>& time_limit,
+    const base::Value* local_override,
     const base::Time current_time,
     const icu::TimeZone* const time_zone) {
   base::Optional<internal::TimeWindowLimit> time_window_limit =
@@ -1209,15 +1230,20 @@
       TimeUsageLimitFromPolicy(time_limit);
   base::Optional<TimeLimitOverride> time_limit_override =
       OverrideFromPolicy(time_limit);
+  base::Optional<TimeLimitOverride> local_time_limit_override =
+      TimeLimitOverride::FromDictionary(local_override);
   return internal::UsageTimeLimitProcessor(
              std::move(time_window_limit), std::move(time_usage_limit),
-             std::move(time_limit_override), base::TimeDelta::FromMinutes(0),
-             base::Time(), current_time, time_zone, base::nullopt)
+             std::move(time_limit_override),
+             std::move(local_time_limit_override),
+             base::TimeDelta::FromMinutes(0), base::Time(), current_time,
+             time_zone, base::nullopt)
       .GetExpectedResetTime();
 }
 
 base::Optional<base::TimeDelta> GetRemainingTimeUsage(
     const std::unique_ptr<base::DictionaryValue>& time_limit,
+    const base::Value* local_override,
     const base::Time current_time,
     const base::TimeDelta& used_time,
     const icu::TimeZone* const time_zone) {
@@ -1227,9 +1253,12 @@
       TimeUsageLimitFromPolicy(time_limit);
   base::Optional<TimeLimitOverride> time_limit_override =
       OverrideFromPolicy(time_limit);
+  base::Optional<TimeLimitOverride> local_time_limit_override =
+      TimeLimitOverride::FromDictionary(local_override);
   return internal::UsageTimeLimitProcessor(
              std::move(time_window_limit), std::move(time_usage_limit),
-             std::move(time_limit_override), used_time, base::Time(),
+             std::move(time_limit_override),
+             std::move(local_time_limit_override), used_time, base::Time(),
              current_time, time_zone, base::nullopt)
       .GetRemainingTimeUsage();
 }
diff --git a/chrome/browser/chromeos/child_accounts/usage_time_limit_processor.h b/chrome/browser/chromeos/child_accounts/usage_time_limit_processor.h
index 3e6ecd1..5392312 100644
--- a/chrome/browser/chromeos/child_accounts/usage_time_limit_processor.h
+++ b/chrome/browser/chromeos/child_accounts/usage_time_limit_processor.h
@@ -141,7 +141,17 @@
 
 // Returns the current state of the user session with the given usage time limit
 // policy.
+// |time_limit| dictionary with UsageTimeLimit policy data.
+// |local_override| dictionary with data of the last local override (authorized
+//                  by parent access code).
+// |used_time| time used in the current day.
+// |usage_timestamp| when was |used_time| data collected. Usually differs from
+//                   |current_time| by milliseconds.
+// |previous_state| state previously returned by UsageTimeLimitProcessor.
+// TODO(agawronska): Passing unique_ptr by const ref is strange and advised
+// against by style guide. Probably should be refactorred.
 State GetState(const std::unique_ptr<base::DictionaryValue>& time_limit,
+               const base::Value* local_override,
                const base::TimeDelta& used_time,
                const base::Time& usage_timestamp,
                const base::Time& current_time,
@@ -149,14 +159,23 @@
                const base::Optional<State>& previous_state);
 
 // Returns the expected time that the used time stored should be reset.
+// |time_limit| dictionary with UsageTimeLimit policy data.
+// |local_override| dictionary with data of the last local override (authorized
+//                  by parent access code).
 base::Time GetExpectedResetTime(
     const std::unique_ptr<base::DictionaryValue>& time_limit,
+    const base::Value* local_override,
     base::Time current_time,
     const icu::TimeZone* const time_zone);
 
 // Returns the remaining time usage if the time usage limit is enabled.
+// |time_limit| dictionary with UsageTimeLimit policy data.
+// |local_override| dictionary with data of the last local override (authorized
+//                  by parent access code).
+// |used_time| time used in the current day.
 base::Optional<base::TimeDelta> GetRemainingTimeUsage(
     const std::unique_ptr<base::DictionaryValue>& time_limit,
+    const base::Value* local_override,
     const base::Time current_time,
     const base::TimeDelta& used_time,
     const icu::TimeZone* const time_zone);
diff --git a/chrome/browser/chromeos/child_accounts/usage_time_limit_processor_unittest.cc b/chrome/browser/chromeos/child_accounts/usage_time_limit_processor_unittest.cc
index 59d6842..afef0ab 100644
--- a/chrome/browser/chromeos/child_accounts/usage_time_limit_processor_unittest.cc
+++ b/chrome/browser/chromeos/child_accounts/usage_time_limit_processor_unittest.cc
@@ -31,6 +31,8 @@
 
 using UsageTimeLimitProcessorTest = testing::Test;
 
+// TODO(agawronska): Looks like there is no reason to use assert. Change
+// ASSERT_EQ to EXPECT_EQ.
 void AssertEqState(State expected, State actual) {
   ASSERT_EQ(expected.is_locked, actual.is_locked);
   ASSERT_EQ(expected.active_policy, actual.active_policy);
@@ -263,7 +265,8 @@
 
   // Check state before Monday time window limit.
   base::Time time_one = utils::TimeFromString("Mon, 1 Jan 2018 20:00 GMT+0300");
-  State state_one = GetState(policy, base::TimeDelta::FromMinutes(0), time_one,
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(0), time_one,
                              time_one, timezone.get(), base::nullopt);
 
   State expected_state_one;
@@ -277,7 +280,8 @@
 
   // Check state during the Monday time window limit.
   base::Time time_two = utils::TimeFromString("Mon, 1 Jan 2018 22:00 GMT+0300");
-  State state_two = GetState(policy, base::TimeDelta::FromMinutes(0), time_two,
+  State state_two = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(0), time_two,
                              time_two, timezone.get(), state_one);
 
   State expected_state_two;
@@ -293,9 +297,9 @@
   // Check state after the Monday time window limit.
   base::Time time_three =
       utils::TimeFromString("Tue, 2 Jan 2018 9:00 GMT+0300");
-  State state_three =
-      GetState(policy, base::TimeDelta::FromMinutes(0), time_three, time_three,
-               timezone.get(), state_two);
+  State state_three = GetState(policy, nullptr /* local_override */,
+                               base::TimeDelta::FromMinutes(0), time_three,
+                               time_three, timezone.get(), state_two);
 
   State expected_state_three;
   expected_state_three.is_locked = false;
@@ -325,8 +329,9 @@
 
   // Check state before time usage limit is enforced.
   base::Time time_one = utils::TimeFromString("Mon, 1 Jan 2018 20:00");
-  State state_one = GetState(policy, base::TimeDelta::FromMinutes(120),
-                             time_one, time_one, timezone.get(), base::nullopt);
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(120), time_one,
+                             time_one, timezone.get(), base::nullopt);
 
   State expected_state_one;
   expected_state_one.is_locked = false;
@@ -341,7 +346,8 @@
 
   // Check state before time usage limit is enforced.
   base::Time time_two = utils::TimeFromString("Tue, 2 Jan 2018 12:00");
-  State state_two = GetState(policy, base::TimeDelta::FromMinutes(60), time_two,
+  State state_two = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(60), time_two,
                              time_two, timezone.get(), state_one);
 
   State expected_state_two;
@@ -357,9 +363,9 @@
 
   // Check state when the time usage limit should be enforced.
   base::Time time_three = utils::TimeFromString("Tue, 2 Jan 2018 21:00");
-  State state_three =
-      GetState(policy, base::TimeDelta::FromMinutes(120), time_three,
-               time_three, timezone.get(), state_two);
+  State state_three = GetState(policy, nullptr /* local_override */,
+                               base::TimeDelta::FromMinutes(120), time_three,
+                               time_three, timezone.get(), state_two);
 
   base::Time wednesday_reset_time =
       utils::TimeFromString("Wed, 3 Jan 2018 8:00");
@@ -399,7 +405,8 @@
 
   // Check state before any policy is enforced.
   base::Time time_one = utils::TimeFromString("Mon, 1 Jan 2018 14:00");
-  State state_one = GetState(policy, base::TimeDelta::FromMinutes(80), time_one,
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(80), time_one,
                              time_one, timezone.get(), base::nullopt);
 
   State expected_state_one;
@@ -415,8 +422,9 @@
 
   // Check state during time usage limit.
   base::Time time_two = utils::TimeFromString("Mon, 1 Jan 2018 16:00");
-  State state_two = GetState(policy, base::TimeDelta::FromMinutes(121),
-                             time_two, time_two, timezone.get(), state_one);
+  State state_two = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(121), time_two,
+                             time_two, timezone.get(), state_one);
 
   base::Time monday_time_window_limit_start =
       utils::TimeFromString("Mon, 1 Jan 2018 21:00");
@@ -436,9 +444,9 @@
 
   // Check state during time window limit and time usage limit enforced.
   base::Time time_three = utils::TimeFromString("Mon, 1 Jan 2018 21:00");
-  State state_three =
-      GetState(policy, base::TimeDelta::FromMinutes(120), time_three,
-               time_three, timezone.get(), state_two);
+  State state_three = GetState(policy, nullptr /* local_override */,
+                               base::TimeDelta::FromMinutes(120), time_three,
+                               time_three, timezone.get(), state_two);
 
   State expected_state_three;
   expected_state_three.is_locked = true;
@@ -457,9 +465,9 @@
 
   // Check state after time usage limit reset and window limit end.
   base::Time time_four = utils::TimeFromString("Fri, 5 Jan 2018 8:30");
-  State state_four =
-      GetState(policy, base::TimeDelta::FromMinutes(120), time_four, time_four,
-               timezone.get(), state_three);
+  State state_four = GetState(policy, nullptr /* local_override */,
+                              base::TimeDelta::FromMinutes(120), time_four,
+                              time_four, timezone.get(), state_three);
 
   State expected_state_four;
   expected_state_four.is_locked = false;
@@ -484,8 +492,9 @@
                            base::TimeDelta::FromHours(1), last_updated);
 
   base::Time time_one = utils::TimeFromString("Fri, 5 Jan 2018 15:00 PST");
-  State state_one = GetState(policy, base::TimeDelta::FromHours(1), time_one,
-                             time_one, timezone.get(), base::nullopt);
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromHours(1), time_one, time_one,
+                             timezone.get(), base::nullopt);
 
   State expected_state_one;
   expected_state_one.is_locked = true;
@@ -512,7 +521,8 @@
                      utils::TimeFromString("Mon, 1 Jan 2018 15:00"));
 
   base::Time time_one = utils::TimeFromString("Mon, 1 Jan 2018 15:05");
-  State state_one = GetState(policy, base::TimeDelta::FromMinutes(0), time_one,
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(0), time_one,
                              time_one, timezone.get(), base::nullopt);
 
   // Check that the device is locked until next morning.
@@ -548,8 +558,9 @@
 
   // Check that the override is invalidating the time window limit.
   base::Time time_one = utils::TimeFromString("Mon, 1 Jan 2018 18:35 GMT+0800");
-  State state_one = GetState(policy, base::TimeDelta::FromMinutes(120),
-                             time_one, time_one, timezone.get(), base::nullopt);
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(120), time_one,
+                             time_one, timezone.get(), base::nullopt);
 
   State expected_state_one;
   expected_state_one.is_locked = false;
@@ -570,8 +581,9 @@
 
   // Check that the new time window limit is enforced.
   base::Time time_two = utils::TimeFromString("Mon, 1 Jan 2018 19:10 GMT+0800");
-  State state_two = GetState(policy, base::TimeDelta::FromMinutes(120),
-                             time_two, time_two, timezone.get(), state_one);
+  State state_two = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(120), time_two,
+                             time_two, timezone.get(), state_one);
 
   State expected_state_two;
   expected_state_two.is_locked = true;
@@ -604,7 +616,8 @@
                      utils::TimeFromString("Mon, 1 Jan 2018 22:00 PST"));
 
   base::Time time_one = utils::TimeFromString("Mon, 1 Jan 2018 22:10 PST");
-  State state_one = GetState(policy, base::TimeDelta::FromMinutes(40), time_one,
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(40), time_one,
                              time_one, timezone.get(), base::nullopt);
 
   State expected_state_one;
@@ -622,8 +635,9 @@
   // started, and that it will be locked until the time usage limit reset time,
   // and not when the time window limit ends.
   base::Time time_two = utils::TimeFromString("Mon, 1 Jan 2018 22:30 PST");
-  State state_two = GetState(policy, base::TimeDelta::FromHours(1), time_two,
-                             time_two, timezone.get(), state_one);
+  State state_two = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromHours(1), time_two, time_two,
+                             timezone.get(), state_one);
 
   State expected_state_two;
   expected_state_two.is_locked = true;
@@ -652,7 +666,8 @@
                            base::TimeDelta::FromMinutes(60), last_updated);
 
   base::Time time_one = utils::TimeFromString("Sun, 7 Jan 2018 15:00 PST");
-  State state_one = GetState(policy, base::TimeDelta::FromMinutes(40), time_one,
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(40), time_one,
                              time_one, timezone.get(), base::nullopt);
 
   State expected_state_one;
@@ -667,7 +682,8 @@
   AssertEqState(expected_state_one, state_one);
 
   base::Time time_two = utils::TimeFromString("Sun, 7 Jan 2018 15:30 PST");
-  State state_two = GetState(policy, base::TimeDelta::FromMinutes(60), time_two,
+  State state_two = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(60), time_two,
                              time_two, timezone.get(), state_one);
 
   State expected_state_two;
@@ -687,9 +703,9 @@
   utils::AddOverride(policy.get(), TimeLimitOverride::Action::kUnlock,
                      utils::TimeFromString("Sun, 7 Jan 2018 16:00 PST"));
   base::Time time_three = utils::TimeFromString("Sun, 7 Jan 2018 16:01 PST");
-  State state_three =
-      GetState(policy, base::TimeDelta::FromMinutes(60), time_three, time_three,
-               timezone.get(), state_two);
+  State state_three = GetState(policy, nullptr /* local_override */,
+                               base::TimeDelta::FromMinutes(60), time_three,
+                               time_three, timezone.get(), state_two);
 
   State expected_state_three;
   expected_state_three.is_locked = false;
@@ -722,7 +738,8 @@
 
   // Check that the device is locked because of the override.
   base::Time time_one = utils::TimeFromString("Mon, 1 Jan 2018 21:00 PST");
-  State state_one = GetState(policy, base::TimeDelta::FromMinutes(40), time_one,
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(40), time_one,
                              time_one, timezone.get(), base::nullopt);
 
   State expected_state_one;
@@ -739,7 +756,8 @@
 
   // Check that the device is still locked after midnight.
   base::Time time_two = utils::TimeFromString("Tue, 2 Jan 2018 1:00 PST");
-  State state_two = GetState(policy, base::TimeDelta::FromMinutes(0), time_two,
+  State state_two = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(0), time_two,
                              time_two, timezone.get(), state_one);
 
   State expected_state_two;
@@ -756,9 +774,9 @@
 
   // Check that the device is unlocked.
   base::Time time_three = utils::TimeFromString("Tue, 2 Jan 2018 6:00 PST");
-  State state_three =
-      GetState(policy, base::TimeDelta::FromMinutes(0), time_three, time_three,
-               timezone.get(), state_two);
+  State state_three = GetState(policy, nullptr /* local_override */,
+                               base::TimeDelta::FromMinutes(0), time_three,
+                               time_three, timezone.get(), state_two);
 
   State expected_state_three;
   expected_state_three.is_locked = false;
@@ -818,9 +836,9 @@
     base::Time window_limit_end_time =
         tuesday_seven_am + base::TimeDelta::FromDays(i);
 
-    State night_state =
-        GetState(policy, base::TimeDelta::FromMinutes(40), night_time,
-                 night_time, timezone.get(), base::nullopt);
+    State night_state = GetState(policy, nullptr /* local_override */,
+                                 base::TimeDelta::FromMinutes(40), night_time,
+                                 night_time, timezone.get(), base::nullopt);
 
     State expected_night_state;
     expected_night_state.is_locked = true;
@@ -834,9 +852,9 @@
 
     AssertEqState(expected_night_state, night_state);
 
-    State morning_state =
-        GetState(policy, base::TimeDelta::FromMinutes(40), morning_time,
-                 night_time, timezone.get(), base::nullopt);
+    State morning_state = GetState(
+        policy, nullptr /* local_override */, base::TimeDelta::FromMinutes(40),
+        morning_time, night_time, timezone.get(), base::nullopt);
 
     State expected_morning_state;
     expected_morning_state.is_locked = true;
@@ -890,9 +908,9 @@
     base::Time usage_limit_reset_time =
         tuesday_six_am + base::TimeDelta::FromDays(i);
 
-    State night_state =
-        GetState(policy, base::TimeDelta::FromHours(3), night_time, night_time,
-                 timezone.get(), base::nullopt);
+    State night_state = GetState(policy, nullptr /* local_override */,
+                                 base::TimeDelta::FromHours(3), night_time,
+                                 night_time, timezone.get(), base::nullopt);
 
     State expected_night_state;
     expected_night_state.is_locked = true;
@@ -907,9 +925,9 @@
 
     AssertEqState(expected_night_state, night_state);
 
-    State morning_state =
-        GetState(policy, base::TimeDelta::FromHours(3), morning_time,
-                 night_time, timezone.get(), night_state);
+    State morning_state = GetState(policy, nullptr /* local_override */,
+                                   base::TimeDelta::FromHours(3), morning_time,
+                                   night_time, timezone.get(), night_state);
 
     State expected_morning_state;
     expected_morning_state.is_locked = true;
@@ -940,7 +958,8 @@
 
   // Check that device is locked.
   base::Time time_one = utils::TimeFromString("Sun, 7 Jan 2018 8:00 GMT");
-  State state_one = GetState(policy, base::TimeDelta::FromMinutes(80), time_one,
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(80), time_one,
                              time_one, timezone.get(), base::nullopt);
 
   State expected_state_one;
@@ -969,8 +988,9 @@
 
   // Check that device is locked.
   base::Time time_one = utils::TimeFromString("Sun, 7 Jan 2018 4:00 GMT");
-  State state_one = GetState(policy, base::TimeDelta::FromHours(2), time_one,
-                             time_one, timezone.get(), base::nullopt);
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromHours(2), time_one, time_one,
+                             timezone.get(), base::nullopt);
 
   State expected_state_one;
   expected_state_one.is_locked = true;
@@ -1000,8 +1020,9 @@
 
   // Check that device is locked.
   base::Time time_one = utils::TimeFromString("Sat, 6 Jan 2018 20:00 PST");
-  State state_one = GetState(policy, base::TimeDelta::FromHours(2), time_one,
-                             time_one, timezone.get(), base::nullopt);
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromHours(2), time_one, time_one,
+                             timezone.get(), base::nullopt);
 
   State expected_state_one;
   expected_state_one.is_locked = true;
@@ -1035,7 +1056,8 @@
 
   // Check that the device is locked because of the override.
   base::Time time_one = utils::TimeFromString("Mon, 1 Jan 2018 15:00 PST");
-  State state_one = GetState(policy, base::TimeDelta::FromMinutes(60), time_one,
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(60), time_one,
                              time_one, timezone.get(), base::nullopt);
 
   State expected_state_one;
@@ -1052,7 +1074,8 @@
 
   // Check that the device is locked because of the bedtime.
   base::Time time_two = utils::TimeFromString("Mon, 1 Jan 2018 18:00 PST");
-  State state_two = GetState(policy, base::TimeDelta::FromMinutes(60), time_two,
+  State state_two = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(60), time_two,
                              time_two, timezone.get(), state_one);
 
   State expected_state_two;
@@ -1069,9 +1092,9 @@
 
   // Check that the device is unlocked after the bedtime ends.
   base::Time time_three = utils::TimeFromString("Mon, 1 Jan 2018 20:00 PST");
-  State state_three =
-      GetState(policy, base::TimeDelta::FromMinutes(60), time_three, time_three,
-               timezone.get(), state_two);
+  State state_three = GetState(policy, nullptr /* local_override */,
+                               base::TimeDelta::FromMinutes(60), time_three,
+                               time_three, timezone.get(), state_two);
 
   State expected_state_three;
   expected_state_three.is_locked = false;
@@ -1100,7 +1123,8 @@
 
   // Check that the device is unlocked because of the override.
   base::Time time_one = utils::TimeFromString("Mon, 1 Jan 2018 12:00 PST");
-  State state_one = GetState(policy, base::TimeDelta::FromMinutes(60), time_one,
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(60), time_one,
                              time_one, timezone.get(), base::nullopt);
 
   State expected_state_one;
@@ -1119,7 +1143,8 @@
 
   // Check that the device is locked because of the bedtime.
   base::Time time_two = utils::TimeFromString("Mon, 1 Jan 2018 14:00 PST");
-  State state_two = GetState(policy, base::TimeDelta::FromMinutes(60), time_two,
+  State state_two = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(60), time_two,
                              time_two, timezone.get(), state_one);
 
   State expected_state_two;
@@ -1136,9 +1161,9 @@
 
   // Check that the device is unlocked after the bedtime ends.
   base::Time time_three = utils::TimeFromString("Mon, 1 Jan 2018 20:00 PST");
-  State state_three =
-      GetState(policy, base::TimeDelta::FromMinutes(60), time_three, time_three,
-               timezone.get(), state_two);
+  State state_three = GetState(policy, nullptr /* local_override */,
+                               base::TimeDelta::FromMinutes(60), time_three,
+                               time_three, timezone.get(), state_two);
 
   State expected_state_three;
   expected_state_three.is_locked = false;
@@ -1170,7 +1195,8 @@
 
   // Check that the device is unlocked because of the unlock override.
   base::Time time_one = utils::TimeFromString("Mon, 1 Jan 2018 22:00 PST");
-  State state_one = GetState(policy, base::TimeDelta::FromMinutes(60), time_one,
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(60), time_one,
                              time_one, timezone.get(), base::nullopt);
 
   State expected_state_one;
@@ -1185,7 +1211,8 @@
 
   // Check that the device is locked after the duration.
   base::Time time_two = utils::TimeFromString("Mon, 1 Jan 2018 22:15 PST");
-  State state_two = GetState(policy, base::TimeDelta::FromMinutes(60), time_two,
+  State state_two = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(60), time_two,
                              time_two, timezone.get(), base::nullopt);
 
   State expected_state_two;
@@ -1215,8 +1242,9 @@
 
   // Check that the device is unlocked because of the usage time.
   base::Time time_one = utils::TimeFromString("Thu, 4 Jan 2018 9:45 GMT");
-  State state_one = GetState(policy, base::TimeDelta::FromMinutes(105),
-                             time_one, time_one, timezone.get(), base::nullopt);
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(105), time_one,
+                             time_one, timezone.get(), base::nullopt);
 
   State expected_state_one;
   expected_state_one.is_locked = false;
@@ -1235,8 +1263,9 @@
       utils::TimeFromString("Thu, 4 Jan 2018 9:45 GMT"),
       base::TimeDelta::FromMinutes(30));
   base::Time time_two = utils::TimeFromString("Thu, 4 Jan 2018 10:00 GMT");
-  State state_two = GetState(policy, base::TimeDelta::FromHours(2), time_two,
-                             time_two, timezone.get(), state_one);
+  State state_two = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromHours(2), time_two, time_two,
+                             timezone.get(), state_one);
 
   State expected_state_two;
   expected_state_two.is_locked = false;
@@ -1252,9 +1281,9 @@
 
   // Check that the device is locked after the duration.
   base::Time time_three = utils::TimeFromString("Thu, 4 Jan 2018 10:15 GMT");
-  State state_three =
-      GetState(policy, base::TimeDelta::FromMinutes(135), time_three,
-               time_three, timezone.get(), state_two);
+  State state_three = GetState(policy, nullptr /* local_override */,
+                               base::TimeDelta::FromMinutes(135), time_three,
+                               time_three, timezone.get(), state_two);
 
   State expected_state_three;
   expected_state_three.is_locked = true;
@@ -1292,8 +1321,9 @@
 
   // Check that the device is unlocked because of the unlock override.
   base::Time time_one = utils::TimeFromString("Tue, 2 Jan 2018 22:45 BRT");
-  State state_one = GetState(policy, base::TimeDelta::FromHours(2), time_one,
-                             time_one, timezone.get(), base::nullopt);
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromHours(2), time_one, time_one,
+                             timezone.get(), base::nullopt);
 
   State expected_state_one;
   expected_state_one.is_locked = false;
@@ -1307,8 +1337,9 @@
 
   // Check that the device is locked after the duration.
   base::Time time_two = utils::TimeFromString("Tue, 2 Jan 2018 23:00 BRT");
-  State state_two = GetState(policy, base::TimeDelta::FromHours(2), time_two,
-                             time_two, timezone.get(), base::nullopt);
+  State state_two = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromHours(2), time_two, time_two,
+                             timezone.get(), base::nullopt);
 
   State expected_state_two;
   expected_state_two.is_locked = true;
@@ -1338,8 +1369,9 @@
 
   // Check that the device is unlocked because of the usage time.
   base::Time time_one = utils::TimeFromString("Mon, 1 Jan 2018 10:00 PST");
-  State state_one = GetState(policy, base::TimeDelta::FromHours(2), time_one,
-                             time_one, timezone.get(), base::nullopt);
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromHours(2), time_one, time_one,
+                             timezone.get(), base::nullopt);
 
   State expected_state_one;
   expected_state_one.is_locked = true;
@@ -1361,8 +1393,9 @@
       utils::TimeFromString("Mon, 1 Jan 2018 10:30 PST"),
       base::TimeDelta::FromHours(2));
   base::Time time_two = utils::TimeFromString("Mon, 1 Jan 2018 11:30 PST");
-  State state_two = GetState(policy, base::TimeDelta::FromHours(3), time_two,
-                             time_two, timezone.get(), state_one);
+  State state_two = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromHours(3), time_two, time_two,
+                             timezone.get(), state_one);
 
   State expected_state_two;
   expected_state_two.is_locked = false;
@@ -1378,9 +1411,9 @@
 
   // Check that the device is locked after the duration.
   base::Time time_three = utils::TimeFromString("Mon, 1 Jan 2018 12:30 PST");
-  State state_three =
-      GetState(policy, base::TimeDelta::FromHours(4), time_three, time_three,
-               timezone.get(), state_two);
+  State state_three = GetState(policy, nullptr /* local_override */,
+                               base::TimeDelta::FromHours(4), time_three,
+                               time_three, timezone.get(), state_two);
 
   State expected_state_three;
   expected_state_three.is_locked = true;
@@ -1411,8 +1444,9 @@
 
   /// Check that the device is unlocked because of the usage time.
   base::Time time_one = utils::TimeFromString("Sat, 6 Jan 2018 9:45 BRT");
-  State state_one = GetState(policy, base::TimeDelta::FromMinutes(105),
-                             time_one, time_one, timezone.get(), base::nullopt);
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(105), time_one,
+                             time_one, timezone.get(), base::nullopt);
 
   State expected_state_one;
   expected_state_one.is_locked = false;
@@ -1431,8 +1465,9 @@
       utils::TimeFromString("Sat, 6 Jan 2018 9:45 BRT"),
       base::TimeDelta::FromMinutes(30));
   base::Time time_two = utils::TimeFromString("Sat, 6 Jan 2018 10:00 BRT");
-  State state_two = GetState(policy, base::TimeDelta::FromHours(2), time_two,
-                             time_two, timezone.get(), state_one);
+  State state_two = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromHours(2), time_two, time_two,
+                             timezone.get(), state_one);
 
   State expected_state_two;
   expected_state_two.is_locked = false;
@@ -1452,9 +1487,9 @@
       utils::TimeFromString("Sat, 6 Jan 2018 10:15 BRT"),
       base::TimeDelta::FromMinutes(30));
   base::Time time_three = utils::TimeFromString("Sat, 6 Jan 2018 10:30 BRT");
-  State state_three =
-      GetState(policy, base::TimeDelta::FromMinutes(150), time_three,
-               time_three, timezone.get(), state_two);
+  State state_three = GetState(policy, nullptr /* local_override */,
+                               base::TimeDelta::FromMinutes(150), time_three,
+                               time_three, timezone.get(), state_two);
 
   State expected_state_three;
   expected_state_three.is_locked = false;
@@ -1489,7 +1524,8 @@
 
   // Check that the device is unlocked because of the override.
   base::Time time_one = utils::TimeFromString("Wed, 3 Jan 2018 22:00 GMT");
-  State state_one = GetState(policy, base::TimeDelta::FromMinutes(60), time_one,
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(60), time_one,
                              time_one, timezone.get(), base::nullopt);
 
   State expected_state_one;
@@ -1507,7 +1543,8 @@
   utils::AddTimeWindowLimit(policy.get(), utils::kWednesday,
                             utils::CreateTime(23, 0), utils::CreateTime(10, 0),
                             time_two);
-  State state_two = GetState(policy, base::TimeDelta::FromMinutes(60), time_two,
+  State state_two = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(60), time_two,
                              time_two, timezone.get(), base::nullopt);
 
   State expected_state_two;
@@ -1536,8 +1573,9 @@
 
   // Check that the device is unlocked because of the usage time.
   base::Time time_one = utils::TimeFromString("Sun, 7 Jan 2018 9:45 PST");
-  State state_one = GetState(policy, base::TimeDelta::FromMinutes(105),
-                             time_one, time_one, timezone.get(), base::nullopt);
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromMinutes(105), time_one,
+                             time_one, timezone.get(), base::nullopt);
 
   State expected_state_one;
   expected_state_one.is_locked = false;
@@ -1556,8 +1594,9 @@
       utils::TimeFromString("Sun, 7 Jan 2018 9:45 PST"),
       base::TimeDelta::FromMinutes(30));
   base::Time time_two = utils::TimeFromString("Sun, 7 Jan 2018 10:00 PST");
-  State state_two = GetState(policy, base::TimeDelta::FromHours(2), time_two,
-                             time_two, timezone.get(), state_one);
+  State state_two = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromHours(2), time_two, time_two,
+                             timezone.get(), state_one);
 
   State expected_state_two;
   expected_state_two.is_locked = false;
@@ -1575,9 +1614,9 @@
   base::Time time_three = utils::TimeFromString("Sun, 7 Jan 2018 10:15 PST");
   utils::AddTimeUsageLimit(policy.get(), utils::kSunday,
                            base::TimeDelta::FromHours(3), time_three);
-  State state_three =
-      GetState(policy, base::TimeDelta::FromHours(2), time_three, time_three,
-               timezone.get(), state_two);
+  State state_three = GetState(policy, nullptr /* local_override */,
+                               base::TimeDelta::FromHours(2), time_three,
+                               time_three, timezone.get(), state_two);
 
   State expected_state_three;
   expected_state_three.is_locked = false;
@@ -1605,8 +1644,9 @@
 
   // Check that the device is unlocked because of the override.
   base::Time time_one = utils::TimeFromString("Wed, 3 Jan 2018 14:00 BRT");
-  State state_one = GetState(policy, base::TimeDelta::FromHours(2), time_one,
-                             time_one, timezone.get(), base::nullopt);
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromHours(2), time_one, time_one,
+                             timezone.get(), base::nullopt);
 
   State expected_state_one;
   expected_state_one.is_locked = true;
@@ -1628,8 +1668,9 @@
 
   // Check that the device is unlocked.
   base::Time time_two = utils::TimeFromString("Wed, 3 Jan 2018 15:00 BRT");
-  State state_two = GetState(policy, base::TimeDelta::FromHours(2), time_two,
-                             time_two, timezone.get(), state_one);
+  State state_two = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromHours(2), time_two, time_two,
+                             timezone.get(), state_one);
 
   State expected_state_two;
   expected_state_two.is_locked = false;
@@ -1648,9 +1689,9 @@
 
   // Check that the device is locked because of the usage limit.
   base::Time time_three = utils::TimeFromString("Wed, 3 Jan 2018 16:00 BRT");
-  State state_three =
-      GetState(policy, base::TimeDelta::FromHours(2), time_three, time_three,
-               timezone.get(), state_two);
+  State state_three = GetState(policy, nullptr /* local_override */,
+                               base::TimeDelta::FromHours(2), time_three,
+                               time_three, timezone.get(), state_two);
 
   State expected_state_three;
   expected_state_three.is_locked = true;
@@ -1674,7 +1715,8 @@
 
   // Check that the device is locked because of the bedtime.
   base::Time time_four = utils::TimeFromString("Wed, 3 Jan 2018 17:00 BRT");
-  State state_four = GetState(policy, base::TimeDelta::FromHours(2), time_four,
+  State state_four = GetState(policy, nullptr /* local_override */,
+                              base::TimeDelta::FromHours(2), time_four,
                               time_four, timezone.get(), state_two);
 
   State expected_state_four;
@@ -1708,8 +1750,9 @@
 
   // Check that the device is locked.
   base::Time time_one = utils::TimeFromString("Wed, 3 Jan 2018 7:00 BRT");
-  State state_one = GetState(policy, base::TimeDelta::FromHours(0), time_one,
-                             time_one, timezone.get(), base::nullopt);
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromHours(0), time_one, time_one,
+                             timezone.get(), base::nullopt);
 
   State expected_state_one;
   expected_state_one.is_locked = true;
@@ -1730,8 +1773,9 @@
 
   // Check that the device is unlocked because of the override.
   base::Time time_two = utils::TimeFromString("Wed, 3 Jan 2018 8:00 BRT");
-  State state_two = GetState(policy, base::TimeDelta::FromHours(0), time_two,
-                             time_two, timezone.get(), state_one);
+  State state_two = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromHours(0), time_two, time_two,
+                             timezone.get(), state_one);
 
   State expected_state_two;
   expected_state_two.is_locked = false;
@@ -1747,9 +1791,9 @@
 
   // Check that the device is locked.
   base::Time time_three = utils::TimeFromString("Thu, 4 Jan 2018 8:00 BRT");
-  State state_three =
-      GetState(policy, base::TimeDelta::FromHours(0), time_three, time_three,
-               timezone.get(), state_two);
+  State state_three = GetState(policy, nullptr /* local_override */,
+                               base::TimeDelta::FromHours(0), time_three,
+                               time_three, timezone.get(), state_two);
 
   State expected_state_three;
   expected_state_three.is_locked = true;
@@ -1767,7 +1811,8 @@
 
   // Check that the device is locked.
   base::Time time_four = utils::TimeFromString("Fri, 5 Jan 2018 8:00 BRT");
-  State state_four = GetState(policy, base::TimeDelta::FromHours(0), time_four,
+  State state_four = GetState(policy, nullptr /* local_override */,
+                              base::TimeDelta::FromHours(0), time_four,
                               time_four, timezone.get(), state_three);
 
   State expected_state_four;
@@ -1787,7 +1832,8 @@
 
   // Check that the device is unlocked.
   base::Time time_five = utils::TimeFromString("Sat, 6 Jan 2018 6:00 BRT");
-  State state_five = GetState(policy, base::TimeDelta::FromHours(0), time_five,
+  State state_five = GetState(policy, nullptr /* local_override */,
+                              base::TimeDelta::FromHours(0), time_five,
                               time_five, timezone.get(), state_four);
 
   State expected_state_five;
@@ -1816,8 +1862,9 @@
 
   // Check that the device is locked.
   base::Time time_one = utils::TimeFromString("Wed, 3 Jan 2018 7:00 BRT");
-  State state_one = GetState(policy, base::TimeDelta::FromHours(0), time_one,
-                             time_one, timezone.get(), base::nullopt);
+  State state_one = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromHours(0), time_one, time_one,
+                             timezone.get(), base::nullopt);
 
   State expected_state_one;
   expected_state_one.is_locked = true;
@@ -1835,8 +1882,9 @@
 
   // Check that the device is locked.
   base::Time time_two = utils::TimeFromString("Thu, 4 Jan 2018 6:00 BRT");
-  State state_two = GetState(policy, base::TimeDelta::FromHours(0), time_two,
-                             time_two, timezone.get(), state_one);
+  State state_two = GetState(policy, nullptr /* local_override */,
+                             base::TimeDelta::FromHours(0), time_two, time_two,
+                             timezone.get(), state_one);
 
   State expected_state_two;
   expected_state_two.is_locked = true;
@@ -1857,9 +1905,9 @@
 
   // Check that the device is unlocked.
   base::Time time_three = utils::TimeFromString("Thu, 4 Jan 2018 8:00 BRT");
-  State state_three =
-      GetState(policy, base::TimeDelta::FromHours(0), time_three, time_three,
-               timezone.get(), state_two);
+  State state_three = GetState(policy, nullptr /* local_override */,
+                               base::TimeDelta::FromHours(0), time_three,
+                               time_three, timezone.get(), state_two);
 
   State expected_state_three;
   expected_state_three.is_locked = false;
@@ -1874,6 +1922,200 @@
   AssertEqState(expected_state_three, state_three);
 }
 
+// Tests that local override changes state to unlocked during window time limit.
+TEST_F(UsageTimeLimitProcessorTest, LocalOverrideAndWindowTimeLimit) {
+  // Window time limit active between 18:00 and 7:00.
+  const int kWindowStart = 18;
+  const int kWindowEnd = 7;
+
+  // Current time is during Monday's window time limit.
+  base::Time current_time;
+  ASSERT_TRUE(
+      base::Time::FromString("Mon, 1 Jan 2018 19:00 GMT", &current_time));
+
+  const base::Time last_updated = current_time - base::TimeDelta::FromHours(4);
+  auto policy = std::make_unique<base::DictionaryValue>();
+  utils::AddTimeWindowLimit(policy.get(), utils::kMonday,
+                            utils::CreateTime(kWindowStart, 0),
+                            utils::CreateTime(kWindowEnd, 0), last_updated);
+  utils::AddTimeWindowLimit(policy.get(), utils::kTuesday,
+                            utils::CreateTime(kWindowStart, 0),
+                            utils::CreateTime(kWindowEnd, 0), last_updated);
+
+  // Local override started before latest policy update - should be ignored.
+  base::Value inactive_local_override =
+      usage_time_limit::TimeLimitOverride(
+          usage_time_limit::TimeLimitOverride::Action::kUnlock,
+          last_updated - base::TimeDelta::FromMinutes(5),
+          base::nullopt /* duration */)
+          .ToDictionary();
+
+  State state = GetState(policy, &inactive_local_override,
+                         base::TimeDelta::FromMinutes(0), current_time,
+                         current_time, icu::TimeZone::createTimeZone("GMT"),
+                         base::nullopt /* previous_state */);
+
+  base::Time monday_bedtime_end;
+  ASSERT_TRUE(
+      base::Time::FromString("Tue, 2 Jan 2018 7:00 GMT", &monday_bedtime_end));
+
+  EXPECT_TRUE(state.is_locked);
+  EXPECT_EQ(ActivePolicies::kFixedLimit, state.active_policy);
+  EXPECT_EQ(ActivePolicies::kNoActivePolicy, state.next_state_active_policy);
+  EXPECT_EQ(monday_bedtime_end, state.next_state_change_time);
+  EXPECT_EQ(monday_bedtime_end, state.next_unlock_time);
+
+  // Local override started after last policy update - should take effect.
+  base::Value active_local_override =
+      usage_time_limit::TimeLimitOverride(
+          usage_time_limit::TimeLimitOverride::Action::kUnlock,
+          current_time - base::TimeDelta::FromMinutes(5),
+          base::nullopt /* duration */)
+          .ToDictionary();
+
+  state =
+      GetState(policy, &active_local_override, base::TimeDelta::FromMinutes(0),
+               current_time, current_time, icu::TimeZone::createTimeZone("GMT"),
+               base::nullopt /* previous_state */);
+
+  base::Time tuesday_bedtime_start;
+  ASSERT_TRUE(base::Time::FromString("Tue, 2 Jan 2018 18:00 GMT",
+                                     &tuesday_bedtime_start));
+
+  // Unlocked by local override.
+  EXPECT_FALSE(state.is_locked);
+  EXPECT_EQ(ActivePolicies::kOverride, state.active_policy);
+  EXPECT_EQ(ActivePolicies::kFixedLimit, state.next_state_active_policy);
+  EXPECT_EQ(tuesday_bedtime_start, state.next_state_change_time);
+  EXPECT_EQ(base::Time(),
+            state.next_unlock_time);  // Unlocked - no next unlock.
+}
+
+// Tests that local override changes state to unlocked when locked because of
+// time usage limit.
+TEST_F(UsageTimeLimitProcessorTest, LocalOverrideAndTimeUsageLimit) {
+  const base::TimeDelta kDailyLimit = base::TimeDelta::FromHours(2);
+
+  base::Time timestamp;
+  ASSERT_TRUE(base::Time::FromString("Mon, 1 Jan 2018 15:00 GMT", &timestamp));
+
+  const base::Time last_updated = timestamp - base::TimeDelta::FromHours(4);
+  std::unique_ptr<base::DictionaryValue> policy =
+      utils::CreateTimeLimitPolicy(utils::CreateTime(6, 0));
+  utils::AddTimeUsageLimit(policy.get(), utils::kMonday, kDailyLimit,
+                           last_updated);
+  utils::AddTimeUsageLimit(policy.get(), utils::kTuesday, kDailyLimit,
+                           last_updated);
+
+  base::Time next_day_reset;
+  ASSERT_TRUE(
+      base::Time::FromString("Mon, 2 Jan 2018 6:00 GMT", &next_day_reset));
+
+  // Previous state - locked by time usage limit.
+  State usage_limit_lock_state;
+  usage_limit_lock_state.is_locked = true;
+  usage_limit_lock_state.active_policy = ActivePolicies::kUsageLimit;
+  usage_limit_lock_state.next_state_active_policy =
+      ActivePolicies::kNoActivePolicy;
+  usage_limit_lock_state.is_time_usage_limit_enabled = true;
+  usage_limit_lock_state.remaining_usage = base::TimeDelta::FromMinutes(0);
+  usage_limit_lock_state.time_usage_limit_started = timestamp;
+  usage_limit_lock_state.next_unlock_time = next_day_reset;
+  usage_limit_lock_state.next_state_change_time = next_day_reset;
+
+  // Local override started before usage time limit - should be ignored.
+  base::Value inactive_local_override =
+      usage_time_limit::TimeLimitOverride(
+          usage_time_limit::TimeLimitOverride::Action::kUnlock,
+          timestamp - base::TimeDelta::FromMinutes(5),
+          base::nullopt /* duration */)
+          .ToDictionary();
+
+  const base::Time current_time = timestamp + base::TimeDelta::FromMinutes(10);
+
+  State state = GetState(
+      policy, &inactive_local_override, kDailyLimit, current_time, current_time,
+      icu::TimeZone::createTimeZone("GMT"), usage_limit_lock_state);
+
+  // State did not change from previous state - time usage lock still active.
+  AssertEqState(usage_limit_lock_state, state);
+
+  // Local override that started after usage time limit - should take effect.
+  base::Value active_local_override =
+      usage_time_limit::TimeLimitOverride(
+          usage_time_limit::TimeLimitOverride::Action::kUnlock, current_time,
+          base::nullopt /* duration */)
+          .ToDictionary();
+
+  state = GetState(policy, &active_local_override, kDailyLimit, current_time,
+                   current_time, icu::TimeZone::createTimeZone("GMT"),
+                   usage_limit_lock_state);
+
+  // Unlocked by local override.
+  EXPECT_FALSE(state.is_locked);
+  EXPECT_EQ(ActivePolicies::kOverride, state.active_policy);
+  EXPECT_EQ(ActivePolicies::kUsageLimit, state.next_state_active_policy);
+  EXPECT_EQ(next_day_reset + kDailyLimit, state.next_state_change_time);
+  EXPECT_EQ(base::Time(),
+            state.next_unlock_time);  // Unlocked - no next unlock.
+}
+
+// Tests that local override changes state to unlocked when locked by remote
+// override.
+TEST_F(UsageTimeLimitProcessorTest, LocalOverrideAndRemoteOverride) {
+  base::Time current_time;
+  ASSERT_TRUE(
+      base::Time::FromString("Mon, 1 Jan 2018 15:00 GMT", &current_time));
+
+  std::unique_ptr<base::DictionaryValue> policy =
+      std::make_unique<base::DictionaryValue>(base::DictionaryValue());
+  utils::AddOverride(policy.get(), TimeLimitOverride::Action::kLock,
+                     current_time - base::TimeDelta::FromHours(1));
+
+  // Local override started before latest policy update - should be ignored.
+  base::Value inactive_local_override =
+      usage_time_limit::TimeLimitOverride(
+          usage_time_limit::TimeLimitOverride::Action::kUnlock,
+          current_time - base::TimeDelta::FromHours(2),
+          base::nullopt /* duration */)
+          .ToDictionary();
+
+  State state = GetState(policy, &inactive_local_override,
+                         base::TimeDelta::FromMinutes(0), current_time,
+                         current_time, icu::TimeZone::createTimeZone("GMT"),
+                         base::nullopt /* previous_state */);
+
+  base::Time next_day;
+  ASSERT_TRUE(base::Time::FromString("Mon, 2 Jan 2018 00:00 GMT", &next_day));
+
+  EXPECT_TRUE(state.is_locked);
+  EXPECT_EQ(ActivePolicies::kOverride, state.active_policy);
+  EXPECT_EQ(ActivePolicies::kNoActivePolicy, state.next_state_active_policy);
+  EXPECT_EQ(next_day, state.next_state_change_time);
+  EXPECT_EQ(next_day, state.next_unlock_time);
+
+  // Local override started after last policy update - should take effect.
+  base::Value active_local_override =
+      usage_time_limit::TimeLimitOverride(
+          usage_time_limit::TimeLimitOverride::Action::kUnlock,
+          current_time - base::TimeDelta::FromMinutes(5),
+          base::nullopt /* duration */)
+          .ToDictionary();
+
+  state =
+      GetState(policy, &active_local_override, base::TimeDelta::FromMinutes(0),
+               current_time, current_time, icu::TimeZone::createTimeZone("GMT"),
+               base::nullopt /* previous_state */);
+
+  // Unlocked by local override.
+  EXPECT_FALSE(state.is_locked);
+  EXPECT_EQ(ActivePolicies::kOverride, state.active_policy);
+  EXPECT_EQ(ActivePolicies::kNoActivePolicy, state.next_state_active_policy);
+  EXPECT_EQ(base::Time(), state.next_state_change_time);  // No next state
+  EXPECT_EQ(base::Time(),
+            state.next_unlock_time);  // Unlocked - no next unlock.
+}
+
 // Test GetExpectedResetTime with an empty policy.
 TEST_F(UsageTimeLimitProcessorTest, GetExpectedResetTimeWithEmptyPolicy) {
   std::unique_ptr<icu::TimeZone> timezone(icu::TimeZone::createTimeZone("GMT"));
@@ -1883,8 +2125,8 @@
       std::make_unique<base::DictionaryValue>(base::DictionaryValue());
 
   base::Time time_one = utils::TimeFromString("Mon, 1 Jan 2018 22:00");
-  base::Time reset_time =
-      GetExpectedResetTime(policy, time_one, timezone.get());
+  base::Time reset_time = GetExpectedResetTime(
+      policy, nullptr /* local_override */, time_one, timezone.get());
 
   ASSERT_EQ(reset_time, utils::TimeFromString("Tue, 2 Jan 2018 0:00"));
 }
@@ -1899,15 +2141,15 @@
 
   // Check that it resets in the same day.
   base::Time time_one = utils::TimeFromString("Tue, 2 Jan 2018 6:00 EST");
-  base::Time reset_time_one =
-      GetExpectedResetTime(policy, time_one, timezone.get());
+  base::Time reset_time_one = GetExpectedResetTime(
+      policy, nullptr /* local_override */, time_one, timezone.get());
 
   ASSERT_EQ(reset_time_one, utils::TimeFromString("Tue, 2 Jan 2018 8:00 EST"));
 
   // Checks that it resets on the following day.
   base::Time time_two = utils::TimeFromString("Tue, 2 Jan 2018 10:00 EST");
-  base::Time reset_time_two =
-      GetExpectedResetTime(policy, time_two, timezone.get());
+  base::Time reset_time_two = GetExpectedResetTime(
+      policy, nullptr /* local_override */, time_two, timezone.get());
 
   ASSERT_EQ(reset_time_two, utils::TimeFromString("Wed, 3 Jan 2018 8:00 EST"));
 }
@@ -1950,8 +2192,9 @@
       std::make_unique<base::DictionaryValue>(base::DictionaryValue());
 
   base::Time time_one = utils::TimeFromString("Mon, 1 Jan 2018 22:00");
-  base::Optional<base::TimeDelta> remaining_usage = GetRemainingTimeUsage(
-      policy, time_one, base::TimeDelta(), timezone.get());
+  base::Optional<base::TimeDelta> remaining_usage =
+      GetRemainingTimeUsage(policy, nullptr /* local_override */, time_one,
+                            base::TimeDelta(), timezone.get());
 
   ASSERT_EQ(remaining_usage, base::nullopt);
 }
@@ -1971,16 +2214,18 @@
 
   // Check that the remaining time is 2 hours.
   base::Time time_one = utils::TimeFromString("Wed, 3 Jan 2018 10:00 BRT");
-  base::Optional<base::TimeDelta> remaining_usage_one = GetRemainingTimeUsage(
-      policy, time_one, base::TimeDelta::FromHours(0), timezone.get());
+  base::Optional<base::TimeDelta> remaining_usage_one =
+      GetRemainingTimeUsage(policy, nullptr /* local_override */, time_one,
+                            base::TimeDelta::FromHours(0), timezone.get());
 
   ASSERT_FALSE(remaining_usage_one == base::nullopt);
   ASSERT_EQ(remaining_usage_one, base::TimeDelta::FromHours(2));
 
   // Check that remaining time changes to 1 hour if device was used for 1 hour.
   base::Time time_two = utils::TimeFromString("Wed, 3 Jan 2018 11:00 BRT");
-  base::Optional<base::TimeDelta> remaining_usage_two = GetRemainingTimeUsage(
-      policy, time_two, base::TimeDelta::FromHours(1), timezone.get());
+  base::Optional<base::TimeDelta> remaining_usage_two =
+      GetRemainingTimeUsage(policy, nullptr /* local_override */, time_two,
+                            base::TimeDelta::FromHours(1), timezone.get());
 
   ASSERT_FALSE(remaining_usage_two == base::nullopt);
   ASSERT_EQ(remaining_usage_two, base::TimeDelta::FromHours(1));
diff --git a/chrome/browser/chromeos/dbus/dbus_helper.cc b/chrome/browser/chromeos/dbus/dbus_helper.cc
index 9f13621..94afeab7 100644
--- a/chrome/browser/chromeos/dbus/dbus_helper.cc
+++ b/chrome/browser/chromeos/dbus/dbus_helper.cc
@@ -7,9 +7,11 @@
 #include "chrome/browser/chromeos/settings/device_settings_service.h"
 #include "chromeos/cryptohome/system_salt_getter.h"
 #include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/hammerd/hammerd_client.h"
 #include "chromeos/dbus/power_manager_client.h"
 #include "chromeos/dbus/system_clock/system_clock_client.h"
 #include "chromeos/tpm/install_attributes.h"
+#include "ui/base/ui_base_features.h"
 
 namespace chromeos {
 
@@ -21,6 +23,13 @@
 
   // Initialize Chrome dbus clients.
   dbus::Bus* bus = DBusThreadManager::Get()->GetSystemBus();
+
+  // Features only needed in Ash. Initialize them here for non MultiProcessMash
+  // to limit the number of places where dbus handlers are initialized. For
+  // MultiProcessMash they are initialized in AshService::InitializeDBusClients.
+  if (!::features::IsMultiProcessMash())
+    chromeos::HammerdClient::Initialize(bus);
+
   PowerManagerClient::Initialize(bus);
   SystemClockClient::Initialize(bus);
 
@@ -32,9 +41,13 @@
 }
 
 void ShutdownDBus() {
-  // NOTE: These must only be called if InitializeDBus() was called.
-  PowerManagerClient::Shutdown();
   SystemClockClient::Shutdown();
+  PowerManagerClient::Shutdown();
+
+  // See comment in InitializeDBus() for MultiProcessMash behavior.
+  if (!::features::IsMultiProcessMash())
+    chromeos::HammerdClient::Shutdown();
+
   DBusThreadManager::Shutdown();
   SystemSaltGetter::Shutdown();
 }
diff --git a/chrome/browser/chromeos/events/event_rewriter_unittest.cc b/chrome/browser/chromeos/events/event_rewriter_unittest.cc
index dd1fb78..91d0d16 100644
--- a/chrome/browser/chromeos/events/event_rewriter_unittest.cc
+++ b/chrome/browser/chromeos/events/event_rewriter_unittest.cc
@@ -706,13 +706,6 @@
   TestRewriteNumPadKeys();
 }
 
-TEST_F(EventRewriterTest, TestRewriteNumPadKeysWithDiamondKeyFlag) {
-  // Make sure the num lock works correctly even when Diamond key exists.
-  base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
-      chromeos::switches::kHasChromeOSDiamondKey, "");
-  TestRewriteNumPadKeys();
-}
-
 // Tests if the rewriter can handle a Command + Num Pad event.
 void EventRewriterTest::TestRewriteNumPadKeysOnAppleKeyboard() {
   // Simulate the default initialization of the Apple Command key remap pref to
@@ -748,17 +741,6 @@
   TestRewriteNumPadKeysOnAppleKeyboard();
 }
 
-TEST_F(EventRewriterTest,
-       TestRewriteNumPadKeysOnAppleKeyboardWithDiamondKeyFlag) {
-  // Makes sure the num lock works correctly even when Diamond key exists.
-  const base::CommandLine original_cl(*base::CommandLine::ForCurrentProcess());
-  base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
-      chromeos::switches::kHasChromeOSDiamondKey, "");
-
-  TestRewriteNumPadKeysOnAppleKeyboard();
-  *base::CommandLine::ForCurrentProcess() = original_cl;
-}
-
 TEST_F(EventRewriterTest, TestRewriteModifiersNoRemap) {
   rewriter_->KeyboardDeviceAddedForTesting(kKeyboardDeviceId, "PC Keyboard");
 
@@ -1316,194 +1298,6 @@
                                       ui::EF_NONE, ui::DomKey::CAPS_LOCK));
 }
 
-TEST_F(EventRewriterTest, TestRewriteDiamondKey) {
-  chromeos::Preferences::RegisterProfilePrefs(prefs()->registry());
-
-  chromeos::input_method::FakeImeKeyboard ime_keyboard;
-  rewriter_->KeyboardDeviceAddedForTesting(kKeyboardDeviceId, "PC Keyboard");
-  rewriter_->set_ime_keyboard_for_testing(&ime_keyboard);
-
-  KeyTestCase tests[] = {
-      // F15 should work as Ctrl when --has-chromeos-diamond-key is not
-      // specified.
-      {ui::ET_KEY_PRESSED,
-       {ui::VKEY_F15, ui::DomCode::F15, ui::EF_NONE, ui::DomKey::F15},
-       {ui::VKEY_CONTROL, ui::DomCode::CONTROL_LEFT, ui::EF_CONTROL_DOWN,
-        ui::DomKey::CONTROL}},
-
-      {ui::ET_KEY_RELEASED,
-       {ui::VKEY_F15, ui::DomCode::F15, ui::EF_NONE, ui::DomKey::F15},
-       {ui::VKEY_CONTROL, ui::DomCode::CONTROL_LEFT, ui::EF_NONE,
-        ui::DomKey::CONTROL}},
-
-      // However, Mod2Mask should not be rewritten to CtrlMask when
-      // --has-chromeos-diamond-key is not specified.
-      {ui::ET_KEY_PRESSED,
-       {ui::VKEY_A, ui::DomCode::US_A, ui::EF_NONE,
-        ui::DomKey::Constant<'a'>::Character},
-       {ui::VKEY_A, ui::DomCode::US_A, ui::EF_NONE,
-        ui::DomKey::Constant<'a'>::Character}},
-  };
-
-  for (const auto& test : tests)
-    CheckKeyTestCase(rewriter_, test);
-}
-
-TEST_F(EventRewriterTest, TestRewriteDiamondKeyWithFlag) {
-  const base::CommandLine original_cl(*base::CommandLine::ForCurrentProcess());
-  base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
-      chromeos::switches::kHasChromeOSDiamondKey, "");
-
-  chromeos::Preferences::RegisterProfilePrefs(prefs()->registry());
-
-  chromeos::input_method::FakeImeKeyboard ime_keyboard;
-  rewriter_->KeyboardDeviceAddedForTesting(kKeyboardDeviceId, "PC Keyboard");
-  rewriter_->set_ime_keyboard_for_testing(&ime_keyboard);
-
-  // By default, F15 should work as Control.
-  EXPECT_EQ(GetExpectedResultAsString(ui::ET_KEY_PRESSED, ui::VKEY_CONTROL,
-                                      ui::DomCode::CONTROL_LEFT,
-                                      ui::EF_CONTROL_DOWN, ui::DomKey::CONTROL),
-            GetRewrittenEventAsString(rewriter_, ui::ET_KEY_PRESSED,
-                                      ui::VKEY_F15, ui::DomCode::F15,
-                                      ui::EF_NONE, ui::DomKey::F15));
-  // Check that Control is applied to a subsequent key press.
-  EXPECT_EQ(GetExpectedResultAsString(ui::ET_KEY_PRESSED, ui::VKEY_A,
-                                      ui::DomCode::US_A, ui::EF_CONTROL_DOWN,
-                                      ui::DomKey::Constant<'a'>::Character),
-            GetRewrittenEventAsString(rewriter_, ui::ET_KEY_PRESSED, ui::VKEY_A,
-                                      ui::DomCode::US_A, ui::EF_NONE,
-                                      ui::DomKey::Constant<'a'>::Character));
-  // Release F15
-  EXPECT_EQ(GetExpectedResultAsString(ui::ET_KEY_RELEASED, ui::VKEY_CONTROL,
-                                      ui::DomCode::CONTROL_LEFT, ui::EF_NONE,
-                                      ui::DomKey::CONTROL),
-            GetRewrittenEventAsString(rewriter_, ui::ET_KEY_RELEASED,
-                                      ui::VKEY_F15, ui::DomCode::F15,
-                                      ui::EF_NONE, ui::DomKey::F15));
-  // Check that Control is no longer applied to a subsequent key press.
-  EXPECT_EQ(GetExpectedResultAsString(ui::ET_KEY_PRESSED, ui::VKEY_A,
-                                      ui::DomCode::US_A, ui::EF_NONE,
-                                      ui::DomKey::Constant<'a'>::Character),
-            GetRewrittenEventAsString(rewriter_, ui::ET_KEY_PRESSED, ui::VKEY_A,
-                                      ui::DomCode::US_A, ui::EF_NONE,
-                                      ui::DomKey::Constant<'a'>::Character));
-
-  IntegerPrefMember diamond;
-  InitModifierKeyPref(&diamond, prefs::kLanguageRemapDiamondKeyTo,
-                      ui::chromeos::ModifierKey::kVoidKey);
-
-  EXPECT_EQ(GetExpectedResultAsString(ui::ET_KEY_PRESSED, ui::VKEY_UNKNOWN,
-                                      ui::DomCode::NONE, ui::EF_NONE,
-                                      ui::DomKey::UNIDENTIFIED),
-            GetRewrittenEventAsString(rewriter_, ui::ET_KEY_PRESSED,
-                                      ui::VKEY_F15, ui::DomCode::F15,
-                                      ui::EF_NONE, ui::DomKey::F15));
-  // Check that no modifier is applied to another key.
-  EXPECT_EQ(GetExpectedResultAsString(ui::ET_KEY_PRESSED, ui::VKEY_A,
-                                      ui::DomCode::US_A, ui::EF_NONE,
-                                      ui::DomKey::Constant<'a'>::Character),
-            GetRewrittenEventAsString(rewriter_, ui::ET_KEY_PRESSED, ui::VKEY_A,
-                                      ui::DomCode::US_A, ui::EF_NONE,
-                                      ui::DomKey::Constant<'a'>::Character));
-
-  InitModifierKeyPref(&diamond, prefs::kLanguageRemapDiamondKeyTo,
-                      ui::chromeos::ModifierKey::kControlKey);
-
-  EXPECT_EQ(GetExpectedResultAsString(ui::ET_KEY_PRESSED, ui::VKEY_CONTROL,
-                                      ui::DomCode::CONTROL_LEFT,
-                                      ui::EF_CONTROL_DOWN, ui::DomKey::CONTROL),
-            GetRewrittenEventAsString(rewriter_, ui::ET_KEY_PRESSED,
-                                      ui::VKEY_F15, ui::DomCode::F15,
-                                      ui::EF_NONE, ui::DomKey::F15));
-  // Check that Control is applied to a subsequent key press.
-  EXPECT_EQ(GetExpectedResultAsString(ui::ET_KEY_PRESSED, ui::VKEY_A,
-                                      ui::DomCode::US_A, ui::EF_CONTROL_DOWN,
-                                      ui::DomKey::Constant<'a'>::Character),
-            GetRewrittenEventAsString(rewriter_, ui::ET_KEY_PRESSED, ui::VKEY_A,
-                                      ui::DomCode::US_A, ui::EF_NONE,
-                                      ui::DomKey::Constant<'a'>::Character));
-  // Release F15
-  EXPECT_EQ(GetExpectedResultAsString(ui::ET_KEY_RELEASED, ui::VKEY_CONTROL,
-                                      ui::DomCode::CONTROL_LEFT, ui::EF_NONE,
-                                      ui::DomKey::CONTROL),
-            GetRewrittenEventAsString(rewriter_, ui::ET_KEY_RELEASED,
-                                      ui::VKEY_F15, ui::DomCode::F15,
-                                      ui::EF_NONE, ui::DomKey::F15));
-  // Check that Control is no longer applied to a subsequent key press.
-  EXPECT_EQ(GetExpectedResultAsString(ui::ET_KEY_PRESSED, ui::VKEY_A,
-                                      ui::DomCode::US_A, ui::EF_NONE,
-                                      ui::DomKey::Constant<'a'>::Character),
-            GetRewrittenEventAsString(rewriter_, ui::ET_KEY_PRESSED, ui::VKEY_A,
-                                      ui::DomCode::US_A, ui::EF_NONE,
-                                      ui::DomKey::Constant<'a'>::Character));
-
-  InitModifierKeyPref(&diamond, prefs::kLanguageRemapDiamondKeyTo,
-                      ui::chromeos::ModifierKey::kAltKey);
-
-  EXPECT_EQ(GetExpectedResultAsString(ui::ET_KEY_PRESSED, ui::VKEY_MENU,
-                                      ui::DomCode::ALT_LEFT, ui::EF_ALT_DOWN,
-                                      ui::DomKey::ALT),
-            GetRewrittenEventAsString(rewriter_, ui::ET_KEY_PRESSED,
-                                      ui::VKEY_F15, ui::DomCode::F15,
-                                      ui::EF_NONE, ui::DomKey::F15));
-  // Check that Alt is applied to a subsequent key press.
-  EXPECT_EQ(GetExpectedResultAsString(ui::ET_KEY_PRESSED, ui::VKEY_A,
-                                      ui::DomCode::US_A, ui::EF_ALT_DOWN,
-                                      ui::DomKey::Constant<'a'>::Character),
-            GetRewrittenEventAsString(rewriter_, ui::ET_KEY_PRESSED, ui::VKEY_A,
-                                      ui::DomCode::US_A, ui::EF_NONE,
-                                      ui::DomKey::Constant<'a'>::Character));
-  // Release F15
-  EXPECT_EQ(GetExpectedResultAsString(ui::ET_KEY_RELEASED, ui::VKEY_MENU,
-                                      ui::DomCode::ALT_LEFT, ui::EF_NONE,
-                                      ui::DomKey::ALT),
-            GetRewrittenEventAsString(rewriter_, ui::ET_KEY_RELEASED,
-                                      ui::VKEY_F15, ui::DomCode::F15,
-                                      ui::EF_NONE, ui::DomKey::F15));
-  // Check that Alt is no longer applied to a subsequent key press.
-  EXPECT_EQ(GetExpectedResultAsString(ui::ET_KEY_PRESSED, ui::VKEY_A,
-                                      ui::DomCode::US_A, ui::EF_NONE,
-                                      ui::DomKey::Constant<'a'>::Character),
-            GetRewrittenEventAsString(rewriter_, ui::ET_KEY_PRESSED, ui::VKEY_A,
-                                      ui::DomCode::US_A, ui::EF_NONE,
-                                      ui::DomKey::Constant<'a'>::Character));
-
-  InitModifierKeyPref(&diamond, prefs::kLanguageRemapDiamondKeyTo,
-                      ui::chromeos::ModifierKey::kCapsLockKey);
-
-  EXPECT_EQ(GetExpectedResultAsString(
-                ui::ET_KEY_PRESSED, ui::VKEY_CAPITAL, ui::DomCode::CAPS_LOCK,
-                ui::EF_CAPS_LOCK_ON | ui::EF_MOD3_DOWN, ui::DomKey::CAPS_LOCK),
-            GetRewrittenEventAsString(rewriter_, ui::ET_KEY_PRESSED,
-                                      ui::VKEY_F15, ui::DomCode::F15,
-                                      ui::EF_NONE, ui::DomKey::F15));
-  // Check that Caps is applied to a subsequent key press.
-  EXPECT_EQ(GetExpectedResultAsString(ui::ET_KEY_PRESSED, ui::VKEY_A,
-                                      ui::DomCode::US_A,
-                                      ui::EF_CAPS_LOCK_ON | ui::EF_MOD3_DOWN,
-                                      ui::DomKey::Constant<'A'>::Character),
-            GetRewrittenEventAsString(rewriter_, ui::ET_KEY_PRESSED, ui::VKEY_A,
-                                      ui::DomCode::US_A, ui::EF_NONE,
-                                      ui::DomKey::Constant<'a'>::Character));
-  // Release F15
-  EXPECT_EQ(GetExpectedResultAsString(ui::ET_KEY_RELEASED, ui::VKEY_CAPITAL,
-                                      ui::DomCode::CAPS_LOCK, ui::EF_NONE,
-                                      ui::DomKey::CAPS_LOCK),
-            GetRewrittenEventAsString(rewriter_, ui::ET_KEY_RELEASED,
-                                      ui::VKEY_F15, ui::DomCode::F15,
-                                      ui::EF_NONE, ui::DomKey::F15));
-  // Check that Control is no longer applied to a subsequent key press.
-  EXPECT_EQ(GetExpectedResultAsString(ui::ET_KEY_PRESSED, ui::VKEY_A,
-                                      ui::DomCode::US_A, ui::EF_NONE,
-                                      ui::DomKey::Constant<'a'>::Character),
-            GetRewrittenEventAsString(rewriter_, ui::ET_KEY_PRESSED, ui::VKEY_A,
-                                      ui::DomCode::US_A, ui::EF_NONE,
-                                      ui::DomKey::Constant<'a'>::Character));
-
-  *base::CommandLine::ForCurrentProcess() = original_cl;
-}
-
 TEST_F(EventRewriterTest, TestRewriteCapsLockToControl) {
   // Remap CapsLock to Control.
   chromeos::Preferences::RegisterProfilePrefs(prefs()->registry());
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
index 2edcaf5..bd7ce7b47 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest.cc
@@ -931,7 +931,8 @@
         TestCase("myFilesFolderRename"),
         TestCase("myFilesFolderRename").EnableMyFilesVolume(),
         TestCase("myFilesUpdatesChildren"),
-        TestCase("myFilesUpdatesChildren").EnableMyFilesVolume()));
+        TestCase("myFilesUpdatesChildren").EnableMyFilesVolume(),
+        TestCase("myFilesExpandWhenSelected").EnableMyFilesVolume()));
 
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     InstallLinuxPackageDialog, /* install_linux_package_dialog.js */
diff --git a/chrome/browser/chromeos/login/auth/cryptohome_authenticator_unittest.cc b/chrome/browser/chromeos/login/auth/cryptohome_authenticator_unittest.cc
index dd997502..9866bf8 100644
--- a/chrome/browser/chromeos/login/auth/cryptohome_authenticator_unittest.cc
+++ b/chrome/browser/chromeos/login/auth/cryptohome_authenticator_unittest.cc
@@ -159,6 +159,10 @@
     remove_ex_should_succeed_ = should_succeed;
   }
 
+  void set_unmount_ex_should_succeed(bool should_succeed) {
+    unmount_ex_should_succeed_ = should_succeed;
+  }
+
   void MountEx(const cryptohome::AccountIdentifier& cryptohome_id,
                const cryptohome::AuthorizationRequest& auth,
                const cryptohome::MountRequest& request,
@@ -230,6 +234,16 @@
         FROM_HERE, base::BindOnce(std::move(callback), reply));
   }
 
+  void UnmountEx(const cryptohome::UnmountRequest& account,
+                 DBusMethodCallback<cryptohome::BaseReply> callback) override {
+    cryptohome::BaseReply reply;
+    if (!unmount_ex_should_succeed_)
+      reply.set_error(cryptohome::CRYPTOHOME_ERROR_REMOVE_FAILED);
+
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), reply));
+  }
+
  private:
   cryptohome::AccountIdentifier expected_id_;
   std::string expected_authorization_secret_;
@@ -237,6 +251,7 @@
   bool migrate_key_should_succeed_ = false;
   bool mount_guest_should_succeed_ = false;
   bool remove_ex_should_succeed_ = false;
+  bool unmount_ex_should_succeed_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(TestCryptohomeClient);
 };
@@ -582,7 +597,7 @@
   EXPECT_TRUE(LoginState::Get()->IsInSafeMode());
 
   // Unset global objects used by this test.
-  fake_cryptohome_client_->set_unmount_result(true);
+  fake_cryptohome_client_->set_unmount_ex_should_succeed(true);
   LoginState::Shutdown();
 }
 
@@ -631,7 +646,7 @@
   EXPECT_TRUE(LoginState::Get()->IsInSafeMode());
 
   // Unset global objects used by this test.
-  fake_cryptohome_client_->set_unmount_result(true);
+  fake_cryptohome_client_->set_unmount_ex_should_succeed(true);
   LoginState::Shutdown();
 }
 
diff --git a/chrome/browser/chromeos/login/chrome_restart_request.cc b/chrome/browser/chromeos/login/chrome_restart_request.cc
index 90152c59b..b6e836d 100644
--- a/chrome/browser/chromeos/login/chrome_restart_request.cc
+++ b/chrome/browser/chromeos/login/chrome_restart_request.cc
@@ -202,7 +202,6 @@
     chromeos::switches::kEnableArc,
     chromeos::switches::kEnterpriseDisableArc,
     chromeos::switches::kEnterpriseEnableForcedReEnrollment,
-    chromeos::switches::kHasChromeOSDiamondKey,
     chromeos::switches::kHasChromeOSKeyboard,
     chromeos::switches::kLoginProfile,
     chromeos::switches::kNaturalScrollDefault,
diff --git a/chrome/browser/chromeos/login/demo_mode/demo_setup_browsertest.cc b/chrome/browser/chromeos/login/demo_mode/demo_setup_browsertest.cc
index 2e0d8882..60130272 100644
--- a/chrome/browser/chromeos/login/demo_mode/demo_setup_browsertest.cc
+++ b/chrome/browser/chromeos/login/demo_mode/demo_setup_browsertest.cc
@@ -24,7 +24,6 @@
 #include "chrome/browser/chromeos/login/oobe_screen.h"
 #include "chrome/browser/chromeos/login/screens/demo_setup_screen.h"
 #include "chrome/browser/chromeos/login/screens/network_screen.h"
-#include "chrome/browser/chromeos/login/screens/screen_exit_code.h"
 #include "chrome/browser/chromeos/login/startup_utils.h"
 #include "chrome/browser/chromeos/login/test/enrollment_helper_mixin.h"
 #include "chrome/browser/chromeos/login/test/js_checker.h"
diff --git a/chrome/browser/chromeos/login/login_utils_browsertest.cc b/chrome/browser/chromeos/login/login_utils_browsertest.cc
index 12c1ebe..b4483e7 100644
--- a/chrome/browser/chromeos/login/login_utils_browsertest.cc
+++ b/chrome/browser/chromeos/login/login_utils_browsertest.cc
@@ -166,7 +166,7 @@
   LoginAndSetKioskNextShellPref(true);
 }
 
-// Checks that the Contained Experience window is launched on sign-in when the
+// Checks that the Kiosk Next Home window is launched on sign-in when the
 // feature is enabled and its pref allows it.
 IN_PROC_BROWSER_TEST_F(LoginUtilsKioskNextShellTest, KioskNextShellLaunch) {
   // Enable all component extensions.
@@ -182,7 +182,7 @@
   apps::AppWindowWaiter waiter(
       extensions::AppWindowRegistry::Get(ProfileHelper::Get()->GetProfileByUser(
           user_manager::UserManager::Get()->GetActiveUser())),
-      extension_misc::kContainedHomeAppId);
+      extension_misc::kKioskNextHomeAppId);
   EXPECT_NE(nullptr,
             waiter.WaitForShownWithTimeout(TestTimeouts::action_timeout()));
   EXPECT_TRUE(fullscreen_observer.did_fullscreen_window_launch());
@@ -193,7 +193,7 @@
   LoginAndSetKioskNextShellPref(false);
 }
 
-// Checks that the Contained Experience window does not launch in sign-in when
+// Checks that the Kiosk Next Home window does not launch in sign-in when
 // its pref is disabled
 IN_PROC_BROWSER_TEST_F(LoginUtilsKioskNextShellTest,
                        KioskNextShellDoesntLaunchWhenPrefIsDisabled) {
@@ -208,7 +208,7 @@
   apps::AppWindowWaiter waiter(
       extensions::AppWindowRegistry::Get(ProfileHelper::Get()->GetProfileByUser(
           user_manager::UserManager::Get()->GetActiveUser())),
-      extension_misc::kContainedHomeAppId);
+      extension_misc::kKioskNextHomeAppId);
   EXPECT_EQ(nullptr,
             waiter.WaitForShownWithTimeout(TestTimeouts::action_timeout()));
 }
diff --git a/chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.cc b/chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.cc
index bb8628f4..5c3bea51 100644
--- a/chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.cc
+++ b/chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.cc
@@ -4,9 +4,14 @@
 
 #include "chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.h"
 
+#include <string>
+#include <vector>
+
 #include "base/feature_list.h"
 #include "base/files/file_path.h"
 #include "base/no_destructor.h"
+#include "base/strings/string_split.h"
+#include "base/system/sys_info.h"
 #include "base/time/time.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
@@ -110,6 +115,17 @@
   return base::FeatureList::IsEnabled(features::kQuickUnlockPin);
 }
 
+// Returns true if the fingerprint sensor is on the keyboard.
+// TODO(crbug.com/938738): Replace this disallowed board name reference
+// with a flag that's determined based on settings from chromeos-config.
+bool IsFingerprintReaderOnKeyboard() {
+  const std::vector<std::string> board =
+      base::SplitString(base::SysInfo::GetLsbReleaseBoard(), "-",
+                        base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+  const std::string board_name = board[0];
+  return board_name == "nami";
+}
+
 bool IsFingerprintEnabled(Profile* profile) {
   if (enable_for_testing_)
     return true;
diff --git a/chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.h b/chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.h
index 9df179f..d4c15ba 100644
--- a/chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.h
+++ b/chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.h
@@ -42,6 +42,12 @@
 // Returns true if the fingerprint is allowed for specified profile.
 bool IsFingerprintEnabled(Profile* profile);
 
+// Whether fingerprint setup UI should use resources that indicate the
+// fingerprint sensor placement for laptops, rather than tablets.
+// TODO(yulunwu): Reevaluate this once the fingerprint UI settings are supported
+// by cros_config.
+bool IsFingerprintReaderOnKeyboard();
+
 // Forcibly enable all quick-unlock modes for testing.
 void EnableForTesting();
 
diff --git a/chrome/browser/chromeos/login/screens/app_downloading_screen.cc b/chrome/browser/chromeos/login/screens/app_downloading_screen.cc
index 210a276..84253f3e4 100644
--- a/chrome/browser/chromeos/login/screens/app_downloading_screen.cc
+++ b/chrome/browser/chromeos/login/screens/app_downloading_screen.cc
@@ -16,9 +16,11 @@
 
 AppDownloadingScreen::AppDownloadingScreen(
     BaseScreenDelegate* base_screen_delegate,
-    AppDownloadingScreenView* view)
+    AppDownloadingScreenView* view,
+    const base::RepeatingClosure& exit_callback)
     : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_APP_DOWNLOADING),
-      view_(view) {
+      view_(view),
+      exit_callback_(exit_callback) {
   DCHECK(view_);
   view_->Bind(this);
 }
@@ -38,7 +40,7 @@
 
 void AppDownloadingScreen::OnUserAction(const std::string& action_id) {
   if (action_id == kUserActionButtonContinueSetup) {
-    Finish(ScreenExitCode::APP_DOWNLOADING_FINISHED);
+    exit_callback_.Run();
     return;
   }
   BaseScreen::OnUserAction(action_id);
diff --git a/chrome/browser/chromeos/login/screens/app_downloading_screen.h b/chrome/browser/chromeos/login/screens/app_downloading_screen.h
index 02dff46..5c4eede 100644
--- a/chrome/browser/chromeos/login/screens/app_downloading_screen.h
+++ b/chrome/browser/chromeos/login/screens/app_downloading_screen.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "chrome/browser/chromeos/login/screens/app_downloading_screen_view.h"
 #include "chrome/browser/chromeos/login/screens/base_screen.h"
@@ -20,7 +21,8 @@
 class AppDownloadingScreen : public BaseScreen {
  public:
   AppDownloadingScreen(BaseScreenDelegate* base_screen_delegate,
-                       AppDownloadingScreenView* view);
+                       AppDownloadingScreenView* view,
+                       const base::RepeatingClosure& exit_callback);
   ~AppDownloadingScreen() override;
 
   // BaseScreen:
@@ -30,6 +32,7 @@
 
  private:
   AppDownloadingScreenView* const view_;
+  base::RepeatingClosure exit_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(AppDownloadingScreen);
 };
diff --git a/chrome/browser/chromeos/login/screens/arc_terms_of_service_screen.cc b/chrome/browser/chromeos/login/screens/arc_terms_of_service_screen.cc
index 16121b5..b735687 100644
--- a/chrome/browser/chromeos/login/screens/arc_terms_of_service_screen.cc
+++ b/chrome/browser/chromeos/login/screens/arc_terms_of_service_screen.cc
@@ -6,7 +6,6 @@
 
 #include "chrome/browser/chromeos/login/screens/arc_terms_of_service_screen_view.h"
 #include "chrome/browser/chromeos/login/screens/base_screen_delegate.h"
-#include "chrome/browser/chromeos/login/screens/screen_exit_code.h"
 #include "chrome/browser/chromeos/login/wizard_controller.h"
 #include "chrome/browser/metrics/metrics_reporting_state.h"
 #include "chrome/browser/profiles/profile.h"
@@ -36,9 +35,11 @@
 
 ArcTermsOfServiceScreen::ArcTermsOfServiceScreen(
     BaseScreenDelegate* base_screen_delegate,
-    ArcTermsOfServiceScreenView* view)
+    ArcTermsOfServiceScreenView* view,
+    const ScreenExitCallback& exit_callback)
     : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_ARC_TERMS_OF_SERVICE),
-      view_(view) {
+      view_(view),
+      exit_callback_(exit_callback) {
   DCHECK(view_);
   if (view_) {
     view_->AddObserver(this);
@@ -68,14 +69,14 @@
 
 void ArcTermsOfServiceScreen::OnUserAction(const std::string& action_id) {
   if (action_id == kUserActionBack) {
-    Finish(ScreenExitCode::ARC_TERMS_OF_SERVICE_BACK);
+    exit_callback_.Run(Result::BACK);
   } else {
     BaseScreen::OnUserAction(action_id);
   }
 }
 
 void ArcTermsOfServiceScreen::OnSkip() {
-  Finish(ScreenExitCode::ARC_TERMS_OF_SERVICE_SKIPPED);
+  exit_callback_.Run(Result::SKIPPED);
 }
 
 void ArcTermsOfServiceScreen::OnAccept(bool review_arc_settings) {
@@ -85,7 +86,7 @@
     profile->GetPrefs()->SetBoolean(prefs::kShowArcSettingsOnSessionStart,
                                     true);
   }
-  Finish(ScreenExitCode::ARC_TERMS_OF_SERVICE_ACCEPTED);
+  exit_callback_.Run(Result::ACCEPTED);
 }
 
 void ArcTermsOfServiceScreen::OnViewDestroyed(
diff --git a/chrome/browser/chromeos/login/screens/arc_terms_of_service_screen.h b/chrome/browser/chromeos/login/screens/arc_terms_of_service_screen.h
index 9eeaa7a..2c4774f 100644
--- a/chrome/browser/chromeos/login/screens/arc_terms_of_service_screen.h
+++ b/chrome/browser/chromeos/login/screens/arc_terms_of_service_screen.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "chrome/browser/chromeos/login/screens/arc_terms_of_service_screen_view_observer.h"
 #include "chrome/browser/chromeos/login/screens/base_screen.h"
@@ -21,12 +22,16 @@
 class ArcTermsOfServiceScreen : public BaseScreen,
                                 public ArcTermsOfServiceScreenViewObserver {
  public:
+  enum class Result { ACCEPTED, SKIPPED, BACK };
+
   // Launches the ARC settings page if the user requested to review them after
   // completing OOBE.
   static void MaybeLaunchArcSettings(Profile* profile);
 
+  using ScreenExitCallback = base::RepeatingCallback<void(Result result)>;
   ArcTermsOfServiceScreen(BaseScreenDelegate* base_screen_delegate,
-                          ArcTermsOfServiceScreenView* view);
+                          ArcTermsOfServiceScreenView* view,
+                          const ScreenExitCallback& exit_callback);
   ~ArcTermsOfServiceScreen() override;
 
   // BaseScreen:
@@ -39,8 +44,12 @@
   void OnAccept(bool review_arc_settings) override;
   void OnViewDestroyed(ArcTermsOfServiceScreenView* view) override;
 
+ protected:
+  ScreenExitCallback* exit_callback() { return &exit_callback_; }
+
  private:
   ArcTermsOfServiceScreenView* view_;
+  ScreenExitCallback exit_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(ArcTermsOfServiceScreen);
 };
diff --git a/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen.cc b/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen.cc
index c6b8a10..26d8f5f 100644
--- a/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen.cc
+++ b/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen.cc
@@ -21,9 +21,11 @@
 
 AssistantOptInFlowScreen::AssistantOptInFlowScreen(
     BaseScreenDelegate* base_screen_delegate,
-    AssistantOptInFlowScreenView* view)
+    AssistantOptInFlowScreenView* view,
+    const base::RepeatingClosure& exit_callback)
     : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_ASSISTANT_OPTIN_FLOW),
-      view_(view) {
+      view_(view),
+      exit_callback_(exit_callback) {
   DCHECK(view_);
   if (view_)
     view_->Bind(this);
@@ -47,7 +49,7 @@
     return;
   }
 #endif
-  Finish(ScreenExitCode::ASSISTANT_OPTIN_FLOW_FINISHED);
+  exit_callback_.Run();
 }
 
 void AssistantOptInFlowScreen::Hide() {
@@ -63,7 +65,7 @@
 
 void AssistantOptInFlowScreen::OnUserAction(const std::string& action_id) {
   if (action_id == kFlowFinished)
-    Finish(ScreenExitCode::ASSISTANT_OPTIN_FLOW_FINISHED);
+    exit_callback_.Run();
   else
     BaseScreen::OnUserAction(action_id);
 }
diff --git a/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen.h b/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen.h
index af404cf..81a8d37d 100644
--- a/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen.h
+++ b/chrome/browser/chromeos/login/screens/assistant_optin_flow_screen.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "chrome/browser/chromeos/login/screens/base_screen.h"
 
@@ -18,7 +19,8 @@
 class AssistantOptInFlowScreen : public BaseScreen {
  public:
   AssistantOptInFlowScreen(BaseScreenDelegate* base_screen_delegate,
-                           AssistantOptInFlowScreenView* view);
+                           AssistantOptInFlowScreenView* view,
+                           const base::RepeatingClosure& exit_callback);
   ~AssistantOptInFlowScreen() override;
 
   // Called when view is destroyed so there's no dead reference to it.
@@ -31,6 +33,7 @@
 
  private:
   AssistantOptInFlowScreenView* view_;
+  base::RepeatingClosure exit_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(AssistantOptInFlowScreen);
 };
diff --git a/chrome/browser/chromeos/login/screens/base_screen.cc b/chrome/browser/chromeos/login/screens/base_screen.cc
index c3094bd7..1366a53 100644
--- a/chrome/browser/chromeos/login/screens/base_screen.cc
+++ b/chrome/browser/chromeos/login/screens/base_screen.cc
@@ -97,10 +97,6 @@
   channel_->CommitContextChanges(diff);
 }
 
-void BaseScreen::Finish(ScreenExitCode exit_code) {
-  base_screen_delegate_->OnExit(exit_code);
-}
-
 void BaseScreen::OnUserAction(const std::string& action_id) {
   LOG(WARNING) << "Unhandled user action: action_id=" << action_id;
 }
diff --git a/chrome/browser/chromeos/login/screens/base_screen.h b/chrome/browser/chromeos/login/screens/base_screen.h
index b1fc32a..b763ca54 100644
--- a/chrome/browser/chromeos/login/screens/base_screen.h
+++ b/chrome/browser/chromeos/login/screens/base_screen.h
@@ -102,10 +102,6 @@
   // Sends all pending context changes to the JS side.
   void CommitContextChanges();
 
-  // Screen can call this method to notify framework that it have finished
-  // it's work with |outcome|.
-  void Finish(ScreenExitCode exit_code);
-
   // The method is called each time some key in screen context is
   // updated by JS side. Default implementation does nothing, so
   // subclasses should override it in order to observe updates in
diff --git a/chrome/browser/chromeos/login/screens/base_screen_delegate.h b/chrome/browser/chromeos/login/screens/base_screen_delegate.h
index 6187d18..c43b5512 100644
--- a/chrome/browser/chromeos/login/screens/base_screen_delegate.h
+++ b/chrome/browser/chromeos/login/screens/base_screen_delegate.h
@@ -5,8 +5,6 @@
 #ifndef CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_BASE_SCREEN_DELEGATE_H_
 #define CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_BASE_SCREEN_DELEGATE_H_
 
-#include "chrome/browser/chromeos/login/screens/screen_exit_code.h"
-
 namespace chromeos {
 
 class BaseScreen;
@@ -16,9 +14,6 @@
 // screens.
 class BaseScreenDelegate {
  public:
-  // Method called by a screen when user's done with it.
-  virtual void OnExit(ScreenExitCode exit_code) = 0;
-
   // Forces current screen showing.
   virtual void ShowCurrentScreen() = 0;
 
diff --git a/chrome/browser/chromeos/login/screens/demo_preferences_screen.cc b/chrome/browser/chromeos/login/screens/demo_preferences_screen.cc
index 23cecbb..2c09627 100644
--- a/chrome/browser/chromeos/login/screens/demo_preferences_screen.cc
+++ b/chrome/browser/chromeos/login/screens/demo_preferences_screen.cc
@@ -7,7 +7,6 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/login/screens/base_screen_delegate.h"
 #include "chrome/browser/chromeos/login/screens/demo_preferences_screen_view.h"
-#include "chrome/browser/chromeos/login/screens/screen_exit_code.h"
 #include "chrome/browser/chromeos/login/screens/welcome_screen.h"
 #include "chrome/browser/chromeos/login/wizard_controller.h"
 #include "chrome/common/pref_names.h"
@@ -45,11 +44,13 @@
 
 DemoPreferencesScreen::DemoPreferencesScreen(
     BaseScreenDelegate* base_screen_delegate,
-    DemoPreferencesScreenView* view)
+    DemoPreferencesScreenView* view,
+    const ScreenExitCallback& exit_callback)
     : BaseScreen(base_screen_delegate,
                  OobeScreen::SCREEN_OOBE_DEMO_PREFERENCES),
       input_manager_observer_(this),
-      view_(view) {
+      view_(view),
+      exit_callback_(exit_callback) {
   DCHECK(view_);
   view_->Bind(this);
 
@@ -88,11 +89,11 @@
 
 void DemoPreferencesScreen::OnUserAction(const std::string& action_id) {
   if (action_id == kUserActionContinue) {
-    Finish(ScreenExitCode::DEMO_MODE_PREFERENCES_CONTINUED);
+    exit_callback_.Run(Result::COMPLETED);
   } else if (action_id == kUserActionClose) {
     // Restore initial locale and input method if the user pressed back button.
     SetApplicationLocaleAndInputMethod(initial_locale_, initial_input_method_);
-    Finish(ScreenExitCode::DEMO_MODE_PREFERENCES_CANCELED);
+    exit_callback_.Run(Result::CANCELED);
   } else {
     BaseScreen::OnUserAction(action_id);
   }
diff --git a/chrome/browser/chromeos/login/screens/demo_preferences_screen.h b/chrome/browser/chromeos/login/screens/demo_preferences_screen.h
index 4aab7a9..13f02d4 100644
--- a/chrome/browser/chromeos/login/screens/demo_preferences_screen.h
+++ b/chrome/browser/chromeos/login/screens/demo_preferences_screen.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "base/scoped_observer.h"
 #include "chrome/browser/chromeos/login/screens/base_screen.h"
@@ -24,8 +25,12 @@
     : public BaseScreen,
       public input_method::InputMethodManager::Observer {
  public:
+  enum class Result { COMPLETED, CANCELED };
+
+  using ScreenExitCallback = base::RepeatingCallback<void(Result result)>;
   DemoPreferencesScreen(BaseScreenDelegate* base_screen_delegate,
-                        DemoPreferencesScreenView* view);
+                        DemoPreferencesScreenView* view,
+                        const ScreenExitCallback& exit_callback);
   ~DemoPreferencesScreen() override;
 
   // BaseScreen:
@@ -38,6 +43,9 @@
   // then it has to call Bind(nullptr).
   void OnViewDestroyed(DemoPreferencesScreenView* view);
 
+ protected:
+  ScreenExitCallback* exit_callback() { return &exit_callback_; }
+
  private:
   // InputMethodManager::Observer:
   void InputMethodChanged(input_method::InputMethodManager* manager,
@@ -59,6 +67,7 @@
       input_manager_observer_;
 
   DemoPreferencesScreenView* view_;
+  ScreenExitCallback exit_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(DemoPreferencesScreen);
 };
diff --git a/chrome/browser/chromeos/login/screens/demo_setup_screen.cc b/chrome/browser/chromeos/login/screens/demo_setup_screen.cc
index 6ac7352..2f5bafb 100644
--- a/chrome/browser/chromeos/login/screens/demo_setup_screen.cc
+++ b/chrome/browser/chromeos/login/screens/demo_setup_screen.cc
@@ -22,9 +22,11 @@
 namespace chromeos {
 
 DemoSetupScreen::DemoSetupScreen(BaseScreenDelegate* base_screen_delegate,
-                                 DemoSetupScreenView* view)
+                                 DemoSetupScreenView* view,
+                                 const ScreenExitCallback& exit_callback)
     : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_OOBE_DEMO_SETUP),
       view_(view),
+      exit_callback_(exit_callback),
       weak_ptr_factory_(this) {
   DCHECK(view_);
   view_->Bind(this);
@@ -49,7 +51,7 @@
   if (action_id == kUserActionStartSetup) {
     StartEnrollment();
   } else if (action_id == kUserActionClose) {
-    Finish(ScreenExitCode::DEMO_MODE_SETUP_CANCELED);
+    exit_callback_.Run(Result::CANCELED);
   } else if (action_id == kUserActionPowerwash) {
     chromeos::DBusThreadManager::Get()
         ->GetSessionManagerClient()
@@ -77,7 +79,7 @@
 }
 
 void DemoSetupScreen::OnSetupSuccess() {
-  Finish(ScreenExitCode::DEMO_MODE_SETUP_FINISHED);
+  exit_callback_.Run(Result::COMPLETED);
 }
 
 void DemoSetupScreen::OnViewDestroyed(DemoSetupScreenView* view) {
diff --git a/chrome/browser/chromeos/login/screens/demo_setup_screen.h b/chrome/browser/chromeos/login/screens/demo_setup_screen.h
index 8f53cbb..e0d0fa91 100644
--- a/chrome/browser/chromeos/login/screens/demo_setup_screen.h
+++ b/chrome/browser/chromeos/login/screens/demo_setup_screen.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/chromeos/login/demo_mode/demo_setup_controller.h"
@@ -21,8 +22,12 @@
 // user to setup retail demo mode on the device.
 class DemoSetupScreen : public BaseScreen {
  public:
+  enum class Result { COMPLETED, CANCELED };
+
+  using ScreenExitCallback = base::RepeatingCallback<void(Result result)>;
   DemoSetupScreen(BaseScreenDelegate* base_screen_delegate,
-                  DemoSetupScreenView* view);
+                  DemoSetupScreenView* view,
+                  const ScreenExitCallback& exit_callback);
   ~DemoSetupScreen() override;
 
   // BaseScreen:
@@ -34,6 +39,9 @@
   // then it has to call Bind(nullptr).
   void OnViewDestroyed(DemoSetupScreenView* view);
 
+ protected:
+  ScreenExitCallback* exit_callback() { return &exit_callback_; }
+
  private:
   void StartEnrollment();
 
@@ -44,6 +52,7 @@
   void OnSetupSuccess();
 
   DemoSetupScreenView* view_;
+  ScreenExitCallback exit_callback_;
 
   base::WeakPtrFactory<DemoSetupScreen> weak_ptr_factory_;
 
diff --git a/chrome/browser/chromeos/login/screens/discover_screen.cc b/chrome/browser/chromeos/login/screens/discover_screen.cc
index 0b45ebf9..d27a4f2 100644
--- a/chrome/browser/chromeos/login/screens/discover_screen.cc
+++ b/chrome/browser/chromeos/login/screens/discover_screen.cc
@@ -19,9 +19,11 @@
 }
 
 DiscoverScreen::DiscoverScreen(BaseScreenDelegate* base_screen_delegate,
-                               DiscoverScreenView* view)
+                               DiscoverScreenView* view,
+                               const base::RepeatingClosure& exit_callback)
     : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_DISCOVER),
-      view_(view) {
+      view_(view),
+      exit_callback_(exit_callback) {
   DCHECK(view_);
   view_->Bind(this);
 }
@@ -36,7 +38,7 @@
       !TabletModeClient::Get()->tablet_mode_enabled() ||
       !chromeos::quick_unlock::IsPinEnabled(prefs) ||
       chromeos::quick_unlock::IsPinDisabledByPolicy(prefs)) {
-    Finish(ScreenExitCode::DISCOVER_FINISHED);
+    exit_callback_.Run();
     return;
   }
   view_->Show();
@@ -51,7 +53,7 @@
 void DiscoverScreen::OnUserAction(const std::string& action_id) {
   // Only honor finish if discover is currently being shown.
   if (action_id == kFinished && is_shown_) {
-    Finish(ScreenExitCode::DISCOVER_FINISHED);
+    exit_callback_.Run();
     return;
   }
   BaseScreen::OnUserAction(action_id);
diff --git a/chrome/browser/chromeos/login/screens/discover_screen.h b/chrome/browser/chromeos/login/screens/discover_screen.h
index d86a72d..4f86c21 100644
--- a/chrome/browser/chromeos/login/screens/discover_screen.h
+++ b/chrome/browser/chromeos/login/screens/discover_screen.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "chrome/browser/chromeos/login/screens/base_screen.h"
 
@@ -18,7 +19,8 @@
 class DiscoverScreen : public BaseScreen {
  public:
   DiscoverScreen(BaseScreenDelegate* base_screen_delegate,
-                 DiscoverScreenView* view);
+                 DiscoverScreenView* view,
+                 const base::RepeatingClosure& exit_callback);
   ~DiscoverScreen() override;
 
   // BaseScreen:
@@ -28,6 +30,7 @@
 
  private:
   DiscoverScreenView* const view_;
+  base::RepeatingClosure exit_callback_;
   bool is_shown_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(DiscoverScreen);
diff --git a/chrome/browser/chromeos/login/screens/enable_debugging_screen.cc b/chrome/browser/chromeos/login/screens/enable_debugging_screen.cc
index 7329f207..356eed9 100644
--- a/chrome/browser/chromeos/login/screens/enable_debugging_screen.cc
+++ b/chrome/browser/chromeos/login/screens/enable_debugging_screen.cc
@@ -10,10 +10,13 @@
 
 namespace chromeos {
 
-EnableDebuggingScreen::EnableDebuggingScreen(BaseScreenDelegate* delegate,
-                                             EnableDebuggingScreenView* view)
+EnableDebuggingScreen::EnableDebuggingScreen(
+    BaseScreenDelegate* delegate,
+    EnableDebuggingScreenView* view,
+    const base::RepeatingClosure& exit_callback)
     : BaseScreen(delegate, OobeScreen::SCREEN_OOBE_ENABLE_DEBUGGING),
-      view_(view) {
+      view_(view),
+      exit_callback_(exit_callback) {
   DCHECK(view_);
   if (view_)
     view_->SetDelegate(this);
@@ -35,8 +38,7 @@
 }
 
 void EnableDebuggingScreen::OnExit(bool success) {
-  Finish(success ? ScreenExitCode::ENABLE_DEBUGGING_FINISHED
-                 : ScreenExitCode::ENABLE_DEBUGGING_CANCELED);
+  exit_callback_.Run();
 }
 
 void EnableDebuggingScreen::OnViewDestroyed(EnableDebuggingScreenView* view) {
diff --git a/chrome/browser/chromeos/login/screens/enable_debugging_screen.h b/chrome/browser/chromeos/login/screens/enable_debugging_screen.h
index d08f6e2..bb2eaf1f 100644
--- a/chrome/browser/chromeos/login/screens/enable_debugging_screen.h
+++ b/chrome/browser/chromeos/login/screens/enable_debugging_screen.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "chrome/browser/chromeos/login/screens/base_screen.h"
 #include "chrome/browser/chromeos/login/screens/enable_debugging_screen_view.h"
@@ -19,7 +20,8 @@
                               public EnableDebuggingScreenView::Delegate {
  public:
   EnableDebuggingScreen(BaseScreenDelegate* delegate,
-                        EnableDebuggingScreenView* view);
+                        EnableDebuggingScreenView* view,
+                        const base::RepeatingClosure& exit_callback);
   ~EnableDebuggingScreen() override;
 
   // BaseScreen implementation:
@@ -30,8 +32,12 @@
   void OnExit(bool success) override;
   void OnViewDestroyed(EnableDebuggingScreenView* view) override;
 
+ protected:
+  base::RepeatingClosure* exit_callback() { return &exit_callback_; }
+
  private:
   EnableDebuggingScreenView* view_;
+  base::RepeatingClosure exit_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(EnableDebuggingScreen);
 };
diff --git a/chrome/browser/chromeos/login/screens/fingerprint_setup_screen.cc b/chrome/browser/chromeos/login/screens/fingerprint_setup_screen.cc
index fba74f3..a748412f 100644
--- a/chrome/browser/chromeos/login/screens/fingerprint_setup_screen.cc
+++ b/chrome/browser/chromeos/login/screens/fingerprint_setup_screen.cc
@@ -15,9 +15,11 @@
 
 FingerprintSetupScreen::FingerprintSetupScreen(
     BaseScreenDelegate* base_screen_delegate,
-    FingerprintSetupScreenView* view)
+    FingerprintSetupScreenView* view,
+    const base::RepeatingClosure& exit_callback)
     : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_FINGERPRINT_SETUP),
-      view_(view) {
+      view_(view),
+      exit_callback_(exit_callback) {
   DCHECK(view_);
   view_->Bind(this);
 }
@@ -28,7 +30,7 @@
 
 void FingerprintSetupScreen::Show() {
   if (chrome_user_manager_util::IsPublicSessionOrEphemeralLogin()) {
-    Finish(ScreenExitCode::FINGERPRINT_SETUP_FINISHED);
+    exit_callback_.Run();
     return;
   }
   view_->Show();
@@ -40,7 +42,7 @@
 
 void FingerprintSetupScreen::OnUserAction(const std::string& action_id) {
   if (action_id == kUserActionClose) {
-    Finish(ScreenExitCode::FINGERPRINT_SETUP_FINISHED);
+    exit_callback_.Run();
     return;
   }
   BaseScreen::OnUserAction(action_id);
diff --git a/chrome/browser/chromeos/login/screens/fingerprint_setup_screen.h b/chrome/browser/chromeos/login/screens/fingerprint_setup_screen.h
index 4e6aaa7..8e5b224 100644
--- a/chrome/browser/chromeos/login/screens/fingerprint_setup_screen.h
+++ b/chrome/browser/chromeos/login/screens/fingerprint_setup_screen.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "chrome/browser/chromeos/login/screens/base_screen.h"
 #include "chrome/browser/chromeos/login/screens/fingerprint_setup_screen_view.h"
@@ -20,7 +21,8 @@
 class FingerprintSetupScreen : public BaseScreen {
  public:
   FingerprintSetupScreen(BaseScreenDelegate* base_screen_delegate,
-                         FingerprintSetupScreenView* view);
+                         FingerprintSetupScreenView* view,
+                         const base::RepeatingClosure& exit_callback);
   ~FingerprintSetupScreen() override;
 
   // BaseScreen:
@@ -30,6 +32,7 @@
 
  private:
   FingerprintSetupScreenView* const view_;
+  base::RepeatingClosure exit_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(FingerprintSetupScreen);
 };
diff --git a/chrome/browser/chromeos/login/screens/hid_detection_screen.cc b/chrome/browser/chromeos/login/screens/hid_detection_screen.cc
index 8d02b4c..b38e2be 100644
--- a/chrome/browser/chromeos/login/screens/hid_detection_screen.cc
+++ b/chrome/browser/chromeos/login/screens/hid_detection_screen.cc
@@ -71,10 +71,13 @@
 const char HIDDetectionScreen::kContextKeyContinueButtonEnabled[] =
     "continue-button-enabled";
 
-HIDDetectionScreen::HIDDetectionScreen(BaseScreenDelegate* base_screen_delegate,
-                                       HIDDetectionView* view)
+HIDDetectionScreen::HIDDetectionScreen(
+    BaseScreenDelegate* base_screen_delegate,
+    HIDDetectionView* view,
+    const base::RepeatingClosure& exit_callback)
     : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_OOBE_HID_DETECTION),
       view_(view),
+      exit_callback_(exit_callback),
       binding_(this),
       weak_ptr_factory_(this) {
   if (view_)
@@ -116,7 +119,7 @@
   if (adapter_is_powered && need_switching_off)
     PowerOff();
 
-  Finish(ScreenExitCode::HID_DETECTION_COMPLETED);
+  exit_callback_.Run();
 }
 
 void HIDDetectionScreen::OnViewDestroyed(HIDDetectionView* view) {
diff --git a/chrome/browser/chromeos/login/screens/hid_detection_screen.h b/chrome/browser/chromeos/login/screens/hid_detection_screen.h
index 230aa91..3862cd63 100644
--- a/chrome/browser/chromeos/login/screens/hid_detection_screen.h
+++ b/chrome/browser/chromeos/login/screens/hid_detection_screen.h
@@ -49,7 +49,8 @@
   using DeviceMap = std::map<std::string, InputDeviceInfoPtr>;
 
   HIDDetectionScreen(BaseScreenDelegate* base_screen_delegate,
-                     HIDDetectionView* view);
+                     HIDDetectionView* view,
+                     const base::RepeatingClosure& exit_callback);
   ~HIDDetectionScreen() override;
 
   // Called when continue button was clicked.
@@ -204,6 +205,7 @@
   void SetAdapterInitialPoweredForTesting(bool powered);
 
   HIDDetectionView* view_;
+  base::RepeatingClosure exit_callback_;
 
   // Default bluetooth adapter, used for all operations.
   scoped_refptr<device::BluetoothAdapter> adapter_;
diff --git a/chrome/browser/chromeos/login/screens/kiosk_autolaunch_screen.cc b/chrome/browser/chromeos/login/screens/kiosk_autolaunch_screen.cc
index 56c49494..7000806 100644
--- a/chrome/browser/chromeos/login/screens/kiosk_autolaunch_screen.cc
+++ b/chrome/browser/chromeos/login/screens/kiosk_autolaunch_screen.cc
@@ -13,9 +13,11 @@
 
 KioskAutolaunchScreen::KioskAutolaunchScreen(
     BaseScreenDelegate* base_screen_delegate,
-    KioskAutolaunchScreenView* view)
+    KioskAutolaunchScreenView* view,
+    const ScreenExitCallback& exit_callback)
     : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_KIOSK_AUTOLAUNCH),
-      view_(view) {
+      view_(view),
+      exit_callback_(exit_callback) {
   DCHECK(view_);
   if (view_)
     view_->SetDelegate(this);
@@ -32,8 +34,7 @@
 }
 
 void KioskAutolaunchScreen::OnExit(bool confirmed) {
-  Finish(confirmed ? ScreenExitCode::KIOSK_AUTOLAUNCH_CONFIRMED
-                   : ScreenExitCode::KIOSK_AUTOLAUNCH_CANCELED);
+  exit_callback_.Run(confirmed ? Result::COMPLETED : Result::CANCELED);
 }
 
 void KioskAutolaunchScreen::OnViewDestroyed(KioskAutolaunchScreenView* view) {
diff --git a/chrome/browser/chromeos/login/screens/kiosk_autolaunch_screen.h b/chrome/browser/chromeos/login/screens/kiosk_autolaunch_screen.h
index bcba0aa6..fef27d2 100644
--- a/chrome/browser/chromeos/login/screens/kiosk_autolaunch_screen.h
+++ b/chrome/browser/chromeos/login/screens/kiosk_autolaunch_screen.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/callback.h"
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "chrome/browser/chromeos/login/screens/base_screen.h"
@@ -19,8 +20,12 @@
 class KioskAutolaunchScreen : public BaseScreen,
                               public KioskAutolaunchScreenView::Delegate {
  public:
+  enum class Result { COMPLETED, CANCELED };
+
+  using ScreenExitCallback = base::RepeatingCallback<void(Result result)>;
   KioskAutolaunchScreen(BaseScreenDelegate* base_screen_delegate,
-                        KioskAutolaunchScreenView* view);
+                        KioskAutolaunchScreenView* view,
+                        const ScreenExitCallback& exit_callback);
   ~KioskAutolaunchScreen() override;
 
   // BaseScreen implementation:
@@ -33,6 +38,7 @@
 
  private:
   KioskAutolaunchScreenView* view_;
+  ScreenExitCallback exit_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(KioskAutolaunchScreen);
 };
diff --git a/chrome/browser/chromeos/login/screens/kiosk_enable_screen.cc b/chrome/browser/chromeos/login/screens/kiosk_enable_screen.cc
index e9ed542..49b19dc 100644
--- a/chrome/browser/chromeos/login/screens/kiosk_enable_screen.cc
+++ b/chrome/browser/chromeos/login/screens/kiosk_enable_screen.cc
@@ -11,10 +11,13 @@
 
 namespace chromeos {
 
-KioskEnableScreen::KioskEnableScreen(BaseScreenDelegate* base_screen_delegate,
-                                     KioskEnableScreenView* view)
+KioskEnableScreen::KioskEnableScreen(
+    BaseScreenDelegate* base_screen_delegate,
+    KioskEnableScreenView* view,
+    const base::RepeatingClosure& exit_callback)
     : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_KIOSK_ENABLE),
-      view_(view) {
+      view_(view),
+      exit_callback_(exit_callback) {
   DCHECK(view_);
   if (view_)
     view_->SetDelegate(this);
@@ -31,7 +34,7 @@
 }
 
 void KioskEnableScreen::OnExit() {
-  Finish(ScreenExitCode::KIOSK_ENABLE_COMPLETED);
+  exit_callback_.Run();
 }
 
 void KioskEnableScreen::OnViewDestroyed(KioskEnableScreenView* view) {
diff --git a/chrome/browser/chromeos/login/screens/kiosk_enable_screen.h b/chrome/browser/chromeos/login/screens/kiosk_enable_screen.h
index f2b101cf..8f1515b 100644
--- a/chrome/browser/chromeos/login/screens/kiosk_enable_screen.h
+++ b/chrome/browser/chromeos/login/screens/kiosk_enable_screen.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/callback.h"
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "chrome/browser/chromeos/login/screens/base_screen.h"
@@ -20,7 +21,8 @@
                           public KioskEnableScreenView::Delegate {
  public:
   KioskEnableScreen(BaseScreenDelegate* base_screen_delegate,
-                    KioskEnableScreenView* view);
+                    KioskEnableScreenView* view,
+                    const base::RepeatingClosure& exit_callback);
   ~KioskEnableScreen() override;
 
   // BaseScreen implementation:
@@ -33,6 +35,7 @@
 
  private:
   KioskEnableScreenView* view_;
+  base::RepeatingClosure exit_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(KioskEnableScreen);
 };
diff --git a/chrome/browser/chromeos/login/screens/marketing_opt_in_screen.cc b/chrome/browser/chromeos/login/screens/marketing_opt_in_screen.cc
index 490ea2d2..ba4dbaa 100644
--- a/chrome/browser/chromeos/login/screens/marketing_opt_in_screen.cc
+++ b/chrome/browser/chromeos/login/screens/marketing_opt_in_screen.cc
@@ -17,9 +17,11 @@
 
 MarketingOptInScreen::MarketingOptInScreen(
     BaseScreenDelegate* base_screen_delegate,
-    MarketingOptInScreenView* view)
+    MarketingOptInScreenView* view,
+    const base::RepeatingClosure& exit_callback)
     : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_MARKETING_OPT_IN),
-      view_(view) {
+      view_(view),
+      exit_callback_(exit_callback) {
   DCHECK(view_);
   view_->Bind(this);
 }
@@ -38,7 +40,7 @@
           chromeos::switches::kEnableMarketingOptInScreen) ||
       prefs->GetBoolean(prefs::kOobeMarketingOptInScreenFinished) ||
       chrome_user_manager_util::IsPublicSessionOrEphemeralLogin()) {
-    Finish(ScreenExitCode::MARKETING_OPT_IN_FINISHED);
+    exit_callback_.Run();
     return;
   }
   view_->Show();
@@ -51,7 +53,7 @@
 void MarketingOptInScreen::OnAllSet(bool play_communications_opt_in,
                                     bool tips_communications_opt_in) {
   // TODO(https://crbug.com/852557)
-  Finish(ScreenExitCode::MARKETING_OPT_IN_FINISHED);
+  exit_callback_.Run();
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/marketing_opt_in_screen.h b/chrome/browser/chromeos/login/screens/marketing_opt_in_screen.h
index dcf09bf..325e5aa 100644
--- a/chrome/browser/chromeos/login/screens/marketing_opt_in_screen.h
+++ b/chrome/browser/chromeos/login/screens/marketing_opt_in_screen.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_MARKETING_OPT_IN_SCREEN_H_
 #define CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_MARKETING_OPT_IN_SCREEN_H_
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "chrome/browser/chromeos/login/screens/base_screen.h"
 
@@ -18,7 +19,8 @@
 class MarketingOptInScreen : public BaseScreen {
  public:
   MarketingOptInScreen(BaseScreenDelegate* base_screen_delegate,
-                       MarketingOptInScreenView* view);
+                       MarketingOptInScreenView* view,
+                       const base::RepeatingClosure& exit_callback);
   ~MarketingOptInScreen() override;
 
   // BaseScreen:
@@ -31,6 +33,7 @@
 
  private:
   MarketingOptInScreenView* const view_;
+  base::RepeatingClosure exit_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(MarketingOptInScreen);
 };
diff --git a/chrome/browser/chromeos/login/screens/mock_arc_terms_of_service_screen.cc b/chrome/browser/chromeos/login/screens/mock_arc_terms_of_service_screen.cc
index 98d582e..6f0e81e2 100644
--- a/chrome/browser/chromeos/login/screens/mock_arc_terms_of_service_screen.cc
+++ b/chrome/browser/chromeos/login/screens/mock_arc_terms_of_service_screen.cc
@@ -8,11 +8,16 @@
 
 MockArcTermsOfServiceScreen::MockArcTermsOfServiceScreen(
     BaseScreenDelegate* base_screen_delegate,
-    ArcTermsOfServiceScreenView* view)
-    : ArcTermsOfServiceScreen(base_screen_delegate, view) {}
+    ArcTermsOfServiceScreenView* view,
+    const ScreenExitCallback& exit_callback)
+    : ArcTermsOfServiceScreen(base_screen_delegate, view, exit_callback) {}
 
 MockArcTermsOfServiceScreen::~MockArcTermsOfServiceScreen() = default;
 
+void MockArcTermsOfServiceScreen::ExitScreen(Result result) {
+  exit_callback()->Run(result);
+}
+
 MockArcTermsOfServiceScreenView::MockArcTermsOfServiceScreenView() = default;
 
 MockArcTermsOfServiceScreenView::~MockArcTermsOfServiceScreenView() {
diff --git a/chrome/browser/chromeos/login/screens/mock_arc_terms_of_service_screen.h b/chrome/browser/chromeos/login/screens/mock_arc_terms_of_service_screen.h
index c16ef28e..f77e316 100644
--- a/chrome/browser/chromeos/login/screens/mock_arc_terms_of_service_screen.h
+++ b/chrome/browser/chromeos/login/screens/mock_arc_terms_of_service_screen.h
@@ -14,11 +14,14 @@
 class MockArcTermsOfServiceScreen : public ArcTermsOfServiceScreen {
  public:
   MockArcTermsOfServiceScreen(BaseScreenDelegate* base_screen_delegate,
-                              ArcTermsOfServiceScreenView* view);
+                              ArcTermsOfServiceScreenView* view,
+                              const ScreenExitCallback& exit_callback);
   ~MockArcTermsOfServiceScreen() override;
 
   MOCK_METHOD0(Show, void());
   MOCK_METHOD0(Hide, void());
+
+  void ExitScreen(Result result);
 };
 
 class MockArcTermsOfServiceScreenView : public ArcTermsOfServiceScreenView {
diff --git a/chrome/browser/chromeos/login/screens/mock_base_screen_delegate.h b/chrome/browser/chromeos/login/screens/mock_base_screen_delegate.h
index 4d0a7fe2d..aa164ae4 100644
--- a/chrome/browser/chromeos/login/screens/mock_base_screen_delegate.h
+++ b/chrome/browser/chromeos/login/screens/mock_base_screen_delegate.h
@@ -20,7 +20,6 @@
   MockBaseScreenDelegate();
   virtual ~MockBaseScreenDelegate();
 
-  MOCK_METHOD1(OnExit, void(ScreenExitCode));
   MOCK_METHOD0(ShowCurrentScreen, void());
   MOCK_METHOD0(GetErrorScreen, ErrorScreen*());
   MOCK_METHOD0(ShowErrorScreen, void());
diff --git a/chrome/browser/chromeos/login/screens/mock_demo_preferences_screen.cc b/chrome/browser/chromeos/login/screens/mock_demo_preferences_screen.cc
index 98ab448..2bf6907b 100644
--- a/chrome/browser/chromeos/login/screens/mock_demo_preferences_screen.cc
+++ b/chrome/browser/chromeos/login/screens/mock_demo_preferences_screen.cc
@@ -8,11 +8,16 @@
 
 MockDemoPreferencesScreen::MockDemoPreferencesScreen(
     BaseScreenDelegate* base_screen_delegate,
-    DemoPreferencesScreenView* view)
-    : DemoPreferencesScreen(base_screen_delegate, view) {}
+    DemoPreferencesScreenView* view,
+    const ScreenExitCallback& exit_callback)
+    : DemoPreferencesScreen(base_screen_delegate, view, exit_callback) {}
 
 MockDemoPreferencesScreen::~MockDemoPreferencesScreen() = default;
 
+void MockDemoPreferencesScreen::ExitScreen(Result result) {
+  exit_callback()->Run(result);
+}
+
 MockDemoPreferencesScreenView::MockDemoPreferencesScreenView() = default;
 
 MockDemoPreferencesScreenView::~MockDemoPreferencesScreenView() {
diff --git a/chrome/browser/chromeos/login/screens/mock_demo_preferences_screen.h b/chrome/browser/chromeos/login/screens/mock_demo_preferences_screen.h
index d1c1038..62bc7b2 100644
--- a/chrome/browser/chromeos/login/screens/mock_demo_preferences_screen.h
+++ b/chrome/browser/chromeos/login/screens/mock_demo_preferences_screen.h
@@ -15,12 +15,15 @@
 class MockDemoPreferencesScreen : public DemoPreferencesScreen {
  public:
   MockDemoPreferencesScreen(BaseScreenDelegate* base_screen_delegate,
-                            DemoPreferencesScreenView* view);
+                            DemoPreferencesScreenView* view,
+                            const ScreenExitCallback& exit_callback);
   ~MockDemoPreferencesScreen() override;
 
   MOCK_METHOD0(Show, void());
   MOCK_METHOD0(Hide, void());
 
+  void ExitScreen(Result result);
+
  private:
   DISALLOW_COPY_AND_ASSIGN(MockDemoPreferencesScreen);
 };
diff --git a/chrome/browser/chromeos/login/screens/mock_demo_setup_screen.cc b/chrome/browser/chromeos/login/screens/mock_demo_setup_screen.cc
index ccd9252..bde099e 100644
--- a/chrome/browser/chromeos/login/screens/mock_demo_setup_screen.cc
+++ b/chrome/browser/chromeos/login/screens/mock_demo_setup_screen.cc
@@ -8,11 +8,16 @@
 
 MockDemoSetupScreen::MockDemoSetupScreen(
     BaseScreenDelegate* base_screen_delegate,
-    DemoSetupScreenView* view)
-    : DemoSetupScreen(base_screen_delegate, view) {}
+    DemoSetupScreenView* view,
+    const ScreenExitCallback& exit_callback)
+    : DemoSetupScreen(base_screen_delegate, view, exit_callback) {}
 
 MockDemoSetupScreen::~MockDemoSetupScreen() = default;
 
+void MockDemoSetupScreen::ExitScreen(Result result) {
+  exit_callback()->Run(result);
+}
+
 MockDemoSetupScreenView::MockDemoSetupScreenView() = default;
 
 MockDemoSetupScreenView::~MockDemoSetupScreenView() {
diff --git a/chrome/browser/chromeos/login/screens/mock_demo_setup_screen.h b/chrome/browser/chromeos/login/screens/mock_demo_setup_screen.h
index 007df29..d7454353 100644
--- a/chrome/browser/chromeos/login/screens/mock_demo_setup_screen.h
+++ b/chrome/browser/chromeos/login/screens/mock_demo_setup_screen.h
@@ -15,11 +15,14 @@
 class MockDemoSetupScreen : public DemoSetupScreen {
  public:
   MockDemoSetupScreen(BaseScreenDelegate* base_screen_delegate,
-                      DemoSetupScreenView* view);
+                      DemoSetupScreenView* view,
+                      const ScreenExitCallback& exit_callback);
   ~MockDemoSetupScreen() override;
 
   MOCK_METHOD0(Show, void());
   MOCK_METHOD0(Hide, void());
+
+  void ExitScreen(Result result);
 };
 
 class MockDemoSetupScreenView : public DemoSetupScreenView {
diff --git a/chrome/browser/chromeos/login/screens/mock_enable_debugging_screen.cc b/chrome/browser/chromeos/login/screens/mock_enable_debugging_screen.cc
index aed23fc..c4cafc8 100644
--- a/chrome/browser/chromeos/login/screens/mock_enable_debugging_screen.cc
+++ b/chrome/browser/chromeos/login/screens/mock_enable_debugging_screen.cc
@@ -8,11 +8,16 @@
 
 MockEnableDebuggingScreen::MockEnableDebuggingScreen(
     BaseScreenDelegate* base_screen_delegate,
-    EnableDebuggingScreenView* view)
-    : EnableDebuggingScreen(base_screen_delegate, view) {}
+    EnableDebuggingScreenView* view,
+    const base::RepeatingClosure& exit_callback)
+    : EnableDebuggingScreen(base_screen_delegate, view, exit_callback) {}
 
 MockEnableDebuggingScreen::~MockEnableDebuggingScreen() {}
 
+void MockEnableDebuggingScreen::ExitScreen() {
+  exit_callback()->Run();
+}
+
 MockEnableDebuggingScreenView::MockEnableDebuggingScreenView() = default;
 
 MockEnableDebuggingScreenView::~MockEnableDebuggingScreenView() {
diff --git a/chrome/browser/chromeos/login/screens/mock_enable_debugging_screen.h b/chrome/browser/chromeos/login/screens/mock_enable_debugging_screen.h
index 16159c9..7dc4a4e 100644
--- a/chrome/browser/chromeos/login/screens/mock_enable_debugging_screen.h
+++ b/chrome/browser/chromeos/login/screens/mock_enable_debugging_screen.h
@@ -15,11 +15,14 @@
 class MockEnableDebuggingScreen : public EnableDebuggingScreen {
  public:
   MockEnableDebuggingScreen(BaseScreenDelegate* base_screen_delegate,
-                            EnableDebuggingScreenView* view);
+                            EnableDebuggingScreenView* view,
+                            const base::RepeatingClosure& exit_callback);
   ~MockEnableDebuggingScreen() override;
 
   MOCK_METHOD0(Show, void());
   MOCK_METHOD0(Hide, void());
+
+  void ExitScreen();
 };
 
 class MockEnableDebuggingScreenView : public EnableDebuggingScreenView {
diff --git a/chrome/browser/chromeos/login/screens/mock_supervision_transition_screen.cc b/chrome/browser/chromeos/login/screens/mock_supervision_transition_screen.cc
index 4b29528..44def19 100644
--- a/chrome/browser/chromeos/login/screens/mock_supervision_transition_screen.cc
+++ b/chrome/browser/chromeos/login/screens/mock_supervision_transition_screen.cc
@@ -8,11 +8,16 @@
 
 MockSupervisionTransitionScreen::MockSupervisionTransitionScreen(
     BaseScreenDelegate* base_screen_delegate,
-    SupervisionTransitionScreenView* view)
-    : SupervisionTransitionScreen(base_screen_delegate, view) {}
+    SupervisionTransitionScreenView* view,
+    const base::RepeatingClosure& exit_callback)
+    : SupervisionTransitionScreen(base_screen_delegate, view, exit_callback) {}
 
 MockSupervisionTransitionScreen::~MockSupervisionTransitionScreen() = default;
 
+void MockSupervisionTransitionScreen::ExitScreen() {
+  exit_callback()->Run();
+}
+
 MockSupervisionTransitionScreenView::MockSupervisionTransitionScreenView() =
     default;
 
diff --git a/chrome/browser/chromeos/login/screens/mock_supervision_transition_screen.h b/chrome/browser/chromeos/login/screens/mock_supervision_transition_screen.h
index dba5b009..ac463d3 100644
--- a/chrome/browser/chromeos/login/screens/mock_supervision_transition_screen.h
+++ b/chrome/browser/chromeos/login/screens/mock_supervision_transition_screen.h
@@ -15,12 +15,15 @@
 class MockSupervisionTransitionScreen : public SupervisionTransitionScreen {
  public:
   MockSupervisionTransitionScreen(BaseScreenDelegate* base_screen_delegate,
-                                  SupervisionTransitionScreenView* view);
+                                  SupervisionTransitionScreenView* view,
+                                  const base::RepeatingClosure& exit_callback);
   virtual ~MockSupervisionTransitionScreen();
 
   MOCK_METHOD0(Show, void());
   MOCK_METHOD0(Hide, void());
 
+  void ExitScreen();
+
  private:
   DISALLOW_COPY_AND_ASSIGN(MockSupervisionTransitionScreen);
 };
diff --git a/chrome/browser/chromeos/login/screens/mock_welcome_screen.cc b/chrome/browser/chromeos/login/screens/mock_welcome_screen.cc
index 71c7dc4..5dd8b94c 100644
--- a/chrome/browser/chromeos/login/screens/mock_welcome_screen.cc
+++ b/chrome/browser/chromeos/login/screens/mock_welcome_screen.cc
@@ -6,10 +6,16 @@
 
 namespace chromeos {
 
-MockWelcomeScreen::MockWelcomeScreen(BaseScreenDelegate* base_screen_delegate,
-                                     Delegate* delegate,
-                                     WelcomeView* view)
-    : WelcomeScreen(base_screen_delegate, delegate, view) {}
+MockWelcomeScreen::MockWelcomeScreen(
+    BaseScreenDelegate* base_screen_delegate,
+    Delegate* delegate,
+    WelcomeView* view,
+    const base::RepeatingClosure& exit_callback)
+    : WelcomeScreen(base_screen_delegate, delegate, view, exit_callback) {}
+
+void MockWelcomeScreen::ExitScreen() {
+  exit_callback()->Run();
+}
 
 MockWelcomeScreen::~MockWelcomeScreen() = default;
 
diff --git a/chrome/browser/chromeos/login/screens/mock_welcome_screen.h b/chrome/browser/chromeos/login/screens/mock_welcome_screen.h
index 4d050f4a..6144705 100644
--- a/chrome/browser/chromeos/login/screens/mock_welcome_screen.h
+++ b/chrome/browser/chromeos/login/screens/mock_welcome_screen.h
@@ -18,13 +18,16 @@
  public:
   MockWelcomeScreen(BaseScreenDelegate* base_screen_delegate,
                     Delegate* delegate,
-                    WelcomeView* view);
+                    WelcomeView* view,
+                    const base::RepeatingClosure& exit_callback);
   ~MockWelcomeScreen() override;
 
   MOCK_METHOD0(Show, void());
   MOCK_METHOD0(Hide, void());
   MOCK_METHOD2(SetConfiguration, void(base::Value* configuration, bool notify));
 
+  void ExitScreen();
+
  private:
   DISALLOW_COPY_AND_ASSIGN(MockWelcomeScreen);
 };
diff --git a/chrome/browser/chromeos/login/screens/mock_wrong_hwid_screen.cc b/chrome/browser/chromeos/login/screens/mock_wrong_hwid_screen.cc
index d49f3df1..283b7c7 100644
--- a/chrome/browser/chromeos/login/screens/mock_wrong_hwid_screen.cc
+++ b/chrome/browser/chromeos/login/screens/mock_wrong_hwid_screen.cc
@@ -8,8 +8,9 @@
 
 MockWrongHWIDScreen::MockWrongHWIDScreen(
     BaseScreenDelegate* base_screen_delegate,
-    WrongHWIDScreenView* view)
-    : WrongHWIDScreen(base_screen_delegate, view) {}
+    WrongHWIDScreenView* view,
+    const base::RepeatingClosure& exit_callback)
+    : WrongHWIDScreen(base_screen_delegate, view, exit_callback) {}
 
 MockWrongHWIDScreen::~MockWrongHWIDScreen() {}
 
diff --git a/chrome/browser/chromeos/login/screens/mock_wrong_hwid_screen.h b/chrome/browser/chromeos/login/screens/mock_wrong_hwid_screen.h
index 61a01c5..1260964 100644
--- a/chrome/browser/chromeos/login/screens/mock_wrong_hwid_screen.h
+++ b/chrome/browser/chromeos/login/screens/mock_wrong_hwid_screen.h
@@ -16,7 +16,8 @@
 class MockWrongHWIDScreen : public WrongHWIDScreen {
  public:
   MockWrongHWIDScreen(BaseScreenDelegate* base_screen_delegate,
-                      WrongHWIDScreenView* view);
+                      WrongHWIDScreenView* view,
+                      const base::RepeatingClosure& exit_callback);
   ~MockWrongHWIDScreen() override;
 
   MOCK_METHOD0(Show, void());
diff --git a/chrome/browser/chromeos/login/screens/multidevice_setup_screen.cc b/chrome/browser/chromeos/login/screens/multidevice_setup_screen.cc
index c7346f8..5dd9175 100644
--- a/chrome/browser/chromeos/login/screens/multidevice_setup_screen.cc
+++ b/chrome/browser/chromeos/login/screens/multidevice_setup_screen.cc
@@ -26,9 +26,11 @@
 
 MultiDeviceSetupScreen::MultiDeviceSetupScreen(
     BaseScreenDelegate* base_screen_delegate,
-    MultiDeviceSetupScreenView* view)
+    MultiDeviceSetupScreenView* view,
+    const base::RepeatingClosure& exit_callback)
     : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_MULTIDEVICE_SETUP),
-      view_(view) {
+      view_(view),
+      exit_callback_(exit_callback) {
   DCHECK(view_);
   view_->Bind(this);
 }
@@ -99,7 +101,7 @@
 }
 
 void MultiDeviceSetupScreen::ExitScreen() {
-  Finish(ScreenExitCode::MULTIDEVICE_SETUP_FINISHED);
+  exit_callback_.Run();
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/multidevice_setup_screen.h b/chrome/browser/chromeos/login/screens/multidevice_setup_screen.h
index f3654329d..15a6990a 100644
--- a/chrome/browser/chromeos/login/screens/multidevice_setup_screen.h
+++ b/chrome/browser/chromeos/login/screens/multidevice_setup_screen.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "chrome/browser/chromeos/login/screens/base_screen.h"
 
@@ -18,7 +19,8 @@
 class MultiDeviceSetupScreen : public BaseScreen {
  public:
   MultiDeviceSetupScreen(BaseScreenDelegate* base_screen_delegate,
-                         MultiDeviceSetupScreenView* view);
+                         MultiDeviceSetupScreenView* view,
+                         const base::RepeatingClosure& exit_callback);
   ~MultiDeviceSetupScreen() override;
 
   // BaseScreen:
@@ -46,6 +48,7 @@
   void ExitScreen();
 
   MultiDeviceSetupScreenView* view_;
+  base::RepeatingClosure exit_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(MultiDeviceSetupScreen);
 };
diff --git a/chrome/browser/chromeos/login/screens/multidevice_setup_screen_unittest.cc b/chrome/browser/chromeos/login/screens/multidevice_setup_screen_unittest.cc
index 0c5bd04b..a62fc53 100644
--- a/chrome/browser/chromeos/login/screens/multidevice_setup_screen_unittest.cc
+++ b/chrome/browser/chromeos/login/screens/multidevice_setup_screen_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/bind_helpers.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "chrome/browser/chromeos/login/screens/mock_base_screen_delegate.h"
 #include "chrome/browser/chromeos/login/screens/multidevice_setup_screen_view.h"
@@ -36,7 +37,8 @@
   // testing::Test:
   void SetUp() override {
     multi_device_setup_screen_ = std::make_unique<MultiDeviceSetupScreen>(
-        &mock_base_screen_delegate_, &fake_multi_device_setup_screen_view_);
+        &mock_base_screen_delegate_, &fake_multi_device_setup_screen_view_,
+        base::DoNothing());
   }
 
   void TearDown() override {}
diff --git a/chrome/browser/chromeos/login/screens/network_screen.cc b/chrome/browser/chromeos/login/screens/network_screen.cc
index 5ea06a9..4c4feb5 100644
--- a/chrome/browser/chromeos/login/screens/network_screen.cc
+++ b/chrome/browser/chromeos/login/screens/network_screen.cc
@@ -12,7 +12,6 @@
 #include "chrome/browser/chromeos/login/screen_manager.h"
 #include "chrome/browser/chromeos/login/screens/base_screen_delegate.h"
 #include "chrome/browser/chromeos/login/screens/network_screen_view.h"
-#include "chrome/browser/chromeos/login/screens/screen_exit_code.h"
 #include "chrome/browser/chromeos/login/wizard_controller.h"
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
diff --git a/chrome/browser/chromeos/login/screens/recommend_apps_screen.cc b/chrome/browser/chromeos/login/screens/recommend_apps_screen.cc
index d0c928b..51be2366 100644
--- a/chrome/browser/chromeos/login/screens/recommend_apps_screen.cc
+++ b/chrome/browser/chromeos/login/screens/recommend_apps_screen.cc
@@ -8,9 +8,11 @@
 
 RecommendAppsScreen::RecommendAppsScreen(
     BaseScreenDelegate* base_screen_delegate,
-    RecommendAppsScreenView* view)
+    RecommendAppsScreenView* view,
+    const ScreenExitCallback& exit_callback)
     : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_RECOMMEND_APPS),
-      view_(view) {
+      view_(view),
+      exit_callback_(exit_callback) {
   DCHECK(view_);
 
   view_->Bind(this);
@@ -35,7 +37,7 @@
 }
 
 void RecommendAppsScreen::OnSkip() {
-  Finish(ScreenExitCode::RECOMMEND_APPS_SKIPPED);
+  exit_callback_.Run(Result::SKIPPED);
 }
 
 void RecommendAppsScreen::OnRetry() {
@@ -43,7 +45,7 @@
 }
 
 void RecommendAppsScreen::OnInstall() {
-  Finish(ScreenExitCode::RECOMMEND_APPS_SELECTED);
+  exit_callback_.Run(Result::SELECTED);
 }
 
 void RecommendAppsScreen::OnViewDestroyed(RecommendAppsScreenView* view) {
diff --git a/chrome/browser/chromeos/login/screens/recommend_apps_screen.h b/chrome/browser/chromeos/login/screens/recommend_apps_screen.h
index dc23bad..b4e1a39 100644
--- a/chrome/browser/chromeos/login/screens/recommend_apps_screen.h
+++ b/chrome/browser/chromeos/login/screens/recommend_apps_screen.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <string>
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "chrome/browser/chromeos/login/screens/base_screen.h"
 #include "chrome/browser/chromeos/login/screens/recommend_apps/recommend_apps_fetcher.h"
@@ -22,8 +23,12 @@
 class RecommendAppsScreen : public BaseScreen,
                             public RecommendAppsScreenViewObserver {
  public:
+  enum class Result { SELECTED, SKIPPED };
+
+  using ScreenExitCallback = base::RepeatingCallback<void(Result result)>;
   RecommendAppsScreen(BaseScreenDelegate* base_screen_delegate,
-                      RecommendAppsScreenView* view);
+                      RecommendAppsScreenView* view,
+                      const ScreenExitCallback& exit_callback);
   ~RecommendAppsScreen() override;
 
   // BaseScreen:
@@ -38,6 +43,7 @@
 
  private:
   RecommendAppsScreenView* view_;
+  ScreenExitCallback exit_callback_;
 
   std::unique_ptr<RecommendAppsFetcher> recommend_apps_fetcher_;
 
diff --git a/chrome/browser/chromeos/login/screens/reset_screen.cc b/chrome/browser/chromeos/login/screens/reset_screen.cc
index 3f65c5b..7aa82103 100644
--- a/chrome/browser/chromeos/login/screens/reset_screen.cc
+++ b/chrome/browser/chromeos/login/screens/reset_screen.cc
@@ -93,9 +93,11 @@
 }  // namespace
 
 ResetScreen::ResetScreen(BaseScreenDelegate* base_screen_delegate,
-                         ResetView* view)
+                         ResetView* view,
+                         const base::RepeatingClosure& exit_callback)
     : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_OOBE_RESET),
       view_(view),
+      exit_callback_(exit_callback),
       weak_ptr_factory_(this) {
   DCHECK(view_);
   if (view_)
@@ -234,14 +236,17 @@
 
 void ResetScreen::OnCancel() {
   if (context_.GetInteger(kContextKeyScreenState, STATE_RESTART_REQUIRED) ==
-      STATE_REVERT_PROMISE)
+      STATE_REVERT_PROMISE) {
     return;
+  }
+
   // Hide Rollback view for the next show.
   if (context_.GetBoolean(kContextKeyIsRollbackAvailable) &&
-      context_.GetBoolean(kContextKeyIsRollbackChecked))
+      context_.GetBoolean(kContextKeyIsRollbackChecked)) {
     OnToggleRollback();
-  Finish(ScreenExitCode::RESET_CANCELED);
+  }
   DBusThreadManager::Get()->GetUpdateEngineClient()->RemoveObserver(this);
+  exit_callback_.Run();
 }
 
 void ResetScreen::OnPowerwash() {
diff --git a/chrome/browser/chromeos/login/screens/reset_screen.h b/chrome/browser/chromeos/login/screens/reset_screen.h
index ac42dc7..9eebd3b 100644
--- a/chrome/browser/chromeos/login/screens/reset_screen.h
+++ b/chrome/browser/chromeos/login/screens/reset_screen.h
@@ -8,6 +8,7 @@
 #include <set>
 #include <string>
 
+#include "base/callback.h"
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
@@ -25,9 +26,13 @@
 class ResetView;
 
 // Representation independent class that controls screen showing reset to users.
+// It run exit callback only if the user cancels the reset. Other user actions
+// will end up in the device restart.
 class ResetScreen : public BaseScreen, public UpdateEngineClient::Observer {
  public:
-  ResetScreen(BaseScreenDelegate* base_screen_delegate, ResetView* view);
+  ResetScreen(BaseScreenDelegate* base_screen_delegate,
+              ResetView* view,
+              const base::RepeatingClosure& exit_callback);
   ~ResetScreen() override;
 
   // Called when view is destroyed so there's no dead reference to it.
@@ -69,6 +74,7 @@
   ErrorScreen* GetErrorScreen();
 
   ResetView* view_;
+  base::RepeatingClosure exit_callback_;
 
   // Help application used for help dialogs.
   scoped_refptr<HelpAppLauncher> help_app_;
diff --git a/chrome/browser/chromeos/login/screens/screen_exit_code.cc b/chrome/browser/chromeos/login/screens/screen_exit_code.cc
deleted file mode 100644
index 9735d450..0000000
--- a/chrome/browser/chromeos/login/screens/screen_exit_code.cc
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/chromeos/login/screens/screen_exit_code.h"
-
-#include "base/logging.h"
-
-namespace chromeos {
-
-std::string ExitCodeToString(ScreenExitCode code) {
-  switch (code) {
-    case ScreenExitCode::WELCOME_CONTINUED:
-      return "WELCOME_CONTINUED";
-    case ScreenExitCode::HID_DETECTION_COMPLETED:
-      return "HID_DETECTION_COMPLETED";
-    case ScreenExitCode::DEPRECATED_CONNECTION_FAILED:
-      return "CONNECTION_FAILED";
-    case ScreenExitCode::DEPRECATED_UPDATE_INSTALLED:
-      return "UPDATE_INSTALLED";
-    case ScreenExitCode::DEPRECATED_UPDATE_NOUPDATE:
-      return "UPDATE_NOUPDATE";
-    case ScreenExitCode::DEPRECATED_UPDATE_ERROR_CHECKING_FOR_UPDATE:
-      return "UPDATE_ERROR_CHECKING_FOR_UPDATE";
-    case ScreenExitCode::DEPRECATED_UPDATE_ERROR_UPDATING:
-      return "UPDATE_ERROR_UPDATING";
-    case ScreenExitCode::DEPRECATED_USER_IMAGE_SELECTED:
-      return "USER_IMAGE_SELECTED";
-    case ScreenExitCode::DEPRECATED_EULA_ACCEPTED:
-      return "EULA_ACCEPTED";
-    case ScreenExitCode::DEPRECATED_EULA_BACK:
-      return "EULA_BACK";
-    case ScreenExitCode::DEPRECATED_ENTERPRISE_AUTO_ENROLLMENT_CHECK_COMPLETED:
-      return "ENTERPRISE_AUTO_ENROLLMENT_CHECK_COMPLETED";
-    case ScreenExitCode::DEPRECATED_ENTERPRISE_ENROLLMENT_COMPLETED:
-      return "ENTERPRISE_ENROLLMENT_COMPLETED";
-    case ScreenExitCode::DEPRECATED_ENTERPRISE_ENROLLMENT_BACK:
-      return "ENTERPRISE_ENROLLMENT_BACK";
-    case ScreenExitCode::RESET_CANCELED:
-      return "RESET_CANCELED";
-    case ScreenExitCode::KIOSK_AUTOLAUNCH_CANCELED:
-      return "KIOSK_AUTOLAUNCH_CANCELED";
-    case ScreenExitCode::KIOSK_AUTOLAUNCH_CONFIRMED:
-      return "KIOSK_AUTOLAUNCH_CONFIRMED";
-    case ScreenExitCode::KIOSK_ENABLE_COMPLETED:
-      return "KIOSK_ENABLE_COMPLETED";
-    case ScreenExitCode::TERMS_OF_SERVICE_DECLINED:
-      return "TERMS_OF_SERVICE_DECLINED";
-    case ScreenExitCode::TERMS_OF_SERVICE_ACCEPTED:
-      return "TERMS_OF_SERVICE_ACCEPTED";
-    case ScreenExitCode::WRONG_HWID_WARNING_SKIPPED:
-      return "WRONG_HWID_WARNING_SKIPPED";
-    case ScreenExitCode::ENABLE_DEBUGGING_FINISHED:
-      return "ENABLE_DEBUGGING_FINISHED";
-    case ScreenExitCode::ENABLE_DEBUGGING_CANCELED:
-      return "ENABLE_DEBUGGING_CANCELED";
-    case ScreenExitCode::ARC_TERMS_OF_SERVICE_SKIPPED:
-      return "ARC_TERMS_OF_SERVICE_SKIPPED";
-    case ScreenExitCode::ARC_TERMS_OF_SERVICE_ACCEPTED:
-      return "ARC_TERMS_OF_SERVICE_ACCEPTED";
-    case ScreenExitCode::DEPRECATED_UPDATE_ERROR_UPDATING_CRITICAL_UPDATE:
-      return "UPDATE_ERROR_UPDATING_CRITICAL_UPDATE";
-    case ScreenExitCode::SYNC_CONSENT_FINISHED:
-      return "SYNC_CONSENT_FINISHED";
-    case ScreenExitCode::DEMO_MODE_SETUP_FINISHED:
-      return "DEMO_MODE_SETUP_FINISHED";
-    case ScreenExitCode::DEMO_MODE_SETUP_CANCELED:
-      return "DEMO_MODE_SETUP_CANCELED";
-    case ScreenExitCode::RECOMMEND_APPS_SKIPPED:
-      return "RECOMMEND_APPS_SKIPPED";
-    case ScreenExitCode::RECOMMEND_APPS_SELECTED:
-      return "RECOMMEND_APPS_SELECTED";
-    case ScreenExitCode::DEMO_MODE_PREFERENCES_CONTINUED:
-      return "DEMO_MODE_PREFERENCES_CONTINUED";
-    case ScreenExitCode::DEMO_MODE_PREFERENCES_CANCELED:
-      return "DEMO_MODE_PREFERENCES_CANCELED";
-    case ScreenExitCode::APP_DOWNLOADING_FINISHED:
-      return "APP_DOWNLOADING_FINISHED";
-    case ScreenExitCode::ARC_TERMS_OF_SERVICE_BACK:
-      return "ARC_TERMS_OF_SERVICE_BACK";
-    case ScreenExitCode::DISCOVER_FINISHED:
-      return "DISCOVER_FINISHED";
-    case ScreenExitCode::DEPRECATED_NETWORK_BACK:
-      return "NETWORK_BACK";
-    case ScreenExitCode::DEPRECATED_NETWORK_CONNECTED:
-      return "NETWORK_CONNECTED";
-    case ScreenExitCode::DEPRECATED_NETWORK_OFFLINE_DEMO_SETUP:
-      return "NETWORK_OFFLINE_DEMO_SETUP";
-    case ScreenExitCode::FINGERPRINT_SETUP_FINISHED:
-      return "FINGERPRINT_SETUP_FINISHED";
-    case ScreenExitCode::MARKETING_OPT_IN_FINISHED:
-      return "MARKETING_OPT_IN_FINISHED";
-    case ScreenExitCode::ASSISTANT_OPTIN_FLOW_FINISHED:
-      return "ASSISTANT_OPTIN_FLOW_FINISHED";
-    case ScreenExitCode::MULTIDEVICE_SETUP_FINISHED:
-      return "MULTIDEVICE_SETUP_FINISHED";
-    case ScreenExitCode::SUPERVISION_TRANSITION_FINISHED:
-      return "SUPERVISION_TRANSITION_FINISHED";
-    case ScreenExitCode::EXIT_CODES_COUNT:
-    default:
-      NOTREACHED();
-      return "";
-  }
-}
-
-}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/screen_exit_code.h b/chrome/browser/chromeos/login/screens/screen_exit_code.h
deleted file mode 100644
index ac1814fb..0000000
--- a/chrome/browser/chromeos/login/screens/screen_exit_code.h
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_SCREEN_EXIT_CODE_H_
-#define CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_SCREEN_EXIT_CODE_H_
-
-#include <string>
-
-namespace chromeos {
-
-// Each login screen or a view shown within login wizard view is itself a
-// state. Upon exit each view returns one of the results by calling
-// the BaseScreenDelegate::OnExit() method. Depending on the result and the
-// current view or state the login wizard decides what is the next view to show.
-//
-// There must be an exit code for each way to exit the screen for each screen.
-//
-// Numeric ids are provided to facilitate interpretation of log files only,
-// they are subject to change without notice.
-enum class ScreenExitCode {
-  // "Continue" was pressed on welcome screen.
-  WELCOME_CONTINUED = 0,
-  HID_DETECTION_COMPLETED = 1,
-  // Connection failed while trying to load a WebPageScreen.
-  DEPRECATED_CONNECTION_FAILED = 2,
-  DEPRECATED_UPDATE_INSTALLED = 3,
-  // This exit code means EITHER that there was no update, OR that there
-  // was an update, but that it was not a "critical" update. "Critical" updates
-  // are those that have a deadline and require the device to reboot.
-  DEPRECATED_UPDATE_NOUPDATE = 4,
-  DEPRECATED_UPDATE_ERROR_CHECKING_FOR_UPDATE = 5,
-  DEPRECATED_UPDATE_ERROR_UPDATING = 6,
-  DEPRECATED_USER_IMAGE_SELECTED = 7,
-  DEPRECATED_EULA_ACCEPTED = 8,
-  DEPRECATED_EULA_BACK = 9,
-  DEPRECATED_ENTERPRISE_AUTO_ENROLLMENT_CHECK_COMPLETED = 10,
-  DEPRECATED_ENTERPRISE_ENROLLMENT_COMPLETED = 11,
-  DEPRECATED_ENTERPRISE_ENROLLMENT_BACK = 12,
-  RESET_CANCELED = 13,
-  KIOSK_AUTOLAUNCH_CANCELED = 14,
-  KIOSK_AUTOLAUNCH_CONFIRMED = 15,
-  KIOSK_ENABLE_COMPLETED = 16,
-  TERMS_OF_SERVICE_DECLINED = 17,
-  TERMS_OF_SERVICE_ACCEPTED = 18,
-  WRONG_HWID_WARNING_SKIPPED = 19,
-  ENABLE_DEBUGGING_FINISHED = 21,
-  ENABLE_DEBUGGING_CANCELED = 22,
-  ARC_TERMS_OF_SERVICE_SKIPPED = 23,
-  ARC_TERMS_OF_SERVICE_ACCEPTED = 24,
-  DEPRECATED_UPDATE_ERROR_UPDATING_CRITICAL_UPDATE = 25,
-  DEPRECATED_ENCRYPTION_MIGRATION_FINISHED = 26,
-  DEPRECATED_ENCRYPTION_MIGRATION_SKIPPED = 27,
-  SYNC_CONSENT_FINISHED = 32,
-  DEMO_MODE_SETUP_FINISHED = 33,
-  DEMO_MODE_SETUP_CANCELED = 34,
-  RECOMMEND_APPS_SKIPPED = 35,
-  RECOMMEND_APPS_SELECTED = 36,
-  DEMO_MODE_PREFERENCES_CONTINUED = 37,
-  DEMO_MODE_PREFERENCES_CANCELED = 38,
-  APP_DOWNLOADING_FINISHED = 39,
-  ARC_TERMS_OF_SERVICE_BACK = 40,
-  DISCOVER_FINISHED = 41,
-  DEPRECATED_NETWORK_BACK = 42,
-  DEPRECATED_NETWORK_CONNECTED = 43,
-  DEPRECATED_NETWORK_OFFLINE_DEMO_SETUP = 44,
-  FINGERPRINT_SETUP_FINISHED = 45,
-  MARKETING_OPT_IN_FINISHED = 46,
-  ASSISTANT_OPTIN_FLOW_FINISHED = 47,
-  MULTIDEVICE_SETUP_FINISHED = 48,
-  DEPRECATED_UPDATE_REJECT_OVER_CELLULAR = 49,
-  SUPERVISION_TRANSITION_FINISHED = 50,
-  EXIT_CODES_COUNT  // not a real code, must be the last
-};
-
-std::string ExitCodeToString(ScreenExitCode code);
-
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_CHROMEOS_LOGIN_SCREENS_SCREEN_EXIT_CODE_H_
diff --git a/chrome/browser/chromeos/login/screens/supervision_transition_screen.cc b/chrome/browser/chromeos/login/screens/supervision_transition_screen.cc
index 9c4525e..dae7f3d 100644
--- a/chrome/browser/chromeos/login/screens/supervision_transition_screen.cc
+++ b/chrome/browser/chromeos/login/screens/supervision_transition_screen.cc
@@ -11,10 +11,12 @@
 
 SupervisionTransitionScreen::SupervisionTransitionScreen(
     BaseScreenDelegate* base_screen_delegate,
-    SupervisionTransitionScreenView* view)
+    SupervisionTransitionScreenView* view,
+    const base::RepeatingClosure& exit_callback)
     : BaseScreen(base_screen_delegate,
                  OobeScreen::SCREEN_SUPERVISION_TRANSITION),
-      view_(view) {
+      view_(view),
+      exit_callback_(exit_callback) {
   if (view_)
     view_->Bind(this);
 }
@@ -41,7 +43,7 @@
 }
 
 void SupervisionTransitionScreen::OnSupervisionTransitionFinished() {
-  Finish(ScreenExitCode::SUPERVISION_TRANSITION_FINISHED);
+  exit_callback_.Run();
 }
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/supervision_transition_screen.h b/chrome/browser/chromeos/login/screens/supervision_transition_screen.h
index 44ccb624..13bc548 100644
--- a/chrome/browser/chromeos/login/screens/supervision_transition_screen.h
+++ b/chrome/browser/chromeos/login/screens/supervision_transition_screen.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/callback.h"
 #include "base/macros.h"
 #include "chrome/browser/chromeos/login/screens/base_screen.h"
 
@@ -18,7 +19,8 @@
 class SupervisionTransitionScreen : public BaseScreen {
  public:
   SupervisionTransitionScreen(BaseScreenDelegate* base_screen_delegate,
-                              SupervisionTransitionScreenView* view);
+                              SupervisionTransitionScreenView* view,
+                              const base::RepeatingClosure& exit_callback);
   ~SupervisionTransitionScreen() override;
 
   // BaseScreen:
@@ -31,8 +33,12 @@
   // Called when transition has finished, exits the screen.
   void OnSupervisionTransitionFinished();
 
+ protected:
+  base::RepeatingClosure* exit_callback() { return &exit_callback_; }
+
  private:
   SupervisionTransitionScreenView* view_;
+  base::RepeatingClosure exit_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(SupervisionTransitionScreen);
 };
diff --git a/chrome/browser/chromeos/login/screens/sync_consent_screen.cc b/chrome/browser/chromeos/login/screens/sync_consent_screen.cc
index 660ddee6..9de6076 100644
--- a/chrome/browser/chromeos/login/screens/sync_consent_screen.cc
+++ b/chrome/browser/chromeos/login/screens/sync_consent_screen.cc
@@ -60,10 +60,13 @@
   }
 }
 
-SyncConsentScreen::SyncConsentScreen(BaseScreenDelegate* base_screen_delegate,
-                                     SyncConsentScreenView* view)
+SyncConsentScreen::SyncConsentScreen(
+    BaseScreenDelegate* base_screen_delegate,
+    SyncConsentScreenView* view,
+    const base::RepeatingClosure& exit_callback)
     : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_SYNC_CONSENT),
-      view_(view) {
+      view_(view),
+      exit_callback_(exit_callback) {
   DCHECK(view_);
   view_->Bind(this);
 }
@@ -79,7 +82,7 @@
   UpdateScreen();
 
   if (behavior_ == SyncScreenBehavior::SKIP) {
-    Finish(ScreenExitCode::SYNC_CONSENT_FINISHED);
+    exit_callback_.Run();
     return;
   }
 
@@ -104,12 +107,12 @@
 void SyncConsentScreen::OnUserAction(const std::string& action_id) {
   if (action_id == kUserActionContinueWithSyncOnly) {
     // TODO(alemate) https://crbug.com/822889
-    Finish(ScreenExitCode::SYNC_CONSENT_FINISHED);
+    exit_callback_.Run();
     return;
   }
   if (action_id == kUserActionContinueWithSyncAndPersonalization) {
     // TODO(alemate) https://crbug.com/822889
-    Finish(ScreenExitCode::SYNC_CONSENT_FINISHED);
+    exit_callback_.Run();
     return;
   }
   BaseScreen::OnUserAction(action_id);
@@ -125,14 +128,14 @@
   RecordConsent(consent_description, consent_confirmation);
   profile_->GetPrefs()->SetBoolean(prefs::kShowSyncSettingsOnSessionStart,
                                    true);
-  Finish(ScreenExitCode::SYNC_CONSENT_FINISHED);
+  exit_callback_.Run();
 }
 
 void SyncConsentScreen::OnContinueWithDefaults(
     const std::vector<int>& consent_description,
     const int consent_confirmation) {
   RecordConsent(consent_description, consent_confirmation);
-  Finish(ScreenExitCode::SYNC_CONSENT_FINISHED);
+  exit_callback_.Run();
 }
 
 void SyncConsentScreen::SetDelegateForTesting(
@@ -191,7 +194,7 @@
 
   // Screen is shown and behavior has changed.
   if (behavior_ == SyncScreenBehavior::SKIP)
-    Finish(ScreenExitCode::SYNC_CONSENT_FINISHED);
+    exit_callback_.Run();
 
   if (behavior_ == SyncScreenBehavior::SHOW) {
     view_->SetThrobberVisible(false /*visible*/);
diff --git a/chrome/browser/chromeos/login/screens/sync_consent_screen.h b/chrome/browser/chromeos/login/screens/sync_consent_screen.h
index 8738a33..2262f38 100644
--- a/chrome/browser/chromeos/login/screens/sync_consent_screen.h
+++ b/chrome/browser/chromeos/login/screens/sync_consent_screen.h
@@ -58,7 +58,8 @@
   static void MaybeLaunchSyncConsentSettings(Profile* profile);
 
   SyncConsentScreen(BaseScreenDelegate* base_screen_delegate,
-                    SyncConsentScreenView* view);
+                    SyncConsentScreenView* view,
+                    const base::RepeatingClosure& exit_callback);
   ~SyncConsentScreen() override;
 
   // BaseScreen:
@@ -110,6 +111,7 @@
   SyncScreenBehavior behavior_ = UNKNOWN;
 
   SyncConsentScreenView* const view_;
+  base::RepeatingClosure exit_callback_;
 
   // Primary user ind his Profile (if screen is shown).
   const user_manager::User* user_ = nullptr;
diff --git a/chrome/browser/chromeos/login/screens/terms_of_service_screen.cc b/chrome/browser/chromeos/login/screens/terms_of_service_screen.cc
index 84876ac..bef70f1 100644
--- a/chrome/browser/chromeos/login/screens/terms_of_service_screen.cc
+++ b/chrome/browser/chromeos/login/screens/terms_of_service_screen.cc
@@ -31,9 +31,11 @@
 
 TermsOfServiceScreen::TermsOfServiceScreen(
     BaseScreenDelegate* base_screen_delegate,
-    TermsOfServiceScreenView* view)
+    TermsOfServiceScreenView* view,
+    const ScreenExitCallback& exit_callback)
     : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_TERMS_OF_SERVICE),
-      view_(view) {
+      view_(view),
+      exit_callback_(exit_callback) {
   DCHECK(view_);
   if (view_)
     view_->SetDelegate(this);
@@ -66,11 +68,11 @@
 }
 
 void TermsOfServiceScreen::OnDecline() {
-  Finish(ScreenExitCode::TERMS_OF_SERVICE_DECLINED);
+  exit_callback_.Run(Result::DECLINED);
 }
 
 void TermsOfServiceScreen::OnAccept() {
-  Finish(ScreenExitCode::TERMS_OF_SERVICE_ACCEPTED);
+  exit_callback_.Run(Result::ACCEPTED);
 }
 
 void TermsOfServiceScreen::OnViewDestroyed(TermsOfServiceScreenView* view) {
diff --git a/chrome/browser/chromeos/login/screens/terms_of_service_screen.h b/chrome/browser/chromeos/login/screens/terms_of_service_screen.h
index 1eeadf7..812a7113 100644
--- a/chrome/browser/chromeos/login/screens/terms_of_service_screen.h
+++ b/chrome/browser/chromeos/login/screens/terms_of_service_screen.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/callback.h"
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "base/timer/timer.h"
@@ -28,8 +29,12 @@
 class TermsOfServiceScreen : public BaseScreen,
                              public TermsOfServiceScreenView::Delegate {
  public:
+  enum class Result { ACCEPTED, DECLINED };
+
+  using ScreenExitCallback = base::RepeatingCallback<void(Result result)>;
   TermsOfServiceScreen(BaseScreenDelegate* base_screen_delegate,
-                       TermsOfServiceScreenView* view);
+                       TermsOfServiceScreenView* view,
+                       const ScreenExitCallback& exit_callback);
   ~TermsOfServiceScreen() override;
 
   // BaseScreen:
@@ -52,6 +57,7 @@
   void OnDownloaded(std::unique_ptr<std::string> response_body);
 
   TermsOfServiceScreenView* view_;
+  ScreenExitCallback exit_callback_;
 
   std::unique_ptr<network::SimpleURLLoader> terms_of_service_loader_;
 
diff --git a/chrome/browser/chromeos/login/screens/welcome_screen.cc b/chrome/browser/chromeos/login/screens/welcome_screen.cc
index e52ab60..396bcc6 100644
--- a/chrome/browser/chromeos/login/screens/welcome_screen.cc
+++ b/chrome/browser/chromeos/login/screens/welcome_screen.cc
@@ -19,7 +19,6 @@
 #include "chrome/browser/chromeos/login/oobe_screen.h"
 #include "chrome/browser/chromeos/login/screen_manager.h"
 #include "chrome/browser/chromeos/login/screens/base_screen_delegate.h"
-#include "chrome/browser/chromeos/login/screens/screen_exit_code.h"
 #include "chrome/browser/chromeos/login/screens/welcome_view.h"
 #include "chrome/browser/chromeos/login/ui/input_events_blocker.h"
 #include "chrome/browser/chromeos/login/wizard_controller.h"
@@ -56,10 +55,12 @@
 
 WelcomeScreen::WelcomeScreen(BaseScreenDelegate* base_screen_delegate,
                              Delegate* delegate,
-                             WelcomeView* view)
+                             WelcomeView* view,
+                             const base::RepeatingClosure& exit_callback)
     : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_OOBE_WELCOME),
       view_(view),
       delegate_(delegate),
+      exit_callback_(exit_callback),
       weak_factory_(this) {
   if (view_)
     view_->Bind(this);
@@ -240,7 +241,7 @@
   if (view_) {
     view_->StopDemoModeDetection();
   }
-  Finish(ScreenExitCode::WELCOME_CONTINUED);
+  exit_callback_.Run();
 }
 
 void WelcomeScreen::OnLanguageChangedCallback(
diff --git a/chrome/browser/chromeos/login/screens/welcome_screen.h b/chrome/browser/chromeos/login/screens/welcome_screen.h
index 037009e..060e303d5 100644
--- a/chrome/browser/chromeos/login/screens/welcome_screen.h
+++ b/chrome/browser/chromeos/login/screens/welcome_screen.h
@@ -9,7 +9,7 @@
 #include <string>
 
 #include "ash/public/interfaces/locale.mojom.h"
-#include "base/callback_forward.h"
+#include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
@@ -49,7 +49,8 @@
 
   WelcomeScreen(BaseScreenDelegate* base_screen_delegate,
                 Delegate* delegate,
-                WelcomeView* view);
+                WelcomeView* view,
+                const base::RepeatingClosure& exit_callback);
   ~WelcomeScreen() override;
 
   static WelcomeScreen* Get(ScreenManager* manager);
@@ -81,6 +82,10 @@
   void AddObserver(Observer* observer);
   void RemoveObserver(Observer* observer);
 
+ protected:
+  // Exposes exit callback to test overrides.
+  base::RepeatingClosure* exit_callback() { return &exit_callback_; }
+
  private:
   // BaseScreen implementation:
   void Show() override;
@@ -127,6 +132,7 @@
 
   WelcomeView* view_ = nullptr;
   Delegate* delegate_ = nullptr;
+  base::RepeatingClosure exit_callback_;
 
   std::string input_method_;
   std::string timezone_;
diff --git a/chrome/browser/chromeos/login/screens/wrong_hwid_screen.cc b/chrome/browser/chromeos/login/screens/wrong_hwid_screen.cc
index 306101f1..9d5023d 100644
--- a/chrome/browser/chromeos/login/screens/wrong_hwid_screen.cc
+++ b/chrome/browser/chromeos/login/screens/wrong_hwid_screen.cc
@@ -10,9 +10,11 @@
 namespace chromeos {
 
 WrongHWIDScreen::WrongHWIDScreen(BaseScreenDelegate* base_screen_delegate,
-                                 WrongHWIDScreenView* view)
+                                 WrongHWIDScreenView* view,
+                                 const base::RepeatingClosure& exit_callback)
     : BaseScreen(base_screen_delegate, OobeScreen::SCREEN_WRONG_HWID),
-      view_(view) {
+      view_(view),
+      exit_callback_(exit_callback) {
   DCHECK(view_);
   if (view_)
     view_->SetDelegate(this);
@@ -34,7 +36,7 @@
 }
 
 void WrongHWIDScreen::OnExit() {
-  Finish(ScreenExitCode::WRONG_HWID_WARNING_SKIPPED);
+  exit_callback_.Run();
 }
 
 void WrongHWIDScreen::OnViewDestroyed(WrongHWIDScreenView* view) {
diff --git a/chrome/browser/chromeos/login/screens/wrong_hwid_screen.h b/chrome/browser/chromeos/login/screens/wrong_hwid_screen.h
index 0ff2ab3..0bfc98e 100644
--- a/chrome/browser/chromeos/login/screens/wrong_hwid_screen.h
+++ b/chrome/browser/chromeos/login/screens/wrong_hwid_screen.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/callback.h"
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "chrome/browser/chromeos/login/screens/base_screen.h"
@@ -20,7 +21,8 @@
                         public WrongHWIDScreenView::Delegate {
  public:
   WrongHWIDScreen(BaseScreenDelegate* base_screen_delegate,
-                  WrongHWIDScreenView* view);
+                  WrongHWIDScreenView* view,
+                  const base::RepeatingClosure& exit_callback);
   ~WrongHWIDScreen() override;
 
   // BaseScreen implementation:
@@ -33,6 +35,7 @@
 
  private:
   WrongHWIDScreenView* view_;
+  base::RepeatingClosure exit_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(WrongHWIDScreen);
 };
diff --git a/chrome/browser/chromeos/login/wizard_controller.cc b/chrome/browser/chromeos/login/wizard_controller.cc
index aaa931a5..2169ed9 100644
--- a/chrome/browser/chromeos/login/wizard_controller.cc
+++ b/chrome/browser/chromeos/login/wizard_controller.cc
@@ -67,7 +67,6 @@
 #include "chrome/browser/chromeos/login/screens/reset_screen.h"
 #include "chrome/browser/chromeos/login/screens/supervision_transition_screen.h"
 #include "chrome/browser/chromeos/login/screens/sync_consent_screen.h"
-#include "chrome/browser/chromeos/login/screens/terms_of_service_screen.h"
 #include "chrome/browser/chromeos/login/screens/update_required_screen.h"
 #include "chrome/browser/chromeos/login/screens/update_screen.h"
 #include "chrome/browser/chromeos/login/screens/welcome_view.h"
@@ -374,8 +373,10 @@
   OobeUI* oobe_ui = GetOobeUI();
 
   if (screen == OobeScreen::SCREEN_OOBE_WELCOME) {
-    return std::make_unique<WelcomeScreen>(this, this,
-                                           oobe_ui->GetWelcomeView());
+    return std::make_unique<WelcomeScreen>(
+        this, this, oobe_ui->GetWelcomeView(),
+        base::BindRepeating(&WizardController::OnWelcomeScreenExit,
+                            weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_OOBE_NETWORK) {
     return std::make_unique<NetworkScreen>(
         this, oobe_ui->GetNetworkScreenView(),
@@ -397,44 +398,70 @@
         base::BindRepeating(&WizardController::OnEnrollmentScreenExit,
                             weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_OOBE_RESET) {
-    return std::make_unique<chromeos::ResetScreen>(this,
-                                                   oobe_ui->GetResetView());
+    return std::make_unique<chromeos::ResetScreen>(
+        this, oobe_ui->GetResetView(),
+        base::BindRepeating(&WizardController::OnResetScreenExit,
+                            weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_OOBE_DEMO_SETUP) {
     return std::make_unique<chromeos::DemoSetupScreen>(
-        this, oobe_ui->GetDemoSetupScreenView());
+        this, oobe_ui->GetDemoSetupScreenView(),
+        base::BindRepeating(&WizardController::OnDemoSetupScreenExit,
+                            weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_OOBE_DEMO_PREFERENCES) {
     return std::make_unique<chromeos::DemoPreferencesScreen>(
-        this, oobe_ui->GetDemoPreferencesScreenView());
+        this, oobe_ui->GetDemoPreferencesScreenView(),
+        base::BindRepeating(&WizardController::OnDemoPreferencesScreenExit,
+                            weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_OOBE_ENABLE_DEBUGGING) {
     return std::make_unique<EnableDebuggingScreen>(
-        this, oobe_ui->GetEnableDebuggingScreenView());
+        this, oobe_ui->GetEnableDebuggingScreenView(),
+        base::BindRepeating(&WizardController::OnEnableDebuggingScreenExit,
+                            weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_KIOSK_ENABLE) {
     return std::make_unique<KioskEnableScreen>(
-        this, oobe_ui->GetKioskEnableScreenView());
+        this, oobe_ui->GetKioskEnableScreenView(),
+        base::BindRepeating(&WizardController::OnKioskEnableScreenExit,
+                            weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_KIOSK_AUTOLAUNCH) {
     return std::make_unique<KioskAutolaunchScreen>(
-        this, oobe_ui->GetKioskAutolaunchScreenView());
+        this, oobe_ui->GetKioskAutolaunchScreenView(),
+        base::BindRepeating(&WizardController::OnKioskAutolaunchScreenExit,
+                            weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_TERMS_OF_SERVICE) {
     return std::make_unique<TermsOfServiceScreen>(
-        this, oobe_ui->GetTermsOfServiceScreenView());
+        this, oobe_ui->GetTermsOfServiceScreenView(),
+        base::BindRepeating(&WizardController::OnTermsOfServiceScreenExit,
+                            weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_SYNC_CONSENT) {
     return std::make_unique<SyncConsentScreen>(
-        this, oobe_ui->GetSyncConsentScreenView());
+        this, oobe_ui->GetSyncConsentScreenView(),
+        base::BindRepeating(&WizardController::OnSyncConsentScreenExit,
+                            weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_ARC_TERMS_OF_SERVICE) {
     return std::make_unique<ArcTermsOfServiceScreen>(
-        this, oobe_ui->GetArcTermsOfServiceScreenView());
+        this, oobe_ui->GetArcTermsOfServiceScreenView(),
+        base::BindRepeating(&WizardController::OnArcTermsOfServiceScreenExit,
+                            weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_RECOMMEND_APPS) {
     return std::make_unique<RecommendAppsScreen>(
-        this, oobe_ui->GetRecommendAppsScreenView());
+        this, oobe_ui->GetRecommendAppsScreenView(),
+        base::BindRepeating(&WizardController::OnRecommendAppsScreenExit,
+                            weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_APP_DOWNLOADING) {
     return std::make_unique<AppDownloadingScreen>(
-        this, oobe_ui->GetAppDownloadingScreenView());
+        this, oobe_ui->GetAppDownloadingScreenView(),
+        base::BindRepeating(&WizardController::OnAppDownloadingScreenExit,
+                            weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_WRONG_HWID) {
-    return std::make_unique<WrongHWIDScreen>(this,
-                                             oobe_ui->GetWrongHWIDScreenView());
+    return std::make_unique<WrongHWIDScreen>(
+        this, oobe_ui->GetWrongHWIDScreenView(),
+        base::BindRepeating(&WizardController::OnWrongHWIDScreenExit,
+                            weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_OOBE_HID_DETECTION) {
     return std::make_unique<chromeos::HIDDetectionScreen>(
-        this, oobe_ui->GetHIDDetectionView());
+        this, oobe_ui->GetHIDDetectionView(),
+        base::BindRepeating(&WizardController::OnHidDetectionScreenExit,
+                            weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_AUTO_ENROLLMENT_CHECK) {
     return std::make_unique<AutoEnrollmentCheckScreen>(
         this, oobe_ui->GetAutoEnrollmentCheckScreenView(),
@@ -448,25 +475,38 @@
         this, oobe_ui->GetEncryptionMigrationScreenView());
   } else if (screen == OobeScreen::SCREEN_SUPERVISION_TRANSITION) {
     return std::make_unique<SupervisionTransitionScreen>(
-        this, oobe_ui->GetSupervisionTransitionScreenView());
+        this, oobe_ui->GetSupervisionTransitionScreenView(),
+        base::BindRepeating(
+            &WizardController::OnSupervisionTransitionScreenExit,
+            weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_UPDATE_REQUIRED) {
     return std::make_unique<UpdateRequiredScreen>(
         this, oobe_ui->GetUpdateRequiredScreenView());
   } else if (screen == OobeScreen::SCREEN_ASSISTANT_OPTIN_FLOW) {
     return std::make_unique<AssistantOptInFlowScreen>(
-        this, oobe_ui->GetAssistantOptInFlowScreenView());
+        this, oobe_ui->GetAssistantOptInFlowScreenView(),
+        base::BindRepeating(&WizardController::OnAssistantOptInFlowScreenExit,
+                            weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_MULTIDEVICE_SETUP) {
     return std::make_unique<MultiDeviceSetupScreen>(
-        this, oobe_ui->GetMultiDeviceSetupScreenView());
+        this, oobe_ui->GetMultiDeviceSetupScreenView(),
+        base::BindRepeating(&WizardController::OnMultiDeviceSetupScreenExit,
+                            weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_DISCOVER) {
-    return std::make_unique<DiscoverScreen>(this,
-                                            oobe_ui->GetDiscoverScreenView());
+    return std::make_unique<DiscoverScreen>(
+        this, oobe_ui->GetDiscoverScreenView(),
+        base::BindRepeating(&WizardController::OnDiscoverScreenExit,
+                            weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_FINGERPRINT_SETUP) {
     return std::make_unique<FingerprintSetupScreen>(
-        this, oobe_ui->GetFingerprintSetupScreenView());
+        this, oobe_ui->GetFingerprintSetupScreenView(),
+        base::BindRepeating(&WizardController::OnFingerprintSetupScreenExit,
+                            weak_factory_.GetWeakPtr()));
   } else if (screen == OobeScreen::SCREEN_MARKETING_OPT_IN) {
     return std::make_unique<MarketingOptInScreen>(
-        this, oobe_ui->GetMarketingOptInScreenView());
+        this, oobe_ui->GetMarketingOptInScreenView(),
+        base::BindRepeating(&WizardController::OnMarketingOptInScreenExit,
+                            weak_factory_.GetWeakPtr()));
   }
   return nullptr;
 }
@@ -715,11 +755,14 @@
   skip_update_enroll_after_eula_ = true;
 }
 
-void WizardController::OnScreenExit(OobeScreen screen) {
+void WizardController::OnScreenExit(OobeScreen screen, int exit_code) {
   DCHECK(current_screen_->screen_id() == screen ||
          (current_screen_->screen_id() == OobeScreen::SCREEN_ERROR_MESSAGE &&
           previous_screen_->screen_id() == screen));
 
+  VLOG(1) << "Wizard screen " << GetOobeScreenName(screen)
+          << " exited with code: " << exit_code;
+
   if (IsOOBEStepToTrack(screen)) {
     RecordUMAHistogramForOOBEStepCompletionTime(
         screen, base::Time::Now() - screen_show_times_[screen]);
@@ -728,9 +771,32 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 // WizardController, ExitHandlers:
+void WizardController::OnWrongHWIDScreenExit() {
+  OnScreenExit(OobeScreen::SCREEN_WRONG_HWID, 0 /* exit_code */);
+
+  if (previous_screen_) {
+    SetCurrentScreen(previous_screen_);
+  } else {
+    ShowLoginScreen(LoginScreenContext());
+  }
+}
+
+void WizardController::OnHidDetectionScreenExit() {
+  OnScreenExit(OobeScreen::SCREEN_OOBE_HID_DETECTION, 0 /* exit_code */);
+
+  // Check for tests configuration.
+  if (!StartupUtils::IsOobeCompleted())
+    ShowWelcomeScreen();
+}
+
+void WizardController::OnWelcomeScreenExit() {
+  OnScreenExit(OobeScreen::SCREEN_OOBE_WELCOME, 0 /* exit_code */);
+
+  ShowNetworkScreen();
+}
+
 void WizardController::OnNetworkScreenExit(NetworkScreen::Result result) {
-  VLOG(1) << "Network screen exit: " << static_cast<int>(result);
-  OnScreenExit(OobeScreen::SCREEN_OOBE_NETWORK);
+  OnScreenExit(OobeScreen::SCREEN_OOBE_NETWORK, static_cast<int>(result));
 
   if (result == NetworkScreen::Result::BACK) {
     if (demo_setup_controller_) {
@@ -797,8 +863,7 @@
 }
 
 void WizardController::OnEulaScreenExit(EulaScreen::Result result) {
-  VLOG(1) << "EULA screen exit: " << static_cast<int>(result);
-  OnScreenExit(OobeScreen::SCREEN_OOBE_EULA);
+  OnScreenExit(OobeScreen::SCREEN_OOBE_EULA, static_cast<int>(result));
 
   switch (result) {
     case EulaScreen::Result::ACCEPTED_WITH_USAGE_STATS_REPORTING:
@@ -837,8 +902,7 @@
 }
 
 void WizardController::OnUpdateScreenExit(UpdateScreen::Result result) {
-  VLOG(1) << "Update screen exit: " << static_cast<int>(result);
-  OnScreenExit(OobeScreen::SCREEN_OOBE_UPDATE);
+  OnScreenExit(OobeScreen::SCREEN_OOBE_UPDATE, static_cast<int>(result));
 
   switch (result) {
     case UpdateScreen::Result::UPDATE_NOT_REQUIRED:
@@ -861,8 +925,7 @@
 }
 
 void WizardController::OnAutoEnrollmentCheckScreenExit() {
-  VLOG(1) << "Auto enrollment check screen exit.";
-  OnScreenExit(OobeScreen::SCREEN_AUTO_ENROLLMENT_CHECK);
+  OnScreenExit(OobeScreen::SCREEN_AUTO_ENROLLMENT_CHECK, 0 /* exit_code */);
 
   // Check whether the device is disabled. OnDeviceDisabledChecked() will be
   // invoked when the result of this check is known. Until then, the current
@@ -875,8 +938,7 @@
 }
 
 void WizardController::OnEnrollmentScreenExit(EnrollmentScreen::Result result) {
-  VLOG(1) << "Enrollment screen exit: " << static_cast<int>(result);
-  OnScreenExit(OobeScreen::SCREEN_OOBE_ENROLLMENT);
+  OnScreenExit(OobeScreen::SCREEN_OOBE_ENROLLMENT, static_cast<int>(result));
 
   switch (result) {
     case EnrollmentScreen::Result::COMPLETED:
@@ -908,66 +970,92 @@
     ShowLoginScreen(LoginScreenContext());
 }
 
-void WizardController::OnHIDDetectionCompleted() {
-  // Check for tests configuration.
-  if (!StartupUtils::IsOobeCompleted())
-    ShowWelcomeScreen();
+void WizardController::OnEnableDebuggingScreenExit() {
+  OnScreenExit(OobeScreen::SCREEN_OOBE_ENABLE_DEBUGGING, 0 /* exit_code */);
+
+  OnDeviceModificationCanceled();
 }
 
-void WizardController::OnWelcomeContinued() {
-  ShowNetworkScreen();
+void WizardController::OnKioskEnableScreenExit() {
+  OnScreenExit(OobeScreen::SCREEN_KIOSK_ENABLE, 0 /* exit_code */);
+
+  ShowLoginScreen(LoginScreenContext());
 }
 
-void WizardController::OnChangedMetricsReportingState(bool enabled) {
-  StatsReportingController::Get()->SetEnabled(
-      ProfileManager::GetActiveUserProfile(), enabled);
-  if (!enabled)
-    return;
-#if defined(GOOGLE_CHROME_BUILD)
-  base::PostTaskWithTraits(
-      FROM_HERE, {base::MayBlock()},
-      base::BindOnce(&breakpad::InitCrashReporter, std::string()));
-#endif
-}
+void WizardController::OnKioskAutolaunchScreenExit(
+    KioskAutolaunchScreen::Result result) {
+  OnScreenExit(OobeScreen::SCREEN_KIOSK_AUTOLAUNCH, 0 /* exit_code */);
 
-void WizardController::OnDeviceModificationCanceled() {
-  if (previous_screen_) {
-    SetCurrentScreen(previous_screen_);
-  } else {
-    ShowLoginScreen(LoginScreenContext());
+  switch (result) {
+    case KioskAutolaunchScreen::Result::COMPLETED:
+      DCHECK(KioskAppManager::Get()->IsAutoLaunchEnabled());
+      AutoLaunchKioskApp();
+      break;
+    case KioskAutolaunchScreen::Result::CANCELED:
+      ShowLoginScreen(LoginScreenContext());
+      break;
   }
 }
 
-void WizardController::OnKioskAutolaunchCanceled() {
-  ShowLoginScreen(LoginScreenContext());
+void WizardController::OnDemoPreferencesScreenExit(
+    DemoPreferencesScreen::Result result) {
+  OnScreenExit(OobeScreen::SCREEN_OOBE_DEMO_PREFERENCES,
+               static_cast<int>(result));
+
+  DCHECK(demo_setup_controller_);
+
+  switch (result) {
+    case DemoPreferencesScreen::Result::COMPLETED:
+      ShowNetworkScreen();
+      break;
+    case DemoPreferencesScreen::Result::CANCELED:
+      demo_setup_controller_.reset();
+      ShowWelcomeScreen();
+      break;
+  }
 }
 
-void WizardController::OnKioskAutolaunchConfirmed() {
-  DCHECK(KioskAppManager::Get()->IsAutoLaunchEnabled());
-  AutoLaunchKioskApp();
+void WizardController::OnDemoSetupScreenExit(DemoSetupScreen::Result result) {
+  OnScreenExit(OobeScreen::SCREEN_OOBE_DEMO_SETUP, static_cast<int>(result));
+
+  DCHECK(demo_setup_controller_);
+  demo_setup_controller_.reset();
+
+  switch (result) {
+    case DemoSetupScreen::Result::COMPLETED:
+      PerformOOBECompletedActions();
+      ShowLoginScreen(LoginScreenContext());
+      break;
+    case DemoSetupScreen::Result::CANCELED:
+      ShowWelcomeScreen();
+      break;
+  }
 }
 
-void WizardController::OnKioskEnableCompleted() {
-  ShowLoginScreen(LoginScreenContext());
-}
+void WizardController::OnTermsOfServiceScreenExit(
+    TermsOfServiceScreen::Result result) {
+  OnScreenExit(OobeScreen::SCREEN_TERMS_OF_SERVICE, static_cast<int>(result));
 
-void WizardController::OnWrongHWIDWarningSkipped() {
-  if (previous_screen_)
-    SetCurrentScreen(previous_screen_);
-  else
-    ShowLoginScreen(LoginScreenContext());
-}
-
-void WizardController::OnTermsOfServiceDeclined() {
-  // If the user declines the Terms of Service, end the session and return to
-  // the login screen.
-  DBusThreadManager::Get()->GetSessionManagerClient()->StopSession();
+  switch (result) {
+    case TermsOfServiceScreen::Result::ACCEPTED:
+      OnTermsOfServiceAccepted();
+      break;
+    case TermsOfServiceScreen::Result::DECLINED:
+      // End the session and return to the login screen.
+      DBusThreadManager::Get()->GetSessionManagerClient()->StopSession();
+      break;
+  }
 }
 
 void WizardController::OnTermsOfServiceAccepted() {
   ShowSyncConsentScreen();
 }
 
+void WizardController::OnSyncConsentScreenExit() {
+  OnScreenExit(OobeScreen::SCREEN_SYNC_CONSENT, 0 /* exit_code */);
+  OnSyncConsentFinished();
+}
+
 void WizardController::OnSyncConsentFinished() {
   if (chromeos::quick_unlock::IsFingerprintEnabled(
           ProfileManager::GetActiveUserProfile())) {
@@ -977,16 +1065,40 @@
   }
 }
 
-void WizardController::OnDiscoverScreenFinished() {
+void WizardController::OnFingerprintSetupScreenExit() {
+  OnScreenExit(OobeScreen::SCREEN_FINGERPRINT_SETUP, 0 /* exit_code */);
+
+  ShowDiscoverScreen();
+}
+
+void WizardController::OnDiscoverScreenExit() {
+  OnScreenExit(OobeScreen::SCREEN_DISCOVER, 0 /* exit_code */);
   ShowMarketingOptInScreen();
 }
 
-void WizardController::OnMarketingOptInFinished() {
+void WizardController::OnMarketingOptInScreenExit() {
+  OnScreenExit(OobeScreen::SCREEN_MARKETING_OPT_IN, 0 /* exit_code */);
   ShowArcTermsOfServiceScreen();
 }
 
-void WizardController::OnFingerprintSetupFinished() {
-  ShowDiscoverScreen();
+void WizardController::OnArcTermsOfServiceScreenExit(
+    ArcTermsOfServiceScreen::Result result) {
+  OnScreenExit(OobeScreen::SCREEN_ARC_TERMS_OF_SERVICE,
+               static_cast<int>(result));
+
+  switch (result) {
+    case ArcTermsOfServiceScreen::Result::ACCEPTED:
+      OnArcTermsOfServiceAccepted();
+      break;
+    case ArcTermsOfServiceScreen::Result::SKIPPED:
+      OnArcTermsOfServiceSkipped();
+      break;
+    case ArcTermsOfServiceScreen::Result::BACK:
+      DCHECK(demo_setup_controller_);
+      DCHECK(StartupUtils::IsEulaAccepted());
+      ShowNetworkScreen();
+      break;
+  }
 }
 
 void WizardController::OnArcTermsOfServiceSkipped() {
@@ -996,6 +1108,7 @@
     OnOobeFlowFinished();
     return;
   }
+
   // If the user finished with the PlayStore Terms of Service, advance to the
   // assistant opt-in flow screen.
   ShowAssistantOptInFlowScreen();
@@ -1021,58 +1134,67 @@
   }
 }
 
-void WizardController::OnArcTermsOfServiceBack() {
-  DCHECK(demo_setup_controller_);
-  DCHECK(StartupUtils::IsEulaAccepted());
-  ShowNetworkScreen();
+void WizardController::OnRecommendAppsScreenExit(
+    RecommendAppsScreen::Result result) {
+  OnScreenExit(OobeScreen::SCREEN_RECOMMEND_APPS, static_cast<int>(result));
+
+  switch (result) {
+    case RecommendAppsScreen::Result::SELECTED:
+      ShowAppDownloadingScreen();
+      break;
+    case RecommendAppsScreen::Result::SKIPPED:
+      ShowAssistantOptInFlowScreen();
+      break;
+  }
 }
 
-void WizardController::OnRecommendAppsSkipped() {
+void WizardController::OnAppDownloadingScreenExit() {
+  OnScreenExit(OobeScreen::SCREEN_APP_DOWNLOADING, 0 /* exit_code */);
+
   ShowAssistantOptInFlowScreen();
 }
 
-void WizardController::OnRecommendAppsSelected() {
-  ShowAppDownloadingScreen();
-}
+void WizardController::OnAssistantOptInFlowScreenExit() {
+  OnScreenExit(OobeScreen::SCREEN_ASSISTANT_OPTIN_FLOW, 0 /* exit_code */);
 
-void WizardController::OnAppDownloadingFinished() {
-  ShowAssistantOptInFlowScreen();
-}
-
-void WizardController::OnSupervisionTransitionFinished() {
-  OnOobeFlowFinished();
-}
-
-void WizardController::OnAssistantOptInFlowFinished() {
   ShowMultiDeviceSetupScreen();
 }
 
-void WizardController::OnMultiDeviceSetupFinished() {
+void WizardController::OnMultiDeviceSetupScreenExit() {
+  OnScreenExit(OobeScreen::SCREEN_MULTIDEVICE_SETUP, 0 /* exit_code */);
+
   OnOobeFlowFinished();
 }
 
-void WizardController::OnDemoSetupFinished() {
-  DCHECK(demo_setup_controller_);
-  demo_setup_controller_.reset();
-  PerformOOBECompletedActions();
-  ShowLoginScreen(LoginScreenContext());
+void WizardController::OnResetScreenExit() {
+  OnScreenExit(OobeScreen::SCREEN_OOBE_RESET, 0 /* exit_code */);
+  OnDeviceModificationCanceled();
 }
 
-void WizardController::OnDemoSetupCanceled() {
-  DCHECK(demo_setup_controller_);
-  demo_setup_controller_.reset();
-  ShowWelcomeScreen();
+void WizardController::OnChangedMetricsReportingState(bool enabled) {
+  StatsReportingController::Get()->SetEnabled(
+      ProfileManager::GetActiveUserProfile(), enabled);
+  if (!enabled)
+    return;
+#if defined(GOOGLE_CHROME_BUILD)
+  base::PostTaskWithTraits(
+      FROM_HERE, {base::MayBlock()},
+      base::BindOnce(&breakpad::InitCrashReporter, std::string()));
+#endif
 }
 
-void WizardController::OnDemoPreferencesContinued() {
-  DCHECK(demo_setup_controller_);
-  ShowNetworkScreen();
+void WizardController::OnDeviceModificationCanceled() {
+  if (previous_screen_) {
+    SetCurrentScreen(previous_screen_);
+  } else {
+    ShowLoginScreen(LoginScreenContext());
+  }
 }
 
-void WizardController::OnDemoPreferencesCanceled() {
-  DCHECK(demo_setup_controller_);
-  demo_setup_controller_.reset();
-  ShowWelcomeScreen();
+void WizardController::OnSupervisionTransitionScreenExit() {
+  OnScreenExit(OobeScreen::SCREEN_SUPERVISION_TRANSITION, 0 /* exit_code */);
+
+  OnOobeFlowFinished();
 }
 
 void WizardController::OnOobeFlowFinished() {
@@ -1419,100 +1541,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 // WizardController, BaseScreenDelegate overrides:
-void WizardController::OnExit(ScreenExitCode exit_code) {
-  VLOG(1) << "Wizard screen exit code: " << ExitCodeToString(exit_code);
-  OnScreenExit(current_screen_->screen_id());
-
-  switch (exit_code) {
-    case ScreenExitCode::HID_DETECTION_COMPLETED:
-      OnHIDDetectionCompleted();
-      break;
-    case ScreenExitCode::WELCOME_CONTINUED:
-      OnWelcomeContinued();
-      break;
-    case ScreenExitCode::ENABLE_DEBUGGING_CANCELED:
-      OnDeviceModificationCanceled();
-      break;
-    case ScreenExitCode::ENABLE_DEBUGGING_FINISHED:
-      OnDeviceModificationCanceled();
-      break;
-    case ScreenExitCode::RESET_CANCELED:
-      OnDeviceModificationCanceled();
-      break;
-    case ScreenExitCode::KIOSK_AUTOLAUNCH_CANCELED:
-      OnKioskAutolaunchCanceled();
-      break;
-    case ScreenExitCode::KIOSK_AUTOLAUNCH_CONFIRMED:
-      OnKioskAutolaunchConfirmed();
-      break;
-    case ScreenExitCode::KIOSK_ENABLE_COMPLETED:
-      OnKioskEnableCompleted();
-      break;
-    case ScreenExitCode::TERMS_OF_SERVICE_DECLINED:
-      OnTermsOfServiceDeclined();
-      break;
-    case ScreenExitCode::TERMS_OF_SERVICE_ACCEPTED:
-      OnTermsOfServiceAccepted();
-      break;
-    case ScreenExitCode::ARC_TERMS_OF_SERVICE_SKIPPED:
-      OnArcTermsOfServiceSkipped();
-      break;
-    case ScreenExitCode::ARC_TERMS_OF_SERVICE_ACCEPTED:
-      OnArcTermsOfServiceAccepted();
-      break;
-    case ScreenExitCode::ARC_TERMS_OF_SERVICE_BACK:
-      OnArcTermsOfServiceBack();
-      break;
-    case ScreenExitCode::WRONG_HWID_WARNING_SKIPPED:
-      OnWrongHWIDWarningSkipped();
-      break;
-    case ScreenExitCode::SYNC_CONSENT_FINISHED:
-      OnSyncConsentFinished();
-      break;
-    case ScreenExitCode::RECOMMEND_APPS_SKIPPED:
-      OnRecommendAppsSkipped();
-      break;
-    case ScreenExitCode::RECOMMEND_APPS_SELECTED:
-      OnRecommendAppsSelected();
-      break;
-    case ScreenExitCode::APP_DOWNLOADING_FINISHED:
-      OnAppDownloadingFinished();
-      break;
-    case ScreenExitCode::DEMO_MODE_SETUP_FINISHED:
-      OnDemoSetupFinished();
-      break;
-    case ScreenExitCode::DEMO_MODE_SETUP_CANCELED:
-      OnDemoSetupCanceled();
-      break;
-    case ScreenExitCode::DEMO_MODE_PREFERENCES_CONTINUED:
-      OnDemoPreferencesContinued();
-      break;
-    case ScreenExitCode::DEMO_MODE_PREFERENCES_CANCELED:
-      OnDemoPreferencesCanceled();
-      break;
-    case ScreenExitCode::DISCOVER_FINISHED:
-      OnDiscoverScreenFinished();
-      break;
-    case ScreenExitCode::FINGERPRINT_SETUP_FINISHED:
-      OnFingerprintSetupFinished();
-      break;
-    case ScreenExitCode::MARKETING_OPT_IN_FINISHED:
-      OnMarketingOptInFinished();
-      break;
-    case ScreenExitCode::ASSISTANT_OPTIN_FLOW_FINISHED:
-      OnAssistantOptInFlowFinished();
-      break;
-    case ScreenExitCode::MULTIDEVICE_SETUP_FINISHED:
-      OnMultiDeviceSetupFinished();
-      break;
-    case ScreenExitCode::SUPERVISION_TRANSITION_FINISHED:
-      OnSupervisionTransitionFinished();
-      break;
-    default:
-      NOTREACHED();
-  }
-}
-
 void WizardController::ShowErrorScreen() {
   VLOG(1) << "Showing error screen.";
   SetCurrentScreen(GetScreen(OobeScreen::SCREEN_ERROR_MESSAGE));
diff --git a/chrome/browser/chromeos/login/wizard_controller.h b/chrome/browser/chromeos/login/wizard_controller.h
index d2d1f4e..3d7693b6 100644
--- a/chrome/browser/chromeos/login/wizard_controller.h
+++ b/chrome/browser/chromeos/login/wizard_controller.h
@@ -22,12 +22,16 @@
 #include "chrome/browser/chromeos/login/enrollment/auto_enrollment_controller.h"
 #include "chrome/browser/chromeos/login/enrollment/enrollment_screen.h"
 #include "chrome/browser/chromeos/login/screen_manager.h"
+#include "chrome/browser/chromeos/login/screens/arc_terms_of_service_screen.h"
 #include "chrome/browser/chromeos/login/screens/base_screen_delegate.h"
+#include "chrome/browser/chromeos/login/screens/demo_preferences_screen.h"
+#include "chrome/browser/chromeos/login/screens/demo_setup_screen.h"
+#include "chrome/browser/chromeos/login/screens/enable_debugging_screen.h"
 #include "chrome/browser/chromeos/login/screens/eula_screen.h"
-#include "chrome/browser/chromeos/login/screens/hid_detection_screen.h"
 #include "chrome/browser/chromeos/login/screens/kiosk_autolaunch_screen.h"
 #include "chrome/browser/chromeos/login/screens/network_screen.h"
-#include "chrome/browser/chromeos/login/screens/reset_screen.h"
+#include "chrome/browser/chromeos/login/screens/recommend_apps_screen.h"
+#include "chrome/browser/chromeos/login/screens/terms_of_service_screen.h"
 #include "chrome/browser/chromeos/login/screens/update_screen.h"
 #include "chrome/browser/chromeos/login/screens/welcome_screen.h"
 #include "chrome/browser/chromeos/policy/enrollment_config.h"
@@ -188,9 +192,13 @@
   void ShowPreviousScreen();
 
   // Shared actions to be performed on a screen exit.
-  void OnScreenExit(OobeScreen screen);
+  // |exit_code| is the screen specific exit code reported by the screen.
+  void OnScreenExit(OobeScreen screen, int exit_code);
 
   // Exit handlers:
+  void OnWrongHWIDScreenExit();
+  void OnHidDetectionScreenExit();
+  void OnWelcomeScreenExit();
   void OnNetworkScreenExit(NetworkScreen::Result result);
   bool ShowEulaOrArcTosAfterNetworkScreen();
   void OnEulaScreenExit(EulaScreen::Result result);
@@ -200,33 +208,37 @@
   void OnAutoEnrollmentCheckScreenExit();
   void OnEnrollmentScreenExit(EnrollmentScreen::Result result);
   void OnEnrollmentDone();
-  void OnHIDDetectionCompleted();
-  void OnWelcomeContinued();
-  void OnDeviceModificationCanceled();
-  void OnKioskAutolaunchCanceled();
-  void OnKioskAutolaunchConfirmed();
-  void OnKioskEnableCompleted();
-  void OnWrongHWIDWarningSkipped();
-  void OnTermsOfServiceDeclined();
+  void OnEnableDebuggingScreenExit();
+  void OnKioskEnableScreenExit();
+  void OnKioskAutolaunchScreenExit(KioskAutolaunchScreen::Result result);
+  void OnDemoPreferencesScreenExit(DemoPreferencesScreen::Result result);
+  void OnDemoSetupScreenExit(DemoSetupScreen::Result result);
+  void OnTermsOfServiceScreenExit(TermsOfServiceScreen::Result result);
   void OnTermsOfServiceAccepted();
+  void OnSyncConsentScreenExit();
   void OnSyncConsentFinished();
-  void OnDiscoverScreenFinished();
-  void OnFingerprintSetupFinished();
+  void OnFingerprintSetupScreenExit();
+  void OnDiscoverScreenExit();
+  void OnMarketingOptInScreenExit();
+  void OnArcTermsOfServiceScreenExit(ArcTermsOfServiceScreen::Result result);
   void OnArcTermsOfServiceSkipped();
   void OnArcTermsOfServiceAccepted();
-  void OnArcTermsOfServiceBack();
-  void OnRecommendAppsSkipped();
-  void OnRecommendAppsSelected();
+  void OnRecommendAppsScreenExit(RecommendAppsScreen::Result result);
+  void OnAppDownloadingScreenExit();
+  void OnAssistantOptInFlowScreenExit();
+  void OnMultiDeviceSetupScreenExit();
+  void OnResetScreenExit();
+  void OnHIDDetectionCompleted();
+  void OnDeviceModificationCanceled();
+  void OnFingerprintSetupFinished();
   void OnAppDownloadingFinished();
   void OnDemoSetupFinished();
   void OnDemoSetupCanceled();
   void OnDemoPreferencesContinued();
   void OnDemoPreferencesCanceled();
   void OnSupervisionTransitionFinished();
-  void OnAssistantOptInFlowFinished();
-  void OnMultiDeviceSetupFinished();
+  void OnSupervisionTransitionScreenExit();
   void OnOobeFlowFinished();
-  void OnMarketingOptInFinished();
 
   // Callback invoked once it has been determined whether the device is disabled
   // or not.
@@ -250,7 +262,6 @@
   void PerformOOBECompletedActions();
 
   // Overridden from BaseScreenDelegate:
-  void OnExit(ScreenExitCode exit_code) override;
   void ShowCurrentScreen() override;
   ErrorScreen* GetErrorScreen() override;
   void ShowErrorScreen() override;
diff --git a/chrome/browser/chromeos/login/wizard_controller_browsertest.cc b/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
index baf05d9..78f01ea 100644
--- a/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
+++ b/chrome/browser/chromeos/login/wizard_controller_browsertest.cc
@@ -417,6 +417,12 @@
               WizardController::default_controller()->current_screen());
   }
 
+  WrongHWIDScreen* GetWrongHWIDScreen() {
+    return static_cast<WrongHWIDScreen*>(
+        WizardController::default_controller()->GetScreen(
+            OobeScreen::SCREEN_WRONG_HWID));
+  }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(WizardControllerTest);
 };
@@ -508,7 +514,10 @@
     ExpectBindUnbind(mock_supervision_transition_screen_view_.get());
     mock_supervision_transition_screen_ = MockScreenExpectLifecycle(
         std::make_unique<MockSupervisionTransitionScreen>(
-            wizard_controller, mock_supervision_transition_screen_view_.get()));
+            wizard_controller, mock_supervision_transition_screen_view_.get(),
+            base::BindRepeating(
+                &WizardController::OnSupervisionTransitionScreenExit,
+                base::Unretained(wizard_controller))));
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
@@ -517,11 +526,6 @@
                                     TestingProfile::kTestUserProfileDir);
   }
 
-  void OnSupervisionTransitionFinished() {
-    WizardController::default_controller()->OnExit(
-        ScreenExitCode::SUPERVISION_TRANSITION_FINISHED);
-  }
-
   MockSupervisionTransitionScreen* mock_supervision_transition_screen_;
   std::unique_ptr<MockSupervisionTransitionScreenView>
       mock_supervision_transition_screen_view_;
@@ -543,7 +547,7 @@
   WizardController::default_controller()->AdvanceToScreen(
       OobeScreen::SCREEN_SUPERVISION_TRANSITION);
   CheckCurrentScreen(OobeScreen::SCREEN_SUPERVISION_TRANSITION);
-  OnSupervisionTransitionFinished();
+  mock_supervision_transition_screen_->ExitScreen();
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(session_manager::SessionManager::Get()->session_state(),
             session_manager::SessionState::ACTIVE);
@@ -579,20 +583,26 @@
     // Set up the mocks for all screens.
     mock_welcome_screen_ =
         MockScreenExpectLifecycle(std::make_unique<MockWelcomeScreen>(
-            wizard_controller, wizard_controller,
-            GetOobeUI()->GetWelcomeView()));
+            wizard_controller, wizard_controller, GetOobeUI()->GetWelcomeView(),
+            base::BindRepeating(&WizardController::OnWelcomeScreenExit,
+                                base::Unretained(wizard_controller))));
 
     mock_demo_preferences_screen_view_ =
         std::make_unique<MockDemoPreferencesScreenView>();
     mock_demo_preferences_screen_ =
         MockScreenExpectLifecycle(std::make_unique<MockDemoPreferencesScreen>(
-            wizard_controller, mock_demo_preferences_screen_view_.get()));
+            wizard_controller, mock_demo_preferences_screen_view_.get(),
+            base::BindRepeating(&WizardController::OnDemoPreferencesScreenExit,
+                                base::Unretained(wizard_controller))));
 
     mock_arc_terms_of_service_screen_view_ =
         std::make_unique<MockArcTermsOfServiceScreenView>();
     mock_arc_terms_of_service_screen_ =
         MockScreenExpectLifecycle(std::make_unique<MockArcTermsOfServiceScreen>(
-            wizard_controller, mock_arc_terms_of_service_screen_view_.get()));
+            wizard_controller, mock_arc_terms_of_service_screen_view_.get(),
+            base::BindRepeating(
+                &WizardController::OnArcTermsOfServiceScreenExit,
+                base::Unretained(wizard_controller))));
 
     device_disabled_screen_view_ =
         std::make_unique<MockDeviceDisabledScreenView>();
@@ -642,33 +652,44 @@
     ExpectSetDelegate(mock_wrong_hwid_screen_view_.get());
     mock_wrong_hwid_screen_ =
         MockScreenExpectLifecycle(std::make_unique<MockWrongHWIDScreen>(
-            wizard_controller, mock_wrong_hwid_screen_view_.get()));
+            wizard_controller, mock_wrong_hwid_screen_view_.get(),
+            base::BindRepeating(&WizardController::OnWrongHWIDScreenExit,
+                                base::Unretained(wizard_controller))));
 
     mock_enable_debugging_screen_view_ =
         std::make_unique<MockEnableDebuggingScreenView>();
     ExpectSetDelegate(mock_enable_debugging_screen_view_.get());
     mock_enable_debugging_screen_ =
         MockScreenExpectLifecycle(std::make_unique<MockEnableDebuggingScreen>(
-            wizard_controller, mock_enable_debugging_screen_view_.get()));
+            wizard_controller, mock_enable_debugging_screen_view_.get(),
+            base::BindRepeating(&WizardController::OnEnableDebuggingScreenExit,
+                                base::Unretained(wizard_controller))));
 
     mock_demo_setup_screen_view_ = std::make_unique<MockDemoSetupScreenView>();
     ExpectBind(mock_demo_setup_screen_view_.get());
     mock_demo_setup_screen_ =
         MockScreenExpectLifecycle(std::make_unique<MockDemoSetupScreen>(
-            wizard_controller, mock_demo_setup_screen_view_.get()));
+            wizard_controller, mock_demo_setup_screen_view_.get(),
+            base::BindRepeating(&WizardController::OnDemoSetupScreenExit,
+                                base::Unretained(wizard_controller))));
 
     mock_demo_preferences_screen_view_ =
         std::make_unique<MockDemoPreferencesScreenView>();
     ExpectBind(mock_demo_preferences_screen_view_.get());
     mock_demo_preferences_screen_ =
         MockScreenExpectLifecycle(std::make_unique<MockDemoPreferencesScreen>(
-            wizard_controller, mock_demo_preferences_screen_view_.get()));
+            wizard_controller, mock_demo_preferences_screen_view_.get(),
+            base::BindRepeating(&WizardController::OnDemoPreferencesScreenExit,
+                                base::Unretained(wizard_controller))));
 
     mock_arc_terms_of_service_screen_view_ =
         std::make_unique<MockArcTermsOfServiceScreenView>();
     mock_arc_terms_of_service_screen_ =
         MockScreenExpectLifecycle(std::make_unique<MockArcTermsOfServiceScreen>(
-            wizard_controller, mock_arc_terms_of_service_screen_view_.get()));
+            wizard_controller, mock_arc_terms_of_service_screen_view_.get(),
+            base::BindRepeating(
+                &WizardController::OnArcTermsOfServiceScreenExit,
+                base::Unretained(wizard_controller))));
 
     // Switch to the initial screen.
     EXPECT_EQ(NULL, wizard_controller->current_screen());
@@ -702,10 +723,6 @@
         default_network->guid(), online_state);
   }
 
-  void OnExit(ScreenExitCode exit_code) {
-    WizardController::default_controller()->OnExit(exit_code);
-  }
-
   chromeos::SimpleGeolocationProvider* GetGeolocationProvider() {
     return WizardController::default_controller()->geolocation_provider_.get();
   }
@@ -758,7 +775,7 @@
     EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(IsNull(), _)).Times(1);
     EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
     EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
-    OnExit(ScreenExitCode::WELCOME_CONTINUED);
+    mock_welcome_screen_->ExitScreen();
 
     CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
     EXPECT_CALL(*mock_network_screen_, Hide()).Times(1);
@@ -865,7 +882,7 @@
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(IsNull(), _)).Times(1);
-  OnExit(ScreenExitCode::WELCOME_CONTINUED);
+  mock_welcome_screen_->ExitScreen();
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
@@ -906,7 +923,7 @@
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(IsNull(), _)).Times(1);
-  OnExit(ScreenExitCode::WELCOME_CONTINUED);
+  mock_welcome_screen_->ExitScreen();
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
@@ -940,7 +957,7 @@
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(IsNull(), _)).Times(1);
-  OnExit(ScreenExitCode::WELCOME_CONTINUED);
+  mock_welcome_screen_->ExitScreen();
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
@@ -980,7 +997,7 @@
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(IsNull(), _)).Times(1);
-  OnExit(ScreenExitCode::WELCOME_CONTINUED);
+  mock_welcome_screen_->ExitScreen();
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
@@ -1029,7 +1046,7 @@
 
   // After warning is skipped, user returns to sign-in screen.
   // And this destroys WizardController.
-  OnExit(ScreenExitCode::WRONG_HWID_WARNING_SKIPPED);
+  GetWrongHWIDScreen()->OnExit();
   EXPECT_FALSE(ExistingUserController::current_controller() == NULL);
 }
 
@@ -1061,7 +1078,7 @@
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(IsNull(), _)).Times(1);
-  OnExit(ScreenExitCode::WELCOME_CONTINUED);
+  mock_welcome_screen_->ExitScreen();
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
@@ -1191,7 +1208,7 @@
   EXPECT_CALL(*mock_welcome_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(IsNull(), _)).Times(1);
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
-  OnExit(ScreenExitCode::WELCOME_CONTINUED);
+  mock_welcome_screen_->ExitScreen();
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
@@ -1237,7 +1254,7 @@
   EXPECT_CALL(*mock_welcome_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(IsNull(), _)).Times(1);
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
-  OnExit(ScreenExitCode::WELCOME_CONTINUED);
+  mock_welcome_screen_->ExitScreen();
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
@@ -1336,7 +1353,7 @@
   EXPECT_CALL(*mock_welcome_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(IsNull(), _)).Times(1);
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
-  OnExit(ScreenExitCode::WELCOME_CONTINUED);
+  mock_welcome_screen_->ExitScreen();
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
@@ -1435,7 +1452,7 @@
   EXPECT_CALL(*mock_welcome_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(IsNull(), _)).Times(1);
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
-  OnExit(ScreenExitCode::WELCOME_CONTINUED);
+  mock_welcome_screen_->ExitScreen();
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
@@ -1580,7 +1597,7 @@
   EXPECT_CALL(*mock_welcome_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(IsNull(), _)).Times(1);
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
-  OnExit(ScreenExitCode::WELCOME_CONTINUED);
+  mock_welcome_screen_->ExitScreen();
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
@@ -1655,7 +1672,7 @@
   EXPECT_CALL(*mock_welcome_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(IsNull(), _)).Times(1);
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
-  OnExit(ScreenExitCode::WELCOME_CONTINUED);
+  mock_welcome_screen_->ExitScreen();
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
@@ -1751,7 +1768,7 @@
   EXPECT_CALL(*mock_welcome_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(IsNull(), _)).Times(1);
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
-  OnExit(ScreenExitCode::WELCOME_CONTINUED);
+  mock_welcome_screen_->ExitScreen();
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
@@ -1796,7 +1813,7 @@
   EXPECT_CALL(*mock_welcome_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(IsNull(), _)).Times(1);
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
-  OnExit(ScreenExitCode::WELCOME_CONTINUED);
+  mock_welcome_screen_->ExitScreen();
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
@@ -1853,7 +1870,7 @@
   EXPECT_CALL(*mock_welcome_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(IsNull(), _)).Times(1);
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
-  OnExit(ScreenExitCode::WELCOME_CONTINUED);
+  mock_welcome_screen_->ExitScreen();
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
@@ -1910,7 +1927,7 @@
   EXPECT_CALL(*mock_welcome_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(IsNull(), _)).Times(1);
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
-  OnExit(ScreenExitCode::WELCOME_CONTINUED);
+  mock_welcome_screen_->ExitScreen();
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
@@ -1993,7 +2010,7 @@
   EXPECT_CALL(*mock_welcome_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(IsNull(), _)).Times(1);
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
-  OnExit(ScreenExitCode::WELCOME_CONTINUED);
+  mock_welcome_screen_->ExitScreen();
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
@@ -2159,7 +2176,7 @@
   EXPECT_CALL(*mock_welcome_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(IsNull(), _)).Times(1);
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
-  OnExit(ScreenExitCode::WELCOME_CONTINUED);
+  mock_welcome_screen_->ExitScreen();
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
@@ -2208,7 +2225,7 @@
   EXPECT_CALL(*mock_welcome_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(IsNull(), _)).Times(1);
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
-  OnExit(ScreenExitCode::WELCOME_CONTINUED);
+  mock_welcome_screen_->ExitScreen();
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_CALL(*mock_eula_screen_, Show()).Times(1);
@@ -2282,7 +2299,7 @@
   EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(NotNull(), _)).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, Show()).Times(1);
 
-  OnExit(ScreenExitCode::ENABLE_DEBUGGING_CANCELED);
+  mock_enable_debugging_screen_->ExitScreen();
 
   // Let update screen smooth time process (time = 0ms).
   content::RunAllPendingInMessageLoop();
@@ -2331,7 +2348,8 @@
   EXPECT_CALL(*mock_demo_preferences_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
 
-  OnExit(ScreenExitCode::DEMO_MODE_PREFERENCES_CONTINUED);
+  mock_demo_preferences_screen_->ExitScreen(
+      DemoPreferencesScreen::Result::COMPLETED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_TRUE(DemoSetupController::IsOobeDemoSetupFlowInProgress());
@@ -2356,7 +2374,8 @@
   EXPECT_CALL(*mock_arc_terms_of_service_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_update_screen_, Show()).Times(1);
 
-  OnExit(ScreenExitCode::ARC_TERMS_OF_SERVICE_ACCEPTED);
+  mock_arc_terms_of_service_screen_->ExitScreen(
+      ArcTermsOfServiceScreen::Result::ACCEPTED);
 
   base::RunLoop().RunUntilIdle();
 
@@ -2379,7 +2398,7 @@
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_DEMO_SETUP);
   EXPECT_TRUE(DemoSetupController::IsOobeDemoSetupFlowInProgress());
 
-  OnExit(ScreenExitCode::DEMO_MODE_SETUP_FINISHED);
+  mock_demo_setup_screen_->ExitScreen(DemoSetupScreen::Result::COMPLETED);
 
   EXPECT_TRUE(StartupUtils::IsOobeCompleted());
   EXPECT_TRUE(ExistingUserController::current_controller());
@@ -2404,7 +2423,8 @@
   EXPECT_CALL(*mock_demo_preferences_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
 
-  OnExit(ScreenExitCode::DEMO_MODE_PREFERENCES_CONTINUED);
+  mock_demo_preferences_screen_->ExitScreen(
+      DemoPreferencesScreen::Result::COMPLETED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_TRUE(DemoSetupController::IsOobeDemoSetupFlowInProgress());
@@ -2429,14 +2449,15 @@
   EXPECT_CALL(*mock_arc_terms_of_service_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_demo_setup_screen_, Show()).Times(1);
 
-  OnExit(ScreenExitCode::ARC_TERMS_OF_SERVICE_ACCEPTED);
+  mock_arc_terms_of_service_screen_->ExitScreen(
+      ArcTermsOfServiceScreen::Result::ACCEPTED);
 
   base::RunLoop().RunUntilIdle();
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_DEMO_SETUP);
   EXPECT_TRUE(DemoSetupController::IsOobeDemoSetupFlowInProgress());
 
-  OnExit(ScreenExitCode::DEMO_MODE_SETUP_FINISHED);
+  mock_demo_setup_screen_->ExitScreen(DemoSetupScreen::Result::COMPLETED);
 
   EXPECT_TRUE(StartupUtils::IsOobeCompleted());
   EXPECT_TRUE(ExistingUserController::current_controller());
@@ -2460,7 +2481,8 @@
   EXPECT_CALL(*mock_demo_preferences_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
 
-  OnExit(ScreenExitCode::DEMO_MODE_PREFERENCES_CONTINUED);
+  mock_demo_preferences_screen_->ExitScreen(
+      DemoPreferencesScreen::Result::COMPLETED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_TRUE(DemoSetupController::IsOobeDemoSetupFlowInProgress());
@@ -2485,7 +2507,8 @@
   EXPECT_CALL(*mock_arc_terms_of_service_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_update_screen_, Show()).Times(1);
 
-  OnExit(ScreenExitCode::ARC_TERMS_OF_SERVICE_ACCEPTED);
+  mock_arc_terms_of_service_screen_->ExitScreen(
+      ArcTermsOfServiceScreen::Result::ACCEPTED);
 
   base::RunLoop().RunUntilIdle();
 
@@ -2512,7 +2535,7 @@
   EXPECT_CALL(*mock_welcome_screen_, Show()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, SetConfiguration(NotNull(), _)).Times(1);
 
-  OnExit(ScreenExitCode::DEMO_MODE_SETUP_CANCELED);
+  mock_demo_setup_screen_->ExitScreen(DemoSetupScreen::Result::CANCELED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_WELCOME);
   EXPECT_FALSE(DemoSetupController::IsOobeDemoSetupFlowInProgress());
@@ -2532,7 +2555,8 @@
   EXPECT_CALL(*mock_demo_preferences_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_welcome_screen_, Show()).Times(1);
 
-  OnExit(ScreenExitCode::DEMO_MODE_PREFERENCES_CANCELED);
+  mock_demo_preferences_screen_->ExitScreen(
+      DemoPreferencesScreen::Result::CANCELED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_WELCOME);
   EXPECT_FALSE(DemoSetupController::IsOobeDemoSetupFlowInProgress());
@@ -2590,7 +2614,8 @@
   EXPECT_CALL(*mock_arc_terms_of_service_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
 
-  OnExit(ScreenExitCode::ARC_TERMS_OF_SERVICE_BACK);
+  mock_arc_terms_of_service_screen_->ExitScreen(
+      ArcTermsOfServiceScreen::Result::BACK);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_TRUE(DemoSetupController::IsOobeDemoSetupFlowInProgress());
@@ -2631,7 +2656,8 @@
   EXPECT_CALL(*mock_demo_preferences_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_network_screen_, Show()).Times(1);
 
-  OnExit(ScreenExitCode::DEMO_MODE_PREFERENCES_CONTINUED);
+  mock_demo_preferences_screen_->ExitScreen(
+      DemoPreferencesScreen::Result::COMPLETED);
 
   CheckCurrentScreen(OobeScreen::SCREEN_OOBE_NETWORK);
   EXPECT_TRUE(DemoSetupController::IsOobeDemoSetupFlowInProgress());
@@ -2656,7 +2682,8 @@
   EXPECT_CALL(*mock_arc_terms_of_service_screen_, Hide()).Times(1);
   EXPECT_CALL(*mock_update_screen_, Show()).Times(1);
 
-  OnExit(ScreenExitCode::ARC_TERMS_OF_SERVICE_ACCEPTED);
+  mock_arc_terms_of_service_screen_->ExitScreen(
+      ArcTermsOfServiceScreen::Result::ACCEPTED);
 
   base::RunLoop().RunUntilIdle();
 
@@ -2718,7 +2745,9 @@
     ExpectBindUnbind(mock_welcome_view_.get());
     mock_welcome_screen_ =
         MockScreenExpectLifecycle(std::make_unique<MockWelcomeScreen>(
-            wizard_controller, wizard_controller, mock_welcome_view_.get()));
+            wizard_controller, wizard_controller, mock_welcome_view_.get(),
+            base::BindRepeating(&WizardController::OnWelcomeScreenExit,
+                                base::Unretained(wizard_controller))));
 
     mock_enrollment_screen_view_ = std::make_unique<MockEnrollmentScreenView>();
     mock_enrollment_screen_ =
@@ -2728,10 +2757,6 @@
                                 base::Unretained(wizard_controller))));
   }
 
-  void OnExit(ScreenExitCode exit_code) {
-    WizardController::default_controller()->OnExit(exit_code);
-  }
-
   OobeScreen GetFirstScreen() {
     return WizardController::default_controller()->first_screen();
   }
@@ -2820,7 +2845,9 @@
     mock_welcome_view_ = std::make_unique<MockWelcomeView>();
     mock_welcome_screen_ =
         MockScreenExpectLifecycle(std::make_unique<MockWelcomeScreen>(
-            wizard_controller, wizard_controller, mock_welcome_view_.get()));
+            wizard_controller, wizard_controller, mock_welcome_view_.get(),
+            base::BindRepeating(&WizardController::OnWelcomeScreenExit,
+                                base::Unretained(wizard_controller))));
   }
 
   void WaitForConfigurationLoaded() {
@@ -2879,7 +2906,4 @@
 
 // TODO(khorimoto): Add tests for MultiDevice Setup UI.
 
-static_assert(static_cast<int>(ScreenExitCode::EXIT_CODES_COUNT) == 51,
-              "tests for new control flow are missing");
-
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc b/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc
index ed5dfbe..13b4644 100644
--- a/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc
+++ b/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc
@@ -42,6 +42,7 @@
 #include "components/policy/core/common/cloud/cloud_policy_core.h"
 #include "components/policy/core/common/cloud/cloud_policy_service.h"
 #include "components/policy/core/common/cloud/cloud_policy_store.h"
+#include "components/policy/core/common/policy_types.h"
 #include "components/policy/core/common/remote_commands/remote_commands_factory.h"
 #include "components/policy/core/common/schema_registry.h"
 #include "components/policy/proto/device_management_backend.pb.h"
@@ -287,7 +288,8 @@
     CHECK(signin_profile_forwarding_schema_registry_);
     CreateComponentCloudPolicyService(
         dm_protocol::kChromeSigninExtensionPolicyType,
-        component_policy_cache_dir, client_to_connect.get(),
+        component_policy_cache_dir, POLICY_SOURCE_CLOUD,
+        client_to_connect.get(),
         signin_profile_forwarding_schema_registry_.get());
   }
 
diff --git a/chrome/browser/chromeos/policy/device_local_account_policy_service.cc b/chrome/browser/chromeos/policy/device_local_account_policy_service.cc
index 4fab967..79b4f33 100644
--- a/chrome/browser/chromeos/policy/device_local_account_policy_service.cc
+++ b/chrome/browser/chromeos/policy/device_local_account_policy_service.cc
@@ -39,6 +39,7 @@
 #include "components/policy/core/common/cloud/resource_cache.h"
 #include "components/policy/core/common/policy_namespace.h"
 #include "components/policy/core/common/policy_switches.h"
+#include "components/policy/core/common/policy_types.h"
 #include "components/policy/policy_constants.h"
 #include "components/policy/proto/device_management_backend.pb.h"
 #include "content/public/browser/browser_thread.h"
@@ -255,8 +256,9 @@
       /* max_cache_size */ base::nullopt));
 
   component_policy_service_.reset(new ComponentCloudPolicyService(
-      dm_protocol::kChromeExtensionPolicyType, this, &schema_registry_, core(),
-      client, std::move(resource_cache), resource_cache_task_runner_));
+      dm_protocol::kChromeExtensionPolicyType, POLICY_SOURCE_CLOUD, this,
+      &schema_registry_, core(), client, std::move(resource_cache),
+      resource_cache_task_runner_));
 }
 
 DeviceLocalAccountPolicyService::DeviceLocalAccountPolicyService(
diff --git a/chrome/browser/chromeos/policy/pre_signin_policy_fetcher.cc b/chrome/browser/chromeos/policy/pre_signin_policy_fetcher.cc
index fdf3f1e5..0b0ecf7 100644
--- a/chrome/browser/chromeos/policy/pre_signin_policy_fetcher.cc
+++ b/chrome/browser/chromeos/policy/pre_signin_policy_fetcher.cc
@@ -130,16 +130,18 @@
 void PreSigninPolicyFetcher::OnPolicyKeyLoaded(
     RetrievePolicyResponseType retrieve_policy_response,
     const std::string& policy_blob) {
-  cryptohome_client_->Unmount(base::BindOnce(
-      &PreSigninPolicyFetcher::OnUnmountTemporaryUserHome,
-      weak_ptr_factory_.GetWeakPtr(), retrieve_policy_response, policy_blob));
+  cryptohome_client_->UnmountEx(
+      cryptohome::UnmountRequest(),
+      base::BindOnce(&PreSigninPolicyFetcher::OnUnmountTemporaryUserHome,
+                     weak_ptr_factory_.GetWeakPtr(), retrieve_policy_response,
+                     policy_blob));
 }
 
 void PreSigninPolicyFetcher::OnUnmountTemporaryUserHome(
     RetrievePolicyResponseType retrieve_policy_response,
     const std::string& policy_blob,
-    base::Optional<bool> unmount_success) {
-  if (!unmount_success.has_value() || !unmount_success.value()) {
+    base::Optional<cryptohome::BaseReply> reply) {
+  if (BaseReplyToMountError(reply) != cryptohome::MOUNT_ERROR_NONE) {
     // The temporary userhome mount could not be unmounted. Log an error and
     // continue, and hope that the unmount will be successful on the next mount
     // (temporary user homes are automatically unmounted by cryptohomed on every
diff --git a/chrome/browser/chromeos/policy/pre_signin_policy_fetcher.h b/chrome/browser/chromeos/policy/pre_signin_policy_fetcher.h
index 6c61ca85..41834e9 100644
--- a/chrome/browser/chromeos/policy/pre_signin_policy_fetcher.h
+++ b/chrome/browser/chromeos/policy/pre_signin_policy_fetcher.h
@@ -102,7 +102,7 @@
   void OnUnmountTemporaryUserHome(
       RetrievePolicyResponseType retrieve_policy_response,
       const std::string& policy_blob,
-      base::Optional<bool> unmount_success);
+      base::Optional<cryptohome::BaseReply> reply);
 
   void OnCachedPolicyValidated(UserCloudPolicyValidator* validator);
 
diff --git a/chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.cc b/chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.cc
index 9c96c418..c6d67a5 100644
--- a/chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.cc
+++ b/chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.cc
@@ -243,7 +243,7 @@
           chromeos::GetDeviceDMTokenForUserPolicyGetter(account_id_));
   CreateComponentCloudPolicyService(
       dm_protocol::kChromeExtensionPolicyType, component_policy_cache_path_,
-      cloud_policy_client.get(), schema_registry());
+      POLICY_SOURCE_CLOUD, cloud_policy_client.get(), schema_registry());
   core()->Connect(std::move(cloud_policy_client));
   client()->AddObserver(this);
 
diff --git a/chrome/browser/chromeos/power/auto_screen_brightness/adapter.cc b/chrome/browser/chromeos/power/auto_screen_brightness/adapter.cc
index 2ec0756..903c79299 100644
--- a/chrome/browser/chromeos/power/auto_screen_brightness/adapter.cc
+++ b/chrome/browser/chromeos/power/auto_screen_brightness/adapter.cc
@@ -28,12 +28,14 @@
                  AlsReader* als_reader,
                  BrightnessMonitor* brightness_monitor,
                  Modeller* modeller,
+                 ModelConfigLoader* model_config_loader,
                  MetricsReporter* metrics_reporter,
                  chromeos::PowerManagerClient* power_manager_client)
     : profile_(profile),
       als_reader_observer_(this),
       brightness_monitor_observer_(this),
       modeller_observer_(this),
+      model_config_loader_observer_(this),
       power_manager_client_observer_(this),
       metrics_reporter_(metrics_reporter),
       power_manager_client_(power_manager_client),
@@ -43,23 +45,18 @@
   DCHECK(als_reader);
   DCHECK(brightness_monitor);
   DCHECK(modeller);
+  DCHECK(model_config_loader);
   DCHECK(power_manager_client);
 
   als_reader_observer_.Add(als_reader);
   brightness_monitor_observer_.Add(brightness_monitor);
   modeller_observer_.Add(modeller);
+  model_config_loader_observer_.Add(model_config_loader);
   power_manager_client_observer_.Add(power_manager_client);
 
   power_manager_client_->WaitForServiceToBeAvailable(
       base::BindOnce(&Adapter::OnPowerManagerServiceAvailable,
                      weak_ptr_factory_.GetWeakPtr()));
-
-  if (!base::FeatureList::IsEnabled(features::kAutoScreenBrightness)) {
-    adapter_status_ = Status::kDisabled;
-    return;
-  }
-
-  InitParams();
 }
 
 Adapter::~Adapter() = default;
@@ -70,13 +67,7 @@
 
   const base::TimeTicks now = tick_clock_->NowTicks();
 
-  if (first_als_time_.is_null())
-    first_als_time_ = now;
-
-  latest_als_time_ = now;
-
-  ambient_light_values_->SaveToBuffer(
-      {params_.average_log_als ? ConvertToLog(lux) : lux, now});
+  ambient_light_values_->SaveToBuffer({lux, now});
 
   MaybeAdjustBrightness(now);
 }
@@ -113,6 +104,17 @@
 
   switch (*als_init_status_) {
     case AlsReader::AlsInitStatus::kSuccess:
+      DCHECK(!params_.metrics_key.empty());
+      if (params_.metrics_key == "eve") {
+        metrics_reporter_->OnUserBrightnessChangeRequested(
+            MetricsReporter::UserAdjustment::kEve);
+        return;
+      }
+      if (params_.metrics_key == "atlas") {
+        metrics_reporter_->OnUserBrightnessChangeRequested(
+            MetricsReporter::UserAdjustment::kAtlas);
+        return;
+      }
       metrics_reporter_->OnUserBrightnessChangeRequested(
           MetricsReporter::UserAdjustment::kSupportedAls);
       return;
@@ -152,6 +154,18 @@
   UpdateStatus();
 }
 
+void Adapter::OnModelConfigLoaded(base::Optional<ModelConfig> model_config) {
+  DCHECK(!model_config_exists_.has_value());
+
+  model_config_exists_ = model_config.has_value();
+
+  if (model_config_exists_.value()) {
+    InitParams(model_config.value());
+  }
+
+  UpdateStatus();
+}
+
 void Adapter::SuspendDone(const base::TimeDelta& /* sleep_duration */) {
   if (params_.user_adjustment_effect == UserAdjustmentEffect::kPauseAuto)
     adapter_disabled_by_user_adjustment_ = false;
@@ -181,78 +195,39 @@
 
 base::Optional<double> Adapter::GetAverageAmbientForTesting(
     base::TimeTicks now) {
-  return ambient_light_values_->AverageAmbient(now);
+  const base::Optional<double> avg = ambient_light_values_->AverageAmbient(now);
+  if (!avg)
+    return base::nullopt;
+
+  return ConvertToLog(avg.value());
 }
 
 double Adapter::GetBrighteningThresholdForTesting() const {
-  return *brightening_lux_threshold_;
+  return *brightening_threshold_;
 }
 
 double Adapter::GetDarkeningThresholdForTesting() const {
-  return *darkening_lux_threshold_;
+  return *darkening_threshold_;
 }
 
-void Adapter::InitParams() {
-  params_.brightening_lux_threshold_ratio = GetFieldTrialParamByFeatureAsDouble(
-      features::kAutoScreenBrightness, "brightening_lux_threshold_ratio",
-      params_.brightening_lux_threshold_ratio);
-  if (params_.brightening_lux_threshold_ratio <= 0) {
+void Adapter::InitParams(const ModelConfig& model_config) {
+  if (!base::FeatureList::IsEnabled(features::kAutoScreenBrightness) &&
+      model_config.metrics_key != "atlas") {
+    // TODO(jiameng): eventually we will control which device has adapter
+    // enabled by finch. For now, atlas will always be enabled. We'll change the
+    // fixed behaviour when finch experiment for it is set up.
     adapter_status_ = Status::kDisabled;
-    LogParameterError(ParameterError::kAdapterError);
     return;
   }
 
-  params_.darkening_lux_threshold_ratio = GetFieldTrialParamByFeatureAsDouble(
-      features::kAutoScreenBrightness, "darkening_lux_threshold_ratio",
-      params_.darkening_lux_threshold_ratio);
-  if (params_.darkening_lux_threshold_ratio <= 0 ||
-      params_.darkening_lux_threshold_ratio > 1) {
-    adapter_status_ = Status::kDisabled;
-    LogParameterError(ParameterError::kAdapterError);
-    return;
-  }
+  params_.metrics_key = model_config.metrics_key;
+  params_.brightening_log_lux_threshold = GetFieldTrialParamByFeatureAsDouble(
+      features::kAutoScreenBrightness, "brightening_log_lux_threshold",
+      params_.brightening_log_lux_threshold);
 
-  params_.immediate_brightening_lux_threshold_ratio =
-      GetFieldTrialParamByFeatureAsDouble(
-          features::kAutoScreenBrightness,
-          "immediate_brightening_lux_threshold_ratio",
-          params_.immediate_brightening_lux_threshold_ratio);
-  if (params_.immediate_brightening_lux_threshold_ratio <
-      params_.brightening_lux_threshold_ratio) {
-    adapter_status_ = Status::kDisabled;
-    LogParameterError(ParameterError::kAdapterError);
-    return;
-  }
-
-  params_.immediate_darkening_lux_threshold_ratio =
-      GetFieldTrialParamByFeatureAsDouble(
-          features::kAutoScreenBrightness,
-          "immediate_darkening_lux_threshold_ratio",
-          params_.immediate_darkening_lux_threshold_ratio);
-  if (params_.immediate_darkening_lux_threshold_ratio <
-          params_.darkening_lux_threshold_ratio ||
-      params_.immediate_darkening_lux_threshold_ratio > 1) {
-    adapter_status_ = Status::kDisabled;
-    LogParameterError(ParameterError::kAdapterError);
-    return;
-  }
-
-  params_.update_brightness_on_startup = GetFieldTrialParamByFeatureAsBool(
-      features::kAutoScreenBrightness, "update_brightness_on_startup",
-      params_.update_brightness_on_startup);
-
-  const int min_seconds_between_brightness_changes =
-      GetFieldTrialParamByFeatureAsInt(
-          features::kAutoScreenBrightness,
-          "min_seconds_between_brightness_changes",
-          params_.min_time_between_brightness_changes.InSeconds());
-  if (min_seconds_between_brightness_changes < 0) {
-    adapter_status_ = Status::kDisabled;
-    LogParameterError(ParameterError::kAdapterError);
-    return;
-  }
-  params_.min_time_between_brightness_changes =
-      base::TimeDelta::FromSeconds(min_seconds_between_brightness_changes);
+  params_.darkening_log_lux_threshold = GetFieldTrialParamByFeatureAsDouble(
+      features::kAutoScreenBrightness, "darkening_log_lux_threshold",
+      params_.darkening_log_lux_threshold);
 
   const int model_curve = base::GetFieldTrialParamByFeatureAsInt(
       features::kAutoScreenBrightness, "model_curve", 2);
@@ -264,25 +239,19 @@
   params_.model_curve = static_cast<ModelCurve>(model_curve);
 
   const int auto_brightness_als_horizon_seconds =
-      GetFieldTrialParamByFeatureAsInt(
-          features::kAutoScreenBrightness,
-          "auto_brightness_als_horizon_seconds",
-          params_.auto_brightness_als_horizon.InSeconds());
+      model_config.auto_brightness_als_horizon_seconds;
 
   if (auto_brightness_als_horizon_seconds <= 0) {
     adapter_status_ = Status::kDisabled;
     LogParameterError(ParameterError::kAdapterError);
     return;
   }
+
   params_.auto_brightness_als_horizon =
       base::TimeDelta::FromSeconds(auto_brightness_als_horizon_seconds);
   ambient_light_values_ = std::make_unique<AmbientLightSampleBuffer>(
       params_.auto_brightness_als_horizon);
 
-  params_.average_log_als = GetFieldTrialParamByFeatureAsBool(
-      features::kAutoScreenBrightness, "average_log_als",
-      params_.average_log_als);
-
   const int user_adjustment_effect_as_int = GetFieldTrialParamByFeatureAsInt(
       features::kAutoScreenBrightness, "user_adjustment_effect",
       static_cast<int>(params_.user_adjustment_effect));
@@ -340,11 +309,19 @@
     return;
   }
 
+  if (!model_config_exists_.has_value())
+    return;
+
+  if (!model_config_exists_.value()) {
+    adapter_status_ = Status::kDisabled;
+    return;
+  }
+
   adapter_status_ = Status::kSuccess;
 }
 
 base::Optional<Adapter::BrightnessChangeCause> Adapter::CanAdjustBrightness(
-    double current_average_ambient) const {
+    double current_log_average_ambient) const {
   if (adapter_status_ != Status::kSuccess ||
       adapter_disabled_by_user_adjustment_)
     return base::nullopt;
@@ -360,37 +337,16 @@
 
   if (latest_brightness_change_time_.is_null()) {
     // Brightness hasn't been changed before.
-    const bool can_adjust_brightness =
-        latest_als_time_ - first_als_time_ >=
-            params_.auto_brightness_als_horizon ||
-        params_.update_brightness_on_startup;
-
-    if (can_adjust_brightness)
-      return BrightnessChangeCause::kInitialAlsReceived;
-
-    return base::nullopt;
+    return BrightnessChangeCause::kInitialAlsReceived;
   }
 
   // The following thresholds should have been set last time when brightness was
   // changed.
-  if (current_average_ambient > *immediate_brightening_lux_threshold_) {
-    return BrightnessChangeCause::kImmediateBrightneningThresholdExceeded;
-  }
-
-  if (current_average_ambient < *immediate_darkening_lux_threshold_) {
-    return BrightnessChangeCause::kImmediateDarkeningThresholdExceeded;
-  }
-
-  if (tick_clock_->NowTicks() - latest_brightness_change_time_ <
-      params_.min_time_between_brightness_changes) {
-    return base::nullopt;
-  }
-
-  if (current_average_ambient > *brightening_lux_threshold_) {
+  if (current_log_average_ambient > *brightening_threshold_) {
     return BrightnessChangeCause::kBrightneningThresholdExceeded;
   }
 
-  if (current_average_ambient < *darkening_lux_threshold_) {
+  if (current_log_average_ambient < *darkening_threshold_) {
     return BrightnessChangeCause::kDarkeningThresholdExceeded;
   }
 
@@ -403,20 +359,17 @@
   if (!average_ambient_lux_opt)
     return;
 
-  const double average_ambient_lux = average_ambient_lux_opt.value();
+  const double log_average_ambient_lux =
+      ConvertToLog(average_ambient_lux_opt.value());
 
   const base::Optional<BrightnessChangeCause> can_adjust_brightness =
-      CanAdjustBrightness(average_ambient_lux);
+      CanAdjustBrightness(log_average_ambient_lux);
 
   if (!can_adjust_brightness.has_value())
     return;
 
-  // If |params_.average_log_als| is true, then |average_ambient_lux| is
-  // the average of log-lux. Hence we don't need to convert it into log space
-  // again.
-  const base::Optional<double> brightness = GetBrightnessBasedOnAmbientLogLux(
-      params_.average_log_als ? average_ambient_lux
-                              : ConvertToLog(average_ambient_lux));
+  const base::Optional<double> brightness =
+      GetBrightnessBasedOnAmbientLogLux(log_average_ambient_lux);
 
   // This could occur if curve isn't set up (e.g. when we want to use
   // personal only that's not yet available).
@@ -442,30 +395,18 @@
   UMA_HISTOGRAM_ENUMERATION("AutoScreenBrightness.BrightnessChange.Cause",
                             cause);
 
-  average_ambient_lux_ = average_ambient_lux;
+  log_average_ambient_lux_ = log_average_ambient_lux;
 
-  UpdateLuxThresholds();
+  UpdateBrightnessChangeThresholds();
 }
 
-void Adapter::UpdateLuxThresholds() {
-  DCHECK(average_ambient_lux_);
-  DCHECK_GE(*average_ambient_lux_, 0);
+void Adapter::UpdateBrightnessChangeThresholds() {
+  DCHECK(log_average_ambient_lux_);
 
-  brightening_lux_threshold_ =
-      *average_ambient_lux_ * (1 + params_.brightening_lux_threshold_ratio);
-  darkening_lux_threshold_ =
-      *average_ambient_lux_ * (1 - params_.darkening_lux_threshold_ratio);
-  immediate_brightening_lux_threshold_ =
-      *average_ambient_lux_ *
-      (1 + params_.immediate_brightening_lux_threshold_ratio);
-  immediate_darkening_lux_threshold_ =
-      *average_ambient_lux_ *
-      (1 - params_.immediate_darkening_lux_threshold_ratio);
-
-  DCHECK_GE(*brightening_lux_threshold_, 0);
-  DCHECK_GE(*darkening_lux_threshold_, 0);
-  DCHECK_GE(*immediate_brightening_lux_threshold_, 0);
-  DCHECK_GE(*immediate_darkening_lux_threshold_, 0);
+  brightening_threshold_ =
+      *log_average_ambient_lux_ + params_.brightening_log_lux_threshold;
+  darkening_threshold_ =
+      *log_average_ambient_lux_ - params_.darkening_log_lux_threshold;
 }
 
 base::Optional<double> Adapter::GetBrightnessBasedOnAmbientLogLux(
diff --git a/chrome/browser/chromeos/power/auto_screen_brightness/adapter.h b/chrome/browser/chromeos/power/auto_screen_brightness/adapter.h
index 7a6993a..23d47be 100644
--- a/chrome/browser/chromeos/power/auto_screen_brightness/adapter.h
+++ b/chrome/browser/chromeos/power/auto_screen_brightness/adapter.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_CHROMEOS_POWER_AUTO_SCREEN_BRIGHTNESS_ADAPTER_H_
 #define CHROME_BROWSER_CHROMEOS_POWER_AUTO_SCREEN_BRIGHTNESS_ADAPTER_H_
 
+#include <string>
+
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
@@ -17,6 +19,8 @@
 #include "chrome/browser/chromeos/power/auto_screen_brightness/als_samples.h"
 #include "chrome/browser/chromeos/power/auto_screen_brightness/brightness_monitor.h"
 #include "chrome/browser/chromeos/power/auto_screen_brightness/metrics_reporter.h"
+#include "chrome/browser/chromeos/power/auto_screen_brightness/model_config.h"
+#include "chrome/browser/chromeos/power/auto_screen_brightness/model_config_loader.h"
 #include "chrome/browser/chromeos/power/auto_screen_brightness/modeller.h"
 #include "chrome/browser/chromeos/power/auto_screen_brightness/monotone_cubic_spline.h"
 #include "chrome/browser/chromeos/power/auto_screen_brightness/utils.h"
@@ -33,6 +37,7 @@
 class Adapter : public AlsReader::Observer,
                 public BrightnessMonitor::Observer,
                 public Modeller::Observer,
+                public ModelConfigLoader::Observer,
                 public PowerManagerClient::Observer {
  public:
   // Type of curve to use.
@@ -66,34 +71,11 @@
   struct Params {
     Params();
 
-    // Average ambient value has to go up (resp. down) by
-    // |brightening_lux_threshold_ratio| (resp. |darkening_lux_threshold_ratio|)
-    // from the current value before brightness could be changed: brightness
-    // will actually be changed if |min_time_between_brightness_changes| has
-    // passed from the previous change.
-    double brightening_lux_threshold_ratio = 0.3;
-    double darkening_lux_threshold_ratio = 0.4;
-
-    // If average ambient value changes by more than the "immediate" thresholds
-    // then brightness transition will happen immediately, without waiting for
-    // |min_time_between_brightness_changes| to elapse. This value should be
-    // greater than or equal to the max of brightening/darkening thresholds
-    // above.
-    double immediate_brightening_lux_threshold_ratio = 0.4;
-    double immediate_darkening_lux_threshold_ratio = 0.5;
-
-    // Whether brightness should be set to the predicted value when the first
-    // ambient reading comes in. If false, we'll wait for
-    // |auto_brightness_als_horizon| of ambient values before setting brightness
-    // for the first time.
-    bool update_brightness_on_startup = true;
-
-    // Once a brightness change occurs, the next one will not occur until at
-    // least |min_time_between_brightness_changes| later, unless ambient change
-    // exceeds |immediate_brightening_lux_threshold_ratio| or
-    // |immediate_darkening_lux_threshold_ratio|.
-    base::TimeDelta min_time_between_brightness_changes =
-        base::TimeDelta::FromSeconds(10);
+    // The log of average ambient value has to go up (resp. down) by
+    // |brightening_log_lux_threshold| (resp. |darkening_log_lux_threshold|)
+    // from the current value before brightness could be changed.
+    double brightening_log_lux_threshold = 0.4;
+    double darkening_log_lux_threshold = 0.5;
 
     ModelCurve model_curve = ModelCurve::kLatest;
 
@@ -103,13 +85,10 @@
     base::TimeDelta auto_brightness_als_horizon =
         base::TimeDelta::FromSeconds(5);
 
-    // If true, we take logs of lux values before averaging. If false, we take
-    // logs of averaged lux values. This should be the same as
-    // that used by the modeller.
-    bool average_log_als = false;
-
     UserAdjustmentEffect user_adjustment_effect =
         UserAdjustmentEffect::kDisableAuto;
+
+    std::string metrics_key;
   };
 
   // These values are persisted to logs. Entries should not be renumbered and
@@ -125,7 +104,9 @@
   // numeric values should never be reused.
   enum class BrightnessChangeCause {
     kInitialAlsReceived = 0,
+    // Deprecated.
     kImmediateBrightneningThresholdExceeded = 1,
+    // Deprecated.
     kImmediateDarkeningThresholdExceeded = 2,
     kBrightneningThresholdExceeded = 3,
     kDarkeningThresholdExceeded = 4,
@@ -136,6 +117,7 @@
           AlsReader* als_reader,
           BrightnessMonitor* brightness_monitor,
           Modeller* modeller,
+          ModelConfigLoader* model_config_loader,
           MetricsReporter* metrics_reporter,
           chromeos::PowerManagerClient* power_manager_client);
   ~Adapter() override;
@@ -156,6 +138,9 @@
       const base::Optional<MonotoneCubicSpline>& global_curve,
       const base::Optional<MonotoneCubicSpline>& personal_curve) override;
 
+  // ModelConfigLoader::Observer overrides:
+  void OnModelConfigLoaded(base::Optional<ModelConfig> model_config) override;
+
   // chromeos::PowerManagerClient::Observer overrides:
   void SuspendDone(const base::TimeDelta& sleep_duration) override;
 
@@ -169,29 +154,29 @@
   base::Optional<MonotoneCubicSpline> GetGlobalCurveForTesting() const;
   base::Optional<MonotoneCubicSpline> GetPersonalCurveForTesting() const;
 
-  // Returns the actual average over |ambient_light_values_|, which is not
-  // necessarily the same as |average_ambient_lux_|.
+  // Returns the actual log average over |ambient_light_values_|.
   base::Optional<double> GetAverageAmbientForTesting(base::TimeTicks now);
   double GetBrighteningThresholdForTesting() const;
   double GetDarkeningThresholdForTesting() const;
 
  private:
-  // Called by the constructor to initialize |params_| possibly from experiment
-  // flags. It will disable the adapter if any param value is invalid.
-  void InitParams();
+  // Called by |OnModelConfigLoaded|. It will initialize all params used by
+  // the modeller from |model_config| and also other experiment flags. If any
+  // param is invalid, it will disable the adapter.
+  void InitParams(const ModelConfig& model_config);
 
   // Called when powerd becomes available.
   void OnPowerManagerServiceAvailable(bool service_is_ready);
 
   // Called to update |adapter_status_| when there's some status change from
-  // AlsReader, BrightnessMonitor, Modeller or power manager.
+  // AlsReader, BrightnessMonitor, Modeller, power manager and after
+  // |InitParams|.
   void UpdateStatus();
 
   // Returns a BrightnessChangeCause if the adapter can change the brightness.
   // This is generally the case when the brightness hasn't been manually
   // set, we've received enough initial ambient light readings, and
-  // the ambient light has changed beyond thresholds for a long enough
-  // period of time.
+  // the ambient light has changed beyond thresholds.
   // Returns nullopt if it shouldn't change the brightness.
   base::Optional<BrightnessChangeCause> CanAdjustBrightness(
       double current_average_ambient) const;
@@ -200,18 +185,19 @@
   // |CanAdjustBrightness| returns true and a required curve is set up:
   // if the required curve is personal but no personal curve is available, then
   // brightness won't be changed.
-  // It will call |UpdateLuxThresholds| if brightness is actually changed.
+  // It will call |UpdateBrightnessChangeThresholds| if brightness is actually
+  // changed.
   void MaybeAdjustBrightness(base::TimeTicks now);
 
   // This is only called when brightness is changed.
-  void UpdateLuxThresholds();
+  void UpdateBrightnessChangeThresholds();
 
-  // Calculates brightness from given |ambient_lux| based on either
+  // Calculates brightness from given |ambient_log_lux| based on either
   // |global_curve_| or |personal_curve_| (as specified by the experiment
   // params). Returns nullopt if a personal curve should be used but it's not
   // available.
   base::Optional<double> GetBrightnessBasedOnAmbientLogLux(
-      double ambient_lux) const;
+      double ambient_log_lux) const;
 
   Profile* const profile_;
 
@@ -220,6 +206,9 @@
       brightness_monitor_observer_;
   ScopedObserver<Modeller, Modeller::Observer> modeller_observer_;
 
+  ScopedObserver<ModelConfigLoader, ModelConfigLoader::Observer>
+      model_config_loader_observer_;
+
   ScopedObserver<chromeos::PowerManagerClient,
                  chromeos::PowerManagerClient::Observer>
       power_manager_client_observer_;
@@ -239,6 +228,12 @@
 
   base::Optional<AlsReader::AlsInitStatus> als_init_status_;
   base::Optional<bool> brightness_monitor_success_;
+
+  // |model_config_exists_| will remain nullopt until |OnModelConfigLoaded| is
+  // called. Its value will then be set to true if the input model config exists
+  // (not nullopt), else its value will be false.
+  base::Optional<bool> model_config_exists_;
+
   bool model_initialized_ = false;
 
   base::Optional<bool> power_manager_service_available_;
@@ -250,27 +245,20 @@
   // reset to false if |params_.user_adjustment_effect| is |kPauseAuto|.
   bool adapter_disabled_by_user_adjustment_ = false;
 
-  // The thresholds are calculated from |average_ambient_lux_|. They are only
-  // updated when brightness should occur (because average ambient value changed
-  // sufficiently).
-  base::Optional<double> brightening_lux_threshold_;
-  base::Optional<double> darkening_lux_threshold_;
-  base::Optional<double> immediate_brightening_lux_threshold_;
-  base::Optional<double> immediate_darkening_lux_threshold_;
+  // The thresholds are calculated from the |log_average_ambient_lux_|.
+  // They are only updated when brightness should occur (because the log of
+  // average ambient value changed sufficiently).
+  base::Optional<double> brightening_threshold_;
+  base::Optional<double> darkening_threshold_;
 
   base::Optional<MonotoneCubicSpline> global_curve_;
   base::Optional<MonotoneCubicSpline> personal_curve_;
 
   // Average ambient value is only calculated when |CanAdjustBrightness|
-  // returns true. This is the average over all values in
+  // returns true. This is the log of average over all values in
   // |ambient_light_values_|. The adapter will notify powerd to change
   // brightness. New thresholds will be calculated from it.
-  base::Optional<double> average_ambient_lux_;
-
-  // First time an ALS value was received.
-  base::TimeTicks first_als_time_;
-  // Latest time an ALS value was received.
-  base::TimeTicks latest_als_time_;
+  base::Optional<double> log_average_ambient_lux_;
 
   // Last time brightness change occurred.
   base::TimeTicks latest_brightness_change_time_;
diff --git a/chrome/browser/chromeos/power/auto_screen_brightness/adapter_unittest.cc b/chrome/browser/chromeos/power/auto_screen_brightness/adapter_unittest.cc
index 48663ec6..2a9c2b0 100644
--- a/chrome/browser/chromeos/power/auto_screen_brightness/adapter_unittest.cc
+++ b/chrome/browser/chromeos/power/auto_screen_brightness/adapter_unittest.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/power/auto_screen_brightness/fake_als_reader.h"
 #include "chrome/browser/chromeos/power/auto_screen_brightness/fake_brightness_monitor.h"
+#include "chrome/browser/chromeos/power/auto_screen_brightness/fake_model_config_loader.h"
 #include "chrome/browser/chromeos/power/auto_screen_brightness/modeller.h"
 #include "chrome/browser/chromeos/power/auto_screen_brightness/monotone_cubic_spline.h"
 #include "chrome/browser/chromeos/power/auto_screen_brightness/utils.h"
@@ -177,16 +178,15 @@
 
     profile_ = profile_builder.Build();
 
-    base::test::ScopedFeatureList scoped_feature_list;
     if (!params.empty()) {
-      scoped_feature_list.InitAndEnableFeatureWithParameters(
+      scoped_feature_list_.InitAndEnableFeatureWithParameters(
           features::kAutoScreenBrightness, params);
     }
 
     adapter_ = std::make_unique<Adapter>(
         profile_.get(), &fake_als_reader_, &fake_brightness_monitor_,
-        &fake_modeller_, nullptr /* metrics_reporter */,
-        chromeos::PowerManagerClient::Get());
+        &fake_modeller_, &fake_model_config_loader_,
+        nullptr /* metrics_reporter */, chromeos::PowerManagerClient::Get());
     adapter_->SetTickClockForTesting(thread_bundle_.GetMockTickClock());
   }
 
@@ -194,11 +194,16 @@
             BrightnessMonitor::Status brightness_monitor_status,
             const base::Optional<MonotoneCubicSpline>& global_curve,
             const base::Optional<MonotoneCubicSpline>& personal_curve,
+            const base::Optional<ModelConfig>& model_config,
             const std::map<std::string, std::string>& params,
             bool brightness_set_by_policy = false) {
     fake_als_reader_.set_als_init_status(als_reader_status);
     fake_brightness_monitor_.set_status(brightness_monitor_status);
     fake_modeller_.InitModellerWithCurves(global_curve, personal_curve);
+    if (model_config) {
+      fake_model_config_loader_.set_model_config(model_config.value());
+    }
+
     SetUpAdapter(params, brightness_set_by_policy);
     thread_bundle_.RunUntilIdle();
   }
@@ -208,6 +213,22 @@
     thread_bundle_.RunUntilIdle();
   }
 
+  // Returns a valid ModelConfig.
+  ModelConfig GetTestModelConfig(const std::string& metrics_key = "abc") {
+    ModelConfig model_config;
+    model_config.auto_brightness_als_horizon_seconds = 5.0;
+    model_config.log_lux = {
+        3.69, 4.83, 6.54, 7.68, 8.25, 8.82,
+    };
+    model_config.brightness = {
+        36.14, 47.62, 85.83, 93.27, 93.27, 100,
+    };
+
+    model_config.metrics_key = metrics_key;
+    model_config.model_als_horizon_seconds = 3.0;
+    return model_config;
+  }
+
  protected:
   content::TestBrowserThreadBundle thread_bundle_;
 
@@ -222,22 +243,19 @@
   FakeAlsReader fake_als_reader_;
   FakeBrightnessMonitor fake_brightness_monitor_;
   FakeModeller fake_modeller_;
+  FakeModelConfigLoader fake_model_config_loader_;
 
   base::HistogramTester histogram_tester_;
 
   const std::map<std::string, std::string> default_params_ = {
-      {"brightening_lux_threshold_ratio", "0.1"},
-      {"darkening_lux_threshold_ratio", "0.2"},
-      {"immediate_brightening_lux_threshold_ratio", "3"},
-      {"immediate_darkening_lux_threshold_ratio", "1"},
-      {"update_brightness_on_startup", "true"},
-      {"min_seconds_between_brightness_changes", "10"},
+      {"brightening_log_lux_threshold", "0.1"},
+      {"darkening_log_lux_threshold", "0.2"},
       {"model_curve", "2"},
-      {"auto_brightness_als_horizon_seconds", "5"},
-      {"average_log_als", "false"},
       {"user_adjustment_effect", "0"},
   };
 
+  base::test::ScopedFeatureList scoped_feature_list_;
+
   std::unique_ptr<Adapter> adapter_;
 
  private:
@@ -247,7 +265,8 @@
 // AlsReader is |kDisabled| when Adapter is created.
 TEST_F(AdapterTest, AlsReaderDisabledOnInit) {
   Init(AlsReader::AlsInitStatus::kDisabled, BrightnessMonitor::Status::kSuccess,
-       global_curve_, base::nullopt /* personal_curve */, default_params_);
+       global_curve_, base::nullopt /* personal_curve */, GetTestModelConfig(),
+       default_params_);
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kDisabled);
 }
@@ -255,7 +274,8 @@
 // BrightnessMonitor is |kDisabled| when Adapter is created.
 TEST_F(AdapterTest, BrightnessMonitorDisabledOnInit) {
   Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kDisabled,
-       global_curve_, base::nullopt /* personal_curve */, default_params_);
+       global_curve_, base::nullopt /* personal_curve */, GetTestModelConfig(),
+       default_params_);
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kDisabled);
 }
@@ -264,6 +284,15 @@
 TEST_F(AdapterTest, ModellerDisabledOnInit) {
   Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
        base::nullopt /* global_curve */, base::nullopt /* personal_curve */,
+       GetTestModelConfig(), default_params_);
+
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kDisabled);
+}
+
+// ModelConfigLoader has an invalid config, hence Modeller is disabled.
+TEST_F(AdapterTest, ModelConfigLoaderDisabledOnInit) {
+  Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
+       global_curve_, base::nullopt /* personal_curve */, ModelConfig(),
        default_params_);
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kDisabled);
@@ -273,7 +302,8 @@
 TEST_F(AdapterTest, AlsReaderDisabledOnNotification) {
   Init(AlsReader::AlsInitStatus::kInProgress,
        BrightnessMonitor::Status::kSuccess, global_curve_,
-       base::nullopt /* personal_curve */, default_params_);
+       base::nullopt /* personal_curve */, GetTestModelConfig(),
+       default_params_);
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kInitializing);
 
@@ -286,13 +316,15 @@
 TEST_F(AdapterTest, AlsReaderEnabledOnNotification) {
   Init(AlsReader::AlsInitStatus::kInProgress,
        BrightnessMonitor::Status::kSuccess, global_curve_,
-       base::nullopt /* personal_curve */, default_params_);
+       base::nullopt /* personal_curve */, GetTestModelConfig(),
+       default_params_);
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kInitializing);
 
   fake_als_reader_.set_als_init_status(AlsReader::AlsInitStatus::kSuccess);
   fake_als_reader_.ReportReaderInitialized();
   thread_bundle_.RunUntilIdle();
+
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
   EXPECT_TRUE(adapter_->GetGlobalCurveForTesting());
   EXPECT_EQ(*adapter_->GetGlobalCurveForTesting(), *global_curve_);
@@ -303,7 +335,8 @@
 TEST_F(AdapterTest, BrightnessMonitorDisabledOnNotification) {
   Init(AlsReader::AlsInitStatus::kSuccess,
        BrightnessMonitor::Status::kInitializing, global_curve_,
-       base::nullopt /* personal_curve */, default_params_);
+       base::nullopt /* personal_curve */, GetTestModelConfig(),
+       default_params_);
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kInitializing);
 
@@ -316,7 +349,8 @@
 TEST_F(AdapterTest, BrightnessMonitorEnabledOnNotification) {
   Init(AlsReader::AlsInitStatus::kSuccess,
        BrightnessMonitor::Status::kInitializing, global_curve_,
-       base::nullopt /* personal_curve */, default_params_);
+       base::nullopt /* personal_curve */, GetTestModelConfig(),
+       default_params_);
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kInitializing);
 
@@ -333,6 +367,7 @@
 TEST_F(AdapterTest, ModellerDisabledOnNotification) {
   fake_als_reader_.set_als_init_status(AlsReader::AlsInitStatus::kSuccess);
   fake_brightness_monitor_.set_status(BrightnessMonitor::Status::kSuccess);
+  fake_model_config_loader_.set_model_config(GetTestModelConfig());
   SetUpAdapter(default_params_);
   thread_bundle_.RunUntilIdle();
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kInitializing);
@@ -348,6 +383,7 @@
 TEST_F(AdapterTest, ModellerEnabledOnNotification) {
   fake_als_reader_.set_als_init_status(AlsReader::AlsInitStatus::kSuccess);
   fake_brightness_monitor_.set_status(BrightnessMonitor::Status::kSuccess);
+  fake_model_config_loader_.set_model_config(GetTestModelConfig());
   SetUpAdapter(default_params_);
   thread_bundle_.RunUntilIdle();
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kInitializing);
@@ -361,9 +397,44 @@
   EXPECT_EQ(*adapter_->GetPersonalCurveForTesting(), *personal_curve_);
 }
 
+// ModelConfigLoader reports an invalid config on later notification.
+TEST_F(AdapterTest, InvalidModelConfigOnNotification) {
+  Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
+       global_curve_, base::nullopt /* personal_curve */, base::nullopt,
+       default_params_);
+
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kInitializing);
+
+  // ModelConfig() creates an invalid config.
+  DCHECK(!IsValidModelConfig(ModelConfig()));
+  fake_model_config_loader_.set_model_config(ModelConfig());
+  fake_model_config_loader_.ReportModelConfigLoaded();
+  thread_bundle_.RunUntilIdle();
+
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kDisabled);
+}
+
+// ModelConfigLoader reports a valid config on later notification.
+TEST_F(AdapterTest, ValidModelConfigOnNotification) {
+  Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
+       global_curve_, base::nullopt /* personal_curve */, base::nullopt,
+       default_params_);
+
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kInitializing);
+
+  fake_model_config_loader_.set_model_config(GetTestModelConfig());
+  fake_model_config_loader_.ReportModelConfigLoaded();
+  thread_bundle_.RunUntilIdle();
+
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
+  EXPECT_TRUE(adapter_->GetGlobalCurveForTesting());
+  EXPECT_EQ(*adapter_->GetGlobalCurveForTesting(), *global_curve_);
+  EXPECT_FALSE(adapter_->GetPersonalCurveForTesting());
+}
+
 TEST_F(AdapterTest, SequenceOfBrightnessUpdatesWithDefaultParams) {
   Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
-       global_curve_, personal_curve_, default_params_);
+       global_curve_, personal_curve_, GetTestModelConfig(), default_params_);
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
   EXPECT_TRUE(adapter_->GetGlobalCurveForTesting());
@@ -371,8 +442,6 @@
   EXPECT_TRUE(adapter_->GetPersonalCurveForTesting());
   EXPECT_EQ(*adapter_->GetPersonalCurveForTesting(), *personal_curve_);
 
-  // Forward by 1sec because in real implementation, |first_als_time_| is zero
-  // if there's no ALS reading received.
   thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
 
   // Brightness is changed after the 1st ALS reading comes in.
@@ -380,30 +449,42 @@
   thread_bundle_.RunUntilIdle();
   EXPECT_EQ(test_observer_.num_changes(), 1);
   EXPECT_EQ(adapter_->GetAverageAmbientForTesting(thread_bundle_.NowTicks()),
-            10.0);
+            ConvertToLog(10.0));
+  EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(),
+                   ConvertToLog(10.0) + 0.1);
+  EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(),
+                   ConvertToLog(10.0) - 0.2);
 
-  // Another ALS value is received in 3 sec, but no brightness update is done.
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(3));
+  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
   fake_als_reader_.ReportAmbientLightUpdate(20);
   thread_bundle_.RunUntilIdle();
-  EXPECT_EQ(test_observer_.num_changes(), 1);
+  EXPECT_EQ(test_observer_.num_changes(), 2);
   EXPECT_EQ(adapter_->GetAverageAmbientForTesting(thread_bundle_.NowTicks()),
-            15.0);
+            ConvertToLog(15.0));
+  EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(),
+                   ConvertToLog(15.0) + 0.1);
+  EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(),
+                   ConvertToLog(15.0) - 0.2);
 
-  // |params.min_time_between_brightness_changes| has elapsed since we've made
+  // |params.auto_brightness_als_horizon_seconds| has elapsed since we've made
   // the change, but there's no new ALS value, hence no brightness change is
   // triggered.
   thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(10));
-  EXPECT_EQ(test_observer_.num_changes(), 1);
+  EXPECT_EQ(test_observer_.num_changes(), 2);
   EXPECT_EQ(adapter_->GetAverageAmbientForTesting(thread_bundle_.NowTicks()),
             base::nullopt);
 
   // A new ALS value triggers a brightness change.
+  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
   fake_als_reader_.ReportAmbientLightUpdate(40);
   thread_bundle_.RunUntilIdle();
-  EXPECT_EQ(test_observer_.num_changes(), 2);
+  EXPECT_EQ(test_observer_.num_changes(), 3);
   EXPECT_EQ(adapter_->GetAverageAmbientForTesting(thread_bundle_.NowTicks()),
-            40);
+            ConvertToLog(40));
+  EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(),
+                   ConvertToLog(40.0) + 0.1);
+  EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(),
+                   ConvertToLog(40.0) - 0.2);
 
   // Adapter will not be applied after a user manual adjustment.
   fake_brightness_monitor_.ReportUserBrightnessChangeRequested();
@@ -418,9 +499,9 @@
   EXPECT_FALSE(adapter_->IsAppliedForTesting());
 
   thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  fake_als_reader_.ReportAmbientLightUpdate(30);
+  fake_als_reader_.ReportAmbientLightUpdate(100);
   thread_bundle_.RunUntilIdle();
-  EXPECT_EQ(test_observer_.num_changes(), 2);
+  EXPECT_EQ(test_observer_.num_changes(), 3);
 
   // Another user manual adjustment came in.
   fake_brightness_monitor_.ReportUserBrightnessChangeRequested();
@@ -431,7 +512,7 @@
 
 TEST_F(AdapterTest, UserBrightnessRequestBeforeAnyModelUpdate) {
   Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
-       global_curve_, personal_curve_, default_params_);
+       global_curve_, personal_curve_, GetTestModelConfig(), default_params_);
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
   EXPECT_TRUE(adapter_->GetGlobalCurveForTesting());
@@ -439,6 +520,8 @@
   EXPECT_TRUE(adapter_->GetPersonalCurveForTesting());
   EXPECT_EQ(*adapter_->GetPersonalCurveForTesting(), *personal_curve_);
 
+  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
+
   // Adapter will not be applied after a user manual adjustment.
   fake_brightness_monitor_.ReportUserBrightnessChangeRequested();
   thread_bundle_.RunUntilIdle();
@@ -453,12 +536,11 @@
 }
 
 TEST_F(AdapterTest, BrightnessLuxThresholds) {
-  // Ensure |min_seconds_between_brightness_changes| is shorter than als horizon
-  // in this test.
   std::map<std::string, std::string> params = default_params_;
-  params["min_seconds_between_brightness_changes"] = "1";
+  params["brightening_log_lux_threshold"] = "1";
+  params["darkening_log_lux_threshold"] = "0.2";
   Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
-       global_curve_, personal_curve_, params);
+       global_curve_, personal_curve_, GetTestModelConfig(), params);
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
   EXPECT_TRUE(adapter_->GetGlobalCurveForTesting());
@@ -472,11 +554,16 @@
   // changed.
   fake_als_reader_.ReportAmbientLightUpdate(20);
   thread_bundle_.RunUntilIdle();
+  double expected_log_avg = ConvertToLog(20);
   EXPECT_EQ(test_observer_.num_changes(), 1);
   EXPECT_EQ(adapter_->GetAverageAmbientForTesting(thread_bundle_.NowTicks()),
-            20);
-  EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(), 22);
-  EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(), 16);
+            expected_log_avg);
+  double expected_brightening_threshold = expected_log_avg + 1;
+  double expected_darkening_threshold = expected_log_avg - 0.2;
+  EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(),
+                   expected_brightening_threshold);
+  EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(),
+                   expected_darkening_threshold);
 
   // A 2nd ALS comes in, but average ambient is within the thresholds, hence
   // brightness isn't changed and thresholds aren't updated.
@@ -485,116 +572,50 @@
   thread_bundle_.RunUntilIdle();
   EXPECT_EQ(1, test_observer_.num_changes());
   EXPECT_EQ(adapter_->GetAverageAmbientForTesting(thread_bundle_.NowTicks()),
-            (20 + 21) / 2.0);
-  EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(), 22);
-  EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(), 16);
+            ConvertToLog((20 + 21) / 2.0));
+  EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(),
+                   expected_brightening_threshold);
+  EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(),
+                   expected_darkening_threshold);
 
-  // A 3rd ALS comes in, but still not enough to trigger brightness change.
+  // // A 3rd ALS comes in, but still not enough to trigger brightness change.
   thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
   fake_als_reader_.ReportAmbientLightUpdate(15);
   thread_bundle_.RunUntilIdle();
   EXPECT_EQ(test_observer_.num_changes(), 1);
   EXPECT_EQ(adapter_->GetAverageAmbientForTesting(thread_bundle_.NowTicks()),
-            (20 + 21 + 15) / 3.0);
-  EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(), 22);
-  EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(), 16);
+            ConvertToLog((20 + 21 + 15) / 3.0));
+  EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(),
+                   expected_brightening_threshold);
+  EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(),
+                   expected_darkening_threshold);
 
   // A 4th ALS makes average value below the darkening threshold, hence
   // brightness is changed. Thresholds are also changed.
   thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  fake_als_reader_.ReportAmbientLightUpdate(7);
+  fake_als_reader_.ReportAmbientLightUpdate(5);
   thread_bundle_.RunUntilIdle();
   EXPECT_EQ(test_observer_.num_changes(), 2);
-  const double expected_average_ambient = (20 + 21 + 15 + 7) / 4.0;
+  expected_log_avg = ConvertToLog((20 + 21 + 15 + 5) / 4.0);
   EXPECT_EQ(adapter_->GetAverageAmbientForTesting(thread_bundle_.NowTicks()),
-            expected_average_ambient);
+            expected_log_avg);
   EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(),
-                   expected_average_ambient * 1.1);
+                   expected_log_avg + 1);
   EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(),
-                   expected_average_ambient * 0.8);
+                   expected_log_avg - 0.2);
 
   thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
   fake_als_reader_.ReportAmbientLightUpdate(8);
   thread_bundle_.RunUntilIdle();
   EXPECT_EQ(adapter_->GetAverageAmbientForTesting(thread_bundle_.NowTicks()),
-            (20 + 21 + 15 + 7 + 8) / 5.0);
+            ConvertToLog((20 + 21 + 15 + 5 + 8) / 5.0));
 
   thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
   fake_als_reader_.ReportAmbientLightUpdate(9);
   thread_bundle_.RunUntilIdle();
 
   EXPECT_EQ(adapter_->GetAverageAmbientForTesting(thread_bundle_.NowTicks()),
-            (21 + 15 + 7 + 8 + 9) / 5.0);
-}
-
-TEST_F(AdapterTest, ImmediateBrightnessTransitionThresholds) {
-  std::map<std::string, std::string> params = default_params_;
-  params["immediate_brightening_lux_threshold_ratio"] = "0.3";
-  params["immediate_darkening_lux_threshold_ratio"] = "0.3";
-
-  Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
-       global_curve_, personal_curve_, params);
-
-  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
-  EXPECT_TRUE(adapter_->GetGlobalCurveForTesting());
-  EXPECT_EQ(*adapter_->GetGlobalCurveForTesting(), *global_curve_);
-  EXPECT_TRUE(adapter_->GetPersonalCurveForTesting());
-  EXPECT_EQ(*adapter_->GetPersonalCurveForTesting(), *personal_curve_);
-
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-
-  // Brightness is changed after the 1st ALS reading comes in.
-  fake_als_reader_.ReportAmbientLightUpdate(10);
-  thread_bundle_.RunUntilIdle();
-  EXPECT_EQ(test_observer_.num_changes(), 1);
-  EXPECT_EQ(adapter_->GetAverageAmbientForTesting(thread_bundle_.NowTicks()),
-            10.0);
-
-  // Another ALS value is received in 1 sec, brightness is changed because it
-  // exceeds immediate transition threshold.
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  fake_als_reader_.ReportAmbientLightUpdate(17);
-  thread_bundle_.RunUntilIdle();
-  EXPECT_EQ(test_observer_.num_changes(), 2);
-  EXPECT_EQ(adapter_->GetAverageAmbientForTesting(thread_bundle_.NowTicks()),
-            (10 + 17) / 2.0);
-
-  // Another ALS value is received in 2 sec, brightness is changed because it
-  // exceeds immediate transition threshold.
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(2));
-  fake_als_reader_.ReportAmbientLightUpdate(1);
-  thread_bundle_.RunUntilIdle();
-  EXPECT_EQ(test_observer_.num_changes(), 3);
-  EXPECT_EQ(adapter_->GetAverageAmbientForTesting(thread_bundle_.NowTicks()),
-            (10 + 17 + 1) / 3.0);
-}
-
-TEST_F(AdapterTest, BrightnessNotUpdatedOnStartup) {
-  std::map<std::string, std::string> params = default_params_;
-  params["update_brightness_on_startup"] = "false";
-
-  Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
-       global_curve_, personal_curve_, params);
-
-  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
-
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-
-  // 1st ALS reading doesn't trigger a brightness change.
-  fake_als_reader_.ReportAmbientLightUpdate(10);
-  thread_bundle_.RunUntilIdle();
-  EXPECT_EQ(test_observer_.num_changes(), 0);
-
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(5));
-  EXPECT_EQ(test_observer_.num_changes(), 0);
-
-  // 2nd ALS comes in so that we have |kAmbientLightShortHorizonSeconds| of
-  // data, hence brightness is changed.
-  fake_als_reader_.ReportAmbientLightUpdate(20);
-  thread_bundle_.RunUntilIdle();
-  EXPECT_EQ(test_observer_.num_changes(), 1);
-  EXPECT_EQ(adapter_->GetAverageAmbientForTesting(thread_bundle_.NowTicks()),
-            20.0);
+            ConvertToLog((21 + 15 + 5 + 8 + 9) / 5.0));
 }
 
 TEST_F(AdapterTest, UsePersonalCurve) {
@@ -603,7 +624,8 @@
 
   // Init modeller with only a global curve.
   Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
-       global_curve_, base::nullopt /* personal_curve */, params);
+       global_curve_, base::nullopt /* personal_curve */, GetTestModelConfig(),
+       params);
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
 
@@ -624,11 +646,11 @@
   EXPECT_EQ(test_observer_.GetCause(),
             power_manager::BacklightBrightnessChange_Cause_MODEL);
 
-  const double expected_average_ambient = (10 + 20) / 2.0;
+  const double expected_log_avg = ConvertToLog((10 + 20) / 2.0);
   EXPECT_EQ(adapter_->GetAverageAmbientForTesting(thread_bundle_.NowTicks()),
-            expected_average_ambient);
+            expected_log_avg);
   const double expected_brightness_percent =
-      personal_curve_->Interpolate(ConvertToLog(expected_average_ambient));
+      personal_curve_->Interpolate(expected_log_avg);
   EXPECT_DOUBLE_EQ(test_observer_.GetBrightnessPercent(),
                    expected_brightness_percent);
 }
@@ -638,7 +660,7 @@
   params["model_curve"] = "0";
 
   Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
-       global_curve_, personal_curve_, params);
+       global_curve_, personal_curve_, GetTestModelConfig(), params);
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
 
@@ -646,11 +668,12 @@
   fake_als_reader_.ReportAmbientLightUpdate(10);
   thread_bundle_.RunUntilIdle();
   EXPECT_EQ(test_observer_.num_changes(), 1);
+  const double expected_log_avg1 = ConvertToLog(10);
   EXPECT_EQ(adapter_->GetAverageAmbientForTesting(thread_bundle_.NowTicks()),
-            10);
+            expected_log_avg1);
 
   const double expected_brightness_percent1 =
-      global_curve_->Interpolate(ConvertToLog(10));
+      global_curve_->Interpolate(expected_log_avg1);
   EXPECT_DOUBLE_EQ(test_observer_.GetBrightnessPercent(),
                    expected_brightness_percent1);
 
@@ -663,66 +686,18 @@
   EXPECT_EQ(test_observer_.GetCause(),
             power_manager::BacklightBrightnessChange_Cause_MODEL);
 
-  const double expected_average_ambient2 = 20;
+  const double expected_log_avg2 = ConvertToLog(20);
   EXPECT_EQ(adapter_->GetAverageAmbientForTesting(thread_bundle_.NowTicks()),
-            expected_average_ambient2);
+            expected_log_avg2);
   const double expected_brightness_percent2 =
-      global_curve_->Interpolate(ConvertToLog(expected_average_ambient2));
+      global_curve_->Interpolate(expected_log_avg2);
   EXPECT_DOUBLE_EQ(test_observer_.GetBrightnessPercent(),
                    expected_brightness_percent2);
 }
 
-TEST_F(AdapterTest, AverageLogAmbient) {
-  std::map<std::string, std::string> params = default_params_;
-  // Ensure |min_seconds_between_brightness_changes| is shorter than als horizon
-  // in this test.
-  params["min_seconds_between_brightness_changes"] = "1";
-  params["average_log_als"] = "true";
-
-  Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
-       global_curve_, personal_curve_, params);
-
-  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
-  EXPECT_TRUE(adapter_->GetGlobalCurveForTesting());
-  EXPECT_EQ(*adapter_->GetGlobalCurveForTesting(), *global_curve_);
-  EXPECT_TRUE(adapter_->GetPersonalCurveForTesting());
-  EXPECT_EQ(*adapter_->GetPersonalCurveForTesting(), *personal_curve_);
-
-  // Brightness is changed after the 1st ALS value, and the thresholds are
-  // changed.
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  fake_als_reader_.ReportAmbientLightUpdate(20);
-  thread_bundle_.RunUntilIdle();
-  EXPECT_EQ(test_observer_.num_changes(), 1);
-  double expected_average_ambient = ConvertToLog(20);
-  EXPECT_EQ(adapter_->GetAverageAmbientForTesting(thread_bundle_.NowTicks()),
-            expected_average_ambient);
-  EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(),
-                   expected_average_ambient * 1.1);
-  EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(),
-                   expected_average_ambient * 0.8);
-  EXPECT_DOUBLE_EQ(test_observer_.GetBrightnessPercent(),
-                   personal_curve_->Interpolate(expected_average_ambient));
-
-  // Second ALS value comes in that exceeds the thresholds.
-  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
-  fake_als_reader_.ReportAmbientLightUpdate(50);
-  thread_bundle_.RunUntilIdle();
-  EXPECT_EQ(2, test_observer_.num_changes());
-  expected_average_ambient = (expected_average_ambient + ConvertToLog(50)) / 2;
-  EXPECT_EQ(adapter_->GetAverageAmbientForTesting(thread_bundle_.NowTicks()),
-            expected_average_ambient);
-  EXPECT_DOUBLE_EQ(adapter_->GetBrighteningThresholdForTesting(),
-                   expected_average_ambient * 1.1);
-  EXPECT_DOUBLE_EQ(adapter_->GetDarkeningThresholdForTesting(),
-                   expected_average_ambient * 0.8);
-  EXPECT_DOUBLE_EQ(test_observer_.GetBrightnessPercent(),
-                   personal_curve_->Interpolate(expected_average_ambient));
-}
-
 TEST_F(AdapterTest, BrightnessSetByPolicy) {
   Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
-       global_curve_, personal_curve_, default_params_,
+       global_curve_, personal_curve_, GetTestModelConfig(), default_params_,
        true /* brightness_set_by_policy */);
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
@@ -738,7 +713,7 @@
   std::map<std::string, std::string> empty_params;
 
   Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
-       global_curve_, personal_curve_, empty_params);
+       global_curve_, personal_curve_, GetTestModelConfig(), empty_params);
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kDisabled);
   // Global and personal curves are received, but they won't be used to change
@@ -754,22 +729,39 @@
   EXPECT_EQ(test_observer_.num_changes(), 0);
 }
 
-TEST_F(AdapterTest, ValidParameters) {
-  std::map<std::string, std::string> params = default_params_;
-  params["darkening_lux_threshold_ratio"] = "0.5";
+TEST_F(AdapterTest, FeatureEnabledForAtlas) {
+  // An empty param map will not enable the experiment flag.
+  std::map<std::string, std::string> empty_params;
 
+  // But metrics_key="atlas" means it's always enabled.
   Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
-       global_curve_, personal_curve_, params);
+       global_curve_, personal_curve_, GetTestModelConfig("atlas"),
+       empty_params);
+
+  EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
+  EXPECT_TRUE(adapter_->GetGlobalCurveForTesting());
+  EXPECT_TRUE(adapter_->GetPersonalCurveForTesting());
+
+  thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
+
+  fake_als_reader_.ReportAmbientLightUpdate(10);
+  thread_bundle_.RunUntilIdle();
+  EXPECT_EQ(test_observer_.num_changes(), 1);
+}
+
+TEST_F(AdapterTest, ValidParameters) {
+  Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
+       global_curve_, personal_curve_, GetTestModelConfig(), default_params_);
 
   histogram_tester_.ExpectTotalCount("AutoScreenBrightness.ParameterError", 0);
 }
 
 TEST_F(AdapterTest, InvalidParameters) {
   std::map<std::string, std::string> params = default_params_;
-  params["darkening_lux_threshold_ratio"] = "2";
+  params["user_adjustment_effect"] = "10";
 
   Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
-       global_curve_, personal_curve_, params);
+       global_curve_, personal_curve_, GetTestModelConfig(), params);
 
   histogram_tester_.ExpectUniqueSample(
       "AutoScreenBrightness.ParameterError",
@@ -778,11 +770,10 @@
 
 TEST_F(AdapterTest, UserAdjustmentEffectPause) {
   std::map<std::string, std::string> params = default_params_;
-  params["min_seconds_between_brightness_changes"] = "1";
   params["user_adjustment_effect"] = "1";
 
   Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
-       global_curve_, personal_curve_, params);
+       global_curve_, personal_curve_, GetTestModelConfig(), params);
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
   EXPECT_TRUE(adapter_->GetGlobalCurveForTesting());
@@ -790,8 +781,6 @@
   EXPECT_TRUE(adapter_->GetPersonalCurveForTesting());
   EXPECT_EQ(*adapter_->GetPersonalCurveForTesting(), *personal_curve_);
 
-  // Forward by 1sec because in real implementation, |first_als_time_| is zero
-  // if there's no ALS reading received.
   thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
 
   // Brightness is changed after the 1st ALS reading comes in.
@@ -830,11 +819,10 @@
 
 TEST_F(AdapterTest, UserAdjustmentEffectContinue) {
   std::map<std::string, std::string> params = default_params_;
-  params["min_seconds_between_brightness_changes"] = "1";
   params["user_adjustment_effect"] = "2";
 
   Init(AlsReader::AlsInitStatus::kSuccess, BrightnessMonitor::Status::kSuccess,
-       global_curve_, personal_curve_, params);
+       global_curve_, personal_curve_, GetTestModelConfig(), params);
 
   EXPECT_EQ(adapter_->GetStatusForTesting(), Adapter::Status::kSuccess);
   EXPECT_TRUE(adapter_->GetGlobalCurveForTesting());
@@ -842,8 +830,6 @@
   EXPECT_TRUE(adapter_->GetPersonalCurveForTesting());
   EXPECT_EQ(*adapter_->GetPersonalCurveForTesting(), *personal_curve_);
 
-  // Forward by 1sec because in real implementation, |first_als_time_| is zero
-  // if there's no ALS reading received.
   thread_bundle_.FastForwardBy(base::TimeDelta::FromSeconds(1));
 
   // Brightness is changed after the 1st ALS reading comes in.
diff --git a/chrome/browser/chromeos/power/auto_screen_brightness/controller.cc b/chrome/browser/chromeos/power/auto_screen_brightness/controller.cc
index 574964f5..8dce08f 100644
--- a/chrome/browser/chromeos/power/auto_screen_brightness/controller.cc
+++ b/chrome/browser/chromeos/power/auto_screen_brightness/controller.cc
@@ -51,7 +51,8 @@
 
   adapter_ = std::make_unique<Adapter>(
       profile, als_reader_.get(), brightness_monitor_.get(), modeller_.get(),
-      metrics_reporter_.get(), power_manager_client);
+      model_config_loader_.get(), metrics_reporter_.get(),
+      power_manager_client);
 }
 
 Controller::~Controller() = default;
diff --git a/chrome/browser/chromeos/power/auto_screen_brightness/metrics_reporter.cc b/chrome/browser/chromeos/power/auto_screen_brightness/metrics_reporter.cc
index 8d6a6ca..454f176 100644
--- a/chrome/browser/chromeos/power/auto_screen_brightness/metrics_reporter.cc
+++ b/chrome/browser/chromeos/power/auto_screen_brightness/metrics_reporter.cc
@@ -22,17 +22,23 @@
     base::TimeDelta::FromSeconds(60);
 
 // Prefs corresponding to UserAdjustment values.
-constexpr std::array<const char*, 3> kDailyCountPrefs = {
-    prefs::kAutoScreenBrightnessMetricsNoAlsUserAdjustmentCount,
-    prefs::kAutoScreenBrightnessMetricsSupportedAlsUserAdjustmentCount,
-    prefs::kAutoScreenBrightnessMetricsUnsupportedAlsUserAdjustmentCount,
+constexpr std::array<const char*, MetricsReporter::kNumberAdjustmentTypes>
+    kDailyCountPrefs = {
+        prefs::kAutoScreenBrightnessMetricsNoAlsUserAdjustmentCount,
+        prefs::kAutoScreenBrightnessMetricsSupportedAlsUserAdjustmentCount,
+        prefs::kAutoScreenBrightnessMetricsUnsupportedAlsUserAdjustmentCount,
+        prefs::kAutoScreenBrightnessMetricsAtlasUserAdjustmentCount,
+        prefs::kAutoScreenBrightnessMetricsEveUserAdjustmentCount,
 };
 
 // Histograms corresponding to UserAdjustment values.
-constexpr std::array<const char*, 3> kDailyCountHistograms = {
-    MetricsReporter::kNoAlsUserAdjustmentName,
-    MetricsReporter::kSupportedAlsUserAdjustmentName,
-    MetricsReporter::kUnsupportedAlsUserAdjustmentName,
+constexpr std::array<const char*, MetricsReporter::kNumberAdjustmentTypes>
+    kDailyCountHistograms = {
+        MetricsReporter::kNoAlsUserAdjustmentName,
+        MetricsReporter::kSupportedAlsUserAdjustmentName,
+        MetricsReporter::kUnsupportedAlsUserAdjustmentName,
+        MetricsReporter::kAtlasUserAdjustmentName,
+        MetricsReporter::kEveUserAdjustmentName,
 };
 
 }  // namespace
@@ -41,6 +47,10 @@
 constexpr char MetricsReporter::kNoAlsUserAdjustmentName[];
 constexpr char MetricsReporter::kSupportedAlsUserAdjustmentName[];
 constexpr char MetricsReporter::kUnsupportedAlsUserAdjustmentName[];
+constexpr char MetricsReporter::kAtlasUserAdjustmentName[];
+constexpr char MetricsReporter::kEveUserAdjustmentName[];
+
+constexpr int MetricsReporter::kNumberAdjustmentTypes;
 
 // This class is needed since metrics::DailyEvent requires taking ownership
 // of its observers. It just forwards events to MetricsReporter.
diff --git a/chrome/browser/chromeos/power/auto_screen_brightness/metrics_reporter.h b/chrome/browser/chromeos/power/auto_screen_brightness/metrics_reporter.h
index dba2dab..3da65780 100644
--- a/chrome/browser/chromeos/power/auto_screen_brightness/metrics_reporter.h
+++ b/chrome/browser/chromeos/power/auto_screen_brightness/metrics_reporter.h
@@ -31,9 +31,14 @@
     kNoAls = 0,
     kSupportedAls = 1,
     kUnsupportedAls = 2,
-    kMaxValue = kUnsupportedAls
+    kAtlas = 3,
+    kEve = 4,
+    kMaxValue = kEve
   };
 
+  static constexpr int kNumberAdjustmentTypes =
+      static_cast<int>(UserAdjustment::kMaxValue) + 1;
+
   // A histogram recorded in UMA, showing reasons why daily metrics are
   // reported.
   static constexpr char kDailyEventIntervalName[] =
@@ -46,6 +51,10 @@
       "AutoScreenBrightness.DailyUserAdjustment.SupportedAls";
   static constexpr char kUnsupportedAlsUserAdjustmentName[] =
       "AutoScreenBrightness.DailyUserAdjustment.UnsupportedAls";
+  static constexpr char kAtlasUserAdjustmentName[] =
+      "AutoScreenBrightness.DailyUserAdjustment.Atlas";
+  static constexpr char kEveUserAdjustmentName[] =
+      "AutoScreenBrightness.DailyUserAdjustment.Eve";
 
   // Registers prefs used by MetricsReporter in |registry|.
   static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
@@ -82,7 +91,7 @@
   base::RepeatingTimer timer_;
 
   // Daily count for each UserAjustment. Ordered by UserAdjustment values.
-  std::array<int, 3> daily_counts_;
+  std::array<int, kNumberAdjustmentTypes> daily_counts_;
 
   DISALLOW_COPY_AND_ASSIGN(MetricsReporter);
 };
diff --git a/chrome/browser/chromeos/power/auto_screen_brightness/metrics_reporter_unittest.cc b/chrome/browser/chromeos/power/auto_screen_brightness/metrics_reporter_unittest.cc
index 462fe8d..6d59392 100644
--- a/chrome/browser/chromeos/power/auto_screen_brightness/metrics_reporter_unittest.cc
+++ b/chrome/browser/chromeos/power/auto_screen_brightness/metrics_reporter_unittest.cc
@@ -25,6 +25,8 @@
 constexpr auto kSupportedAls = MetricsReporter::UserAdjustment::kSupportedAls;
 constexpr auto kUnsupportedAls =
     MetricsReporter::UserAdjustment::kUnsupportedAls;
+constexpr auto kAtlas = MetricsReporter::UserAdjustment::kAtlas;
+constexpr auto kEve = MetricsReporter::UserAdjustment::kEve;
 
 }  // namespace
 
@@ -67,7 +69,9 @@
   // and verifies that it reports one sample with each of the passed values.
   void TriggerDailyEventAndVerifyHistograms(int no_als_count,
                                             int supported_als_count,
-                                            int unsupported_als_count) {
+                                            int unsupported_als_count,
+                                            int atlas_count,
+                                            int eve_count) {
     base::HistogramTester histogram_tester;
 
     TriggerDailyEvent(metrics::DailyEvent::IntervalType::DAY_ELAPSED);
@@ -79,6 +83,10 @@
     histogram_tester.ExpectUniqueSample(
         MetricsReporter::kUnsupportedAlsUserAdjustmentName,
         unsupported_als_count, 1);
+    histogram_tester.ExpectUniqueSample(
+        MetricsReporter::kAtlasUserAdjustmentName, atlas_count, 1);
+    histogram_tester.ExpectUniqueSample(MetricsReporter::kEveUserAdjustmentName,
+                                        eve_count, 1);
   }
 
   base::test::ScopedTaskEnvironment scoped_task_environment_;
@@ -93,27 +101,38 @@
   // Report the following user adjustments:
   // - 3 without ALS
   // - 2 with supported ALS
+  // - 2 with Atlas
+  // - 3 with Eve
   // No user adjustment on unsupported ALS is reported.
   SendOnUserBrightnessChangeRequested(kNoAls);
+  SendOnUserBrightnessChangeRequested(kAtlas);
+  SendOnUserBrightnessChangeRequested(kEve);
   SendOnUserBrightnessChangeRequested(kSupportedAls);
+  SendOnUserBrightnessChangeRequested(kEve);
   SendOnUserBrightnessChangeRequested(kNoAls);
+  SendOnUserBrightnessChangeRequested(kEve);
   SendOnUserBrightnessChangeRequested(kNoAls);
   SendOnUserBrightnessChangeRequested(kSupportedAls);
-  TriggerDailyEventAndVerifyHistograms(3, 2, 0);
+  SendOnUserBrightnessChangeRequested(kAtlas);
+  TriggerDailyEventAndVerifyHistograms(3, 2, 0, 2, 3);
 
   // The next day, the following user adjustments:
   // - 1 without ALS
   // - 1 with supported
   // - 3 with unsupported ALS
+  // - 1 with Atlas
+  // - 1 with Eve
   SendOnUserBrightnessChangeRequested(kUnsupportedAls);
+  SendOnUserBrightnessChangeRequested(kEve);
   SendOnUserBrightnessChangeRequested(kNoAls);
   SendOnUserBrightnessChangeRequested(kUnsupportedAls);
   SendOnUserBrightnessChangeRequested(kUnsupportedAls);
   SendOnUserBrightnessChangeRequested(kSupportedAls);
-  TriggerDailyEventAndVerifyHistograms(1, 1, 3);
+  SendOnUserBrightnessChangeRequested(kAtlas);
+  TriggerDailyEventAndVerifyHistograms(1, 1, 3, 1, 1);
 
   // The next day, no user adjustment is reported.
-  TriggerDailyEventAndVerifyHistograms(0, 0, 0);
+  TriggerDailyEventAndVerifyHistograms(0, 0, 0, 0, 0);
 }
 
 TEST_F(MetricsReporterTest, LoadInitialCountsFromPrefs) {
@@ -123,13 +142,17 @@
       prefs::kAutoScreenBrightnessMetricsNoAlsUserAdjustmentCount, 1);
   pref_service_.SetInteger(
       prefs::kAutoScreenBrightnessMetricsSupportedAlsUserAdjustmentCount, 2);
+  pref_service_.SetInteger(
+      prefs::kAutoScreenBrightnessMetricsAtlasUserAdjustmentCount, 2);
+  pref_service_.SetInteger(
+      prefs::kAutoScreenBrightnessMetricsEveUserAdjustmentCount, 4);
   ResetReporter();
-  TriggerDailyEventAndVerifyHistograms(1, 2, 0);
+  TriggerDailyEventAndVerifyHistograms(1, 2, 0, 2, 4);
 
   // The previous report should've cleared the prefs, so a new reporter should
   // start out at zero.
   ResetReporter();
-  TriggerDailyEventAndVerifyHistograms(0, 0, 0);
+  TriggerDailyEventAndVerifyHistograms(0, 0, 0, 0, 0);
 }
 
 TEST_F(MetricsReporterTest, IgnoreDailyEventFirstRun) {
@@ -141,6 +164,8 @@
   tester.ExpectTotalCount(MetricsReporter::kSupportedAlsUserAdjustmentName, 0);
   tester.ExpectTotalCount(MetricsReporter::kUnsupportedAlsUserAdjustmentName,
                           0);
+  tester.ExpectTotalCount(MetricsReporter::kAtlasUserAdjustmentName, 0);
+  tester.ExpectTotalCount(MetricsReporter::kEveUserAdjustmentName, 0);
 }
 
 TEST_F(MetricsReporterTest, IgnoreDailyEventClockChanged) {
@@ -154,10 +179,12 @@
   tester.ExpectTotalCount(MetricsReporter::kSupportedAlsUserAdjustmentName, 0);
   tester.ExpectTotalCount(MetricsReporter::kUnsupportedAlsUserAdjustmentName,
                           0);
+  tester.ExpectTotalCount(MetricsReporter::kAtlasUserAdjustmentName, 0);
+  tester.ExpectTotalCount(MetricsReporter::kEveUserAdjustmentName, 0);
 
   // The existing stats should be cleared when the clock change notification is
   // received, so the next report should only contain zeros.
-  TriggerDailyEventAndVerifyHistograms(0, 0, 0);
+  TriggerDailyEventAndVerifyHistograms(0, 0, 0, 0, 0);
 }
 
 }  // namespace auto_screen_brightness
diff --git a/chrome/browser/chromeos/preferences.cc b/chrome/browser/chromeos/preferences.cc
index 330fbee0..f391461 100644
--- a/chrome/browser/chromeos/preferences.cc
+++ b/chrome/browser/chromeos/preferences.cc
@@ -83,7 +83,6 @@
     prefs::kLanguageRemapCapsLockKeyTo,
     prefs::kLanguageRemapEscapeKeyTo,
     prefs::kLanguageRemapBackspaceKeyTo,
-    prefs::kLanguageRemapDiamondKeyTo,
     prefs::kLanguageRemapExternalCommandKeyTo,
     prefs::kLanguageRemapExternalMetaKeyTo};
 
@@ -387,10 +386,6 @@
       prefs::kLanguageRemapBackspaceKeyTo,
       static_cast<int>(ui::chromeos::ModifierKey::kBackspaceKey),
       user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF);
-  registry->RegisterIntegerPref(
-      prefs::kLanguageRemapDiamondKeyTo,
-      static_cast<int>(ui::chromeos::ModifierKey::kControlKey),
-      user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF);
   // The Command key on external Apple keyboards is remapped by default to Ctrl
   // until the user changes it from the keyboard settings.
   registry->RegisterIntegerPref(
diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chrome/browser/extensions/api/settings_private/prefs_util.cc
index 74cdc63..3f396fc 100644
--- a/chrome/browser/extensions/api/settings_private/prefs_util.cc
+++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc
@@ -501,8 +501,6 @@
       settings_api::PrefType::PREF_TYPE_NUMBER;
   (*s_whitelist)[::prefs::kLanguageRemapEscapeKeyTo] =
       settings_api::PrefType::PREF_TYPE_NUMBER;
-  (*s_whitelist)[::prefs::kLanguageRemapDiamondKeyTo] =
-      settings_api::PrefType::PREF_TYPE_NUMBER;
   (*s_whitelist)[::prefs::kLanguageRemapExternalCommandKeyTo] =
       settings_api::PrefType::PREF_TYPE_NUMBER;
   (*s_whitelist)[::prefs::kLanguageRemapExternalMetaKeyTo] =
diff --git a/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc b/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc
index 2f9a2e9..6ebdee2 100644
--- a/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc
+++ b/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc
@@ -113,7 +113,7 @@
     ExtensionWebRequestEventRouter::EventResponse* response) {
   ExtensionWebRequestEventRouter::GetInstance()->OnEventHandled(
       profile, extension_id, event_name, sub_event_name, request_id,
-      response);
+      0 /* embedder_process_id */, 0 /* web_view_instance_id */, response);
 }
 
 // Returns whether |warnings| contains an extension for |extension_id|.
@@ -1081,7 +1081,8 @@
   response->cancel = true;
   ExtensionWebRequestEventRouter::GetInstance()->OnEventHandled(
       &profile_, extension_id, kEventName, kEventName + "/1",
-      request->identifier(), response);
+      request->identifier(), 0 /* embedder_process_id */,
+      0 /* web_view_instance_id */, response);
   {
     base::RunLoop run_loop;
     run_loop.RunUntilIdle();
diff --git a/chrome/browser/extensions/chrome_component_extension_resource_manager.cc b/chrome/browser/extensions/chrome_component_extension_resource_manager.cc
index e9a5ef1..aaae659 100644
--- a/chrome/browser/extensions/chrome_component_extension_resource_manager.cc
+++ b/chrome/browser/extensions/chrome_component_extension_resource_manager.cc
@@ -42,8 +42,6 @@
     {"chrome_app/chrome_app_icon_32.png", IDR_CHROME_APP_ICON_32},
     {"chrome_app/chrome_app_icon_192.png", IDR_CHROME_APP_ICON_192},
     {"pdf/ink/ink_lib_binary.js", IDR_INK_LIB_BINARY_JS},
-    {"pdf/ink/pthread-main.js", IDR_INK_PTHREAD_MAIN_JS},
-    {"pdf/ink/glcore_base.js.mem", IDR_INK_GLCORE_BASE_JS_MEM, true},
     {"pdf/ink/glcore_base.wasm", IDR_INK_GLCORE_BASE_WASM, true},
     {"pdf/ink/glcore_wasm_bootstrap_compiled.js",
      IDR_INK_GLCORE_WASM_BOOTSTRAP_COMPILED_JS},
diff --git a/chrome/browser/extensions/chrome_url_request_util.cc b/chrome/browser/extensions/chrome_url_request_util.cc
index 51e835b..659f17f 100644
--- a/chrome/browser/extensions/chrome_url_request_util.cc
+++ b/chrome/browser/extensions/chrome_url_request_util.cc
@@ -239,6 +239,10 @@
   void OnMimeTypeRead(scoped_refptr<base::RefCountedMemory> data,
                       std::string* read_mime_type,
                       bool read_result) {
+    if (!read_result) {
+      client_->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
+      return;
+    }
     network::ResourceResponseHead head;
     head.request_start = base::TimeTicks::Now();
     head.response_start = base::TimeTicks::Now();
diff --git a/chrome/browser/extensions/component_extensions_whitelist/whitelist.cc b/chrome/browser/extensions/component_extensions_whitelist/whitelist.cc
index 46aa36bb..1f71194 100644
--- a/chrome/browser/extensions/component_extensions_whitelist/whitelist.cc
+++ b/chrome/browser/extensions/component_extensions_whitelist/whitelist.cc
@@ -96,9 +96,9 @@
     case IDR_VIDEO_PLAYER_MANIFEST:
     case IDR_WALLPAPERMANAGER_MANIFEST:
 #if defined(GOOGLE_CHROME_BUILD)
-    case IDR_CONTAINED_HOME_MANIFEST:
     case IDR_GENIUS_APP_MANIFEST:
     case IDR_HELP_MANIFEST:
+    case IDR_KIOSK_NEXT_HOME_MANIFEST:
     case IDR_QUICKOFFICE_MANIFEST:
 #endif  // defined(GOOGLE_CHROME_BUILD)
 #endif  // defined(OS_CHROMEOS)
diff --git a/chrome/browser/extensions/component_loader.cc b/chrome/browser/extensions/component_loader.cc
index 52511c2..1070508f 100644
--- a/chrome/browser/extensions/component_loader.cc
+++ b/chrome/browser/extensions/component_loader.cc
@@ -555,8 +555,8 @@
     }
 
     if (base::FeatureList::IsEnabled(ash::features::kKioskNextShell)) {
-      Add(IDR_CONTAINED_HOME_MANIFEST,
-          base::FilePath(FILE_PATH_LITERAL("chromeos/contained_home")));
+      Add(IDR_KIOSK_NEXT_HOME_MANIFEST,
+          base::FilePath(FILE_PATH_LITERAL("chromeos/kiosk_next_home")));
     }
 #endif  // defined(GOOGLE_CHROME_BUILD)
 
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 54e74de..a3a9f40 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -740,6 +740,11 @@
     "expiry_milestone": 76
   },
   {
+    "name": "enable-app-grid-ghost",
+    "owners": [ "mmourgos" ],
+    "expiry_milestone": 76
+  },
+  {
     "name": "enable-app-list-search-autocomplete",
     "owners": [ "newcomer" ],
     "expiry_milestone": 75
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index ca43bd3..d1f02ab8 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3109,6 +3109,10 @@
     "If Enabled, double tapping in webpages while in tablet mode will zoom the "
     "page.";
 
+const char kEnableAppGridGhostName[] = "App Grid Ghosting";
+const char kEnableAppGridGhostDescription[] =
+    "Enables ghosting during an item drag in launcher.";
+
 const char kEnableAppListSearchAutocompleteName[] =
     "App List Search Autocomplete";
 const char kEnableAppListSearchAutocompleteDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index f56922b..bf75c77 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1856,6 +1856,9 @@
 extern const char kDoubleTapToZoomInTabletModeName[];
 extern const char kDoubleTapToZoomInTabletModeDescription[];
 
+extern const char kEnableAppGridGhostName[];
+extern const char kEnableAppGridGhostDescription[];
+
 extern const char kEnableAppListSearchAutocompleteName[];
 extern const char kEnableAppListSearchAutocompleteDescription[];
 
diff --git a/chrome/browser/lookalikes/lookalike_url_navigation_throttle_browsertest.cc b/chrome/browser/lookalikes/lookalike_url_navigation_throttle_browsertest.cc
index 512e8af..7b6cf39 100644
--- a/chrome/browser/lookalikes/lookalike_url_navigation_throttle_browsertest.cc
+++ b/chrome/browser/lookalikes/lookalike_url_navigation_throttle_browsertest.cc
@@ -134,7 +134,7 @@
 }
 
 // Load given URL and verify that it loaded an interstitial and hid the URL.
-void LoadInterstitialAt(Browser* browser, const GURL& url) {
+void LoadAndCheckInterstitialAt(Browser* browser, const GURL& url) {
   content::WebContents* web_contents =
       browser->tab_strip_model()->GetActiveWebContents();
 
@@ -275,7 +275,7 @@
             browser->profile(), ServiceAccessType::EXPLICIT_ACCESS);
     ui_test_utils::WaitForHistoryToLoad(history_service);
 
-    LoadInterstitialAt(browser, navigated_url);
+    LoadAndCheckInterstitialAt(browser, navigated_url);
     SendInterstitialCommandSync(browser,
                                 SecurityInterstitialCommand::CMD_DONT_PROCEED);
     EXPECT_EQ(expected_suggested_url,
@@ -326,7 +326,7 @@
             browser->profile(), ServiceAccessType::EXPLICIT_ACCESS);
     ui_test_utils::WaitForHistoryToLoad(history_service);
 
-    LoadInterstitialAt(browser, navigated_url);
+    LoadAndCheckInterstitialAt(browser, navigated_url);
 
     // Clicking the ignore button in the interstitial should remove the
     // interstitial and navigate to the original URL.
@@ -691,11 +691,11 @@
     const GURL kNavigatedUrl =
         GetLongRedirect("goooglé.com", "example.net", "example.com");
     SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
-    LoadInterstitialAt(browser(), kNavigatedUrl);
+    LoadAndCheckInterstitialAt(browser(), kNavigatedUrl);
   }
 
-  // LoadInterstitialAt assumes there's not an interstitial already showing
-  // (since otherwise it can't be sure that the navigation caused it).
+  // LoadAndCheckInterstitialAt assumes there's not an interstitial already
+  // showing (since otherwise it can't be sure that the navigation caused it).
   NavigateToURLSync(browser(), GetURL("example.com"));
 
   {
@@ -703,7 +703,7 @@
     const GURL kNavigatedUrl =
         GetLongRedirect("example.net", "goooglé.com", "example.com");
     SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
-    LoadInterstitialAt(browser(), kNavigatedUrl);
+    LoadAndCheckInterstitialAt(browser(), kNavigatedUrl);
   }
 }
 
@@ -727,7 +727,7 @@
   const GURL navigated_url = GetURL("goooglé.com");
   const GURL subsequent_url = GetURL("example.com");
 
-  LoadInterstitialAt(browser(), navigated_url);
+  LoadAndCheckInterstitialAt(browser(), navigated_url);
   NavigateToURLSync(browser(), subsequent_url);
   CheckUkm({navigated_url}, "UserAction", UserAction::kCloseOrBack);
 }
@@ -738,7 +738,7 @@
                        UkmRecordedAfterSuggestionAccepted) {
   const GURL navigated_url = GetURL("goooglé.com");
 
-  LoadInterstitialAt(browser(), navigated_url);
+  LoadAndCheckInterstitialAt(browser(), navigated_url);
   SendInterstitialCommandSync(browser(),
                               SecurityInterstitialCommand::CMD_DONT_PROCEED);
   CheckUkm({navigated_url}, "UserAction", UserAction::kAcceptSuggestion);
@@ -750,7 +750,7 @@
                        UkmRecordedAfterSuggestionIgnored) {
   const GURL navigated_url = GetURL("goooglé.com");
 
-  LoadInterstitialAt(browser(), navigated_url);
+  LoadAndCheckInterstitialAt(browser(), navigated_url);
   SendInterstitialCommandSync(browser(),
                               SecurityInterstitialCommand::CMD_PROCEED);
   CheckUkm({navigated_url}, "UserAction", UserAction::kClickThrough);
@@ -759,7 +759,7 @@
 // Verify that the URL shows normally on pages after a lookalike interstitial.
 IN_PROC_BROWSER_TEST_F(LookalikeUrlInterstitialPageBrowserTest,
                        UrlShownAfterInterstitial) {
-  LoadInterstitialAt(browser(), GetURL("goooglé.com"));
+  LoadAndCheckInterstitialAt(browser(), GetURL("goooglé.com"));
 
   // URL should be showing again when we navigate to a normal URL
   NavigateToURLSync(browser(), GetURL("example.com"));
@@ -769,3 +769,39 @@
   NavigateToURLSync(browser(), GURL("chrome://newtab"));
   EXPECT_FALSE(IsUrlShowing(browser()));
 }
+
+// Verify that bypassing warnings in the main profile does not affect incognito.
+IN_PROC_BROWSER_TEST_F(LookalikeUrlInterstitialPageBrowserTest,
+                       MainProfileDoesNotAffectIncognito) {
+  const GURL kNavigatedUrl = GetURL("googlé.com");
+
+  // Set low engagement scores in the main profile and in incognito.
+  Browser* incognito = CreateIncognitoBrowser();
+  SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
+  SetEngagementScore(incognito, kNavigatedUrl, kLowEngagement);
+
+  LoadAndCheckInterstitialAt(browser(), kNavigatedUrl);
+  // PROCEEDing will disable the interstitial on subsequent navigations
+  SendInterstitialCommandSync(browser(),
+                              SecurityInterstitialCommand::CMD_PROCEED);
+
+  LoadAndCheckInterstitialAt(incognito, kNavigatedUrl);
+}
+
+// Verify that bypassing warnings in incognito does not affect the main profile.
+IN_PROC_BROWSER_TEST_F(LookalikeUrlInterstitialPageBrowserTest,
+                       IncognitoDoesNotAffectMainProfile) {
+  const GURL kNavigatedUrl = GetURL("googlé.com");
+
+  // Set low engagement scores in the main profile and in incognito.
+  Browser* incognito = CreateIncognitoBrowser();
+  SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
+  SetEngagementScore(incognito, kNavigatedUrl, kLowEngagement);
+
+  LoadAndCheckInterstitialAt(incognito, kNavigatedUrl);
+  // PROCEEDing will disable the interstitial on subsequent navigations
+  SendInterstitialCommandSync(incognito,
+                              SecurityInterstitialCommand::CMD_PROCEED);
+
+  LoadAndCheckInterstitialAt(browser(), kNavigatedUrl);
+}
diff --git a/chrome/browser/metrics/perf/profile_provider_chromeos_unittest.cc b/chrome/browser/metrics/perf/profile_provider_chromeos_unittest.cc
index 4f14a08f..683548f1 100644
--- a/chrome/browser/metrics/perf/profile_provider_chromeos_unittest.cc
+++ b/chrome/browser/metrics/perf/profile_provider_chromeos_unittest.cc
@@ -140,6 +140,7 @@
   void TearDown() override {
     profile_provider_.reset();
     chromeos::PowerManagerClient::Shutdown();
+    chromeos::LoginState::Shutdown();
   }
 
  protected:
diff --git a/chrome/browser/resources/chromeos/BUILD.gn b/chrome/browser/resources/chromeos/BUILD.gn
index e8d171e8..8f349ca 100644
--- a/chrome/browser/resources/chromeos/BUILD.gn
+++ b/chrome/browser/resources/chromeos/BUILD.gn
@@ -25,9 +25,9 @@
   deps = [
     "bluetooth_pairing_dialog:closure_compile",
     "braille_ime:closure_compile",
-    "contained_home:closure_compile",
     "internet_config_dialog:closure_compile",
     "internet_detail_dialog:closure_compile",
+    "kiosk_next_home:closure_compile",
     "login:closure_compile",
     "multidevice_setup:closure_compile",
     "network_ui:closure_compile",
diff --git a/chrome/browser/resources/chromeos/camera/src/css/main.css b/chrome/browser/resources/chromeos/camera/src/css/main.css
index 38217d7..e0c75d2 100644
--- a/chrome/browser/resources/chromeos/camera/src/css/main.css
+++ b/chrome/browser/resources/chromeos/camera/src/css/main.css
@@ -240,42 +240,54 @@
   margin: 8px 0;
 }
 
-#shutter {
-  background-image: url(../images/camera_shutter_photo_start.svg);
+button.shutter {
+  display: none;
   height: 72px;
   width: 72px;
   z-index: 1;  /* On top of transforming switch-mode buttons. */
 }
 
-#shutter:hover {
+body.record-mode:not(.taking) #start-recordvideo,
+body.record-mode.taking #stop-recordvideo,
+body:not(.taking):not(.record-mode) #start-takephoto,
+body:not(.timer):not(.record-mode) #start-takephoto,
+body.taking.timer:not(.record-mode) #stop-takephoto {
+  display: inline-block;
+}
+
+#start-takephoto {
+  background-image: url(../images/camera_shutter_photo_start.svg);
+}
+
+#start-takephoto:hover {
   background-image: url(../images/camera_shutter_photo_start_hover.svg);
 }
 
-#shutter:active {
+#start-takephoto:active {
   background-image: url(../images/camera_shutter_photo_start_active.svg);
 }
 
-body.taking.timer #shutter {
+#stop-takephoto {
   background-image: url(../images/camera_shutter_photo_stop.svg);
 }
 
-body.taking.timer #shutter:hover {
+#stop-takephoto:hover {
   background-image: url(../images/camera_shutter_photo_stop_hover.svg);
 }
 
-body.record-mode #shutter {
+#start-recordvideo {
   background-image: url(../images/camera_shutter_recording_start.svg);
 }
 
-body.record-mode #shutter:hover {
+#start-recordvideo:hover {
   background-image: url(../images/camera_shutter_recording_start_hover.svg);
 }
 
-body.record-mode.taking #shutter {
+#stop-recordvideo {
   background-image: url(../images/camera_shutter_recording_stop.svg);
 }
 
-body.record-mode.taking #shutter:hover {
+#stop-recordvideo:hover {
   background-image: url(../images/camera_shutter_recording_stop_hover.svg);
 }
 
diff --git a/chrome/browser/resources/chromeos/camera/src/js/sound.js b/chrome/browser/resources/chromeos/camera/src/js/sound.js
index 21f7084..bcf9ffd0 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/sound.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/sound.js
@@ -30,7 +30,7 @@
         setTimeout(resolve, parseInt(element.dataset.timeout || 0), 10);
     cancel = () => {
       clearTimeout(timeout);
-      reject();
+      reject(new Error('cancel'));
     };
     element.currentTime = 0;
     element.play();
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/camera.js b/chrome/browser/resources/chromeos/camera/src/js/views/camera.js
index d3a9778..4ab14e4 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/views/camera.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/views/camera.js
@@ -79,13 +79,6 @@
   this.recordTime_ = new cca.views.camera.RecordTime();
 
   /**
-   * Button for taking photos and recording videos.
-   * @type {HTMLButtonElement}
-   * @private
-   */
-  this.shutterButton_ = document.querySelector('#shutter');
-
-  /**
    * @type {string}
    * @private
    */
@@ -119,7 +112,7 @@
 
   /**
    * Promise for the current take of photo or recording.
-   * @type {Promise<Blob>}
+   * @type {?Promise}
    * @private
    */
   this.take_ = null;
@@ -127,8 +120,11 @@
   // End of properties, seal the object.
   Object.seal(this);
 
-  this.shutterButton_.addEventListener('click',
-      () => this.onShutterButtonClicked_());
+  document.querySelectorAll('#start-takephoto, #start-recordvideo')
+      .forEach((btn) => btn.addEventListener('click', () => this.beginTake_()));
+
+  document.querySelectorAll('#stop-takephoto, #stop-recordvideo')
+      .forEach((btn) => btn.addEventListener('click', () => this.endTake_()));
 
   // Monitor the states to stop camera when locked/minimized.
   chrome.idle.onStateChanged.addListener((newState) => {
@@ -160,52 +156,85 @@
  * @override
  */
 cca.views.Camera.prototype.focus = function() {
-  this.shutterButton_.focus();
+  // Avoid focusing invisible shutters.
+  document.querySelectorAll('.shutter')
+      .forEach((btn) => btn.offsetParent && btn.focus());
 };
 
-/**
- * Handles clicking on the shutter button.
- * @param {Event} event Mouse event
- * @private
- */
-cca.views.Camera.prototype.onShutterButtonClicked_ = function(event) {
-  if (!cca.state.get('streaming')) {
-    return;
-  }
-  if (cca.state.get('taking')) {
-    // End the prior ongoing take if any; a new take shouldn't be started
-    // until the prior one is ended.
-    this.endTake_();
-    return;
-  }
-  try {
-    if (this.recordMode) {
-      this.prepareMediaRecorder_();
-    } else {
-      this.prepareImageCapture_();
-    }
-    this.beginTake_();
-  } catch (e) {
-    console.error(e);
-    cca.toast.show(this.recordMode ?
-        'error_msg_record_start_failed' : 'error_msg_take_photo_failed');
-  }
-};
 
 /**
- * Updates the shutter button's label for taking/record-mode state changes.
+ * Begins to take photo or recording with the current options, e.g. timer.
  * @private
  */
-cca.views.Camera.prototype.updateShutterLabel_ = function() {
-  var label;
-  if (this.recordMode) {
-    label = cca.state.get('taking') ?
-        'record_video_stop_button' : 'record_video_start_button';
-  } else {
-    label = (cca.state.get('taking') && cca.state.get('timer')) ?
-        'take_photo_cancel_button' : 'take_photo_button';
+cca.views.Camera.prototype.beginTake_ = function() {
+  if (!cca.state.get('streaming') || cca.state.get('taking')) {
+    return;
   }
-  this.shutterButton_.setAttribute('aria-label', chrome.i18n.getMessage(label));
+
+  cca.state.set('taking', true);
+  this.focus();  // Refocus the visible shutter button for ChromeVox.
+  this.take_ =
+      cca.views.camera.timertick.start()
+          .then(() => {
+            // Play a sound before starting to record and delay the take to
+            // avoid the sound being recorded if necessary.
+            this.deferred_capture_ =
+                this.recordMode ? cca.sound.play('#sound-rec-start') : null;
+            return this.deferred_capture_ &&
+                this.deferred_capture_.finally(
+                    () => this.deferred_capture_ = null);
+          })
+          .then(() => {
+            if (this.recordMode) {
+              try {
+                this.prepareMediaRecorder_();
+              } catch (e) {
+                cca.toast.show('error_msg_record_start_failed');
+                throw e;
+              }
+              // Take of recording will be ended by another shutter click.
+              return this.createRecordingBlob_().catch((e) => {
+                cca.toast.show('error_msg_empty_recording');
+                throw e;
+              });
+            } else {
+              try {
+                this.prepareImageCapture_();
+              } catch (e) {
+                cca.toast.show('error_msg_take_photo_failed');
+                throw e;
+              }
+              return this.createPhotoBlob_().catch((e) => {
+                cca.toast.show('error_msg_take_photo_failed');
+                throw e;
+              });
+            }
+          })
+          .then((blob) => {
+            if (blob) {
+              // Play a sound and save the result after a successful take.
+              cca.metrics.log(
+                  cca.metrics.Type.CAPTURE, this.facingMode_, blob.mins);
+              var recordMode = this.recordMode;
+              cca.sound.play(recordMode ? '#sound-rec-end' : '#sound-shutter');
+              return this.model_.savePicture(blob, recordMode)
+                  .catch((e) => {
+                    cca.toast.show('error_msg_save_file_failed');
+                    throw e;
+                  });
+            }
+          })
+          .catch((e) => {
+            if (e && e.message == 'cancel') {
+              return;
+            }
+            console.error(e);
+          })
+          .finally(() => {
+            this.take_ = null;
+            cca.state.set('taking', false);
+            this.focus();  // Refocus the visible shutter button for ChromeVox.
+          });
 };
 
 /**
@@ -227,38 +256,6 @@
 };
 
 /**
- * Begins to take photo or recording with the current options, e.g. timer.
- * @private
- */
-cca.views.Camera.prototype.beginTake_ = function() {
-  cca.state.set('taking', true);
-  this.updateShutterLabel_();
-
-  cca.views.camera.timertick.start().then(() => {
-    // Play a sound before starting to record and delay the take to avoid the
-    // sound being recorded if necessary.
-    this.deferred_capture_ =
-        this.recordMode ? cca.sound.play('#sound-rec-start') : null;
-    return this.deferred_capture_ &&
-        this.deferred_capture_.finally(() => this.deferred_capture_ = null);
-  }).then(() => {
-    if (this.recordMode) {
-      // Take of recording will be ended by another shutter click.
-      this.take_ = this.createRecordingBlob_().catch((error) => {
-        cca.toast.show('error_msg_empty_recording');
-        throw error;
-      });
-    } else {
-      this.take_ = this.createPhotoBlob_().catch((error) => {
-        cca.toast.show('error_msg_take_photo_failed');
-        throw error;
-      });
-      this.endTake_();
-    }
-  }).catch(() => {});
-};
-
-/**
  * Ends the current take (or clears scheduled further takes if any.)
  * @return {!Promise} Promise for the operation.
  * @private
@@ -272,24 +269,7 @@
     this.mediaRecorder_.stop();
   }
 
-  return Promise.resolve(this.take_).then((blob) => {
-    if (blob && !blob.handled) {
-      // Play a sound and save the result after a successful take.
-      cca.metrics.log(cca.metrics.Type.CAPTURE, this.facingMode_, blob.mins);
-      blob.handled = true;
-      var recordMode = this.recordMode;
-      cca.sound.play(recordMode ? '#sound-rec-end' : '#sound-shutter');
-      return this.model_.savePicture(blob, recordMode).catch((error) => {
-        cca.toast.show('error_msg_save_file_failed');
-        throw error;
-      });
-    }
-  }).catch(console.error).finally(() => {
-    // Re-enable UI controls after finishing the take.
-    this.take_ = null;
-    cca.state.set('taking', false);
-    this.updateShutterLabel_();
-  });
+  return Promise.resolve(this.take_);
 };
 
 /**
@@ -438,8 +418,6 @@
  * @private
  */
 cca.views.Camera.prototype.stop_ = function() {
-  // Update shutter label as record-mode might be toggled before reaching here.
-  this.updateShutterLabel_();
   // Wait for ongoing 'start' and 'take' done before restarting camera.
   return Promise.all([
     this.started_,
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/camera/timertick.js b/chrome/browser/resources/chromeos/camera/src/js/views/camera/timertick.js
index 74db9a7..f359139 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/views/camera/timertick.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/views/camera/timertick.js
@@ -48,7 +48,7 @@
         tickTimeout = null;
       }
       cca.util.animateCancel(tickMsg);
-      reject();
+      reject(new Error('cancel'));
     };
 
     var tickCounter = cca.state.get('_10sec') ? 10 : 3;
diff --git a/chrome/browser/resources/chromeos/camera/src/views/main.html b/chrome/browser/resources/chromeos/camera/src/views/main.html
index 5bc98164..a78f44d 100644
--- a/chrome/browser/resources/chromeos/camera/src/views/main.html
+++ b/chrome/browser/resources/chromeos/camera/src/views/main.html
@@ -52,8 +52,14 @@
       <div class="actions-group buttons circle">
         <button id="switch-takephoto" tabindex="0"
                 i18n-label="switch_take_photo_button"></button>
-        <button id="shutter" tabindex="0"
+        <button id="start-recordvideo" class="shutter" tabindex="0"
+                i18n-label="record_video_start_button"></button>
+        <button id="stop-recordvideo" class="shutter" tabindex="0"
+                i18n-label="record_video_stop_button"></button>
+        <button id="start-takephoto" class="shutter" tabindex="0"
                 i18n-label="take_photo_button"></button>
+        <button id="stop-takephoto" class="shutter" tabindex="0"
+                i18n-label="take_photo_cancel_button"></button>
         <button id="switch-recordvideo" tabindex="0"
                 i18n-label="switch_record_video_button"></button>
       </div>
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js
index d87ed43..35857cd 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/background.js
@@ -187,7 +187,11 @@
     cvox.ChromeVox.braille.thaw();
 
     if (newRange && !newRange.isValid()) {
-      chrome.accessibilityPrivate.setFocusRing([]);
+      chrome.accessibilityPrivate.setFocusRings([{
+        rects: [],
+        type: chrome.accessibilityPrivate.FocusType.GLOW,
+        color: constants.FOCUS_COLOR
+      }]);
       return;
     }
 
@@ -197,7 +201,11 @@
     });
 
     if (!this.currentRange_) {
-      chrome.accessibilityPrivate.setFocusRing([]);
+      chrome.accessibilityPrivate.setFocusRings([{
+        rects: [],
+        type: chrome.accessibilityPrivate.FocusType.GLOW,
+        color: constants.FOCUS_COLOR
+      }]);
       return;
     }
 
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/constants.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/constants.js
index 7fea274b..7298da4d 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/constants.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/constants.js
@@ -43,3 +43,10 @@
  * @const
  */
 constants.SYSTEM_VOICE = 'chromeos_system_voice';
+
+/**
+ * Color for the ChromeVox focus ring.
+ * @type {string}
+ * @const
+ */
+constants.FOCUS_COLOR = '#F7983A';
diff --git a/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js b/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
index c1101e85..f7f79fa 100644
--- a/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
+++ b/chrome/browser/resources/chromeos/chromevox/cvox2/background/output.js
@@ -1092,7 +1092,11 @@
 
     // Display.
     if (this.speechCategory_ != cvox.TtsCategory.LIVE)
-      chrome.accessibilityPrivate.setFocusRing(this.locations_);
+      chrome.accessibilityPrivate.setFocusRings([{
+        rects: this.locations_,
+        type: chrome.accessibilityPrivate.FocusType.GLOW,
+        color: constants.FOCUS_COLOR
+      }]);
   },
 
   /**
diff --git a/chrome/browser/resources/chromeos/contained_home/contained_home_resources.grdp b/chrome/browser/resources/chromeos/contained_home/contained_home_resources.grdp
deleted file mode 100644
index 433244f..0000000
--- a/chrome/browser/resources/chromeos/contained_home/contained_home_resources.grdp
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<grit-part>
-  <include name="IDR_CONTAINED_HOME_BG_JS" file="chromeos/contained_home/bg.js" type="BINDATA" />
-  <include name="IDR_CONTAINED_HOME_ICON_192" file="chromeos/contained_home/static/icon192.png" type="BINDATA" />
-  <include name="IDR_CONTAINED_HOME_MAIN_HTML" file="chromeos/contained_home/main.html" type="chrome_html" />
-  <include name="IDR_CONTAINED_HOME_API_JS" file="chromeos/contained_home/api.js" type="BINDATA" />
-  <include name="IDR_CONTAINED_HOME_API_IMPL_JS" file="chromeos/contained_home/api_impl.js" type="BINDATA" />
-</grit-part>
diff --git a/chrome/browser/resources/chromeos/input_method/xkb_manifest.json b/chrome/browser/resources/chromeos/input_method/xkb_manifest.json
index 7f3285f7..0bc467a 100644
--- a/chrome/browser/resources/chromeos/input_method/xkb_manifest.json
+++ b/chrome/browser/resources/chromeos/input_method/xkb_manifest.json
@@ -214,7 +214,7 @@
         "fr-FR"
       ],
       "layouts": [
-        "fr"
+        "fr(oss)"
       ],
       "input_view": "inputview.html?id=fr.compact.qwerty&language=fr&passwordLayout=fr.compact.qwerty&name=keyboard_french"
     },
diff --git a/chrome/browser/resources/chromeos/contained_home/BUILD.gn b/chrome/browser/resources/chromeos/kiosk_next_home/BUILD.gn
similarity index 87%
rename from chrome/browser/resources/chromeos/contained_home/BUILD.gn
rename to chrome/browser/resources/chromeos/kiosk_next_home/BUILD.gn
index 88a33a4..6d2aa6f 100644
--- a/chrome/browser/resources/chromeos/contained_home/BUILD.gn
+++ b/chrome/browser/resources/chromeos/kiosk_next_home/BUILD.gn
@@ -6,11 +6,11 @@
 
 js_type_check("closure_compile") {
   deps = [
-    ":contained_home_api",
+    ":kiosk_next_api",
   ]
 }
 
-js_library("contained_home_api") {
+js_library("kiosk_next_api") {
   sources = [
     "api.js",
     "api_impl.js",
diff --git a/chrome/browser/resources/chromeos/contained_home/api.js b/chrome/browser/resources/chromeos/kiosk_next_home/api.js
similarity index 72%
rename from chrome/browser/resources/chromeos/contained_home/api.js
rename to chrome/browser/resources/chromeos/kiosk_next_home/api.js
index 5ebe641..7305d246 100644
--- a/chrome/browser/resources/chromeos/contained_home/api.js
+++ b/chrome/browser/resources/chromeos/kiosk_next_home/api.js
@@ -3,24 +3,24 @@
 // found in the LICENSE file.
 
 /**
- * @fileoverview Chrome OS Contained Home API definition.
+ * @fileoverview Chrome OS Kiosk Next Home API definition.
  */
 
 /**
- * Namespace for the contained home bridge and related data.
+ * Namespace for the Kiosk Next Home bridge and related data.
  * @const
  */
-var containedHome = {};
+var kioskNextHome = {};
 
 /**
- * System bridge API for the contained home experience.
+ * System bridge API for the Kiosk Next Home.
  *
  * @interface
  */
-containedHome.Bridge = class {
+kioskNextHome.Bridge = class {
   /**
    * Adds listener for system events.
-   * @param {!containedHome.Listener} listener Listener for system events.
+   * @param {!kioskNextHome.Listener} listener Listener for system events.
    */
   addListener(listener) {}
 
@@ -34,14 +34,14 @@
 
   /**
    * Returns a list of apps installed in the user session.
-   * @return {!Promise<!Array<!containedHome.InstalledApp>>} Promise for the
+   * @return {!Promise<!Array<!kioskNextHome.InstalledApp>>} Promise for the
    *     list of apps.
    */
   getInstalledApps() {}
 
   /**
    * Launches a content (app, video, etc).
-   * @param {!containedHome.ContentSource} contentSource
+   * @param {!kioskNextHome.ContentSource} contentSource
    * @param {string} contentId
    * @param {?Object=} opt_params Optional params to locate the content.
    * @return {!Promise<boolean>} Promise that is resolved after the content is
@@ -56,7 +56,7 @@
  * A "Content Source" describes how to launch/view the content.
  * @enum {string}
  */
-containedHome.ContentSource = {
+kioskNextHome.ContentSource = {
   /** The content is, or is hosted inside, an ARC++ app. */
   ARC_INTENT: 'arc_intent',
 };
@@ -65,7 +65,7 @@
  * Types of installed apps on ChromeOS.
  * @enum {string}
  */
-containedHome.AppType = {
+kioskNextHome.AppType = {
   /** The app is an ARC++ app (Android app). */
   ARC: 'arc',
 };
@@ -74,9 +74,9 @@
  * A record representing an installed app on the system.
  * @record
  */
-containedHome.InstalledApp = class {
+kioskNextHome.InstalledApp = class {
   constructor() {
-    /** @type {!containedHome.AppType} The type of app. */
+    /** @type {!kioskNextHome.AppType} The type of app. */
     this.appType;
     /**
      * @type {string} Stable, unique identifier for the app. For ARC++ apps,
@@ -96,22 +96,22 @@
  * Different ways an installed app can change.
  * @enum {string}
  */
-containedHome.AppEventType = {
+kioskNextHome.AppEventType = {
   INSTALLED: 'installed',
   UNINSTALLED: 'uninstalled',
 };
 
 /**
  * Interface for a listener of system events, subscribed via
- * {!containedHome.Bridge}.
+ * {!kioskNextHome.Bridge}.
  *
  * @interface
  */
-containedHome.Listener = class {
+kioskNextHome.Listener = class {
   /**
    * Called when an app state change.
-   * @param {!containedHome.InstalledApp} app The app whose state changed.
-   * @param {!containedHome.AppEventType} appEventType Type of the event
+   * @param {!kioskNextHome.InstalledApp} app The app whose state changed.
+   * @param {!kioskNextHome.AppEventType} appEventType Type of the event
    *     indicating what changed for the app.
    */
   onInstalledAppChanged(app, appEventType) {}
@@ -119,7 +119,7 @@
 
 /**
  * Provides bridge implementation.
- * @return {!containedHome.Bridge} Bridge instance that can be used to interact
+ * @return {!kioskNextHome.Bridge} Bridge instance that can be used to interact
  *     with ChromeOS.
  */
-containedHome.getChromeOsBridge = function() {};
+kioskNextHome.getChromeOsBridge = function() {};
diff --git a/chrome/browser/resources/chromeos/contained_home/api_impl.js b/chrome/browser/resources/chromeos/kiosk_next_home/api_impl.js
similarity index 77%
rename from chrome/browser/resources/chromeos/contained_home/api_impl.js
rename to chrome/browser/resources/chromeos/kiosk_next_home/api_impl.js
index 25cd282..9b07289 100644
--- a/chrome/browser/resources/chromeos/contained_home/api_impl.js
+++ b/chrome/browser/resources/chromeos/kiosk_next_home/api_impl.js
@@ -3,18 +3,18 @@
 // found in the LICENSE file.
 
 /**
- * @fileoverview ContainedHome implementation.
+ * @fileoverview Kiosk Next Home API implementation.
  */
 
-/** @implements {containedHome.Bridge} */
-class ContainedHomeBridge {
+/** @implements {kioskNextHome.Bridge} */
+class KioskNextHomeBridge {
   constructor() {
-    /** @type {!Array<!containedHome.Listener>} */
+    /** @type {!Array<!kioskNextHome.Listener>} */
     this.listeners = [];
 
     chrome.arcAppsPrivate.onInstalled.addListener(installedApp => {
       const app = {
-        appType: containedHome.AppType.ARC,
+        appType: kioskNextHome.AppType.ARC,
         appId: installedApp.packageName,
         displayName: installedApp.packageName,
         suspended: false,
@@ -22,7 +22,7 @@
       };
       for (const listener of this.listeners) {
         listener.onInstalledAppChanged(
-            app, containedHome.AppEventType.INSTALLED);
+            app, kioskNextHome.AppEventType.INSTALLED);
       }
     });
   }
@@ -52,7 +52,7 @@
         const installedApps = [];
         for (const launchableApp of launchableApps) {
           installedApps.push({
-            appType: containedHome.AppType.ARC,
+            appType: kioskNextHome.AppType.ARC,
             appId: launchableApp.packageName,
             displayName: launchableApp.packageName,
             suspended: false,
@@ -66,7 +66,7 @@
 
   /** @override */
   launchContent(contentSource, contentId, opt_params) {
-    if (contentSource === containedHome.ContentSource.ARC_INTENT) {
+    if (contentSource === kioskNextHome.ContentSource.ARC_INTENT) {
       // TODO(brunoad): create and migrate to a more generic API.
       chrome.arcAppsPrivate.launchApp(contentId);
     }
@@ -76,9 +76,9 @@
 
 /**
  * Provides bridge implementation.
- * @return {!containedHome.Bridge} Bridge instance that can be used to interact
+ * @return {!kioskNextHome.Bridge} Bridge instance that can be used to interact
  *     with ChromeOS.
  */
-containedHome.getChromeOsBridge = function() {
-  return new ContainedHomeBridge();
+kioskNextHome.getChromeOsBridge = function() {
+  return new KioskNextHomeBridge();
 };
diff --git a/chrome/browser/resources/chromeos/contained_home/bg.js b/chrome/browser/resources/chromeos/kiosk_next_home/bg.js
similarity index 100%
rename from chrome/browser/resources/chromeos/contained_home/bg.js
rename to chrome/browser/resources/chromeos/kiosk_next_home/bg.js
diff --git a/chrome/browser/resources/chromeos/kiosk_next_home/kiosk_next_resources.grdp b/chrome/browser/resources/chromeos/kiosk_next_home/kiosk_next_resources.grdp
new file mode 100644
index 0000000..2af207ab
--- /dev/null
+++ b/chrome/browser/resources/chromeos/kiosk_next_home/kiosk_next_resources.grdp
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<grit-part>
+  <include name="IDR_KIOSK_NEXT_BG_JS" file="chromeos/kiosk_next_home/bg.js" type="BINDATA" />
+  <include name="IDR_KIOSK_NEXT_ICON_192" file="chromeos/kiosk_next_home/static/icon192.png" type="BINDATA" />
+  <include name="IDR_KIOSK_NEXT_MAIN_HTML" file="chromeos/kiosk_next_home/main.html" type="chrome_html" />
+  <include name="IDR_KIOSK_NEXT_API_JS" file="chromeos/kiosk_next_home/api.js" type="BINDATA" />
+  <include name="IDR_KIOSK_NEXT_API_IMPL_JS" file="chromeos/kiosk_next_home/api_impl.js" type="BINDATA" />
+</grit-part>
diff --git a/chrome/browser/resources/chromeos/contained_home/contained_home_resources_internal.grdp b/chrome/browser/resources/chromeos/kiosk_next_home/kiosk_next_resources_internal.grdp
similarity index 100%
rename from chrome/browser/resources/chromeos/contained_home/contained_home_resources_internal.grdp
rename to chrome/browser/resources/chromeos/kiosk_next_home/kiosk_next_resources_internal.grdp
diff --git a/chrome/browser/resources/chromeos/contained_home/main.html b/chrome/browser/resources/chromeos/kiosk_next_home/main.html
similarity index 100%
rename from chrome/browser/resources/chromeos/contained_home/main.html
rename to chrome/browser/resources/chromeos/kiosk_next_home/main.html
diff --git a/chrome/browser/resources/chromeos/contained_home/manifest.json b/chrome/browser/resources/chromeos/kiosk_next_home/manifest.json
similarity index 92%
rename from chrome/browser/resources/chromeos/contained_home/manifest.json
rename to chrome/browser/resources/chromeos/kiosk_next_home/manifest.json
index bafcc4f..c9c1d25d 100644
--- a/chrome/browser/resources/chromeos/contained_home/manifest.json
+++ b/chrome/browser/resources/chromeos/kiosk_next_home/manifest.json
@@ -1,10 +1,10 @@
 {
   // chrome-extension://nbaolgedfgoedkjbfmpediclncanmpbc/
   "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq8KnOSFXgBTWY7w+jQ/T1kPD9r821cEgvPdcot2XBdA2OVF2+TJjyLmLRa/AVQzpsMrEWzPbbXSil+GFjWsiWzcohuLphMQe5pHzRVXd19ouJZNr+KL16/e74fZ77ECI9R/D0Vh6it/QICdCaLgHbjWo2AS7vhJGtp2GJcWpXG5sbG8W8BDayY5qySwAE35dFjpeeR0bDTz44+9LFE0s+sd65LDwn37nc+pJvDNNYipGP2lYC9eMk1wAydz9x3c2iYRzcGyHjbX1Z7gQvM4w8Amdjsb8f5mZeXGNKE+gvcD7kyiR7rXgK1EfaNvDCzl/uIXXPfIh5oUK9iUkdX6oNwIDAQAB",
-  "name": "Contained Home",
+  "name": "Kiosk Next Home",
   "version": "0.1.0",
   "manifest_version": 2,
-  "description": "Contained Home",
+  "description": "Kiosk Next Home",
   "icons": {
     "192": "static/icon192.png"
   },
diff --git a/chrome/browser/resources/chromeos/contained_home/static/icon192.png b/chrome/browser/resources/chromeos/kiosk_next_home/static/icon192.png
similarity index 100%
rename from chrome/browser/resources/chromeos/contained_home/static/icon192.png
rename to chrome/browser/resources/chromeos/kiosk_next_home/static/icon192.png
Binary files differ
diff --git a/chrome/browser/resources/chromeos/login/fingerprint_setup.css b/chrome/browser/resources/chromeos/login/fingerprint_setup.css
index ccb6b4a8..14de38a 100644
--- a/chrome/browser/resources/chromeos/login/fingerprint_setup.css
+++ b/chrome/browser/resources/chromeos/login/fingerprint_setup.css
@@ -3,7 +3,7 @@
  * found in the LICENSE file. */
 
 [slot='subtitle'] {
-  color: #5F6368;
+  color: rgb(95, 99, 104);
   font-weight: 400; /* roboto-regular */
 }
 
@@ -27,7 +27,7 @@
 
 #sensorLocation {
   background-image:
-      url(chrome://theme/IDR_LOGIN_FINGERPRINT_SCANNER_ANIMATION);
+      url(chrome://oobe/fingerprint_scanner_animation.png);
   background-size: 352px 242px;
   height: 242px;
   width: 352px;
diff --git a/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js b/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
index 1ad5afd..3428953e5 100644
--- a/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
+++ b/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
@@ -406,12 +406,25 @@
    * @private
    */
   clearFocusRing_: function() {
-    chrome.accessibilityPrivate.setFocusRing([]);
+    this.setFocusRings_([]);
     chrome.accessibilityPrivate.setHighlights(
         [], this.prefsManager_.highlightColor());
   },
 
   /**
+   * Sets the focus ring to |rects|.
+   * @param {!Array<!chrome.accessibilityPrivate.ScreenRect>} rects
+   * @private
+   */
+  setFocusRings_: function(rects) {
+    chrome.accessibilityPrivate.setFocusRings([{
+      rects: rects,
+      type: chrome.accessibilityPrivate.FocusType.GLOW,
+      color: this.prefsManager_.focusRingColor()
+    }]);
+  },
+
+  /**
    * Runs content scripts that allow Select-to-Speak access to
    * Google Docs content without a11y mode enabled, in every open
    * tab. Should be run when Select-to-Speak starts up so that any
@@ -466,8 +479,7 @@
       },
       // onSelectionChanged: Mouse selection rect changed.
       onSelectionChanged: rect => {
-        chrome.accessibilityPrivate.setFocusRing(
-            [rect], this.prefsManager_.focusRingColor());
+        this.setFocusRings_([rect]);
       },
       // onKeystrokeSelection: Keys pressed for reading highlighted text.
       onKeystrokeSelection: () => {
@@ -880,12 +892,9 @@
     // the one node. if it has siblings, highlight the parent.
     if (this.currentBlockParent_ != null &&
         node.role == RoleType.INLINE_TEXT_BOX) {
-      chrome.accessibilityPrivate.setFocusRing(
-          [this.currentBlockParent_.location],
-          this.prefsManager_.focusRingColor());
+      this.setFocusRings_([this.currentBlockParent_.location]);
     } else {
-      chrome.accessibilityPrivate.setFocusRing(
-          [node.location], this.prefsManager_.focusRingColor());
+      this.setFocusRings_([node.location]);
     }
   },
 
diff --git a/chrome/browser/resources/chromeos/switch_access/navigation_manager.js b/chrome/browser/resources/chromeos/switch_access/navigation_manager.js
index c2331d6c..fc98d5e 100644
--- a/chrome/browser/resources/chromeos/switch_access/navigation_manager.js
+++ b/chrome/browser/resources/chromeos/switch_access/navigation_manager.js
@@ -313,7 +313,12 @@
     if (!removedByRWA && treeChange.target !== this.node_)
       return;
 
-    chrome.accessibilityPrivate.setFocusRing([]);
+    chrome.accessibilityPrivate.setFocusRings([{
+      id: SAConstants.PRIMARY_FOCUS,
+      rects: [],
+      type: chrome.accessibilityPrivate.FocusType.GLOW,
+      color: NavigationManager.Color.LEAF
+    }]);
 
     // Current node not invalid until after treeChange callback, so move to
     // valid node after callback. Delay added to prevent moving to another
@@ -402,7 +407,12 @@
       color = NavigationManager.Color.LEAF;
 
     color = opt_color || color;
-    chrome.accessibilityPrivate.setFocusRing([this.node_.location], color);
+    chrome.accessibilityPrivate.setFocusRings([{
+      id: SAConstants.PRIMARY_FOCUS,
+      rects: [this.node_.location],
+      type: chrome.accessibilityPrivate.FocusType.GLOW,
+      color: color
+    }]);
   }
 
   /**
diff --git a/chrome/browser/resources/chromeos/switch_access/switch_access_constants.js b/chrome/browser/resources/chromeos/switch_access/switch_access_constants.js
index 546d6cf..00663dc 100644
--- a/chrome/browser/resources/chromeos/switch_access/switch_access_constants.js
+++ b/chrome/browser/resources/chromeos/switch_access/switch_access_constants.js
@@ -29,6 +29,22 @@
   FOCUS_CLASS: 'focus',
 
   /**
+   * The ID used for the focus ring around the current element.
+   * Must be kept in sync with accessibility_manager.cc.
+   * @type {string}
+   * @const
+   */
+  PRIMARY_FOCUS: 'primary',
+
+  /**
+   * The ID used for the focus ring around the current scope.
+   * Must be kept in sync with accessibility_manager.cc.
+   * @type {string}
+   * @const
+   */
+  SCOPE_FOCUS: 'scope',
+
+  /**
    * Actions available in the Switch Access Menu.
    * @enum {string}
    * @const
diff --git a/chrome/browser/resources/component_extension_resources.grd b/chrome/browser/resources/component_extension_resources.grd
index 110e3307..8d4939b 100644
--- a/chrome/browser/resources/component_extension_resources.grd
+++ b/chrome/browser/resources/component_extension_resources.grd
@@ -91,8 +91,8 @@
         <include name="IDR_ARC_SUPPORT_RECOMMEND_APP_LIST_VIEW_JS" file="chromeos/arc_support/recommend_app_list_view.js" type="BINDATA" />
         <include name="IDR_ARC_SUPPORT_RECOMMEND_APP_LIST_VIEW_HTML" file="chromeos/arc_support/recommend_app_list_view.html" type="chrome_html" flattenhtml="true" />
         <if expr="_google_chrome">
-          <part file="chromeos/contained_home/contained_home_resources.grdp" />
-          <part file="chromeos/contained_home/contained_home_resources_internal.grdp" />
+          <part file="chromeos/kiosk_next_home/kiosk_next_resources.grdp" />
+          <part file="chromeos/kiosk_next_home/kiosk_next_resources_internal.grdp" />
         </if>
       </if>
       <if expr="enable_plugins">
diff --git a/chrome/browser/resources/connection_manager.js b/chrome/browser/resources/connection_manager.js
deleted file mode 100644
index 9bc3e9a..0000000
--- a/chrome/browser/resources/connection_manager.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2010 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.
-
-
-function chromeos() {}
-
-chromeos.connectionManager = function() {};
-
-chromeos.connectionManager.transaction_status_callback_ = null;
-chromeos.connectionManager.parent_page_url_ = 'chrome://mobilesetup';
-
-chromeos.connectionManager.setTransactionStatus = function(status, callback) {
-  chromeos.connectionManager.transaction_status_callback_ = callback;
-  chromeos.connectionManager.reportTransactionStatus_(status);
-};
-
-chromeos.connectionManager.reportTransactionStatus_ = function(status) {
-  const msg = {
-    'type': 'reportTransactionStatusMsg',
-    'domain': location.href,
-    'status': status
-  };
-  window.parent.postMessage(msg, chromeos.connectionManager.parent_page_url_);
-};
diff --git a/chrome/browser/resources/management/management_ui.html b/chrome/browser/resources/management/management_ui.html
index 6a7f8ad..e3ec1d4 100644
--- a/chrome/browser/resources/management/management_ui.html
+++ b/chrome/browser/resources/management/management_ui.html
@@ -40,8 +40,7 @@
       }
 
       h2 {
-        @apply --cr-title-text;
-        font-weight: 600;
+        @apply --cr-card-external-title;
       }
 
       .subtitle {
@@ -54,7 +53,7 @@
 
       .sections-container {
         @apply --cr-centered-card-container;
-        margin-top: var(--cr-centered-card-container-vertical-margin);
+        margin-top: var(--cr-section-vertical-margin);
       }
 
       .card {
@@ -80,10 +79,6 @@
         justify-content: center;
       }
 
-      .content-indented {
-        margin-inline-start: 20px;
-      }
-
       .three-line {
         min-height: var(--cr-section-three-line-min-height);
       }
@@ -155,95 +150,92 @@
     </cr-toolbar>
     <main id="mainContent">
       <div class="sections-container">
-        <div class="card">
-          <if expr="not chromeos">
-            <section class="three-line single-column">
-              <p inner-h-t-m-l="[[i18nAdvanced('browserManagementNotice')]]">
-              </p>
-            </section>
-          </if>
-<if expr="chromeos">
-          <template is="dom-if"
-              if="[[showDeviceReportingInfo_(deviceReportingInfo_)]]">
-            <section class="three-line single-column">
-              <h2>$i18n{deviceReporting}</h2>
-              <div class="content-indented subtitle">
-                  $i18n{deviceConfiguration}
-              </div>
-              <template is="dom-repeat" items="[[deviceReportingInfo_]]">
-                <div class="content-indented device-reporting">
-                  <span>
-                    <iron-icon icon="[[getIconForDeviceReportingType_(
-                        item.reportingType)]]"></iron-icon>
-                    [[i18n(item.messageId)]]
-                  </span>
-                </div>
-              </template>
-            </section>
-          </template>
+<if expr="not chromeos">
+        <section class="card three-line single-column">
+          <p inner-h-t-m-l="[[i18nAdvanced('browserManagementNotice')]]"></p>
+        </section>
 </if>
-          <template is="dom-if"
-              if="[[showBrowserReportingInfo_(browserReportingInfo_)]]">
-            <section class="three-line single-column">
-              <h2>$i18n{browserReporting}</h2>
-              <div class="content-indented subtitle">
-                $i18n{browserReportingExplanation}
+<if expr="chromeos">
+        <template is="dom-if"
+            if="[[showDeviceReportingInfo_(deviceReportingInfo_)]]">
+          <h2>$i18n{deviceReporting}</h2>
+          <section class="card three-line single-column">
+            <div class="subtitle">
+                $i18n{deviceConfiguration}
+            </div>
+            <template is="dom-repeat" items="[[deviceReportingInfo_]]">
+              <div class="device-reporting">
+                <span>
+                  <iron-icon icon="[[getIconForDeviceReportingType_(
+                      item.reportingType)]]"></iron-icon>
+                  [[i18n(item.messageId)]]
+                </span>
               </div>
-              <template is="dom-repeat" items="[[browserReportingInfo_]]">
-                <div class="content-indented browser-report">
-                  <iron-icon icon="[[item.icon]]"></iron-icon>
-                  <ul>
-                    <template is="dom-repeat" items="[[item.messageIds]]"
-                        as="messageId">
-                      <li inner-h-t-m-l="[[i18nAdvanced(messageId)]]"></li>
-                    </template>
-                  </ul>
-                </div>
-              </template>
-            </section>
-          </template>
-          <template is="dom-if"
-              if="[[showExtensionReportingInfo_(extensions_)]]">
-            <section class="three-line single-column">
-              <h2>$i18n{extensionReporting}</h2>
-              <div class="content-indented subtitle">
-                $i18n{extensionsInstalled}
-              </div>
-              <div class="extensions-list">
-                <div class="list-item header">
-                  <div class="extension-name">$i18n{extensionName}</div>
-                  <div class="extension-permissions">
-                    $i18n{extensionPermissions}
-                  </div>
-                </div>
-                <template is="dom-repeat" items="[[extensions_]]">
-                  <div class="list-item">
-                    <div class="extension-name">
-                      <img src="[[item.icon]]"
-                          aria-describedby="a11yAssociation">
-                      <span>[[item.name]]</span>
-                    </div>
-                    <div class="extension-permissions">
-                      <ul>
-                        <template is="dom-repeat" items="[[item.permissions]]"
-                            as="permission">
-                          <li>[[permission]]</li>
-                        </template>
-                      </ul>
-                    </div>
-                </template>
-              </div>
-            </section>
-          </template>
-          <if expr="chromeos">
-            <template is="dom-if" if="[[localTrustRoots_]]">
-              <section class="three-line single-column">
-                <h2>$i18n{localTrustRoots}</h2>
-                <div id="trust-roots-configuration">[[localTrustRoots_]]</div>
-              </section>
             </template>
-          </if>
-        </div>
+          </section>
+        </template>
+</if>
+        <template is="dom-if"
+            if="[[showBrowserReportingInfo_(browserReportingInfo_)]]">
+          <h2>$i18n{browserReporting}</h2>
+          <section class="card three-line single-column">
+            <div class="subtitle">
+              $i18n{browserReportingExplanation}
+            </div>
+            <template is="dom-repeat" items="[[browserReportingInfo_]]">
+              <div class="browser-report">
+                <iron-icon icon="[[item.icon]]"></iron-icon>
+                <ul>
+                  <template is="dom-repeat" items="[[item.messageIds]]"
+                      as="messageId">
+                    <li inner-h-t-m-l="[[i18nAdvanced(messageId)]]"></li>
+                  </template>
+                </ul>
+              </div>
+            </template>
+          </section>
+        </template>
+        <template is="dom-if"
+            if="[[showExtensionReportingInfo_(extensions_)]]">
+          <h2>$i18n{extensionReporting}</h2>
+          <section class="card three-line single-column">
+            <div class="subtitle">
+              $i18n{extensionsInstalled}
+            </div>
+            <div class="extensions-list">
+              <div class="list-item header">
+                <div class="extension-name">$i18n{extensionName}</div>
+                <div class="extension-permissions">
+                  $i18n{extensionPermissions}
+                </div>
+              </div>
+              <template is="dom-repeat" items="[[extensions_]]">
+                <div class="list-item">
+                  <div class="extension-name">
+                    <img src="[[item.icon]]"
+                        aria-describedby="a11yAssociation">
+                    <span>[[item.name]]</span>
+                  </div>
+                  <div class="extension-permissions">
+                    <ul>
+                      <template is="dom-repeat" items="[[item.permissions]]"
+                          as="permission">
+                        <li>[[permission]]</li>
+                      </template>
+                    </ul>
+                  </div>
+              </template>
+            </div>
+          </section>
+        </template>
+<if expr="chromeos">
+        <template is="dom-if" if="[[localTrustRoots_]]">
+          <h2>$i18n{localTrustRoots}</h2>
+          <section class="card three-line single-column">
+            <div id="trust-roots-configuration">[[localTrustRoots_]]</div>
+          </section>
+        </template>
+</if>
       </div>
     </main>
   </template>
diff --git a/chrome/browser/resources/settings/autofill_page/passwords_export_dialog.html b/chrome/browser/resources/settings/autofill_page/passwords_export_dialog.html
index a46cee60..bc044b5 100644
--- a/chrome/browser/resources/settings/autofill_page/passwords_export_dialog.html
+++ b/chrome/browser/resources/settings/autofill_page/passwords_export_dialog.html
@@ -1,6 +1,7 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
 <link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
+<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.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-progress/paper-progress.html">
@@ -10,9 +11,17 @@
   <template>
     <style include="settings-shared iron-flex">
       paper-progress {
-        width: 100%;
         --paper-progress-active-color: var(--google-blue-500);
+        width: 100%;
       }
+
+      :host-context([dark]) paper-progress {
+        /* TODO(dbeam): this is the same as downloads (and probably anywhere
+         * else that uses paper-progress). Should we make something like a
+         * paper_progress_style_css.html? */
+        --paper-progress-active-color: var(--google-blue-refresh-300);
+      }
+
       .action-button {
         margin-inline-start: 8px;
       }
diff --git a/chrome/browser/resources/settings/device_page/keyboard.html b/chrome/browser/resources/settings/device_page/keyboard.html
index b75cefe..89c25c9 100644
--- a/chrome/browser/resources/settings/device_page/keyboard.html
+++ b/chrome/browser/resources/settings/device_page/keyboard.html
@@ -36,15 +36,6 @@
           menu-options="[[keyMapTargets_]]">
       </settings-dropdown-menu>
     </div>
-    <template is="dom-if" if="[[showDiamondKey_]]">
-      <div class="settings-box" id="diamondKey">
-        <div class="start">$i18n{keyboardKeyDiamond}</div>
-        <settings-dropdown-menu label="$i18n{keyboardKeyDiamond}"
-            pref="{{prefs.settings.language.remap_diamond_key_to}}"
-            menu-options="[[keyMapTargets_]]">
-        </settings-dropdown-menu>
-      </div>
-    </template>
     <div class="settings-box">
       <div class="start">$i18n{keyboardKeyEscape}</div>
       <settings-dropdown-menu label="$i18n{keyboardKeyEscape}"
diff --git a/chrome/browser/resources/settings/device_page/keyboard.js b/chrome/browser/resources/settings/device_page/keyboard.js
index 718e375..9d829ce 100644
--- a/chrome/browser/resources/settings/device_page/keyboard.js
+++ b/chrome/browser/resources/settings/device_page/keyboard.js
@@ -37,9 +37,6 @@
     /** @private Whether to show Caps Lock options. */
     showCapsLock_: Boolean,
 
-    /** @private Whether to show diamond key options. */
-    showDiamondKey_: Boolean,
-
     /** @private Whether this device has an internal keyboard. */
     hasInternalKeyboard_: Boolean,
 
@@ -147,7 +144,6 @@
     this.hasInternalKeyboard_ = keyboardParams['hasInternalKeyboard'];
     this.hasAssistantKey_ = keyboardParams['hasAssistantKey'];
     this.showCapsLock_ = keyboardParams['showCapsLock'];
-    this.showDiamondKey_ = keyboardParams['showDiamondKey'];
     this.showExternalMetaKey_ = keyboardParams['showExternalMetaKey'];
     this.showAppleCommandKey_ = keyboardParams['showAppleCommandKey'];
   },
diff --git a/chrome/browser/resources/settings/internet_page/internet_subpage.html b/chrome/browser/resources/settings/internet_page/internet_subpage.html
index ce1d5ac..7fd0d1844 100644
--- a/chrome/browser/resources/settings/internet_page/internet_subpage.html
+++ b/chrome/browser/resources/settings/internet_page/internet_subpage.html
@@ -2,6 +2,7 @@
 
 <link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_network_list.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_toggle/cr_toggle.html">
+<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
@@ -12,7 +13,7 @@
   <template>
     <style include="settings-shared iron-flex">
       #networkListDiv {
-        margin-top: var(--settings-page-vertical-margin);
+        margin-top: var(--cr-section-vertical-margin);
         min-height: var(--settings-row-min-height);
       }
 
diff --git a/chrome/browser/resources/settings/internet_page/tether_connection_dialog.html b/chrome/browser/resources/settings/internet_page/tether_connection_dialog.html
index 2a305ca..2d9a2c9 100644
--- a/chrome/browser/resources/settings/internet_page/tether_connection_dialog.html
+++ b/chrome/browser/resources/settings/internet_page/tether_connection_dialog.html
@@ -2,6 +2,7 @@
 <link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_network_icon.html">
 <link rel="import" href="chrome://resources/cr_elements/chromeos/network/cr_onc_types.html">
 <link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html">
+<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
 <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
@@ -21,7 +22,7 @@
       }
 
       [slot=body] > * {
-        margin-left: 5px;
+        margin-inline-start: 5px;
       }
 
       iron-icon {
@@ -31,7 +32,7 @@
       #host-device-text-container {
         display: flex;
         flex-direction: column;
-        margin-left: 18px;
+        margin-inline-start: 18px;
       }
 
       #availability-title {
@@ -50,7 +51,7 @@
       #tether-explanation,
       #tether-carrier-warning,
       #tether-description-title {
-        margin-top: var(--settings-page-vertical-margin);
+        margin-top: var(--cr-section-vertical-margin);
       }
 
       #tether-carrier-warning {
@@ -58,7 +59,7 @@
       }
 
       #tether-description-list {
-        padding-left: 16px;
+        padding-inline-start: 16px;
       }
 
       #host-device-lost-container {
diff --git a/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.html b/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.html
index fda8a287d..4274c2b 100644
--- a/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.html
+++ b/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.html
@@ -22,8 +22,17 @@
         };
       }
 
+      .fingerprint-scanner-tablet {
+        background:
+            url(chrome://theme/IDR_LOGIN_FINGERPRINT_SCANNER_TABLET_ANIMATION);
+      }
+
+      .fingerprint-scanner-laptop {
+        background:
+            url(chrome://theme/IDR_LOGIN_FINGERPRINT_SCANNER_LAPTOP_ANIMATION);
+      }
+
       #scannerLocation {
-        background: url(chrome://theme/IDR_LOGIN_FINGERPRINT_SCANNER_ANIMATION);
         background-position: center;
         background-repeat: no-repeat;
         background-size: 298px 205px;
@@ -55,6 +64,7 @@
           <span>[[getInstructionMessage_(step_, problemMessage_)]]</span>
         </div>
         <div id="scannerLocation" hidden="[[!showScannerLocation_(step_)]]"
+            class$="[[getFingerprintScannerAnimationClass_()]]"
             aria-label="$i18n{configureFingerprintScannerStepAriaLabel}"
             aria-live="polite" >
         </div>
diff --git a/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.js b/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.js
index 39061e6..e3f1867 100644
--- a/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.js
+++ b/chrome/browser/resources/settings/people_page/setup_fingerprint_dialog.js
@@ -300,5 +300,17 @@
 
     this.$.arc.setProgress(oldValue, newValue, newValue === 100);
   },
+
+  /**
+   * Returns the class name for fingerprint scanner animation.
+   * @private
+   */
+  getFingerprintScannerAnimationClass_: function() {
+    if (loadTimeData.getBoolean('fingerprintUnlockEnabled') &&
+        loadTimeData.getBoolean('isFingerprintReaderOnKeyboard')) {
+      return 'fingerprint-scanner-laptop';
+    }
+    return 'fingerprint-scanner-tablet';
+  },
 });
 })();
diff --git a/chrome/browser/resources/settings/settings_page/settings_section.html b/chrome/browser/resources/settings/settings_page/settings_section.html
index e4036f1..81438e6 100644
--- a/chrome/browser/resources/settings/settings_page/settings_section.html
+++ b/chrome/browser/resources/settings/settings_page/settings_section.html
@@ -16,19 +16,8 @@
         position: relative;
       }
 
-      #header {
-        margin-bottom: 12px;
-      }
-
       #header .title {
-        color: var(--cr-primary-text-color);
-        font-size: 108%;
-        font-weight: 400;
-        letter-spacing: .25px;
-        margin-bottom: 0;
-        margin-top: var(--settings-page-vertical-margin);
-        padding-bottom: 4px;
-        padding-top: 8px;
+        @apply --cr-card-external-title;
       }
 
       :host(:not(.expanded)) #card {
diff --git a/chrome/browser/resources/settings/settings_vars_css.html b/chrome/browser/resources/settings/settings_vars_css.html
index 7af8cd9..5d4b4de 100644
--- a/chrome/browser/resources/settings/settings_vars_css.html
+++ b/chrome/browser/resources/settings/settings_vars_css.html
@@ -30,8 +30,6 @@
     /* No --settings-nav-icon-color needed in light mode. */
     --settings-nav-item-color: var(--google-grey-refresh-700);
 
-    --settings-page-vertical-margin: 21px;
-
     --settings-row-min-height: var(--cr-section-min-height);
     --settings-row-two-line-min-height: var(--cr-section-two-line-min-height);
     --settings-row-three-line-min-height:
diff --git a/chrome/browser/resources/settings/site_settings/category_setting_exceptions.html b/chrome/browser/resources/settings/site_settings/category_setting_exceptions.html
index c837275e..5164f1c 100644
--- a/chrome/browser/resources/settings/site_settings/category_setting_exceptions.html
+++ b/chrome/browser/resources/settings/site_settings/category_setting_exceptions.html
@@ -1,20 +1,10 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
-<link rel="import" href="../settings_shared_css.html">
 <link rel="import" href="constants.html">
 <link rel="import" href="site_list.html">
 
 <dom-module id="category-setting-exceptions">
   <template>
-    <style include="settings-shared">
-      site-list {
-        border-bottom: 1px solid var(--paper-grey-300);
-      }
-
-      site-list:last-of-type {
-        border-bottom: none;
-      }
-    </style>
     <site-list
         category="[[category]]"
         category-subtype="[[ContentSetting.BLOCK]]"
diff --git a/chrome/browser/resources/settings/site_settings/site_details.html b/chrome/browser/resources/settings/site_settings/site_details.html
index 944392a..c4cb1f14 100644
--- a/chrome/browser/resources/settings/site_settings/site_details.html
+++ b/chrome/browser/resources/settings/site_settings/site_details.html
@@ -27,6 +27,9 @@
         padding-inline-end: 0;
       }
 
+      #storageText {
+        display: flex;
+      }
       /* When 'Usage' is omitted, subheadings are removed. Reduce the start
        * padding allowed for lists without headings and add back vertical space
        * that would normally be provided by the subheading. */
@@ -81,12 +84,19 @@
         </div>
         <div class="list-frame">
           <div class="list-item" id="noStorage"
-              hidden$="[[hasUsage_(storedData_)]]">
+              hidden$="[[hasUsage_(storedData_, numCookies_)]]">
             <div class="start">$i18n{siteSettingsUsageNone}</div>
           </div>
-          <div class="list-item" id="storage" hidden$="[[!storedData_]]">
-            <div class="start">[[storedData_]]</div>
-            <paper-button class="secondary-button" id="clearStorage" 
+          <div class="list-item" id="storage"
+              hidden$="[[!hasUsage_(storedData_, numCookies_)]]">
+            <div class="start" id="storageText">
+              <div hidden$="[[!storedData_]]">[[storedData_]]</div>
+              <div hidden$="[[!hasDataAndCookies_(storedData_, numCookies_)]]">
+                &nbsp;&middot;&nbsp;
+              </div>
+              <div hidden$="[[!numCookies_]]">[[numCookies_]]</div>
+            </div>
+            <paper-button class="secondary-button" id="clearStorage"
                 role="button" aria-disabled="false"
                 on-click="onConfirmClearStorage_"
                 aria-label="$i18n{siteSettingsDelete}">
@@ -207,7 +217,8 @@
       </div>
     </template>
     <website-usage-private-api id="usageApi"
-        website-data-usage="{{storedData_}}">
+        website-data-usage="{{storedData_}}"
+        website-cookie-usage="{{numCookies_}}">
     </website-usage-private-api>
   </template>
   <script src="site_details.js"></script>
diff --git a/chrome/browser/resources/settings/site_settings/site_details.js b/chrome/browser/resources/settings/site_settings/site_details.js
index 8f92a6a..db96fad2 100644
--- a/chrome/browser/resources/settings/site_settings/site_details.js
+++ b/chrome/browser/resources/settings/site_settings/site_details.js
@@ -45,6 +45,15 @@
       value: '',
     },
 
+    /**
+     * The number of cookies stored for the origin.
+     * @private
+     */
+    numCookies_: {
+      type: String,
+      value: '',
+    },
+
     /** @private */
     enableSiteSettings_: {
       type: Boolean,
@@ -254,8 +263,18 @@
    *     disk or battery).
    * @private
    */
-  hasUsage_: function(storage) {
-    return storage != '';
+  hasUsage_: function(storage, cookies) {
+    return storage != '' || cookies != '';
+  },
+
+  /**
+   * Checks whether this site has both storage and cookies information to show.
+   * @return {boolean} Whether there are both storage and cookies information to
+   *     show.
+   * @private
+   */
+  hasDataAndCookies_: function(storage, cookies) {
+    return storage != '' && cookies != '';
   },
 
   /** @private */
diff --git a/chrome/browser/resources/settings/site_settings/site_entry.js b/chrome/browser/resources/settings/site_settings/site_entry.js
index e0dbcfd..36877033 100644
--- a/chrome/browser/resources/settings/site_settings/site_entry.js
+++ b/chrome/browser/resources/settings/site_settings/site_entry.js
@@ -115,8 +115,12 @@
    * @private
    */
   grouped_: function(siteGroup) {
-    if (siteGroup) {
-      return siteGroup.origins.length != 1;
+    if (!siteGroup) {
+      return false;
+    }
+    if (siteGroup.origins.length > 1 ||
+        siteGroup.numCookies > siteGroup.origins[0].numCookies) {
+      return true;
     }
     return false;
   },
diff --git a/chrome/browser/resources/settings/site_settings/website_usage_private_api.js b/chrome/browser/resources/settings/site_settings/website_usage_private_api.js
index 23720b0..214b3ff 100644
--- a/chrome/browser/resources/settings/site_settings/website_usage_private_api.js
+++ b/chrome/browser/resources/settings/site_settings/website_usage_private_api.js
@@ -16,6 +16,14 @@
       type: String,
       notify: true,
     },
+
+    /**
+     * The number of cookies used by the given website.
+     */
+    websiteCookieUsage: {
+      type: String,
+      notify: true,
+    },
   },
 
   /** @override */
@@ -76,7 +84,7 @@
    * @param {string} usage The string showing how much data the given host
    *     is using.
    */
-  const returnUsageTotal = function(host, usage) {
+  const returnUsageTotal = function(host, usage, cookies) {
     const instance =
         settings.WebsiteUsagePrivateApi.websiteUsagePolymerInstance;
     if (instance == null) {
@@ -85,6 +93,7 @@
 
     if (hostName == host) {
       instance.websiteDataUsage = usage;
+      instance.websiteCookieUsage = cookies;
     }
   };
 
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.html b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.html
index f20e0eab..38eb113a 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.html
+++ b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.html
@@ -88,20 +88,30 @@
       }
 
       .ntp-background-grid-button {
-        @apply --cr-card-elevation;
         align-items: stretch;
         background: var(--cr-card-background-color);
-        border: 0;
+        border: solid 1px var(--google-grey-refresh-300);
+        border-radius: 4px;
         color: var(--cr-secondary-text-color);
         cursor: pointer;
         display: flex;
         flex-direction: column;
         height: 100%;
+        overflow: hidden;
         padding: 0;
         text-align: start;
+        transition: border-color 400ms, box-shadow 500ms;
         width: 100%;
       }
 
+      #backgroundPreview.active + .content .ntp-background-grid-button {
+        border-color: var(--google-grey-refresh-700);
+      }
+
+      .ntp-background-grid-button:hover {
+        box-shadow: 0 3px 6px 2px rgba(0, 36, 100, .1);
+      }
+
       /* Remove outline when button is focused using the mouse. */
       .ntp-background-grid-button:focus:not(.keyboard-focused) {
         outline: none;
@@ -185,8 +195,6 @@
         <template is="dom-repeat" items="[[backgrounds_]]">
           <button
               active$="[[isSelectedBackground_(item, selectedBackground_)]]"
-              aria-pressed$="[[getAriaPressedValue_(item,
-                                                    selectedBackground_)]]"
               class="ntp-background-grid-button"
               on-click="onBackgroundClick_"
               on-keyup="onBackgroundKeyUp_"
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.js b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.js
index ad37645c..de0128f 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.js
+++ b/chrome/browser/resources/welcome/onboarding_welcome/ntp_background/nux_ntp_background.js
@@ -55,14 +55,6 @@
   },
 
   /**
-   * @param {!nux.NtpBackgroundData} background
-   * @private
-   */
-  getAriaPressedValue_: function(background) {
-    return this.isSelectedBackground_(background).toString();
-  },
-
-  /**
    * @return {boolean}
    * @private
    */
diff --git a/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.cc b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.cc
index 1435ad47..591d2ce 100644
--- a/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.cc
+++ b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.cc
@@ -129,10 +129,6 @@
     return;
   }
 
-  if (item->GetMimeType() != kApkMimeType) {
-    return;
-  }
-
   if (!CanSendPing(item)) {
     return;
   }
@@ -165,13 +161,9 @@
 }
 
 bool AndroidTelemetryService::CanSendPing(download::DownloadItem* item) {
-  content::WebContents* web_contents =
-      content::DownloadItemUtils::GetWebContents(item);
-  if (!web_contents) {
-    // TODO(vakh): This can happen sometimes when a download resumes on a
-    // browser re-launch. Identify this case and document it better.
-    RecordApkDownloadTelemetryOutcome(
-        ApkDownloadTelemetryOutcome::NOT_SENT_MISSING_WEB_CONTENTS);
+  if (item->GetMimeType() != kApkMimeType) {
+    // This case is not recorded since we are not interested here in finding out
+    // how often people download non-APK files.
     return false;
   }
 
@@ -181,7 +173,7 @@
     return false;
   }
 
-  if (web_contents->GetBrowserContext()->IsOffTheRecord()) {
+  if (profile_->IsOffTheRecord()) {
     RecordApkDownloadTelemetryOutcome(
         ApkDownloadTelemetryOutcome::NOT_SENT_INCOGNITO);
     return false;
diff --git a/chrome/browser/sync/test/integration/two_client_bookmarks_sync_test.cc b/chrome/browser/sync/test/integration/two_client_bookmarks_sync_test.cc
index 3b5a04e..f3f1fff 100644
--- a/chrome/browser/sync/test/integration/two_client_bookmarks_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_bookmarks_sync_test.cc
@@ -1336,16 +1336,8 @@
   ASSERT_FALSE(ContainsDuplicateBookmarks(0));
 }
 
-// Flaky on windows, see htpp://crbug.com/919877
-#if defined(OS_WIN)
-#define MAYBE_MC_MergeSimpleBMHierarchyEqualSetsUnderBMBar \
-  DISABLED_MC_MergeSimpleBMHierarchyEqualSetsUnderBMBar
-#else
-#define MAYBE_MC_MergeSimpleBMHierarchyEqualSetsUnderBMBar \
-  MC_MergeSimpleBMHierarchyEqualSetsUnderBMBar
-#endif
 IN_PROC_BROWSER_TEST_P(TwoClientBookmarksSyncTestIncludingUssTests,
-                       MAYBE_MC_MergeSimpleBMHierarchyEqualSetsUnderBMBar) {
+                       MC_MergeSimpleBMHierarchyEqualSetsUnderBMBar) {
   ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
   DisableVerifier();
 
@@ -1356,8 +1348,14 @@
     ASSERT_NE(nullptr, AddURL(1, i, title, url));
   }
 
-  ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
+  // Enable sync on Client 0 and wait until bookmarks are committed.
+  ASSERT_TRUE(GetClient(0)->SetupSync()) << "GetClient(0)->SetupSync() failed.";
+  ASSERT_TRUE(UpdatedProgressMarkerChecker(GetSyncService(0)).Wait());
+
+  // Enable sync on Client 1 and wait until all bookmarks are merged.
+  ASSERT_TRUE(GetClient(1)->SetupSync()) << "GetClient(1)->SetupSync() failed.";
   ASSERT_TRUE(BookmarksMatchChecker().Wait());
+
   ASSERT_FALSE(ContainsDuplicateBookmarks(0));
 }
 
diff --git a/chrome/browser/tracing/chrome_tracing_delegate.cc b/chrome/browser/tracing/chrome_tracing_delegate.cc
index 9931e7b..0d8ce04 100644
--- a/chrome/browser/tracing/chrome_tracing_delegate.cc
+++ b/chrome/browser/tracing/chrome_tracing_delegate.cc
@@ -22,7 +22,6 @@
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_otr_state.h"
 #include "chrome/common/pref_names.h"
-#include "chrome/common/trace_event_args_whitelist.h"
 #include "components/metrics/metrics_pref_names.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
@@ -216,8 +215,3 @@
   metadata_dict->SetString("revision", version_info::GetLastChange());
   return metadata_dict;
 }
-
-content::MetadataFilterPredicate
-ChromeTracingDelegate::GetMetadataFilterPredicate() {
-  return base::Bind(&IsMetadataWhitelisted);
-}
diff --git a/chrome/browser/tracing/chrome_tracing_delegate.h b/chrome/browser/tracing/chrome_tracing_delegate.h
index 517dc0d3..49e922f 100644
--- a/chrome/browser/tracing/chrome_tracing_delegate.h
+++ b/chrome/browser/tracing/chrome_tracing_delegate.h
@@ -50,8 +50,6 @@
 
   std::unique_ptr<base::DictionaryValue> GenerateMetadataDict() override;
 
-  content::MetadataFilterPredicate GetMetadataFilterPredicate() override;
-
  private:
 #if defined(OS_ANDROID)
   // TabModelListObserver implementation.
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 1ba095b6..c3bacdd6 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
@@ -34,6 +34,8 @@
 
 constexpr char kAppListLatency[] = "Apps.AppListRecommendedResponse.Latency";
 constexpr char kAppListCounts[] = "Apps.AppListRecommendedResponse.Count";
+constexpr char kAppListImpressionsBeforeOpen[] =
+    "Apps.AppListRecommendedImpResultCountAfterOpen";
 
 // Fields for working with pref syncable state.
 
@@ -444,6 +446,10 @@
 
 void ArcAppReinstallSearchProvider::OnOpened(const std::string& id) {
   UpdateStateTime(profile_, id, kOpenTime);
+  int64_t impression_count;
+  if (GetStateInt64(profile_, id, kImpressionCount, &impression_count)) {
+    UMA_HISTOGRAM_COUNTS_100(kAppListImpressionsBeforeOpen, impression_count);
+  }
   UpdateResults();
 }
 
diff --git a/chrome/browser/ui/ash/kiosk_next_shell_client.cc b/chrome/browser/ui/ash/kiosk_next_shell_client.cc
index 68387a9..d0779c1 100644
--- a/chrome/browser/ui/ash/kiosk_next_shell_client.cc
+++ b/chrome/browser/ui/ash/kiosk_next_shell_client.cc
@@ -38,7 +38,7 @@
       chromeos::ProfileHelper::Get()->GetProfileByAccountId(account_id);
   const extensions::Extension* app =
       extensions::ExtensionRegistry::Get(profile)->GetInstalledExtension(
-          extension_misc::kContainedHomeAppId);
+          extension_misc::kKioskNextHomeAppId);
   DCHECK(app);
   apps::LaunchPlatformApp(profile, app,
                           extensions::AppLaunchSource::SOURCE_CHROME_INTERNAL);
diff --git a/chrome/browser/ui/browser_commands.cc b/chrome/browser/ui/browser_commands.cc
index ecf3d02..dd36acd 100644
--- a/chrome/browser/ui/browser_commands.cc
+++ b/chrome/browser/ui/browser_commands.cc
@@ -1186,10 +1186,11 @@
 }
 
 void ShowAvatarMenu(Browser* browser) {
+  const bool incognito =
+      browser->profile()->GetProfileType() == Profile::INCOGNITO_PROFILE;
   browser->window()->ShowAvatarBubbleFromAvatarButton(
-      browser->profile()->IsOffTheRecord()
-          ? BrowserWindow::AVATAR_BUBBLE_MODE_INCOGNITO
-          : BrowserWindow::AVATAR_BUBBLE_MODE_DEFAULT,
+      incognito ? BrowserWindow::AVATAR_BUBBLE_MODE_INCOGNITO
+                : BrowserWindow::AVATAR_BUBBLE_MODE_DEFAULT,
       signin::ManageAccountsParams(),
       signin_metrics::AccessPoint::ACCESS_POINT_AVATAR_BUBBLE_SIGN_IN, true);
 }
diff --git a/chrome/browser/ui/views/ime/ime_window_view.cc b/chrome/browser/ui/views/ime/ime_window_view.cc
index cc9f6d0..e6516d8 100644
--- a/chrome/browser/ui/views/ime/ime_window_view.cc
+++ b/chrome/browser/ui/views/ime/ime_window_view.cc
@@ -172,10 +172,6 @@
   return base::UTF8ToUTF16(ime_window_->title());
 }
 
-gfx::ImageSkia ImeWindowView::GetWindowAppIcon() {
-  return GetWindowIcon();
-}
-
 gfx::ImageSkia ImeWindowView::GetWindowIcon() {
   return ime_window_->icon() ? ime_window_->icon()->image_skia()
                              : gfx::ImageSkia();
diff --git a/chrome/browser/ui/views/ime/ime_window_view.h b/chrome/browser/ui/views/ime/ime_window_view.h
index a3636878..63e16e2 100644
--- a/chrome/browser/ui/views/ime/ime_window_view.h
+++ b/chrome/browser/ui/views/ime/ime_window_view.h
@@ -67,7 +67,6 @@
   bool CanMaximize() const override;
   bool CanMinimize() const override;
   base::string16 GetWindowTitle() const override;
-  gfx::ImageSkia GetWindowAppIcon() override;
   gfx::ImageSkia GetWindowIcon() override;
   void DeleteDelegate() override;
 
diff --git a/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc b/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc
index 56499ea..7d133679 100644
--- a/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc
+++ b/chrome/browser/ui/views/profiles/avatar_toolbar_button.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/ui/views/profiles/avatar_toolbar_button.h"
 
+#include <vector>
+
 #include "build/build_config.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/app/vector_icons/vector_icons.h"
@@ -60,7 +62,6 @@
       browser_list_observer_(this),
       profile_observer_(this),
       identity_manager_observer_(this) {
-
   if (IsIncognitoCounterActive())
     browser_list_observer_.Add(BrowserList::GetInstance());
 
@@ -137,14 +138,14 @@
     text = l10n_util::GetPluralStringFUTF16(IDS_AVATAR_BUTTON_INCOGNITO,
                                             incognito_window_count);
   } else if (sync_state == SyncState::kError) {
-    color =
-        AdjustHighlightColorForContrast(gfx::kGoogleRed300, gfx::kGoogleRed600,
-                                        gfx::kGoogleRed050, gfx::kGoogleRed900);
+    color = AdjustHighlightColorForContrast(
+        GetThemeProvider(), gfx::kGoogleRed300, gfx::kGoogleRed600,
+        gfx::kGoogleRed050, gfx::kGoogleRed900);
     text = l10n_util::GetStringUTF16(IDS_AVATAR_BUTTON_SYNC_ERROR);
   } else if (sync_state == SyncState::kPaused) {
     color = AdjustHighlightColorForContrast(
-        gfx::kGoogleBlue300, gfx::kGoogleBlue600, gfx::kGoogleBlue050,
-        gfx::kGoogleBlue900);
+        GetThemeProvider(), gfx::kGoogleBlue300, gfx::kGoogleBlue600,
+        gfx::kGoogleBlue050, gfx::kGoogleBlue900);
 
     text = l10n_util::GetStringUTF16(IDS_AVATAR_BUTTON_SYNC_PAUSED);
   }
@@ -397,33 +398,3 @@
 
   SetLayoutInsetDelta(layout_insets);
 }
-
-SkColor AvatarToolbarButton::AdjustHighlightColorForContrast(
-    SkColor desired_dark_color,
-    SkColor desired_light_color,
-    SkColor dark_extreme,
-    SkColor light_extreme) const {
-  const ui::ThemeProvider* theme_provider = GetThemeProvider();
-  if (!theme_provider)
-    return desired_light_color;
-  SkColor toolbar_color =
-      GetThemeProvider()->GetColor(ThemeProperties::COLOR_TOOLBAR);
-  SkColor contrasting_color = color_utils::PickContrastingColor(
-      desired_dark_color, desired_light_color, toolbar_color);
-  SkColor limit =
-      contrasting_color == desired_dark_color ? dark_extreme : light_extreme;
-  // Setting highlight color will set the text to the highlight color, and the
-  // background to the same color with a low alpha. This means that our target
-  // contrast is between the text (the highlight color) and a blend of the
-  // highlight color and the toolbar color.
-  SkColor base_color = color_utils::AlphaBlend(contrasting_color, toolbar_color,
-                                               kToolbarButtonBackgroundAlpha);
-
-  // Add a fudge factor to the minimum contrast ratio since we'll actually be
-  // blending with the adjusted color.
-  SkAlpha blend_alpha = color_utils::GetBlendValueWithMinimumContrast(
-      contrasting_color, limit, base_color,
-      color_utils::kMinimumReadableContrastRatio * 1.05);
-
-  return color_utils::AlphaBlend(limit, contrasting_color, blend_alpha);
-}
diff --git a/chrome/browser/ui/views/profiles/avatar_toolbar_button.h b/chrome/browser/ui/views/profiles/avatar_toolbar_button.h
index 778ae58..ab399f1e 100644
--- a/chrome/browser/ui/views/profiles/avatar_toolbar_button.h
+++ b/chrome/browser/ui/views/profiles/avatar_toolbar_button.h
@@ -81,18 +81,6 @@
 
   void SetInsets();
 
-  // Chooses from |desired_dark_color| and |desired_light_color| based on
-  // whether the toolbar background is dark or light.
-  //
-  // If the resulting color will achieve sufficient contrast,
-  // returns it. Otherwise, blends it towards |dark_extreme| if it's light, or
-  // |dark_extreme| if it's dark until minimum contrast is achieved, and returns
-  // the result.
-  SkColor AdjustHighlightColorForContrast(SkColor desired_dark_color,
-                                          SkColor desired_light_color,
-                                          SkColor dark_extreme,
-                                          SkColor light_extreme) const;
-
   Browser* const browser_;
   Profile* const profile_;
 
diff --git a/chrome/browser/ui/views/profiles/avatar_toolbar_button_unittest.cc b/chrome/browser/ui/views/profiles/avatar_toolbar_button_unittest.cc
index dcc7c39..0b16ff7 100644
--- a/chrome/browser/ui/views/profiles/avatar_toolbar_button_unittest.cc
+++ b/chrome/browser/ui/views/profiles/avatar_toolbar_button_unittest.cc
@@ -30,8 +30,9 @@
   DCHECK_LT(color_utils::GetContrastRatio(highlight_color, toolbar_color),
             color_utils::kMinimumReadableContrastRatio);
 
-  SkColor result = button->AdjustHighlightColorForContrast(
-      highlight_color, highlight_color, SK_ColorBLACK, SK_ColorWHITE);
+  SkColor result = ToolbarButton::AdjustHighlightColorForContrast(
+      button->GetThemeProvider(), highlight_color, highlight_color,
+      SK_ColorBLACK, SK_ColorWHITE);
   EXPECT_GT(color_utils::GetContrastRatio(result, toolbar_color),
             color_utils::kMinimumReadableContrastRatio);
 }
diff --git a/chrome/browser/ui/views/profiles/profile_chooser_view.cc b/chrome/browser/ui/views/profiles/profile_chooser_view.cc
index ae6b3a2..591fb24 100644
--- a/chrome/browser/ui/views/profiles/profile_chooser_view.cc
+++ b/chrome/browser/ui/views/profiles/profile_chooser_view.cc
@@ -550,6 +550,7 @@
                               content_list_vert_spacing);
   layout->AddView(new views::Separator());
 
+  layout->AddPaddingRow(1.0, content_list_vert_spacing);
   users_button_ = new HoverButton(
       this, gfx::CreateVectorIcon(kCloseAllIcon, 16, gfx::kChromeIconGrey),
       l10n_util::GetStringUTF16(IDS_INCOGNITO_PROFILE_MENU_CLOSE_BUTTON));
@@ -558,6 +559,10 @@
                               content_list_vert_spacing);
   layout->AddView(users_button_);
 
+  const int small_vertical_spacing =
+      provider->GetDistanceMetric(DISTANCE_RELATED_CONTROL_VERTICAL_SMALL);
+  layout->AddPaddingRow(1.0, small_vertical_spacing);
+
   return view;
 }
 
diff --git a/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc b/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc
index 6225abc1..3534198 100644
--- a/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc
+++ b/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc
@@ -57,10 +57,13 @@
 
 namespace {
 
-// Button background and icon color for in-product help promos.
-// TODO(collinbaker): https://crbug.com/909747 handle themed toolbar colors, and
-// maybe move this into theme system.
-constexpr SkColor kFeaturePromoHighlightColor = gfx::kGoogleBlue600;
+// Button background and icon colors for in-product help promos. The first is
+// the preferred color, but the selected color depends on the
+// background. TODO(collinbaker): consider moving these into theme system.
+constexpr SkColor kFeaturePromoHighlightDarkColor = gfx::kGoogleBlue600;
+constexpr SkColor kFeaturePromoHighlightDarkExtremeColor = gfx::kGoogleBlue900;
+constexpr SkColor kFeaturePromoHighlightLightColor = gfx::kGoogleGrey100;
+constexpr SkColor kFeaturePromoHighlightLightExtremeColor = SK_ColorWHITE;
 
 // Cycle duration of ink drop pulsing animation used for in-product help.
 constexpr base::TimeDelta kFeaturePromoPulseDuration =
@@ -252,7 +255,7 @@
           ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON);
 #if BUILDFLAG(ENABLE_DESKTOP_IN_PRODUCT_HELP)
       if (promo_feature_)
-        severity_color = kFeaturePromoHighlightColor;
+        severity_color = GetPromoHighlightColor();
 #endif
       break;
     case AppMenuIconController::Severity::LOW:
@@ -317,6 +320,15 @@
     SetBorder(views::CreateEmptyBorder(new_insets));
 }
 
+#if BUILDFLAG(ENABLE_DESKTOP_IN_PRODUCT_HELP)
+SkColor BrowserAppMenuButton::GetPromoHighlightColor() const {
+  return ToolbarButton::AdjustHighlightColorForContrast(
+      GetThemeProvider(), kFeaturePromoHighlightDarkColor,
+      kFeaturePromoHighlightLightColor, kFeaturePromoHighlightDarkExtremeColor,
+      kFeaturePromoHighlightLightExtremeColor);
+}
+#endif
+
 gfx::Rect BrowserAppMenuButton::GetAnchorBoundsInScreen() const {
   gfx::Rect bounds = GetBoundsInScreen();
   gfx::Insets insets =
@@ -395,7 +407,7 @@
 SkColor BrowserAppMenuButton::GetInkDropBaseColor() const {
 #if BUILDFLAG(ENABLE_DESKTOP_IN_PRODUCT_HELP)
   if (promo_feature_)
-    return kFeaturePromoHighlightColor;
+    return GetPromoHighlightColor();
 #endif
   return AppMenuButton::GetInkDropBaseColor();
 }
diff --git a/chrome/browser/ui/views/toolbar/browser_app_menu_button.h b/chrome/browser/ui/views/toolbar/browser_app_menu_button.h
index f6a04e1..74775f4 100644
--- a/chrome/browser/ui/views/toolbar/browser_app_menu_button.h
+++ b/chrome/browser/ui/views/toolbar/browser_app_menu_button.h
@@ -70,6 +70,11 @@
  private:
   void UpdateBorder();
 
+#if BUILDFLAG(ENABLE_DESKTOP_IN_PRODUCT_HELP)
+  // Picks the best promo color given the current background color.
+  SkColor GetPromoHighlightColor() const;
+#endif
+
   // AppMenuButton:
   const char* GetClassName() const override;
   bool GetDropFormats(int* formats,
diff --git a/chrome/browser/ui/views/toolbar/toolbar_button.cc b/chrome/browser/ui/views/toolbar/toolbar_button.cc
index c4ecccd..dcff72c 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_button.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_button.cc
@@ -11,6 +11,7 @@
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.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/layout_constants.h"
@@ -22,6 +23,7 @@
 #include "ui/base/models/menu_model.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
+#include "ui/gfx/color_utils.h"
 #include "ui/views/animation/ink_drop.h"
 #include "ui/views/animation/ink_drop_highlight.h"
 #include "ui/views/background.h"
@@ -265,6 +267,37 @@
   ShowDropDownMenu(source_type);
 }
 
+// static
+SkColor ToolbarButton::AdjustHighlightColorForContrast(
+    const ui::ThemeProvider* theme_provider,
+    SkColor desired_dark_color,
+    SkColor desired_light_color,
+    SkColor dark_extreme,
+    SkColor light_extreme) {
+  if (!theme_provider)
+    return desired_light_color;
+  const SkColor toolbar_color =
+      theme_provider->GetColor(ThemeProperties::COLOR_TOOLBAR);
+  const SkColor contrasting_color = color_utils::PickContrastingColor(
+      desired_dark_color, desired_light_color, toolbar_color);
+  const SkColor limit =
+      contrasting_color == desired_dark_color ? dark_extreme : light_extreme;
+  // Setting highlight color will set the text to the highlight color, and the
+  // background to the same color with a low alpha. This means that our target
+  // contrast is between the text (the highlight color) and a blend of the
+  // highlight color and the toolbar color.
+  const SkColor base_color = color_utils::AlphaBlend(
+      contrasting_color, toolbar_color, kToolbarButtonBackgroundAlpha);
+
+  // Add a fudge factor to the minimum contrast ratio since we'll actually be
+  // blending with the adjusted color.
+  const SkAlpha blend_alpha = color_utils::GetBlendValueWithMinimumContrast(
+      contrasting_color, limit, base_color,
+      color_utils::kMinimumReadableContrastRatio * 1.05);
+
+  return color_utils::AlphaBlend(limit, contrasting_color, blend_alpha);
+}
+
 bool ToolbarButton::ShouldShowMenu() {
   return model_ != nullptr;
 }
diff --git a/chrome/browser/ui/views/toolbar/toolbar_button.h b/chrome/browser/ui/views/toolbar/toolbar_button.h
index d548418..c91d68d 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_button.h
+++ b/chrome/browser/ui/views/toolbar/toolbar_button.h
@@ -9,6 +9,7 @@
 
 #include "base/macros.h"
 #include "base/optional.h"
+#include "ui/base/theme_provider.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/views/context_menu_controller.h"
 #include "ui/views/controls/button/button.h"
@@ -89,6 +90,20 @@
 
   ui::MenuModel* menu_model_for_test() { return model_.get(); }
 
+  // Chooses from |desired_dark_color| and |desired_light_color| based on
+  // whether the toolbar background is dark or light.
+  //
+  // If the resulting color will achieve sufficient contrast,
+  // returns it. Otherwise, blends it towards |dark_extreme| if it's light, or
+  // |dark_extreme| if it's dark until minimum contrast is achieved, and returns
+  // the result.
+  static SkColor AdjustHighlightColorForContrast(
+      const ui::ThemeProvider* theme_provider,
+      SkColor desired_dark_color,
+      SkColor desired_light_color,
+      SkColor dark_extreme,
+      SkColor light_extreme);
+
  protected:
   // Returns if menu should be shown. Override this to change default behavior.
   virtual bool ShouldShowMenu();
diff --git a/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc b/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc
index f6cec78..2cbdfee 100644
--- a/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc
+++ b/chrome/browser/ui/webui/chromeos/login/oobe_ui.cc
@@ -7,6 +7,7 @@
 #include <stddef.h>
 
 #include <memory>
+#include <string>
 #include <utility>
 
 #include "ash/public/cpp/ash_features.h"
@@ -23,6 +24,7 @@
 #include "chrome/browser/browser_process_platform_part.h"
 #include "chrome/browser/chromeos/login/enrollment/auto_enrollment_check_screen_view.h"
 #include "chrome/browser/chromeos/login/enrollment/enrollment_screen_view.h"
+#include "chrome/browser/chromeos/login/quick_unlock/quick_unlock_utils.h"
 #include "chrome/browser/chromeos/login/screens/demo_preferences_screen_view.h"
 #include "chrome/browser/chromeos/login/screens/demo_setup_screen_view.h"
 #include "chrome/browser/chromeos/login/screens/error_screen.h"
@@ -103,6 +105,7 @@
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/base/ui_base_features.h"
 #include "ui/base/webui/web_ui_util.h"
+#include "ui/chromeos/resources/grit/ui_chromeos_resources.h"
 #include "ui/display/display.h"
 #include "ui/events/devices/input_device.h"
 #include "ui/events/devices/input_device_manager.h"
@@ -196,6 +199,16 @@
   source->AddResourcePath(kEnrollmentJSPath, IDR_OOBE_ENROLLMENT_JS);
 }
 
+void AddFingerprintResources(content::WebUIDataSource* source) {
+  if (quick_unlock::IsFingerprintReaderOnKeyboard()) {
+    source->AddResourcePath("fingerprint_scanner_animation.png",
+                            IDR_LOGIN_FINGERPRINT_SCANNER_LAPTOP_ANIMATION);
+  } else {
+    source->AddResourcePath("fingerprint_scanner_animation.png",
+                            IDR_LOGIN_FINGERPRINT_SCANNER_TABLET_ANIMATION);
+  }
+}
+
 // Default and non-shared resource definition for kOobeDisplay display type.
 // chrome://oobe/oobe
 void AddOobeDisplayTypeDefaultResources(content::WebUIDataSource* source) {
@@ -251,6 +264,7 @@
   // Configure shared resources
   AddProductLogoResources(source);
 
+  AddFingerprintResources(source);
   AddSyncConsentResources(source);
   AddArcScreensResources(source);
   AddEnterpriseEnrollmentResources(source);
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler.cc b/chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler.cc
index 1339b8c..20bc089 100644
--- a/chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler.cc
@@ -140,13 +140,9 @@
                              keyboards_state.has_external_non_apple_keyboard ||
                              !base::CommandLine::ForCurrentProcess()->HasSwitch(
                                  chromeos::switches::kHasChromeOSKeyboard);
-  const bool has_diamond_key =
-      base::CommandLine::ForCurrentProcess()->HasSwitch(
-          chromeos::switches::kHasChromeOSDiamondKey);
 
   base::Value keyboard_params(base::Value::Type::DICTIONARY);
   keyboard_params.SetKey("showCapsLock", base::Value(has_caps_lock));
-  keyboard_params.SetKey("showDiamondKey", base::Value(has_diamond_key));
   keyboard_params.SetKey(
       "showExternalMetaKey",
       base::Value(keyboards_state.has_external_non_apple_keyboard));
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler_unittest.cc b/chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler_unittest.cc
index 2b82878..c43acae1 100644
--- a/chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler_unittest.cc
@@ -49,7 +49,6 @@
   // which keys should be shown. False is returned if the message was invalid or
   // not found.
   bool GetLastShowKeysChangedMessage(bool* has_caps_lock_out,
-                                     bool* has_diamond_key_out,
                                      bool* has_external_meta_key_out,
                                      bool* has_apple_command_key_out,
                                      bool* has_internal_search_out)
@@ -72,7 +71,6 @@
       const base::Value* keyboard_params = data->arg2();
       const std::vector<std::pair<std::string, bool*>> path_to_out_param = {
           {"showCapsLock", has_caps_lock_out},
-          {"showDiamondKey", has_diamond_key_out},
           {"showExternalMetaKey", has_external_meta_key_out},
           {"showAppleCommandKey", has_apple_command_key_out},
           {"hasInternalKeyboard", has_internal_search_out},
@@ -98,35 +96,21 @@
     bool has_caps_lock = false;
     bool ignored = false;
     if (!GetLastShowKeysChangedMessage(&has_caps_lock, &ignored, &ignored,
-                                       &ignored, &ignored)) {
+                                       &ignored)) {
       ADD_FAILURE() << "Didn't get " << KeyboardHandler::kShowKeysChangedName;
       return false;
     }
     return has_caps_lock;
   }
 
-  // Returns true if the last keys-changed message reported that a "diamond" key
-  // is present and false otherwise. A failure is added if a message wasn't
-  // found.
-  bool HasDiamondKey() {
-    bool has_diamond_key = false;
-    bool ignored = false;
-    if (!GetLastShowKeysChangedMessage(&ignored, &has_diamond_key, &ignored,
-                                       &ignored, &ignored)) {
-      ADD_FAILURE() << "Didn't get " << KeyboardHandler::kShowKeysChangedName;
-      return false;
-    }
-    return has_diamond_key;
-  }
-
   // Returns true if the last keys-changed message reported that a Meta key on
   // an external keyboard is present and false otherwise. A failure is added if
   // a message wasn't found.
   bool HasExternalMetaKey() {
     bool has_external_meta = false;
     bool ignored = false;
-    if (!GetLastShowKeysChangedMessage(&ignored, &ignored, &has_external_meta,
-                                       &ignored, &ignored)) {
+    if (!GetLastShowKeysChangedMessage(&ignored, &has_external_meta, &ignored,
+                                       &ignored)) {
       ADD_FAILURE() << "Didn't get " << KeyboardHandler::kShowKeysChangedName;
       return false;
     }
@@ -139,7 +123,7 @@
   bool HasAppleCommandKey() {
     bool has_apple_command_key = false;
     bool ignored = false;
-    if (!GetLastShowKeysChangedMessage(&ignored, &ignored, &ignored,
+    if (!GetLastShowKeysChangedMessage(&ignored, &ignored,
                                        &has_apple_command_key, &ignored)) {
       ADD_FAILURE() << "Didn't get " << KeyboardHandler::kShowKeysChangedName;
       return false;
@@ -153,7 +137,7 @@
   bool HasInternalSearchKey() {
     bool has_internal_search_key = false;
     bool ignored = false;
-    if (!GetLastShowKeysChangedMessage(&ignored, &ignored, &ignored, &ignored,
+    if (!GetLastShowKeysChangedMessage(&ignored, &ignored, &ignored,
                                        &has_internal_search_key)) {
       ADD_FAILURE() << "Didn't get " << KeyboardHandler::kShowKeysChangedName;
       return false;
@@ -177,7 +161,6 @@
   handler_test_api_.Initialize();
   EXPECT_FALSE(HasInternalSearchKey());
   EXPECT_FALSE(HasCapsLock());
-  EXPECT_FALSE(HasDiamondKey());
   EXPECT_FALSE(HasExternalMetaKey());
   EXPECT_FALSE(HasAppleCommandKey());
 }
@@ -188,7 +171,6 @@
   handler_test_api_.Initialize();
   EXPECT_FALSE(HasInternalSearchKey());
   EXPECT_TRUE(HasCapsLock());
-  EXPECT_FALSE(HasDiamondKey());
   EXPECT_FALSE(HasExternalMetaKey());
   EXPECT_FALSE(HasAppleCommandKey());
 }
@@ -202,7 +184,6 @@
   handler_test_api_.Initialize();
   EXPECT_TRUE(HasInternalSearchKey());
   EXPECT_FALSE(HasCapsLock());
-  EXPECT_FALSE(HasDiamondKey());
   EXPECT_FALSE(HasExternalMetaKey());
   EXPECT_FALSE(HasAppleCommandKey());
 
@@ -213,7 +194,6 @@
       {2, ui::INPUT_DEVICE_USB, "external keyboard"}});
   EXPECT_TRUE(HasInternalSearchKey());
   EXPECT_TRUE(HasCapsLock());
-  EXPECT_FALSE(HasDiamondKey());
   EXPECT_TRUE(HasExternalMetaKey());
   EXPECT_FALSE(HasAppleCommandKey());
 
@@ -224,7 +204,6 @@
       {3, ui::INPUT_DEVICE_USB, "Apple Inc. Apple Keyboard"}});
   EXPECT_TRUE(HasInternalSearchKey());
   EXPECT_TRUE(HasCapsLock());
-  EXPECT_FALSE(HasDiamondKey());
   EXPECT_FALSE(HasExternalMetaKey());
   EXPECT_TRUE(HasAppleCommandKey());
 
@@ -235,7 +214,6 @@
       {3, ui::INPUT_DEVICE_USB, "Apple Inc. Apple Keyboard"}});
   EXPECT_FALSE(HasInternalSearchKey());
   EXPECT_TRUE(HasCapsLock());
-  EXPECT_FALSE(HasDiamondKey());
   EXPECT_TRUE(HasExternalMetaKey());
   EXPECT_TRUE(HasAppleCommandKey());
 
@@ -247,7 +225,6 @@
       {4, ui::INPUT_DEVICE_USB, "Topre Corporation Realforce 87"}});
   EXPECT_FALSE(HasInternalSearchKey());
   EXPECT_TRUE(HasCapsLock());
-  EXPECT_FALSE(HasDiamondKey());
   EXPECT_TRUE(HasExternalMetaKey());
   EXPECT_FALSE(HasAppleCommandKey());
 
@@ -255,19 +232,6 @@
   input_device_client_test_api_.SetKeyboardDevices({});
   EXPECT_FALSE(HasInternalSearchKey());
   EXPECT_FALSE(HasCapsLock());
-  EXPECT_FALSE(HasDiamondKey());
-  EXPECT_FALSE(HasExternalMetaKey());
-  EXPECT_FALSE(HasAppleCommandKey());
-}
-
-TEST_F(KeyboardHandlerTest, DiamondKey) {
-  base::CommandLine::ForCurrentProcess()->AppendSwitch(
-      chromeos::switches::kHasChromeOSKeyboard);
-  base::CommandLine::ForCurrentProcess()->AppendSwitch(
-      chromeos::switches::kHasChromeOSDiamondKey);
-  handler_test_api_.Initialize();
-  EXPECT_FALSE(HasCapsLock());
-  EXPECT_TRUE(HasDiamondKey());
   EXPECT_FALSE(HasExternalMetaKey());
   EXPECT_FALSE(HasAppleCommandKey());
 }
diff --git a/chrome/browser/ui/webui/settings/md_settings_ui.cc b/chrome/browser/ui/webui/settings/md_settings_ui.cc
index 99f3ab4..291fbdf2 100644
--- a/chrome/browser/ui/webui/settings/md_settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/md_settings_ui.cc
@@ -113,6 +113,7 @@
 #include "chromeos/services/multidevice_setup/public/cpp/prefs.h"
 #include "components/arc/arc_util.h"
 #include "ui/base/ui_base_features.h"
+#include "ui/chromeos/resources/grit/ui_chromeos_resources.h"
 #else  // !defined(OS_CHROMEOS)
 #include "chrome/browser/signin/account_consistency_mode_manager.h"
 #include "chrome/browser/ui/webui/settings/settings_default_browser_handler.h"
@@ -312,9 +313,15 @@
   html_source->AddBoolean(
       "quickUnlockDisabledByPolicy",
       chromeos::quick_unlock::IsPinDisabledByPolicy(profile->GetPrefs()));
-  html_source->AddBoolean(
-      "fingerprintUnlockEnabled",
-      chromeos::quick_unlock::IsFingerprintEnabled(profile));
+  const bool fingerprint_unlock_enabled =
+      chromeos::quick_unlock::IsFingerprintEnabled(profile);
+  html_source->AddBoolean("fingerprintUnlockEnabled",
+                          fingerprint_unlock_enabled);
+  if (fingerprint_unlock_enabled) {
+    html_source->AddBoolean(
+        "isFingerprintReaderOnKeyboard",
+        chromeos::quick_unlock::IsFingerprintReaderOnKeyboard());
+  }
   html_source->AddBoolean("lockScreenNotificationsEnabled",
                           ash::features::IsLockScreenNotificationsEnabled());
   html_source->AddBoolean(
diff --git a/chrome/browser/ui/webui/settings/people_handler_unittest.cc b/chrome/browser/ui/webui/settings/people_handler_unittest.cc
index 258d75cb..e4b50c4 100644
--- a/chrome/browser/ui/webui/settings/people_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/people_handler_unittest.cc
@@ -443,8 +443,8 @@
       .WillByDefault(Return(true));
   // Sync engine is stopped initially, and will start up.
   ON_CALL(*mock_pss_, GetTransportState())
-      .WillByDefault(Return(
-          syncer::SyncService::TransportState::WAITING_FOR_START_REQUEST));
+      .WillByDefault(
+          Return(syncer::SyncService::TransportState::START_DEFERRED));
   EXPECT_CALL(*mock_pss_->GetUserSettingsMock(), SetSyncRequested(true));
   SetDefaultExpectationsForConfigPage();
 
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler.cc b/chrome/browser/ui/webui/settings/site_settings_handler.cc
index 4d946eab..3a626436 100644
--- a/chrome/browser/ui/webui/settings/site_settings_handler.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_handler.cc
@@ -473,6 +473,7 @@
   // Site Details Page does not display the number of cookies for the origin.
   const CookieTreeNode* root = cookies_tree_model_->GetRoot();
   std::string usage_string = "";
+  std::string cookie_string = "";
   for (int i = 0; i < root->child_count(); ++i) {
     const CookieTreeNode* site = root->GetChild(i);
     std::string title = base::UTF16ToUTF8(site->GetTitle());
@@ -481,10 +482,16 @@
     int64_t size = site->InclusiveSize();
     if (size != 0)
       usage_string = base::UTF16ToUTF8(ui::FormatBytes(size));
+    int num_cookies = site->NumberOfCookies();
+    if (num_cookies != 0) {
+      cookie_string = base::UTF16ToUTF8(l10n_util::GetPluralStringFUTF16(
+          IDS_SETTINGS_SITE_SETTINGS_NUM_COOKIES, num_cookies));
+    }
     break;
   }
   CallJavascriptFunction("settings.WebsiteUsagePrivateApi.returnUsageTotal",
-                         base::Value(usage_host_), base::Value(usage_string));
+                         base::Value(usage_host_), base::Value(usage_string),
+                         base::Value(cookie_string));
 }
 
 #if defined(OS_CHROMEOS)
@@ -586,7 +593,7 @@
     cookies_tree_model_->RemoveCookiesTreeObserver(this);
     cookies_tree_model_.reset();
   }
-  EnsureCookiesTreeModelCreated(/*omit_cookies=*/true);
+  EnsureCookiesTreeModelCreated();
 }
 
 void SiteSettingsHandler::HandleClearUsage(
@@ -783,12 +790,12 @@
         origin_info.SetKey("usage", base::Value(size_info_it->second));
       const auto& origin_cookie_num_it =
           origin_cookie_map.find(GURL(origin).host());
-      // Add cookies numbers for origins that isn't an eTLD+1.
-      if (GURL(origin).host() != etld_plus1 &&
-          origin_cookie_num_it != origin_cookie_map.end()) {
+      if (origin_cookie_num_it != origin_cookie_map.end()) {
         origin_info.SetKey(kNumCookies,
                            base::Value(origin_cookie_num_it->second));
-        cookie_num += origin_cookie_num_it->second;
+        // Add cookies numbers for origins that isn't an eTLD+1.
+        if (GURL(origin).host() != etld_plus1)
+          cookie_num += origin_cookie_num_it->second;
       }
     }
     site_group.SetKey(kNumCookies, base::Value(cookie_num));
@@ -1348,11 +1355,10 @@
   profile_->GetPrefs()->SetBoolean(prefs::kBlockAutoplayEnabled, value);
 }
 
-void SiteSettingsHandler::EnsureCookiesTreeModelCreated(bool omit_cookies) {
+void SiteSettingsHandler::EnsureCookiesTreeModelCreated() {
   if (cookies_tree_model_)
     return;
-  cookies_tree_model_ =
-      CookiesTreeModel::CreateForProfile(profile_, omit_cookies);
+  cookies_tree_model_ = CookiesTreeModel::CreateForProfile(profile_);
   cookies_tree_model_->AddCookiesTreeObserver(this);
 }
 
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler.h b/chrome/browser/ui/webui/settings/site_settings_handler.h
index ec98186..3bcce17e 100644
--- a/chrome/browser/ui/webui/settings/site_settings_handler.h
+++ b/chrome/browser/ui/webui/settings/site_settings_handler.h
@@ -123,7 +123,7 @@
                            HandleClearEtldPlus1DataAndCookies);
 
   // Creates the CookiesTreeModel if necessary.
-  void EnsureCookiesTreeModelCreated(bool omit_cookies = false);
+  void EnsureCookiesTreeModelCreated();
 
   // Add or remove this class as an observer for content settings and chooser
   // contexts corresponding to |profile|.
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc b/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
index 78b07c19..9f481b1a 100644
--- a/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
@@ -778,7 +778,7 @@
             origin_info->FindKey("origin")->GetString());
   EXPECT_EQ(0, origin_info->FindKey("engagement")->GetDouble());
   EXPECT_EQ(0, origin_info->FindKey("usage")->GetDouble());
-  EXPECT_EQ(0, origin_info->FindKey("numCookies")->GetDouble());
+  EXPECT_EQ(1, origin_info->FindKey("numCookies")->GetDouble());
 }
 
 TEST_F(SiteSettingsHandlerTest, Origins) {
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn
index c7a8750..0dd334a8 100644
--- a/chrome/browser/web_applications/BUILD.gn
+++ b/chrome/browser/web_applications/BUILD.gn
@@ -58,6 +58,8 @@
   testonly = true
 
   sources = [
+    "test/test_app_registrar.cc",
+    "test/test_app_registrar.h",
     "test/test_data_retriever.cc",
     "test/test_data_retriever.h",
     "test/test_file_utils.cc",
diff --git a/chrome/browser/web_applications/components/app_registrar.h b/chrome/browser/web_applications/components/app_registrar.h
index 561f627..21a3c27f 100644
--- a/chrome/browser/web_applications/components/app_registrar.h
+++ b/chrome/browser/web_applications/components/app_registrar.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_APP_REGISTRAR_H_
 #define CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_APP_REGISTRAR_H_
 
+#include "base/callback_forward.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
 
 namespace web_app {
@@ -22,6 +23,20 @@
   // user. For example, if a user uninstalls a default app ('default apps' are
   // considered external apps), then this will return true.
   virtual bool WasExternalAppUninstalledByUser(const AppId& app_id) const = 0;
+
+  // Returns true if the app has a scope (some legacy apps don't). See
+  // GetScopeUrlForApp() for more context about scope.
+  virtual bool HasScopeUrl(const AppId& app_id) const = 0;
+
+  // Scope is the navigation scope of this WebApp. This means all URLs that the
+  // app considers part of itself.
+  //
+  // Some legacy apps don't have a scope and returning an empty GURL for them
+  // could lead to incorrecly thinking some URLs are in-scope of the app. To
+  // avoid this, this method CHECKs if the app doesn't have a scope. Callers can
+  // use HasScopeUrl() to know if the app has a scope before calling this
+  // method.
+  virtual GURL GetScopeUrlForApp(const AppId& app_id) const = 0;
 };
 
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_registrar.cc b/chrome/browser/web_applications/extensions/bookmark_app_registrar.cc
index 8eaaa0e..ec66545b 100644
--- a/chrome/browser/web_applications/extensions/bookmark_app_registrar.cc
+++ b/chrome/browser/web_applications/extensions/bookmark_app_registrar.cc
@@ -7,7 +7,9 @@
 #include <utility>
 
 #include "base/callback_helpers.h"
+#include "chrome/browser/extensions/convert_web_app.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/common/extensions/api/url_handlers/url_handlers_parser.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
@@ -26,9 +28,7 @@
 }
 
 bool BookmarkAppRegistrar::IsInstalled(const web_app::AppId& app_id) const {
-  auto* app =
-      ExtensionRegistry::Get(profile_)->enabled_extensions().GetByID(app_id);
-  return app != nullptr;
+  return GetExtension(app_id) != nullptr;
 }
 
 bool BookmarkAppRegistrar::WasExternalAppUninstalledByUser(
@@ -36,4 +36,32 @@
   return ExtensionPrefs::Get(profile_)->IsExternalExtensionUninstalled(app_id);
 }
 
+bool BookmarkAppRegistrar::HasScopeUrl(const web_app::AppId& app_id) const {
+  const auto* extension = GetExtension(app_id);
+  DCHECK(extension);
+
+  if (!extension->from_bookmark())
+    return false;
+
+  return UrlHandlers::GetUrlHandlers(extension) != nullptr;
+}
+
+GURL BookmarkAppRegistrar::GetScopeUrlForApp(
+    const web_app::AppId& app_id) const {
+  // Returning an empty GURL for a Bookmark App that doesn't have a scope, could
+  // lead to incorrecly thinking some URLs are in-scope of the app. To avoid
+  // this, CHECK that the Bookmark App has a scope. Callers can use
+  // HasScopeUrl() to know if they can call this method.
+  CHECK(HasScopeUrl(app_id));
+
+  GURL scope_url = GetScopeURLFromBookmarkApp(GetExtension(app_id));
+  CHECK(scope_url.is_valid());
+  return scope_url;
+}
+
+const Extension* BookmarkAppRegistrar::GetExtension(
+    const web_app::AppId& app_id) const {
+  return ExtensionRegistry::Get(profile_)->enabled_extensions().GetByID(app_id);
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/web_applications/extensions/bookmark_app_registrar.h b/chrome/browser/web_applications/extensions/bookmark_app_registrar.h
index fb3b1a9b..f31fe1d 100644
--- a/chrome/browser/web_applications/extensions/bookmark_app_registrar.h
+++ b/chrome/browser/web_applications/extensions/bookmark_app_registrar.h
@@ -12,6 +12,8 @@
 
 namespace extensions {
 
+class Extension;
+
 class BookmarkAppRegistrar : public web_app::AppRegistrar {
  public:
   explicit BookmarkAppRegistrar(Profile* profile);
@@ -22,8 +24,12 @@
   bool IsInstalled(const web_app::AppId& app_id) const override;
   bool WasExternalAppUninstalledByUser(
       const web_app::AppId& app_id) const override;
+  bool HasScopeUrl(const web_app::AppId& app_id) const override;
+  GURL GetScopeUrlForApp(const web_app::AppId& app_id) const override;
 
  private:
+  const Extension* GetExtension(const web_app::AppId& app_id) const;
+
   Profile* profile_;
 };
 
diff --git a/chrome/browser/web_applications/extensions/pending_bookmark_app_manager_unittest.cc b/chrome/browser/web_applications/extensions/pending_bookmark_app_manager_unittest.cc
index 23b0d63..ea4a106 100644
--- a/chrome/browser/web_applications/extensions/pending_bookmark_app_manager_unittest.cc
+++ b/chrome/browser/web_applications/extensions/pending_bookmark_app_manager_unittest.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/web_applications/components/web_app_constants.h"
 #include "chrome/browser/web_applications/extensions/bookmark_app_installation_task.h"
 #include "chrome/browser/web_applications/extensions/bookmark_app_registrar.h"
+#include "chrome/browser/web_applications/test/test_app_registrar.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/crx_file/id_util.h"
@@ -118,15 +119,15 @@
   DISALLOW_COPY_AND_ASSIGN(TestExtensionRegistryObserver);
 };
 
-}  // namespace
-
 class TestBookmarkAppInstallationTask : public BookmarkAppInstallationTask {
  public:
   TestBookmarkAppInstallationTask(Profile* profile,
+                                  web_app::TestAppRegistrar* registrar,
                                   web_app::PendingAppManager::AppInfo app_info,
                                   bool succeeds)
       : BookmarkAppInstallationTask(profile, std::move(app_info)),
         profile_(profile),
+        registrar_(registrar),
         succeeds_(succeeds),
         extension_ids_map_(profile_->GetPrefs()) {}
   ~TestBookmarkAppInstallationTask() override = default;
@@ -142,6 +143,7 @@
           CreateDummyExtension(app_id));
       extension_ids_map_.Insert(app_info().url, app_id,
                                 app_info().install_source);
+      registrar_->AddAsInstalled(app_id);
     }
 
     std::move(on_install_called_).Run();
@@ -155,6 +157,7 @@
 
  private:
   Profile* profile_;
+  web_app::TestAppRegistrar* registrar_;
   bool succeeds_;
   web_app::ExtensionIdsMap extension_ids_map_;
 
@@ -163,6 +166,8 @@
   DISALLOW_COPY_AND_ASSIGN(TestBookmarkAppInstallationTask);
 };
 
+}  // namespace
+
 class PendingBookmarkAppManagerTest : public ChromeRenderViewHostTestHarness {
  public:
   PendingBookmarkAppManagerTest()
@@ -192,7 +197,7 @@
         std::make_unique<TestExtensionRegistryObserver>(
             ExtensionRegistry::Get(profile()));
 
-    registrar_ = std::make_unique<extensions::BookmarkAppRegistrar>(profile());
+    registrar_ = std::make_unique<web_app::TestAppRegistrar>();
   }
 
   void TearDown() override {
@@ -213,7 +218,7 @@
       web_app::PendingAppManager::AppInfo app_info,
       bool succeeds) {
     auto task = std::make_unique<TestBookmarkAppInstallationTask>(
-        profile, std::move(app_info), succeeds);
+        profile, registrar_.get(), std::move(app_info), succeeds);
     auto* task_ptr = task.get();
     task->SetOnInstallCalled(base::BindLambdaForTesting([task_ptr, this]() {
       ++installation_task_run_count_;
@@ -328,6 +333,8 @@
     return test_extension_registry_observer_->uninstalled_extension_ids();
   }
 
+  web_app::TestAppRegistrar* registrar() { return registrar_.get(); }
+
  private:
   content::WebContentsTester* web_contents_tester_ = nullptr;
   base::Optional<GURL> install_callback_url_;
@@ -344,7 +351,7 @@
   PendingBookmarkAppManager::TaskFactory successful_installation_task_creator_;
   PendingBookmarkAppManager::TaskFactory failing_installation_task_creator_;
 
-  std::unique_ptr<web_app::AppRegistrar> registrar_;
+  std::unique_ptr<web_app::TestAppRegistrar> registrar_;
 
   DISALLOW_COPY_AND_ASSIGN(PendingBookmarkAppManagerTest);
 };
@@ -942,7 +949,7 @@
 
   // Simulate the extension for the app getting uninstalled.
   const std::string app_id = GenerateFakeAppId(GURL(kFooWebAppUrl));
-  ExtensionRegistry::Get(profile())->RemoveEnabled(app_id);
+  registrar()->RemoveAsInstalled(app_id);
 
   // Trying to uninstall the app should fail and have no effect.
   pending_app_manager->UninstallApps(
@@ -985,9 +992,8 @@
 
   // Simulate external extension for the app getting uninstalled by the user.
   const std::string app_id = GenerateFakeAppId(GURL(kFooWebAppUrl));
-  ExtensionRegistry::Get(profile())->RemoveEnabled(app_id);
-  ExtensionPrefs::Get(profile())->OnExtensionUninstalled(
-      app_id, Manifest::EXTERNAL_POLICY, false /* external_uninstall */);
+  registrar()->AddAsExternalAppUninstalledByUser(app_id);
+  registrar()->RemoveAsInstalled(app_id);
 
   // Trying to uninstall the app should fail and have no effect.
   pending_app_manager->UninstallApps(
diff --git a/chrome/browser/web_applications/test/test_app_registrar.cc b/chrome/browser/web_applications/test/test_app_registrar.cc
new file mode 100644
index 0000000..bae4cb2b
--- /dev/null
+++ b/chrome/browser/web_applications/test/test_app_registrar.cc
@@ -0,0 +1,52 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/web_applications/test/test_app_registrar.h"
+
+#include "base/callback.h"
+#include "base/stl_util.h"
+#include "url/gurl.h"
+
+namespace web_app {
+
+TestAppRegistrar::TestAppRegistrar() = default;
+
+TestAppRegistrar::~TestAppRegistrar() = default;
+
+void TestAppRegistrar::AddAsInstalled(const AppId& app_id) {
+  installed_apps_.insert(app_id);
+}
+
+void TestAppRegistrar::RemoveAsInstalled(const AppId& app_id) {
+  DCHECK(base::ContainsKey(installed_apps_, app_id));
+  installed_apps_.erase(app_id);
+}
+
+void TestAppRegistrar::AddAsExternalAppUninstalledByUser(const AppId& app_id) {
+  DCHECK(!base::ContainsKey(uninstalled_external_apps_, app_id));
+  uninstalled_external_apps_.insert(app_id);
+}
+
+void TestAppRegistrar::Init(base::OnceClosure callback) {}
+
+bool TestAppRegistrar::IsInstalled(const AppId& app_id) const {
+  return base::ContainsKey(installed_apps_, app_id);
+}
+
+bool TestAppRegistrar::WasExternalAppUninstalledByUser(
+    const AppId& app_id) const {
+  return base::ContainsKey(uninstalled_external_apps_, app_id);
+}
+
+bool TestAppRegistrar::HasScopeUrl(const AppId& app_id) const {
+  NOTIMPLEMENTED();
+  return false;
+}
+
+GURL TestAppRegistrar::GetScopeUrlForApp(const AppId& app_id) const {
+  NOTIMPLEMENTED();
+  return GURL();
+}
+
+}  // namespace web_app
diff --git a/chrome/browser/web_applications/test/test_app_registrar.h b/chrome/browser/web_applications/test/test_app_registrar.h
new file mode 100644
index 0000000..14bb9bc
--- /dev/null
+++ b/chrome/browser/web_applications/test/test_app_registrar.h
@@ -0,0 +1,43 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_WEB_APPLICATIONS_TEST_TEST_APP_REGISTRAR_H_
+#define CHROME_BROWSER_WEB_APPLICATIONS_TEST_TEST_APP_REGISTRAR_H_
+
+#include <set>
+
+#include "chrome/browser/web_applications/components/app_registrar.h"
+#include "chrome/browser/web_applications/components/web_app_helpers.h"
+
+namespace web_app {
+
+class TestAppRegistrar : public AppRegistrar {
+ public:
+  TestAppRegistrar();
+  ~TestAppRegistrar() override;
+
+  // Adds |app_id| to the map of installed apps.
+  void AddAsInstalled(const AppId& app_id);
+
+  // Removes |app_id| from the map of installed apps.
+  void RemoveAsInstalled(const AppId& app_id);
+
+  // Adds |app_id| to the map of external extensions uninstalled by the user.
+  void AddAsExternalAppUninstalledByUser(const AppId& app_id);
+
+  // AppRegistrar
+  void Init(base::OnceClosure callback) override;
+  bool IsInstalled(const AppId& app_id) const override;
+  bool WasExternalAppUninstalledByUser(const AppId& app_id) const override;
+  bool HasScopeUrl(const AppId& app_id) const override;
+  GURL GetScopeUrlForApp(const AppId& app_id) const override;
+
+ private:
+  std::set<AppId> installed_apps_;
+  std::set<AppId> uninstalled_external_apps_;
+};
+
+}  // namespace web_app
+
+#endif  // CHROME_BROWSER_WEB_APPLICATIONS_TEST_TEST_APP_REGISTRAR_H_
diff --git a/chrome/browser/web_applications/web_app_provider.h b/chrome/browser/web_applications/web_app_provider.h
index bbb0ddb..fd72c4a7 100644
--- a/chrome/browser/web_applications/web_app_provider.h
+++ b/chrome/browser/web_applications/web_app_provider.h
@@ -63,6 +63,8 @@
   // Start registry. All subsystems depend on it.
   void StartRegistry();
 
+  AppRegistrar& registrar() { return *registrar_; }
+
   // UIs can use InstallManager for user-initiated Web Apps install.
   InstallManager& install_manager() { return *install_manager_; }
 
diff --git a/chrome/browser/web_applications/web_app_registrar.cc b/chrome/browser/web_applications/web_app_registrar.cc
index 983281e..649f1324 100644
--- a/chrome/browser/web_applications/web_app_registrar.cc
+++ b/chrome/browser/web_applications/web_app_registrar.cc
@@ -84,4 +84,14 @@
   return false;
 }
 
+bool WebAppRegistrar::HasScopeUrl(const AppId& app_id) const {
+  NOTIMPLEMENTED();
+  return false;
+}
+
+GURL WebAppRegistrar::GetScopeUrlForApp(const AppId& app_id) const {
+  NOTIMPLEMENTED();
+  return GURL();
+}
+
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/web_app_registrar.h b/chrome/browser/web_applications/web_app_registrar.h
index cae09dcb..b14c87ac 100644
--- a/chrome/browser/web_applications/web_app_registrar.h
+++ b/chrome/browser/web_applications/web_app_registrar.h
@@ -39,6 +39,8 @@
   void Init(base::OnceClosure callback) override;
   bool IsInstalled(const AppId& app_id) const override;
   bool WasExternalAppUninstalledByUser(const AppId& app_id) const override;
+  bool HasScopeUrl(const AppId& app_id) const override;
+  GURL GetScopeUrlForApp(const AppId& app_id) const override;
 
  private:
   void OnDatabaseOpened(base::OnceClosure callback, Registry registry);
diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn
index d410fea..d3b62651 100644
--- a/chrome/common/BUILD.gn
+++ b/chrome/common/BUILD.gn
@@ -189,8 +189,6 @@
     "stack_sampling_configuration.h",
     "thread_profiler.cc",
     "thread_profiler.h",
-    "trace_event_args_whitelist.cc",
-    "trace_event_args_whitelist.h",
     "tts_messages.h",
     "tts_utterance_request.cc",
     "tts_utterance_request.h",
diff --git a/chrome/common/OWNERS b/chrome/common/OWNERS
index 32b5f23f..144ecfa 100644
--- a/chrome/common/OWNERS
+++ b/chrome/common/OWNERS
@@ -53,11 +53,6 @@
 per-file autocomplete_match_type.*=sky@chromium.org
 per-file crash_keys*=rsesek@chromium.org
 
-# Tracing
-per-file trace_event_args_whitelist*=nduca@chromium.org
-per-file trace_event_args_whitelist*=dsinclair@chromium.org
-per-file trace_event_args_whitelist*=oysteine@chromium.org
-
 # WebUI. See also chrome/browser/ui/webui/OWNERS.
 per-file webui_url_constants.cc=file://ui/webui/PLATFORM_OWNERS
 per-file webui_url_constants.h=file://ui/webui/PLATFORM_OWNERS
diff --git a/chrome/common/extensions/api/accessibility_private.json b/chrome/common/extensions/api/accessibility_private.json
index 725c87a1..2c2af89 100644
--- a/chrome/common/extensions/api/accessibility_private.json
+++ b/chrome/common/extensions/api/accessibility_private.json
@@ -103,6 +103,41 @@
         "type": "string",
         "description": "The state of the Select-to-Speak extension",
         "enum": ["selecting", "speaking", "inactive"]
+      },
+      {
+        "id": "FocusType",
+        "type": "string",
+        "description": "The type of visual appearance for the focus ring.",
+        "enum": ["glow", "solid", "dashed"]
+      },
+      {
+        "id": "FocusRingInfo",
+        "type": "object",
+        "properties": {
+          "rects": {
+            "type": "array",
+            "items": { "$ref": "ScreenRect" },
+            "description": "Array of rectangles to draw the accessibility focus ring around."
+          },
+          "type": {
+            "$ref": "FocusType",
+            "description": "The FocusType for the ring."
+          },
+          "color": {
+            "type": "string",
+            "description": "A CSS-style color string (e.g. #3F8213) that describes the primary color of the focus ring."
+          },
+          "secondaryColor": {
+            "type": "string",
+            "description": "A CSS-style color string (e.g. #3F82E4) that describes the secondary color of the focus ring, if there is one.",
+            "optional": true
+          },
+          "id": {
+            "type": "string",
+            "description": "An identifier for this focus ring, unique within the extension.",
+            "optional": true
+          }
+        }
       }
     ],
     "functions": [
@@ -138,21 +173,15 @@
         ]
       },
       {
-        "name": "setFocusRing",
+        "name": "setFocusRings",
         "type": "function",
-        "description": "Sets the bounds of the accessibility focus ring.",
+        "description": "Sets the given accessibility focus rings for this extension.",
         "parameters": [
           {
-            "name": "rects",
+            "name": "focusRings",
             "type": "array",
-            "items": { "$ref": "ScreenRect" },
-            "description": "Array of rectangles to draw the accessibility focus ring around."
-          },
-          {
-            "name": "color",
-            "type": "string",
-            "description": "CSS-style hex color string beginning with # like #FF9982 or #EEE.",
-            "optional": true
+            "items": { "$ref": "FocusRingInfo" },
+            "description": "Array of focus rings to draw."
           }
         ]
       },
diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc
index 5cf7246..24339da 100644
--- a/chrome/common/extensions/extension_constants.cc
+++ b/chrome/common/extensions/extension_constants.cc
@@ -73,7 +73,7 @@
 const char kChromeCameraAppId[] = "hfhhnacclhffhdffklopdkcgdhifgngh";
 const char kChromeCameraAppDevId[] = "flgnmkgjffmkephdokeeliiopbjaafpm";
 const char kChromeCameraAppPath[] = "chromeos/camera";
-const char kContainedHomeAppId[] = "nbaolgedfgoedkjbfmpediclncanmpbc";
+const char kKioskNextHomeAppId[] = "nbaolgedfgoedkjbfmpediclncanmpbc";
 #endif
 
 const char kAppStateNotInstalled[] = "not_installed";
diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h
index e6217d6..083dfc2 100644
--- a/chrome/common/extensions/extension_constants.h
+++ b/chrome/common/extensions/extension_constants.h
@@ -235,8 +235,8 @@
 extern const char kChromeCameraAppDevId[];
 // Path to preinstalled Chrome camera app.
 extern const char kChromeCameraAppPath[];
-// The app ID of the contained home app.
-extern const char kContainedHomeAppId[];
+// The app ID of the Kiosk Next Home app.
+extern const char kKioskNextHomeAppId[];
 #endif
 
 // What causes an extension to be installed? Used in histograms, so don't
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index f13549f..856bb2d 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -847,6 +847,10 @@
 // system volume, and higher than 1.0 is louder.
 const char kTextToSpeechVolume[] = "settings.tts.speech_volume";
 
+// A dictionary containing the latest Time Limits override authorized by parent
+// access code.
+const char kTimeLimitLocalOverride[] = "screen_time.local_override";
+
 // A dictionary preference holding the usage time limit definitions for a user.
 const char kUsageTimeLimit[] = "screen_time.limit";
 
@@ -1971,6 +1975,10 @@
 
 // Integer prefs used to back event counts reported by
 // chromeos::power::auto_screen_brightness::MetricsReporter.
+const char kAutoScreenBrightnessMetricsAtlasUserAdjustmentCount[] =
+    "auto_screen_brightness.metrics.atlas_user_adjustment_count";
+const char kAutoScreenBrightnessMetricsEveUserAdjustmentCount[] =
+    "auto_screen_brightness.metrics.eve_user_adjustment_count";
 const char kAutoScreenBrightnessMetricsNoAlsUserAdjustmentCount[] =
     "auto_screen_brightness.metrics.no_als_user_adjustment_count";
 const char kAutoScreenBrightnessMetricsSupportedAlsUserAdjustmentCount[] =
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 6d921196..fa23f8ef 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -275,6 +275,7 @@
 extern const char kTextToSpeechRate[];
 extern const char kTextToSpeechPitch[];
 extern const char kTextToSpeechVolume[];
+extern const char kTimeLimitLocalOverride[];
 extern const char kUsageTimeLimit[];
 extern const char kScreenTimeLastState[];
 extern const char kEnableSyncConsent[];
@@ -652,6 +653,8 @@
 extern const char kRemoveUsersRemoteCommand[];
 extern const char kCameraMediaConsolidated[];
 extern const char kAutoScreenBrightnessMetricsDailySample[];
+extern const char kAutoScreenBrightnessMetricsAtlasUserAdjustmentCount[];
+extern const char kAutoScreenBrightnessMetricsEveUserAdjustmentCount[];
 extern const char kAutoScreenBrightnessMetricsNoAlsUserAdjustmentCount[];
 extern const char kAutoScreenBrightnessMetricsSupportedAlsUserAdjustmentCount[];
 extern const char
diff --git a/chrome/common/trace_event_args_whitelist.h b/chrome/common/trace_event_args_whitelist.h
deleted file mode 100644
index ffff519..0000000
--- a/chrome/common/trace_event_args_whitelist.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_COMMON_TRACE_EVENT_ARGS_WHITELIST_H_
-#define CHROME_COMMON_TRACE_EVENT_ARGS_WHITELIST_H_
-
-#include "base/trace_event/trace_event_impl.h"
-
-// Used to filter trace event arguments against a whitelist of events that
-// have been manually vetted to not include any PII.
-bool IsTraceEventArgsWhitelisted(
-    const char* category_group_name,
-    const char* event_name,
-    base::trace_event::ArgumentNameFilterPredicate* arg_name_filter);
-
-// Used to filter metadata against a whitelist of metadata names that have been
-// manually vetted to not include any PII.
-bool IsMetadataWhitelisted(const std::string& metadata_name);
-
-#endif  // CHROME_COMMON_TRACE_EVENT_ARGS_WHITELIST_H_
diff --git a/chrome/renderer/BUILD.gn b/chrome/renderer/BUILD.gn
index 245a7102..e86e1f0 100644
--- a/chrome/renderer/BUILD.gn
+++ b/chrome/renderer/BUILD.gn
@@ -102,8 +102,8 @@
     "sandbox_status_extension_android.h",
     "security_filter_peer.cc",
     "security_filter_peer.h",
-    "security_interstitials/security_interstitial_page_controller.cc",
-    "security_interstitials/security_interstitial_page_controller.h",
+    "ssl/ssl_certificate_error_page_controller.cc",
+    "ssl/ssl_certificate_error_page_controller.h",
     "supervised_user/supervised_user_error_page_controller.cc",
     "supervised_user/supervised_user_error_page_controller.h",
     "tts_dispatcher.cc",
diff --git a/chrome/renderer/net/net_error_helper.cc b/chrome/renderer/net/net_error_helper.cc
index 2eb6a965..0081b5b 100644
--- a/chrome/renderer/net/net_error_helper.cc
+++ b/chrome/renderer/net/net_error_helper.cc
@@ -24,7 +24,7 @@
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/render_messages.h"
 #include "chrome/renderer/chrome_render_thread_observer.h"
-#include "chrome/renderer/security_interstitials/security_interstitial_page_controller.h"
+#include "chrome/renderer/ssl/ssl_certificate_error_page_controller.h"
 #include "chrome/renderer/supervised_user/supervised_user_error_page_controller.h"
 #include "components/error_page/common/error.h"
 #include "components/error_page/common/error_page_params.h"
@@ -167,7 +167,7 @@
     : RenderFrameObserver(render_frame),
       content::RenderFrameObserverTracker<NetErrorHelper>(render_frame),
       weak_controller_delegate_factory_(this),
-      weak_security_interstitial_controller_delegate_factory_(this),
+      weak_ssl_error_controller_delegate_factory_(this),
       weak_supervised_user_error_controller_delegate_factory_(this) {
   RenderThread::Get()->AddObserver(this);
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
@@ -324,7 +324,7 @@
   // error page, the controller has not yet been attached, so this won't affect
   // it.
   weak_controller_delegate_factory_.InvalidateWeakPtrs();
-  weak_security_interstitial_controller_delegate_factory_.InvalidateWeakPtrs();
+  weak_ssl_error_controller_delegate_factory_.InvalidateWeakPtrs();
   weak_supervised_user_error_controller_delegate_factory_.InvalidateWeakPtrs();
 
   core_->OnCommitLoad(GetFrameType(render_frame()),
@@ -433,10 +433,9 @@
                                  failed_url, true /* replace_current_item */);
 }
 
-void NetErrorHelper::EnablePageHelperFunctions() {
-  SecurityInterstitialPageController::Install(
-      render_frame(),
-      weak_security_interstitial_controller_delegate_factory_.GetWeakPtr());
+void NetErrorHelper::EnablePageHelperFunctions(net::Error net_error) {
+  SSLCertificateErrorPageController::Install(
+      render_frame(), weak_ssl_error_controller_delegate_factory_.GetWeakPtr());
   NetErrorPageController::Install(
       render_frame(), weak_controller_delegate_factory_.GetWeakPtr());
 
diff --git a/chrome/renderer/net/net_error_helper.h b/chrome/renderer/net/net_error_helper.h
index 0b259ec..cf104cf8 100644
--- a/chrome/renderer/net/net_error_helper.h
+++ b/chrome/renderer/net/net_error_helper.h
@@ -18,7 +18,7 @@
 #include "chrome/common/supervised_user_commands.mojom.h"
 #include "chrome/renderer/net/net_error_helper_core.h"
 #include "chrome/renderer/net/net_error_page_controller.h"
-#include "chrome/renderer/security_interstitials/security_interstitial_page_controller.h"
+#include "chrome/renderer/ssl/ssl_certificate_error_page_controller.h"
 #include "chrome/renderer/supervised_user/supervised_user_error_page_controller.h"
 #include "chrome/renderer/supervised_user/supervised_user_error_page_controller_delegate.h"
 #include "components/error_page/common/net_error_info.h"
@@ -55,7 +55,7 @@
       public content::RenderThreadObserver,
       public NetErrorHelperCore::Delegate,
       public NetErrorPageController::Delegate,
-      public SecurityInterstitialPageController::Delegate,
+      public SSLCertificateErrorPageController::Delegate,
       public SupervisedUserErrorPageControllerDelegate,
       public chrome::mojom::NetworkDiagnosticsClient,
       public chrome::mojom::NavigationCorrector {
@@ -75,7 +75,7 @@
   void UpdateEasterEggHighScore(int high_score) override;
   void ResetEasterEggHighScore() override;
 
-  // SecurityInterstitialPageController::Delegate implementation
+  // SSLCertificateErrorPageController::Delegate implementation
   void SendCommand(
       security_interstitials::SecurityInterstitialCommand command) override;
 
@@ -130,7 +130,7 @@
       bool* auto_fetch_allowed,
       std::string* html) const override;
   void LoadErrorPage(const std::string& html, const GURL& failed_url) override;
-  void EnablePageHelperFunctions() override;
+  void EnablePageHelperFunctions(net::Error net_error) override;
   void UpdateErrorPage(const error_page::Error& error,
                        bool is_failed_post,
                        bool can_use_local_diagnostics_service) override;
@@ -207,8 +207,8 @@
   base::WeakPtrFactory<NetErrorPageController::Delegate>
       weak_controller_delegate_factory_;
 
-  base::WeakPtrFactory<SecurityInterstitialPageController::Delegate>
-      weak_security_interstitial_controller_delegate_factory_;
+  base::WeakPtrFactory<SSLCertificateErrorPageController::Delegate>
+      weak_ssl_error_controller_delegate_factory_;
 
   base::WeakPtrFactory<SupervisedUserErrorPageControllerDelegate>
       weak_supervised_user_error_controller_delegate_factory_;
diff --git a/chrome/renderer/net/net_error_helper_core.cc b/chrome/renderer/net/net_error_helper_core.cc
index 2f25b7a..721c1a2 100644
--- a/chrome/renderer/net/net_error_helper_core.cc
+++ b/chrome/renderer/net/net_error_helper_core.cc
@@ -510,6 +510,8 @@
          // Do not trigger for blacklisted URLs.
          // https://crbug.com/803839
          info.error.reason() != net::ERR_BLOCKED_BY_ADMINISTRATOR &&
+         // Do not trigger for requests that were blocked by the browser itself.
+         info.error.reason() != net::ERR_BLOCKED_BY_CLIENT &&
          !info.was_failed_post &&
          // Don't auto-reload non-http/https schemas.
          // https://crbug.com/471713
@@ -678,7 +680,8 @@
   delegate_->SetIsShowingDownloadButton(
       committed_error_page_info_->download_button_in_page);
 
-  delegate_->EnablePageHelperFunctions();
+  delegate_->EnablePageHelperFunctions(
+      static_cast<net::Error>(committed_error_page_info_->error.reason()));
 
 #if defined(OS_ANDROID)
   if (committed_error_page_info_->offline_content_feature_state ==
diff --git a/chrome/renderer/net/net_error_helper_core.h b/chrome/renderer/net/net_error_helper_core.h
index 2720c244..14ebf9c 100644
--- a/chrome/renderer/net/net_error_helper_core.h
+++ b/chrome/renderer/net/net_error_helper_core.h
@@ -80,7 +80,7 @@
 
     // Create extra Javascript bindings in the error page. Will only be invoked
     // after an error page has finished loading.
-    virtual void EnablePageHelperFunctions() = 0;
+    virtual void EnablePageHelperFunctions(net::Error net_error) = 0;
 
     // Updates the currently displayed error page with a new error code.  The
     // currently displayed error page must have finished loading, and must have
diff --git a/chrome/renderer/net/net_error_helper_core_unittest.cc b/chrome/renderer/net/net_error_helper_core_unittest.cc
index 6847390d..86a8b86 100644
--- a/chrome/renderer/net/net_error_helper_core_unittest.cc
+++ b/chrome/renderer/net/net_error_helper_core_unittest.cc
@@ -390,7 +390,7 @@
     last_error_html_ = html;
   }
 
-  void EnablePageHelperFunctions() override {
+  void EnablePageHelperFunctions(net::Error net_error) override {
     enable_page_helper_functions_count_++;
   }
 
diff --git a/chrome/renderer/safe_browsing/phishing_classifier.cc b/chrome/renderer/safe_browsing/phishing_classifier.cc
index cce30e7..e91d928 100644
--- a/chrome/renderer/safe_browsing/phishing_classifier.cc
+++ b/chrome/renderer/safe_browsing/phishing_classifier.cc
@@ -5,6 +5,7 @@
 #include "chrome/renderer/safe_browsing/phishing_classifier.h"
 
 #include <string>
+#include <utility>
 
 #include "base/bind.h"
 #include "base/callback.h"
@@ -99,9 +100,8 @@
   return scorer_ != NULL;
 }
 
-void PhishingClassifier::BeginClassification(
-    const base::string16* page_text,
-    const DoneCallback& done_callback) {
+void PhishingClassifier::BeginClassification(const base::string16* page_text,
+                                             DoneCallback done_callback) {
   DCHECK(is_ready());
 
   // The RenderView should have called CancelPendingClassification() before
@@ -112,7 +112,7 @@
   CancelPendingClassification();
 
   page_text_ = page_text;
-  done_callback_ = done_callback;
+  done_callback_ = std::move(done_callback);
 
   // For consistency, we always want to invoke the DoneCallback
   // asynchronously, rather than directly from this method.  To ensure that
@@ -154,8 +154,8 @@
   // in several chunks of work and invokes the callback when finished.
   dom_extractor_->ExtractFeatures(
       frame->GetDocument(), features_.get(),
-      base::Bind(&PhishingClassifier::DOMExtractionFinished,
-                 base::Unretained(this)));
+      base::BindOnce(&PhishingClassifier::DOMExtractionFinished,
+                     base::Unretained(this)));
 }
 
 void PhishingClassifier::CancelPendingClassification() {
@@ -174,11 +174,9 @@
     // Term feature extraction can take awhile, so it runs asynchronously
     // in several chunks of work and invokes the callback when finished.
     term_extractor_->ExtractFeatures(
-        page_text_,
-        features_.get(),
-        shingle_hashes_.get(),
-        base::Bind(&PhishingClassifier::TermExtractionFinished,
-                   base::Unretained(this)));
+        page_text_, features_.get(), shingle_hashes_.get(),
+        base::BindOnce(&PhishingClassifier::TermExtractionFinished,
+                       base::Unretained(this)));
   } else {
     RunFailureCallback();
   }
@@ -225,7 +223,7 @@
 }
 
 void PhishingClassifier::RunCallback(const ClientPhishingRequest& verdict) {
-  done_callback_.Run(verdict);
+  std::move(done_callback_).Run(verdict);
   Clear();
 }
 
diff --git a/chrome/renderer/safe_browsing/phishing_classifier.h b/chrome/renderer/safe_browsing/phishing_classifier.h
index 13c4093..b95e1d3 100644
--- a/chrome/renderer/safe_browsing/phishing_classifier.h
+++ b/chrome/renderer/safe_browsing/phishing_classifier.h
@@ -49,7 +49,7 @@
   // is true, the page is considered phishy by the client-side model,
   // and the browser should ping back to get a final verdict.  The
   // verdict.client_score() is set to kInvalidScore if classification failed.
-  typedef base::Callback<void(const ClientPhishingRequest& /* verdict */)>
+  typedef base::OnceCallback<void(const ClientPhishingRequest& /* verdict */)>
       DoneCallback;
 
   static const float kInvalidScore;
@@ -88,7 +88,7 @@
   // It is an error to call BeginClassification if the classifier is not yet
   // ready.
   virtual void BeginClassification(const base::string16* page_text,
-                                   const DoneCallback& callback);
+                                   DoneCallback callback);
 
   // Called by the RenderView (on the render thread) when a page is unloading
   // or the RenderView is being destroyed.  This cancels any extraction that
diff --git a/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc b/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc
index 7841744..c711cbd8 100644
--- a/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc
+++ b/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc
@@ -125,8 +125,9 @@
     feature_map_.Clear();
 
     classifier_->BeginClassification(
-        page_text, base::Bind(&PhishingClassifierTest::ClassificationFinished,
-                              base::Unretained(this)));
+        page_text,
+        base::BindOnce(&PhishingClassifierTest::ClassificationFinished,
+                       base::Unretained(this)));
     base::RunLoop().RunUntilIdle();
   }
 
diff --git a/chrome/renderer/safe_browsing/phishing_classifier_delegate.cc b/chrome/renderer/safe_browsing/phishing_classifier_delegate.cc
index 4ef5ac5..aabccd1 100644
--- a/chrome/renderer/safe_browsing/phishing_classifier_delegate.cc
+++ b/chrome/renderer/safe_browsing/phishing_classifier_delegate.cc
@@ -278,8 +278,8 @@
   is_classifying_ = true;
   classifier_->BeginClassification(
       &classifier_page_text_,
-      base::Bind(&PhishingClassifierDelegate::ClassificationDone,
-                 base::Unretained(this)));
+      base::BindOnce(&PhishingClassifierDelegate::ClassificationDone,
+                     base::Unretained(this)));
 }
 
 void PhishingClassifierDelegate::OnDestruct() {
diff --git a/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc b/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc
index b15f44e..054a4a51 100644
--- a/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc
+++ b/chrome/renderer/safe_browsing/phishing_classifier_delegate_browsertest.cc
@@ -41,8 +41,7 @@
 
   ~MockPhishingClassifier() override {}
 
-  MOCK_METHOD2(BeginClassification,
-               void(const base::string16*, const DoneCallback&));
+  MOCK_METHOD2(BeginClassification, void(const base::string16*, DoneCallback));
   MOCK_METHOD0(CancelPendingClassification, void());
 
  private:
diff --git a/chrome/renderer/safe_browsing/phishing_dom_feature_extractor.cc b/chrome/renderer/safe_browsing/phishing_dom_feature_extractor.cc
index 0a09776a..db2e19de 100644
--- a/chrome/renderer/safe_browsing/phishing_dom_feature_extractor.cc
+++ b/chrome/renderer/safe_browsing/phishing_dom_feature_extractor.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/renderer/safe_browsing/phishing_dom_feature_extractor.h"
 
+#include <utility>
+
 #include "base/bind.h"
 #include "base/compiler_specific.h"
 #include "base/location.h"
@@ -112,10 +114,9 @@
   CheckNoPendingExtraction();
 }
 
-void PhishingDOMFeatureExtractor::ExtractFeatures(
-    blink::WebDocument document,
-    FeatureMap* features,
-    const DoneCallback& done_callback) {
+void PhishingDOMFeatureExtractor::ExtractFeatures(blink::WebDocument document,
+                                                  FeatureMap* features,
+                                                  DoneCallback done_callback) {
   // The RenderView should have called CancelPendingExtraction() before
   // starting a new extraction, so DCHECK this.
   CheckNoPendingExtraction();
@@ -124,7 +125,7 @@
   CancelPendingExtraction();
 
   features_ = features;
-  done_callback_ = done_callback;
+  done_callback_ = std::move(done_callback);
 
   page_feature_state_.reset(new PageFeatureState(clock_->Now()));
   cur_document_ = document;
@@ -360,7 +361,7 @@
                       clock_->Now() - page_feature_state_->start_time);
 
   DCHECK(!done_callback_.is_null());
-  done_callback_.Run(success);
+  std::move(done_callback_).Run(success);
   Clear();
 }
 
diff --git a/chrome/renderer/safe_browsing/phishing_dom_feature_extractor.h b/chrome/renderer/safe_browsing/phishing_dom_feature_extractor.h
index 3fcdf4b..fe65a778 100644
--- a/chrome/renderer/safe_browsing/phishing_dom_feature_extractor.h
+++ b/chrome/renderer/safe_browsing/phishing_dom_feature_extractor.h
@@ -32,7 +32,7 @@
  public:
   // Callback to be run when feature extraction finishes.  The callback
   // argument is true if extraction was successful, false otherwise.
-  typedef base::Callback<void(bool)> DoneCallback;
+  typedef base::OnceCallback<void(bool)> DoneCallback;
 
   // Creates a PhishingDOMFeatureExtractor instance.
   // |clock| is used for timing feature extractor operations, and may be
@@ -48,7 +48,7 @@
   // takes ownership of the callback.
   void ExtractFeatures(blink::WebDocument document,
                        FeatureMap* features,
-                       const DoneCallback& done_callback);
+                       DoneCallback done_callback);
 
   // Cancels any pending feature extraction.  The DoneCallback will not be run.
   // Must be called if there is a feature extraction in progress when the page
diff --git a/chrome/renderer/safe_browsing/phishing_dom_feature_extractor_browsertest.cc b/chrome/renderer/safe_browsing/phishing_dom_feature_extractor_browsertest.cc
index 02f8b75..ad89e44 100644
--- a/chrome/renderer/safe_browsing/phishing_dom_feature_extractor_browsertest.cc
+++ b/chrome/renderer/safe_browsing/phishing_dom_feature_extractor_browsertest.cc
@@ -161,8 +161,8 @@
 
     extractor_->ExtractFeatures(
         GetMainFrame()->GetDocument(), features,
-        base::Bind(&PhishingDOMFeatureExtractorTest::AnotherExtractionDone,
-                   weak_factory_.GetWeakPtr()));
+        base::BindOnce(&PhishingDOMFeatureExtractorTest::AnotherExtractionDone,
+                       weak_factory_.GetWeakPtr()));
     message_loop_->Run();
   }
 
@@ -174,8 +174,8 @@
 
     extractor_->ExtractFeatures(
         GetMainFrame()->GetDocument(), features,
-        base::Bind(&PhishingDOMFeatureExtractorTest::AnotherExtractionDone,
-                   weak_factory_.GetWeakPtr()));
+        base::BindOnce(&PhishingDOMFeatureExtractorTest::AnotherExtractionDone,
+                       weak_factory_.GetWeakPtr()));
     message_loop_->Run();
   }
 
diff --git a/chrome/renderer/safe_browsing/phishing_term_feature_extractor.cc b/chrome/renderer/safe_browsing/phishing_term_feature_extractor.cc
index cb663d1d..d1e87c63 100644
--- a/chrome/renderer/safe_browsing/phishing_term_feature_extractor.cc
+++ b/chrome/renderer/safe_browsing/phishing_term_feature_extractor.cc
@@ -111,7 +111,7 @@
     const base::string16* page_text,
     FeatureMap* features,
     std::set<uint32_t>* shingle_hashes,
-    const DoneCallback& done_callback) {
+    DoneCallback done_callback) {
   // The RenderView should have called CancelPendingExtraction() before
   // starting a new extraction, so DCHECK this.
   CheckNoPendingExtraction();
@@ -121,8 +121,7 @@
 
   page_text_ = page_text;
   features_ = features;
-  shingle_hashes_ = shingle_hashes,
-  done_callback_ = done_callback;
+  shingle_hashes_ = shingle_hashes, done_callback_ = std::move(done_callback);
 
   state_.reset(new ExtractionState(*page_text_, clock_->Now()));
   base::ThreadTaskRunnerHandle::Get()->PostTask(
@@ -282,7 +281,7 @@
                       clock_->Now() - state_->start_time);
 
   DCHECK(!done_callback_.is_null());
-  done_callback_.Run(success);
+  std::move(done_callback_).Run(success);
   Clear();
 }
 
diff --git a/chrome/renderer/safe_browsing/phishing_term_feature_extractor.h b/chrome/renderer/safe_browsing/phishing_term_feature_extractor.h
index 3b6f0d5..7e266b0 100644
--- a/chrome/renderer/safe_browsing/phishing_term_feature_extractor.h
+++ b/chrome/renderer/safe_browsing/phishing_term_feature_extractor.h
@@ -38,7 +38,7 @@
  public:
   // Callback to be run when feature extraction finishes.  The callback
   // argument is true if extraction was successful, false otherwise.
-  typedef base::Callback<void(bool)> DoneCallback;
+  typedef base::OnceCallback<void(bool)> DoneCallback;
 
   // Creates a PhishingTermFeatureExtractor which will extract features for
   // all of the terms whose SHA-256 hashes are in |page_term_hashes|.  These
@@ -83,7 +83,7 @@
   void ExtractFeatures(const base::string16* page_text,
                        FeatureMap* features,
                        std::set<uint32_t>* shingle_hashes,
-                       const DoneCallback& done_callback);
+                       DoneCallback done_callback);
 
   // Cancels any pending feature extraction.  The DoneCallback will not be run.
   // Must be called if there is a feature extraction in progress when the page
diff --git a/chrome/renderer/safe_browsing/phishing_term_feature_extractor_unittest.cc b/chrome/renderer/safe_browsing/phishing_term_feature_extractor_unittest.cc
index 39d5ce1..d3c7f299 100644
--- a/chrome/renderer/safe_browsing/phishing_term_feature_extractor_unittest.cc
+++ b/chrome/renderer/safe_browsing/phishing_term_feature_extractor_unittest.cc
@@ -97,11 +97,9 @@
                        std::set<uint32_t>* shingle_hashes) {
     success_ = false;
     extractor_->ExtractFeatures(
-        page_text,
-        features,
-        shingle_hashes,
-        base::Bind(&PhishingTermFeatureExtractorTest::ExtractionDone,
-                   base::Unretained(this)));
+        page_text, features, shingle_hashes,
+        base::BindOnce(&PhishingTermFeatureExtractorTest::ExtractionDone,
+                       base::Unretained(this)));
     active_run_loop_ = std::make_unique<base::RunLoop>();
     active_run_loop_->Run();
     return success_;
@@ -111,11 +109,9 @@
                               FeatureMap* features,
                               std::set<uint32_t>* shingle_hashes) {
     extractor_->ExtractFeatures(
-        page_text,
-        features,
-        shingle_hashes,
-        base::Bind(&PhishingTermFeatureExtractorTest::ExtractionDone,
-                   base::Unretained(this)));
+        page_text, features, shingle_hashes,
+        base::BindOnce(&PhishingTermFeatureExtractorTest::ExtractionDone,
+                       base::Unretained(this)));
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
         base::BindOnce(&PhishingTermFeatureExtractorTest::QuitExtraction,
diff --git a/chrome/renderer/security_interstitials/security_interstitial_page_controller.cc b/chrome/renderer/security_interstitials/security_interstitial_page_controller.cc
deleted file mode 100644
index 126e46c..0000000
--- a/chrome/renderer/security_interstitials/security_interstitial_page_controller.cc
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/renderer/security_interstitials/security_interstitial_page_controller.h"
-
-#include "components/security_interstitials/core/controller_client.h"
-#include "content/public/renderer/render_frame.h"
-#include "gin/handle.h"
-#include "gin/object_template_builder.h"
-#include "third_party/blink/public/web/blink.h"
-#include "third_party/blink/public/web/web_local_frame.h"
-
-gin::WrapperInfo SecurityInterstitialPageController::kWrapperInfo = {
-    gin::kEmbedderNativeGin};
-
-SecurityInterstitialPageController::Delegate::~Delegate() {}
-
-void SecurityInterstitialPageController::Install(
-    content::RenderFrame* render_frame,
-    base::WeakPtr<Delegate> delegate) {
-  v8::Isolate* isolate = blink::MainThreadIsolate();
-  v8::HandleScope handle_scope(isolate);
-  v8::Local<v8::Context> context =
-      render_frame->GetWebFrame()->MainWorldScriptContext();
-  if (context.IsEmpty())
-    return;
-
-  v8::Context::Scope context_scope(context);
-
-  gin::Handle<SecurityInterstitialPageController> controller =
-      gin::CreateHandle(isolate,
-                        new SecurityInterstitialPageController(delegate));
-  if (controller.IsEmpty())
-    return;
-
-  v8::Local<v8::Object> global = context->Global();
-  global->Set(gin::StringToV8(isolate, "certificateErrorPageController"),
-              controller.ToV8());
-}
-
-SecurityInterstitialPageController::SecurityInterstitialPageController(
-    base::WeakPtr<Delegate> delegate)
-    : delegate_(delegate) {}
-
-SecurityInterstitialPageController::~SecurityInterstitialPageController() {}
-
-void SecurityInterstitialPageController::DontProceed() {
-  SendCommand(
-      security_interstitials::SecurityInterstitialCommand::CMD_DONT_PROCEED);
-}
-
-void SecurityInterstitialPageController::Proceed() {
-  SendCommand(security_interstitials::SecurityInterstitialCommand::CMD_PROCEED);
-}
-
-void SecurityInterstitialPageController::ShowMoreSection() {
-  SendCommand(security_interstitials::SecurityInterstitialCommand::
-                  CMD_SHOW_MORE_SECTION);
-}
-
-void SecurityInterstitialPageController::OpenHelpCenter() {
-  SendCommand(security_interstitials::SecurityInterstitialCommand::
-                  CMD_OPEN_HELP_CENTER);
-}
-
-void SecurityInterstitialPageController::OpenDiagnostic() {
-  SendCommand(
-      security_interstitials::SecurityInterstitialCommand::CMD_OPEN_DIAGNOSTIC);
-}
-
-void SecurityInterstitialPageController::Reload() {
-  SendCommand(security_interstitials::SecurityInterstitialCommand::CMD_RELOAD);
-}
-
-void SecurityInterstitialPageController::OpenDateSettings() {
-  SendCommand(security_interstitials::SecurityInterstitialCommand::
-                  CMD_OPEN_DATE_SETTINGS);
-}
-
-void SecurityInterstitialPageController::OpenLogin() {
-  SendCommand(
-      security_interstitials::SecurityInterstitialCommand::CMD_OPEN_LOGIN);
-}
-
-void SecurityInterstitialPageController::DoReport() {
-  SendCommand(
-      security_interstitials::SecurityInterstitialCommand::CMD_DO_REPORT);
-}
-
-void SecurityInterstitialPageController::DontReport() {
-  SendCommand(
-      security_interstitials::SecurityInterstitialCommand::CMD_DONT_REPORT);
-}
-
-void SecurityInterstitialPageController::OpenReportingPrivacy() {
-  SendCommand(security_interstitials::SecurityInterstitialCommand::
-                  CMD_OPEN_REPORTING_PRIVACY);
-}
-
-void SecurityInterstitialPageController::OpenWhitepaper() {
-  SendCommand(
-      security_interstitials::SecurityInterstitialCommand::CMD_OPEN_WHITEPAPER);
-}
-
-void SecurityInterstitialPageController::ReportPhishingError() {
-  SendCommand(security_interstitials::SecurityInterstitialCommand::
-                  CMD_REPORT_PHISHING_ERROR);
-}
-
-void SecurityInterstitialPageController::SendCommand(
-    security_interstitials::SecurityInterstitialCommand command) {
-  if (delegate_) {
-    delegate_->SendCommand(command);
-  }
-}
-
-gin::ObjectTemplateBuilder
-SecurityInterstitialPageController::GetObjectTemplateBuilder(
-    v8::Isolate* isolate) {
-  return gin::Wrappable<SecurityInterstitialPageController>::
-      GetObjectTemplateBuilder(isolate)
-          .SetMethod("dontProceed",
-                     &SecurityInterstitialPageController::DontProceed)
-          .SetMethod("proceed", &SecurityInterstitialPageController::Proceed)
-          .SetMethod("showMoreSection",
-                     &SecurityInterstitialPageController::ShowMoreSection)
-          .SetMethod("openHelpCenter",
-                     &SecurityInterstitialPageController::OpenHelpCenter)
-          .SetMethod("openDiagnostic",
-                     &SecurityInterstitialPageController::OpenDiagnostic)
-          .SetMethod("reload", &SecurityInterstitialPageController::Reload)
-          .SetMethod("openDateSettings",
-                     &SecurityInterstitialPageController::OpenDateSettings)
-          .SetMethod("openLogin",
-                     &SecurityInterstitialPageController::OpenLogin)
-          .SetMethod("doReport", &SecurityInterstitialPageController::DoReport)
-          .SetMethod("dontReport",
-                     &SecurityInterstitialPageController::DontReport)
-          .SetMethod("openReportingPrivacy",
-                     &SecurityInterstitialPageController::OpenReportingPrivacy)
-          .SetMethod("openWhitepaper",
-                     &SecurityInterstitialPageController::OpenWhitepaper)
-          .SetMethod("reportPhishingError",
-                     &SecurityInterstitialPageController::ReportPhishingError);
-}
diff --git a/chrome/renderer/security_interstitials/DEPS b/chrome/renderer/ssl/DEPS
similarity index 100%
rename from chrome/renderer/security_interstitials/DEPS
rename to chrome/renderer/ssl/DEPS
diff --git a/chrome/renderer/security_interstitials/OWNERS b/chrome/renderer/ssl/OWNERS
similarity index 100%
rename from chrome/renderer/security_interstitials/OWNERS
rename to chrome/renderer/ssl/OWNERS
diff --git a/chrome/renderer/ssl/ssl_certificate_error_page_controller.cc b/chrome/renderer/ssl/ssl_certificate_error_page_controller.cc
new file mode 100644
index 0000000..a5516d60
--- /dev/null
+++ b/chrome/renderer/ssl/ssl_certificate_error_page_controller.cc
@@ -0,0 +1,144 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/renderer/ssl/ssl_certificate_error_page_controller.h"
+
+#include "components/security_interstitials/core/controller_client.h"
+#include "content/public/renderer/render_frame.h"
+#include "gin/handle.h"
+#include "gin/object_template_builder.h"
+#include "third_party/blink/public/web/blink.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+
+gin::WrapperInfo SSLCertificateErrorPageController::kWrapperInfo = {
+    gin::kEmbedderNativeGin};
+
+SSLCertificateErrorPageController::Delegate::~Delegate() {}
+
+void SSLCertificateErrorPageController::Install(
+    content::RenderFrame* render_frame,
+    base::WeakPtr<Delegate> delegate) {
+  v8::Isolate* isolate = blink::MainThreadIsolate();
+  v8::HandleScope handle_scope(isolate);
+  v8::Local<v8::Context> context =
+      render_frame->GetWebFrame()->MainWorldScriptContext();
+  if (context.IsEmpty())
+    return;
+
+  v8::Context::Scope context_scope(context);
+
+  gin::Handle<SSLCertificateErrorPageController> controller = gin::CreateHandle(
+      isolate, new SSLCertificateErrorPageController(delegate));
+  if (controller.IsEmpty())
+    return;
+
+  v8::Local<v8::Object> global = context->Global();
+  global->Set(gin::StringToV8(isolate, "certificateErrorPageController"),
+              controller.ToV8());
+}
+
+SSLCertificateErrorPageController::SSLCertificateErrorPageController(
+    base::WeakPtr<Delegate> delegate)
+    : delegate_(delegate) {}
+
+SSLCertificateErrorPageController::~SSLCertificateErrorPageController() {}
+
+void SSLCertificateErrorPageController::DontProceed() {
+  SendCommand(
+      security_interstitials::SecurityInterstitialCommand::CMD_DONT_PROCEED);
+}
+
+void SSLCertificateErrorPageController::Proceed() {
+  SendCommand(security_interstitials::SecurityInterstitialCommand::CMD_PROCEED);
+}
+
+void SSLCertificateErrorPageController::ShowMoreSection() {
+  SendCommand(security_interstitials::SecurityInterstitialCommand::
+                  CMD_SHOW_MORE_SECTION);
+}
+
+void SSLCertificateErrorPageController::OpenHelpCenter() {
+  SendCommand(security_interstitials::SecurityInterstitialCommand::
+                  CMD_OPEN_HELP_CENTER);
+}
+
+void SSLCertificateErrorPageController::OpenDiagnostic() {
+  SendCommand(
+      security_interstitials::SecurityInterstitialCommand::CMD_OPEN_DIAGNOSTIC);
+}
+
+void SSLCertificateErrorPageController::Reload() {
+  SendCommand(security_interstitials::SecurityInterstitialCommand::CMD_RELOAD);
+}
+
+void SSLCertificateErrorPageController::OpenDateSettings() {
+  SendCommand(security_interstitials::SecurityInterstitialCommand::
+                  CMD_OPEN_DATE_SETTINGS);
+}
+
+void SSLCertificateErrorPageController::OpenLogin() {
+  SendCommand(
+      security_interstitials::SecurityInterstitialCommand::CMD_OPEN_LOGIN);
+}
+
+void SSLCertificateErrorPageController::DoReport() {
+  SendCommand(
+      security_interstitials::SecurityInterstitialCommand::CMD_DO_REPORT);
+}
+
+void SSLCertificateErrorPageController::DontReport() {
+  SendCommand(
+      security_interstitials::SecurityInterstitialCommand::CMD_DONT_REPORT);
+}
+
+void SSLCertificateErrorPageController::OpenReportingPrivacy() {
+  SendCommand(security_interstitials::SecurityInterstitialCommand::
+                  CMD_OPEN_REPORTING_PRIVACY);
+}
+
+void SSLCertificateErrorPageController::OpenWhitepaper() {
+  SendCommand(
+      security_interstitials::SecurityInterstitialCommand::CMD_OPEN_WHITEPAPER);
+}
+
+void SSLCertificateErrorPageController::ReportPhishingError() {
+  SendCommand(security_interstitials::SecurityInterstitialCommand::
+                  CMD_REPORT_PHISHING_ERROR);
+}
+
+void SSLCertificateErrorPageController::SendCommand(
+    security_interstitials::SecurityInterstitialCommand command) {
+  if (delegate_) {
+    delegate_->SendCommand(command);
+  }
+}
+
+gin::ObjectTemplateBuilder
+SSLCertificateErrorPageController::GetObjectTemplateBuilder(
+    v8::Isolate* isolate) {
+  return gin::Wrappable<SSLCertificateErrorPageController>::
+      GetObjectTemplateBuilder(isolate)
+          .SetMethod("dontProceed",
+                     &SSLCertificateErrorPageController::DontProceed)
+          .SetMethod("proceed", &SSLCertificateErrorPageController::Proceed)
+          .SetMethod("showMoreSection",
+                     &SSLCertificateErrorPageController::ShowMoreSection)
+          .SetMethod("openHelpCenter",
+                     &SSLCertificateErrorPageController::OpenHelpCenter)
+          .SetMethod("openDiagnostic",
+                     &SSLCertificateErrorPageController::OpenDiagnostic)
+          .SetMethod("reload", &SSLCertificateErrorPageController::Reload)
+          .SetMethod("openDateSettings",
+                     &SSLCertificateErrorPageController::OpenDateSettings)
+          .SetMethod("openLogin", &SSLCertificateErrorPageController::OpenLogin)
+          .SetMethod("doReport", &SSLCertificateErrorPageController::DoReport)
+          .SetMethod("dontReport",
+                     &SSLCertificateErrorPageController::DontReport)
+          .SetMethod("openReportingPrivacy",
+                     &SSLCertificateErrorPageController::OpenReportingPrivacy)
+          .SetMethod("openWhitepaper",
+                     &SSLCertificateErrorPageController::OpenWhitepaper)
+          .SetMethod("reportPhishingError",
+                     &SSLCertificateErrorPageController::ReportPhishingError);
+}
diff --git a/chrome/renderer/security_interstitials/security_interstitial_page_controller.h b/chrome/renderer/ssl/ssl_certificate_error_page_controller.h
similarity index 76%
rename from chrome/renderer/security_interstitials/security_interstitial_page_controller.h
rename to chrome/renderer/ssl/ssl_certificate_error_page_controller.h
index 967a9e7..b550222 100644
--- a/chrome/renderer/security_interstitials/security_interstitial_page_controller.h
+++ b/chrome/renderer/ssl/ssl_certificate_error_page_controller.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_RENDERER_SECURITY_INTERSTITIALS_SECURITY_INTERSTITIAL_PAGE_CONTROLLER_H_
-#define CHROME_RENDERER_SECURITY_INTERSTITIALS_SECURITY_INTERSTITIAL_PAGE_CONTROLLER_H_
+#ifndef CHROME_RENDERER_SSL_SSL_CERTIFICATE_ERROR_PAGE_CONTROLLER_H_
+#define CHROME_RENDERER_SSL_SSL_CERTIFICATE_ERROR_PAGE_CONTROLLER_H_
 
 #include "base/memory/weak_ptr.h"
 #include "components/security_interstitials/core/controller_client.h"
@@ -16,8 +16,8 @@
 // This class makes various helper functions available to interstitials
 // when committed interstitials are on. It is bound to the JavaScript
 // window.certificateErrorPageController object.
-class SecurityInterstitialPageController
-    : public gin::Wrappable<SecurityInterstitialPageController> {
+class SSLCertificateErrorPageController
+    : public gin::Wrappable<SSLCertificateErrorPageController> {
  public:
   static gin::WrapperInfo kWrapperInfo;
 
@@ -40,8 +40,8 @@
                       base::WeakPtr<Delegate> delegate);
 
  private:
-  explicit SecurityInterstitialPageController(base::WeakPtr<Delegate> delegate);
-  ~SecurityInterstitialPageController() override;
+  explicit SSLCertificateErrorPageController(base::WeakPtr<Delegate> delegate);
+  ~SSLCertificateErrorPageController() override;
 
   void DontProceed();
   void Proceed();
@@ -65,7 +65,7 @@
 
   base::WeakPtr<Delegate> const delegate_;
 
-  DISALLOW_COPY_AND_ASSIGN(SecurityInterstitialPageController);
+  DISALLOW_COPY_AND_ASSIGN(SSLCertificateErrorPageController);
 };
 
-#endif  // CHROME_RENDERER_SECURITY_INTERSTITIALS_SECURITY_INTERSTITIAL_PAGE_CONTROLLER_H_
+#endif  // CHROME_RENDERER_SSL_SSL_CERTIFICATE_ERROR_PAGE_CONTROLLER_H_
diff --git a/chrome/services/app_service/README.md b/chrome/services/app_service/README.md
index 3b73e36a..36bcd38 100644
--- a/chrome/services/app_service/README.md
+++ b/chrome/services/app_service/README.md
@@ -256,8 +256,12 @@
 especially as the desired icon resolutions (e.g. 64dip or 256dip) aren't known
 up-front. Instead of sending an icon at all possible resolutions, the
 `Publisher` sends an `IconKey`: enough information to load the icon at given
-resoultions. The `IconKey` is an `AppType app_type` plus additional data
-(`uint64 u_key` and `string s_key`) whose semantics depend on the `app_type`.
+resolutions.
+
+The `IconKey` is an `AppType app_type` and a `uint32 icon_effects` bitmask
+(whether to apply various image processing effects such as desaturation to
+gray), plus additional data (`uint64 u_key` and `string s_key`) whose semantics
+depend on the `app_type`.
 
 For example, some icons are statically built into the Chrome or Chrome OS
 binary, as PNG-formatted resources, and can be loaded (synchronously, without
@@ -267,6 +271,14 @@
 (monotonically increasing) epoch number so that an icon update results in a
 different `u_key` and hence a different `IconKey`.
 
+Grouping the `IconKey` with the other `LoadIcon` arguments, the combination
+identifies a static (unchanging, but possibly obsolete) image: if a new version
+of an app results in a new icon, or if a change in app state results in a
+grayed out icon, this is represented by a different `IconKey`, such as a
+different `u_key` or `icon_effects` value. As a consequence, the combined
+`LoadIcon` arguments can be used to key a cache or map of `IconValue`s, or to
+recognize and coalesce multiple concurrent requests to the same combination.
+
 Consumers (via the `AppServiceProxy`) can always ask the `AppService` to load
 an icon. As an optimization, if the `AppServiceProxy` knows how to load an icon
 for a given `IconKey`, it can skip the Mojo round trip and bulk data IPC and
@@ -300,6 +312,9 @@
       // The semantics of u_key and s_key depend on the app_type.
       uint64 u_key;
       string s_key;
+      // A bitmask of icon post-processing effects, such as desaturation to
+      // gray and rounding the corners.
+      uint32 icon_effects;
     };
 
     enum IconCompression {
@@ -315,9 +330,6 @@
       bool is_placeholder_icon;
     };
 
-TBD: post-processing effects like rounded corners, badges or grayed out (for
-disabled apps) icons.
-
 
 ## Placeholder Icons
 
@@ -405,4 +417,4 @@
 
 ---
 
-Updated on 2019-03-02.
+Updated on 2019-03-07.
diff --git a/chrome/services/app_service/app_service_impl_unittest.cc b/chrome/services/app_service/app_service_impl_unittest.cc
index 15659cb..437a1af 100644
--- a/chrome/services/app_service/app_service_impl_unittest.cc
+++ b/chrome/services/app_service/app_service_impl_unittest.cc
@@ -196,7 +196,7 @@
     pub0.load_icon_s_key = "-";
     pub1.load_icon_s_key = "-";
     pub2.load_icon_s_key = "-";
-    auto icon_key = apps::mojom::IconKey::New(app_type, 0, "o");
+    auto icon_key = apps::mojom::IconKey::New(app_type, 0, "o", 0);
     constexpr bool allow_placeholder_icon = false;
     impl.LoadIcon(
         std::move(icon_key), apps::mojom::IconCompression::kUncompressed,
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 79466d0..eb0e290 100644
--- a/chrome/services/app_service/public/cpp/app_update_unittest.cc
+++ b/chrome/services/app_service/public/cpp/app_update_unittest.cc
@@ -241,7 +241,7 @@
     // IconKey tests.
 
     if (state) {
-      auto x = apps::mojom::IconKey::New(app_type, 100, std::string());
+      auto x = apps::mojom::IconKey::New(app_type, 100, std::string(), 0);
       state->icon_key = x.Clone();
       expect_icon_key_ = x.Clone();
       expect_icon_key_changed_ = false;
@@ -249,7 +249,7 @@
     }
 
     if (delta) {
-      auto x = apps::mojom::IconKey::New(app_type, 0, "one hundred");
+      auto x = apps::mojom::IconKey::New(app_type, 0, "one hundred", 0);
       delta->icon_key = x.Clone();
       expect_icon_key_ = x.Clone();
       expect_icon_key_changed_ = true;
diff --git a/chrome/services/app_service/public/mojom/types.mojom b/chrome/services/app_service/public/mojom/types.mojom
index d6a8bc16..0e328d4 100644
--- a/chrome/services/app_service/public/mojom/types.mojom
+++ b/chrome/services/app_service/public/mojom/types.mojom
@@ -89,6 +89,9 @@
   // The semantics of u_key and s_key depend on the app_type.
   uint64 u_key;
   string s_key;
+  // A bitmask of icon post-processing effects, such as desaturation to gray
+  // and rounding the corners.
+  uint32 icon_effects;
 };
 
 enum IconCompression {
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index aef854ae..a5590450 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2370,9 +2370,6 @@
     # For tests in tools/perf/process_perf_results_unittest.py
     "//build/android/pylib/",
     "//tools/swarming_client/",
-
-    # For tests in tools/perf/cli_tools/update_wpr
-    "//third_party/catapult/experimental/soundwave/services/",
   ]
 
   if (enable_mus) {
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/tabmodel/MockTabModel.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/tabmodel/MockTabModel.java
index dc984356..d376f86 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/tabmodel/MockTabModel.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/tabmodel/MockTabModel.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.test.util.browser.tabmodel;
 
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabBuilder;
 import org.chromium.chrome.browser.tabmodel.EmptyTabModel;
 import org.chromium.chrome.browser.tabmodel.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabModel;
@@ -42,8 +43,8 @@
     }
 
     public Tab addTab(int id) {
-        Tab tab = mDelegate == null
-                ? new Tab(id, isIncognito(), null) : mDelegate.createTab(id, isIncognito());
+        Tab tab = mDelegate == null ? new TabBuilder().setId(id).setIncognito(isIncognito()).build()
+                                    : mDelegate.createTab(id, isIncognito());
         mTabs.add(tab);
         return tab;
     }
diff --git a/chrome/test/data/chromeos/mobile_activation.html b/chrome/test/data/chromeos/mobile_activation.html
deleted file mode 100644
index 8d44f4e..0000000
--- a/chrome/test/data/chromeos/mobile_activation.html
+++ /dev/null
@@ -1,55 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-<meta charset="utf-8">
-<title>Mobile activation testing page</title>
-
-<style>
-body {
-  background: -webkit-gradient(linear, left top, left bottom,
-              from(#EBEBEB), to(#CFCFCF));
-  font-family: 'Lucida Grande', Helvetica, sans-serif;
-  font-size: 10px;
-  height: 300px;
-  overflow: hidden;
-}
-</style>
-
-<script src="connection_manager.js"></script>
-<script>
-function $(id) {
-  return document.getElementById(id);
-}
-
-chromeos.connectionManager.getDeviceInfo(function(device) {
-  $('device-info').innerHTML =
-    'carrier = ' + device.carrier + '<br>' +
-    'MEID = ' + device.MEID + '<br>' +
-    'IMEI = ' + device.IMEI + '<br>' +
-    'IMSI = ' + device.IMSI + '<br>' +
-    'ESN = ' + device.ESN + '<br>' +
-    'MDN = ' + device.MDN;
-});
-
-function sendStatus(status) {
-  chromeos.connectionManager.setTransactionStatus(status,
-      function() {});
-}
-
-</script>
-
-</head>
-<body>
-<h2>Plan Payment Page<h2>
-<div>Press a button below to signal transaction status back to the browser</div>
-<div id='buttons'>
-<button id='ok-btn' onclick="sendStatus('OK')">OK</button>
-<button id='fail-btn' onclick="sendStatus('FAILED')">Failed</button>
-</div>
-<div>
-Device Information:
-<div id="device-info"></div>
-</div>
-</body>
-</html>
-
diff --git a/chrome/test/data/webui/settings/device_page_tests.js b/chrome/test/data/webui/settings/device_page_tests.js
index a800d400..f37e22ec 100644
--- a/chrome/test/data/webui/settings/device_page_tests.js
+++ b/chrome/test/data/webui/settings/device_page_tests.js
@@ -319,11 +319,6 @@
             type: chrome.settingsPrivate.PrefType.NUMBER,
             value: 4,
           },
-          remap_diamond_key_to: {
-            key: 'settings.language.remap_diamond_key_to',
-            type: chrome.settingsPrivate.PrefType.NUMBER,
-            value: 3,
-          },
           remap_escape_key_to: {
             key: 'settings.language.remap_escape_key_to',
             type: chrome.settingsPrivate.PrefType.NUMBER,
@@ -602,12 +597,10 @@
             keyboardPage = page;
             // Initially, the optional keys are hidden.
             expectFalse(!!keyboardPage.$$('#capsLockKey'));
-            expectFalse(!!keyboardPage.$$('#diamondKey'));
 
-            // Pretend the diamond key is available, and no internal keyboard.
+            // Pretend no internal keyboard is available.
             let keyboardParams = {
               'showCapsLock': false,
-              'showDiamondKey': true,
               'showExternalMetaKey': false,
               'showAppleCommandKey': false,
               'hasInternalKeyboard': false,
@@ -616,7 +609,6 @@
             Polymer.dom.flush();
             expectFalse(!!keyboardPage.$$('#internalSearchKey'));
             expectFalse(!!keyboardPage.$$('#capsLockKey'));
-            expectTrue(!!keyboardPage.$$('#diamondKey'));
             expectFalse(!!keyboardPage.$$('#externalMetaKey'));
             expectFalse(!!keyboardPage.$$('#externalCommandKey'));
 
@@ -626,7 +618,6 @@
             Polymer.dom.flush();
             expectFalse(!!keyboardPage.$$('#internalSearchKey'));
             expectTrue(!!keyboardPage.$$('#capsLockKey'));
-            expectTrue(!!keyboardPage.$$('#diamondKey'));
             expectFalse(!!keyboardPage.$$('#externalMetaKey'));
             expectFalse(!!keyboardPage.$$('#externalCommandKey'));
 
@@ -636,7 +627,6 @@
             Polymer.dom.flush();
             expectFalse(!!keyboardPage.$$('#internalSearchKey'));
             expectTrue(!!keyboardPage.$$('#capsLockKey'));
-            expectTrue(!!keyboardPage.$$('#diamondKey'));
             expectTrue(!!keyboardPage.$$('#externalMetaKey'));
             expectFalse(!!keyboardPage.$$('#externalCommandKey'));
 
@@ -646,7 +636,6 @@
             Polymer.dom.flush();
             expectFalse(!!keyboardPage.$$('#internalSearchKey'));
             expectTrue(!!keyboardPage.$$('#capsLockKey'));
-            expectTrue(!!keyboardPage.$$('#diamondKey'));
             expectTrue(!!keyboardPage.$$('#externalMetaKey'));
             expectTrue(!!keyboardPage.$$('#externalCommandKey'));
 
@@ -656,7 +645,6 @@
             Polymer.dom.flush();
             expectTrue(!!keyboardPage.$$('#internalSearchKey'));
             expectTrue(!!keyboardPage.$$('#capsLockKey'));
-            expectTrue(!!keyboardPage.$$('#diamondKey'));
             expectTrue(!!keyboardPage.$$('#externalMetaKey'));
             expectTrue(!!keyboardPage.$$('#externalCommandKey'));
 
diff --git a/chrome/test/data/webui/welcome/nux_ntp_background_test.js b/chrome/test/data/webui/welcome/nux_ntp_background_test.js
index 6c7f3e7..30739b0 100644
--- a/chrome/test/data/webui/welcome/nux_ntp_background_test.js
+++ b/chrome/test/data/webui/welcome/nux_ntp_background_test.js
@@ -97,11 +97,8 @@
 
       options[1].click();
       assertFalse(options[0].hasAttribute('active'));
-      assertEquals(options[0].getAttribute('aria-pressed'), 'false');
       assertTrue(options[1].hasAttribute('active'));
-      assertEquals(options[1].getAttribute('aria-pressed'), 'true');
       assertFalse(options[2].hasAttribute('active'));
-      assertEquals(options[2].getAttribute('aria-pressed'), 'false');
     });
 
     test('test setting the background when hitting next', function() {
diff --git a/chromeos/constants/chromeos_switches.cc b/chromeos/constants/chromeos_switches.cc
index 2fee3ea..98e2b8cb 100644
--- a/chromeos/constants/chromeos_switches.cc
+++ b/chromeos/constants/chromeos_switches.cc
@@ -425,9 +425,6 @@
 // JPEG file).
 const char kGuestWallpaperSmall[] = "guest-wallpaper-small";
 
-// If true, the Chromebook has a keyboard with a diamond key.
-const char kHasChromeOSDiamondKey[] = "has-chromeos-diamond-key";
-
 // If set, the system is a Chromebook with a "standard Chrome OS keyboard",
 // which generally means one with a Search key in the standard Caps Lock
 // location above the Left Shift key. It should be unset for Chromebooks with
diff --git a/chromeos/constants/chromeos_switches.h b/chromeos/constants/chromeos_switches.h
index 2169183..c78173a47 100644
--- a/chromeos/constants/chromeos_switches.h
+++ b/chromeos/constants/chromeos_switches.h
@@ -173,7 +173,6 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kGuestSession[];
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kGuestWallpaperLarge[];
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kGuestWallpaperSmall[];
-COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kHasChromeOSDiamondKey[];
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const char kHasChromeOSKeyboard[];
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 extern const char kHideAndroidFilesInFilesApp[];
diff --git a/chromeos/dbus/BUILD.gn b/chromeos/dbus/BUILD.gn
index 97fb58f..a09cbad 100644
--- a/chromeos/dbus/BUILD.gn
+++ b/chromeos/dbus/BUILD.gn
@@ -119,8 +119,6 @@
     "fake_easy_unlock_client.h",
     "fake_gsm_sms_client.cc",
     "fake_gsm_sms_client.h",
-    "fake_hammerd_client.cc",
-    "fake_hammerd_client.h",
     "fake_image_burner_client.cc",
     "fake_image_burner_client.h",
     "fake_image_loader_client.cc",
@@ -169,8 +167,10 @@
     "fake_virtual_file_provider_client.h",
     "gsm_sms_client.cc",
     "gsm_sms_client.h",
-    "hammerd_client.cc",
-    "hammerd_client.h",
+    "hammerd/fake_hammerd_client.cc",
+    "hammerd/fake_hammerd_client.h",
+    "hammerd/hammerd_client.cc",
+    "hammerd/hammerd_client.h",
     "image_burner_client.cc",
     "image_burner_client.h",
     "image_loader_client.cc",
diff --git a/chromeos/dbus/cryptohome_client.cc b/chromeos/dbus/cryptohome_client.cc
index d8a97ae1..97195be 100644
--- a/chromeos/dbus/cryptohome_client.cc
+++ b/chromeos/dbus/cryptohome_client.cc
@@ -134,10 +134,17 @@
   }
 
   // CryptohomeClient override.
-  void Unmount(DBusMethodCallback<bool> callback) override {
+  void UnmountEx(const cryptohome::UnmountRequest& request,
+                 DBusMethodCallback<cryptohome::BaseReply> callback) override {
     dbus::MethodCall method_call(cryptohome::kCryptohomeInterface,
-                                 cryptohome::kCryptohomeUnmount);
-    CallBoolMethod(&method_call, std::move(callback));
+                                 cryptohome::kCryptohomeUnmountEx);
+    dbus::MessageWriter writer(&method_call);
+    writer.AppendProtoAsArrayOfBytes(request);
+
+    proxy_->CallMethod(
+        &method_call, kTpmDBusTimeoutMs,
+        base::BindOnce(&CryptohomeClientImpl::OnBaseReplyMethod,
+                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
   }
 
   // CryptohomeClient override.
diff --git a/chromeos/dbus/cryptohome_client.h b/chromeos/dbus/cryptohome_client.h
index 4fb75cc..6870e52 100644
--- a/chromeos/dbus/cryptohome_client.h
+++ b/chromeos/dbus/cryptohome_client.h
@@ -39,6 +39,7 @@
 class RemoveKeyRequest;
 class SetBootAttributeRequest;
 class SetFirmwareManagementParametersRequest;
+class UnmountRequest;
 class UpdateKeyRequest;
 
 }  // namespace cryptohome
@@ -147,8 +148,11 @@
   // Calls IsMounted method and returns true when the call succeeds.
   virtual void IsMounted(DBusMethodCallback<bool> callback) = 0;
 
-  // Calls Unmount method and returns true when the call succeeds.
-  virtual void Unmount(DBusMethodCallback<bool> callback) = 0;
+  // Calls UnmountEx method. |callback| is called after the method call
+  // succeeds.
+  virtual void UnmountEx(
+      const cryptohome::UnmountRequest& request,
+      DBusMethodCallback<cryptohome::BaseReply> callback) = 0;
 
   // Calls MigrateKeyEx method. |callback| is called after the method call
   // succeeds.
diff --git a/chromeos/dbus/dbus_clients_common.cc b/chromeos/dbus/dbus_clients_common.cc
index 4ddf3db..8dca1ee 100644
--- a/chromeos/dbus/dbus_clients_common.cc
+++ b/chromeos/dbus/dbus_clients_common.cc
@@ -15,7 +15,6 @@
 #include "chromeos/dbus/fake_cras_audio_client.h"
 #include "chromeos/dbus/fake_cryptohome_client.h"
 #include "chromeos/dbus/fake_gsm_sms_client.h"
-#include "chromeos/dbus/fake_hammerd_client.h"
 #include "chromeos/dbus/fake_modem_messaging_client.h"
 #include "chromeos/dbus/fake_permission_broker_client.h"
 #include "chromeos/dbus/fake_shill_device_client.h"
@@ -27,7 +26,6 @@
 #include "chromeos/dbus/fake_sms_client.h"
 #include "chromeos/dbus/fake_upstart_client.h"
 #include "chromeos/dbus/gsm_sms_client.h"
-#include "chromeos/dbus/hammerd_client.h"
 #include "chromeos/dbus/machine_learning_client.h"
 #include "chromeos/dbus/modem_messaging_client.h"
 #include "chromeos/dbus/permission_broker_client.h"
@@ -91,12 +89,6 @@
     gsm_sms_client_.reset(gsm_sms_client);
   }
 
-  if (use_real_clients) {
-    hammerd_client_ = HammerdClient::Create();
-  } else {
-    hammerd_client_ = std::make_unique<FakeHammerdClient>();
-  }
-
   machine_learning_client_ = MachineLearningClient::Create(client_impl_type);
 
   if (use_real_clients)
@@ -134,7 +126,6 @@
   cras_audio_client_->Init(system_bus);
   cryptohome_client_->Init(system_bus);
   gsm_sms_client_->Init(system_bus);
-  hammerd_client_->Init(system_bus);
   machine_learning_client_->Init(system_bus);
   modem_messaging_client_->Init(system_bus);
   permission_broker_client_->Init(system_bus);
diff --git a/chromeos/dbus/dbus_clients_common.h b/chromeos/dbus/dbus_clients_common.h
index b1c4d29..4f313593 100644
--- a/chromeos/dbus/dbus_clients_common.h
+++ b/chromeos/dbus/dbus_clients_common.h
@@ -21,7 +21,6 @@
 class CrasAudioClient;
 class CryptohomeClient;
 class GsmSMSClient;
-class HammerdClient;
 class MachineLearningClient;
 class ModemMessagingClient;
 class PermissionBrokerClient;
@@ -56,7 +55,6 @@
   std::unique_ptr<CrasAudioClient> cras_audio_client_;
   std::unique_ptr<CryptohomeClient> cryptohome_client_;
   std::unique_ptr<GsmSMSClient> gsm_sms_client_;
-  std::unique_ptr<HammerdClient> hammerd_client_;
   std::unique_ptr<MachineLearningClient> machine_learning_client_;
   std::unique_ptr<ModemMessagingClient> modem_messaging_client_;
   std::unique_ptr<ShillDeviceClient> shill_device_client_;
diff --git a/chromeos/dbus/dbus_thread_manager.cc b/chromeos/dbus/dbus_thread_manager.cc
index e123999..e47f197 100644
--- a/chromeos/dbus/dbus_thread_manager.cc
+++ b/chromeos/dbus/dbus_thread_manager.cc
@@ -29,7 +29,6 @@
 #include "chromeos/dbus/debug_daemon_client.h"
 #include "chromeos/dbus/easy_unlock_client.h"
 #include "chromeos/dbus/gsm_sms_client.h"
-#include "chromeos/dbus/hammerd_client.h"
 #include "chromeos/dbus/image_burner_client.h"
 #include "chromeos/dbus/image_loader_client.h"
 #include "chromeos/dbus/lorgnette_manager_client.h"
@@ -215,10 +214,6 @@
   return clients_common_->gsm_sms_client_.get();
 }
 
-HammerdClient* DBusThreadManager::GetHammerdClient() {
-  return clients_common_->hammerd_client_.get();
-}
-
 ImageBurnerClient* DBusThreadManager::GetImageBurnerClient() {
   return clients_browser_ ? clients_browser_->image_burner_client_.get()
                           : nullptr;
@@ -419,12 +414,6 @@
       std::move(client);
 }
 
-void DBusThreadManagerSetter::SetHammerdClient(
-    std::unique_ptr<HammerdClient> client) {
-  DBusThreadManager::Get()->clients_common_->hammerd_client_ =
-      std::move(client);
-}
-
 void DBusThreadManagerSetter::SetRuntimeProbeClient(
     std::unique_ptr<RuntimeProbeClient> client) {
   DBusThreadManager::Get()->clients_browser_->runtime_probe_client_ =
diff --git a/chromeos/dbus/dbus_thread_manager.h b/chromeos/dbus/dbus_thread_manager.h
index b8f20698..3dbbbdb1 100644
--- a/chromeos/dbus/dbus_thread_manager.h
+++ b/chromeos/dbus/dbus_thread_manager.h
@@ -43,7 +43,6 @@
 class DiagnosticsdClient;
 class EasyUnlockClient;
 class GsmSMSClient;
-class HammerdClient;
 class ImageBurnerClient;
 class ImageLoaderClient;
 class LorgnetteManagerClient;
@@ -150,7 +149,6 @@
   DiagnosticsdClient* GetDiagnosticsdClient();
   EasyUnlockClient* GetEasyUnlockClient();
   GsmSMSClient* GetGsmSMSClient();
-  HammerdClient* GetHammerdClient();
   ImageBurnerClient* GetImageBurnerClient();
   ImageLoaderClient* GetImageLoaderClient();
   LorgnetteManagerClient* GetLorgnetteManagerClient();
@@ -214,7 +212,6 @@
   void SetCrosDisksClient(std::unique_ptr<CrosDisksClient> client);
   void SetCryptohomeClient(std::unique_ptr<CryptohomeClient> client);
   void SetDebugDaemonClient(std::unique_ptr<DebugDaemonClient> client);
-  void SetHammerdClient(std::unique_ptr<HammerdClient> client);
   void SetImageBurnerClient(std::unique_ptr<ImageBurnerClient> client);
   void SetImageLoaderClient(std::unique_ptr<ImageLoaderClient> client);
   void SetMediaAnalyticsClient(std::unique_ptr<MediaAnalyticsClient> client);
diff --git a/chromeos/dbus/fake_cryptohome_client.cc b/chromeos/dbus/fake_cryptohome_client.cc
index 4a70687..369c1db 100644
--- a/chromeos/dbus/fake_cryptohome_client.cc
+++ b/chromeos/dbus/fake_cryptohome_client.cc
@@ -85,9 +85,10 @@
       FROM_HERE, base::BindOnce(std::move(callback), true));
 }
 
-void FakeCryptohomeClient::Unmount(DBusMethodCallback<bool> callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(std::move(callback), unmount_result_));
+void FakeCryptohomeClient::UnmountEx(
+    const cryptohome::UnmountRequest& request,
+    DBusMethodCallback<cryptohome::BaseReply> callback) {
+  ReturnProtobufMethodCallback(cryptohome::BaseReply(), std::move(callback));
 }
 
 void FakeCryptohomeClient::MigrateKeyEx(
diff --git a/chromeos/dbus/fake_cryptohome_client.h b/chromeos/dbus/fake_cryptohome_client.h
index e7922d2d..bc20bf1 100644
--- a/chromeos/dbus/fake_cryptohome_client.h
+++ b/chromeos/dbus/fake_cryptohome_client.h
@@ -36,7 +36,8 @@
   void WaitForServiceToBeAvailable(
       WaitForServiceToBeAvailableCallback callback) override;
   void IsMounted(DBusMethodCallback<bool> callback) override;
-  void Unmount(DBusMethodCallback<bool> callback) override;
+  void UnmountEx(const cryptohome::UnmountRequest& request,
+                 DBusMethodCallback<cryptohome::BaseReply> callback) override;
   void MigrateKeyEx(
       const cryptohome::AccountIdentifier& account,
       const cryptohome::AuthorizationRequest& auth_request,
diff --git a/chromeos/dbus/fake_hammerd_client.cc b/chromeos/dbus/hammerd/fake_hammerd_client.cc
similarity index 78%
rename from chromeos/dbus/fake_hammerd_client.cc
rename to chromeos/dbus/hammerd/fake_hammerd_client.cc
index fff02656..b8eb0ab76 100644
--- a/chromeos/dbus/fake_hammerd_client.cc
+++ b/chromeos/dbus/hammerd/fake_hammerd_client.cc
@@ -2,15 +2,29 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chromeos/dbus/fake_hammerd_client.h"
+#include "chromeos/dbus/hammerd/fake_hammerd_client.h"
 
 namespace chromeos {
 
-FakeHammerdClient::FakeHammerdClient() = default;
+namespace {
+FakeHammerdClient* g_instance = nullptr;
+}
 
-FakeHammerdClient::~FakeHammerdClient() = default;
+FakeHammerdClient::FakeHammerdClient() {
+  CHECK(!g_instance);
+  g_instance = this;
+}
 
-void FakeHammerdClient::Init(dbus::Bus* bus) {}
+FakeHammerdClient::~FakeHammerdClient() {
+  CHECK_EQ(g_instance, this);
+  g_instance = nullptr;
+}
+
+// static
+FakeHammerdClient* FakeHammerdClient::Get() {
+  CHECK(g_instance);
+  return g_instance;
+}
 
 void FakeHammerdClient::AddObserver(Observer* observer) {
   observers_.AddObserver(observer);
diff --git a/chromeos/dbus/fake_hammerd_client.h b/chromeos/dbus/hammerd/fake_hammerd_client.h
similarity index 77%
rename from chromeos/dbus/fake_hammerd_client.h
rename to chromeos/dbus/hammerd/fake_hammerd_client.h
index ff6ad7d..32309ae 100644
--- a/chromeos/dbus/fake_hammerd_client.h
+++ b/chromeos/dbus/hammerd/fake_hammerd_client.h
@@ -2,15 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROMEOS_DBUS_FAKE_HAMMERD_CLIENT_H_
-#define CHROMEOS_DBUS_FAKE_HAMMERD_CLIENT_H_
+#ifndef CHROMEOS_DBUS_HAMMERD_FAKE_HAMMERD_CLIENT_H_
+#define CHROMEOS_DBUS_HAMMERD_FAKE_HAMMERD_CLIENT_H_
 
 #include <vector>
 
 #include "base/component_export.h"
 #include "base/macros.h"
 #include "base/observer_list.h"
-#include "chromeos/dbus/hammerd_client.h"
+#include "chromeos/dbus/hammerd/hammerd_client.h"
 
 namespace chromeos {
 
@@ -19,8 +19,10 @@
   FakeHammerdClient();
   ~FakeHammerdClient() override;
 
+  // Checks that a FakeHammerdClient instance was initialized and returns it.
+  static FakeHammerdClient* Get();
+
   // HammerdClient:
-  void Init(dbus::Bus* bus) override;
   void AddObserver(Observer* observer) override;
   void RemoveObserver(Observer* observer) override;
 
@@ -41,4 +43,4 @@
 
 }  // namespace chromeos
 
-#endif  // CHROMEOS_DBUS_FAKE_HAMMERD_CLIENT_H_
+#endif  // CHROMEOS_DBUS_HAMMERD_FAKE_HAMMERD_CLIENT_H_
diff --git a/chromeos/dbus/hammerd_client.cc b/chromeos/dbus/hammerd/hammerd_client.cc
similarity index 88%
rename from chromeos/dbus/hammerd_client.cc
rename to chromeos/dbus/hammerd/hammerd_client.cc
index 69637fa1..acd200a 100644
--- a/chromeos/dbus/hammerd_client.cc
+++ b/chromeos/dbus/hammerd/hammerd_client.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chromeos/dbus/hammerd_client.h"
+#include "chromeos/dbus/hammerd/hammerd_client.h"
 
 #include <string>
 
@@ -11,6 +11,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
+#include "chromeos/dbus/hammerd/fake_hammerd_client.h"
 #include "dbus/bus.h"
 #include "dbus/message.h"
 #include "dbus/object_path.h"
@@ -21,13 +22,15 @@
 
 namespace {
 
+HammerdClient* g_instance = nullptr;
+
 class HammerdClientImpl : public HammerdClient {
  public:
   HammerdClientImpl() = default;
   ~HammerdClientImpl() override = default;
 
   // HammerdClient:
-  void Init(dbus::Bus* bus) override {
+  void Init(dbus::Bus* bus) {
     bus_proxy_ =
         bus->GetObjectProxy(hammerd::kHammerdServiceName,
                             dbus::ObjectPath(hammerd::kHammerdServicePath));
@@ -156,9 +159,32 @@
 
 }  // namespace
 
+HammerdClient::HammerdClient() = default;
+
+HammerdClient::~HammerdClient() = default;
+
 // static
-std::unique_ptr<HammerdClient> HammerdClient::Create() {
-  return std::make_unique<HammerdClientImpl>();
+void HammerdClient::Initialize(dbus::Bus* bus) {
+  CHECK(!g_instance);
+  if (bus) {
+    auto* instance = new HammerdClientImpl();
+    instance->Init(bus);
+    g_instance = instance;
+  } else {
+    g_instance = new FakeHammerdClient();
+  }
+}
+
+// static
+void HammerdClient::Shutdown() {
+  CHECK(g_instance);
+  delete g_instance;
+  g_instance = nullptr;
+}
+
+// static
+HammerdClient* HammerdClient::Get() {
+  return g_instance;
 }
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/hammerd_client.h b/chromeos/dbus/hammerd/hammerd_client.h
similarity index 79%
rename from chromeos/dbus/hammerd_client.h
rename to chromeos/dbus/hammerd/hammerd_client.h
index 5f1bcb7..f855ec1 100644
--- a/chromeos/dbus/hammerd_client.h
+++ b/chromeos/dbus/hammerd/hammerd_client.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROMEOS_DBUS_HAMMERD_CLIENT_H_
-#define CHROMEOS_DBUS_HAMMERD_CLIENT_H_
+#ifndef CHROMEOS_DBUS_HAMMERD_HAMMERD_CLIENT_H_
+#define CHROMEOS_DBUS_HAMMERD_HAMMERD_CLIENT_H_
 
 #include <stdint.h>
 
@@ -23,7 +23,7 @@
 //  * the connected base pairing events.
 // The client forwards the received signals to its observers (together with any
 // data extracted from the signal object).
-class COMPONENT_EXPORT(CHROMEOS_DBUS) HammerdClient : public DBusClient {
+class COMPONENT_EXPORT(CHROMEOS_DBUS) HammerdClient {
  public:
   class Observer {
    public:
@@ -54,14 +54,19 @@
     virtual void InvalidBaseConnected() = 0;
   };
 
-  ~HammerdClient() override = default;
+  HammerdClient();
+  virtual ~HammerdClient();
+
+  // Creates and initializes the global instance. If |bus| is null, a
+  // FakeHammerdClient instance will be created.
+  static void Initialize(dbus::Bus* bus);
+  static void Shutdown();
+  static HammerdClient* Get();
 
   virtual void AddObserver(Observer* observer) = 0;
   virtual void RemoveObserver(Observer* observer) = 0;
-
-  static std::unique_ptr<HammerdClient> Create();
 };
 
 }  // namespace chromeos
 
-#endif  // CHROMEOS_DBUS_HAMMERD_CLIENT_H_
+#endif  // CHROMEOS_DBUS_HAMMERD_HAMMERD_CLIENT_H_
diff --git a/chromeos/login/auth/cryptohome_authenticator.cc b/chromeos/login/auth/cryptohome_authenticator.cc
index 9dcb546..b6605c67 100644
--- a/chromeos/login/auth/cryptohome_authenticator.cc
+++ b/chromeos/login/auth/cryptohome_authenticator.cc
@@ -792,6 +792,14 @@
   }
 }
 
+// Callback invoked when UnmountEx returns.
+void CryptohomeAuthenticator::OnUnmountEx(
+    base::Optional<cryptohome::BaseReply> reply) {
+  if (BaseReplyToMountError(reply) != cryptohome::MOUNT_ERROR_NONE)
+    LOGIN_LOG(ERROR) << "Couldn't unmount user's homedir";
+  OnAuthFailure(AuthFailure(AuthFailure::OWNER_REQUIRED));
+}
+
 void CryptohomeAuthenticator::OnAuthFailure(const AuthFailure& error) {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
 
@@ -857,14 +865,6 @@
   Resolve();
 }
 
-void CryptohomeAuthenticator::OnUnmount(base::Optional<bool> success) {
-  if (!success.has_value() || !success.value()) {
-    // Maybe we should reboot immediately here?
-    LOGIN_LOG(ERROR) << "Couldn't unmount users home!";
-  }
-  OnAuthFailure(AuthFailure(AuthFailure::OWNER_REQUIRED));
-}
-
 void CryptohomeAuthenticator::Resolve() {
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
   bool create_if_nonexistent = false;
@@ -984,8 +984,9 @@
       break;
     case OWNER_REQUIRED: {
       current_state_->ResetCryptohomeStatus();
-      DBusThreadManager::Get()->GetCryptohomeClient()->Unmount(
-          base::BindOnce(&CryptohomeAuthenticator::OnUnmount, this));
+      DBusThreadManager::Get()->GetCryptohomeClient()->UnmountEx(
+          cryptohome::UnmountRequest(),
+          base::BindOnce(&CryptohomeAuthenticator::OnUnmountEx, this));
       break;
     }
     case FAILED_OLD_ENCRYPTION:
diff --git a/chromeos/login/auth/cryptohome_authenticator.h b/chromeos/login/auth/cryptohome_authenticator.h
index ae50bd8..9e60485a 100644
--- a/chromeos/login/auth/cryptohome_authenticator.h
+++ b/chromeos/login/auth/cryptohome_authenticator.h
@@ -27,6 +27,10 @@
 class BrowserContext;
 }
 
+namespace cryptohome {
+class BaseReply;
+}
+
 namespace chromeos {
 
 class AuthStatusConsumer;
@@ -158,6 +162,9 @@
   void RecoverEncryptedData(const std::string& old_password) override;
   void ResyncEncryptedData() override;
 
+  // Called after UnmountEx finishes.
+  void OnUnmountEx(base::Optional<cryptohome::BaseReply> reply);
+
   // AuthAttemptStateResolver overrides.
   // Attempts to make a decision and call back |consumer_| based on
   // the state we have gathered at the time of call.  If a decision
diff --git a/components/BUILD.gn b/components/BUILD.gn
index b5eb6cdd..68fcbda 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -5,7 +5,6 @@
 import("//build/config/chrome_build.gni")
 import("//build/config/features.gni")
 import("//build/config/ui.gni")
-import("//components/mpris/features.gni")
 import("//components/nacl/features.gni")
 import("//components/ui_devtools/devtools.gni")
 import("//media/media_options.gni")
@@ -384,10 +383,6 @@
     ]
   }
 
-  if (use_mpris) {
-    deps += [ "//components/mpris:unit_tests" ]
-  }
-
   # No components should depend on Chrome.
   assert_no_deps = [ "//chrome/*" ]
 
diff --git a/components/autofill/core/browser/local_card_migration_manager.cc b/components/autofill/core/browser/local_card_migration_manager.cc
index 2a2cb4c4..ab4fb69 100644
--- a/components/autofill/core/browser/local_card_migration_manager.cc
+++ b/components/autofill/core/browser/local_card_migration_manager.cc
@@ -208,18 +208,24 @@
       observer_for_testing_ ||
       ::autofill::IsCreditCardUploadEnabled(
           client_->GetPrefs(), client_->GetSyncService(),
-          client_->GetIdentityManager()->GetPrimaryAccountInfo().email);
+          personal_data_manager_->GetAccountInfoForPaymentsServer().email);
 
   bool has_google_payments_account =
       (payments::GetBillingCustomerId(personal_data_manager_,
                                       payments_client_->GetPrefService()) != 0);
 
-  bool sync_feature_enabled =
-      (personal_data_manager_->GetSyncSigninState() ==
-       AutofillSyncSigninState::kSignedInAndSyncFeature);
+  AutofillSyncSigninState sync_state =
+      personal_data_manager_->GetSyncSigninState();
 
   return migration_experiment_enabled && credit_card_upload_enabled &&
-         has_google_payments_account && sync_feature_enabled;
+         has_google_payments_account &&
+         // User signed-in and turned sync on.
+         (sync_state == AutofillSyncSigninState::kSignedInAndSyncFeature ||
+          // User signed-in but not turned on sync.
+          (sync_state == AutofillSyncSigninState::
+                             kSignedInAndWalletSyncTransportEnabled &&
+           base::FeatureList::IsEnabled(
+               features::kAutofillEnableLocalCardMigrationForNonSyncUser)));
 }
 
 void LocalCardMigrationManager::OnDidGetUploadDetails(
diff --git a/components/autofill/core/browser/local_card_migration_manager_unittest.cc b/components/autofill/core/browser/local_card_migration_manager_unittest.cc
index d9099992..654309b 100644
--- a/components/autofill/core/browser/local_card_migration_manager_unittest.cc
+++ b/components/autofill/core/browser/local_card_migration_manager_unittest.cc
@@ -29,6 +29,7 @@
 #include "components/autofill/core/browser/mock_autocomplete_history_manager.h"
 #include "components/autofill/core/browser/payments/test_payments_client.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
+#include "components/autofill/core/browser/sync_utils.h"
 #include "components/autofill/core/browser/test_autofill_client.h"
 #include "components/autofill/core/browser/test_autofill_clock.h"
 #include "components/autofill/core/browser/test_autofill_driver.h"
@@ -112,16 +113,6 @@
     request_context_ = nullptr;
   }
 
-  void EnableAutofillCreditCardLocalCardMigrationExperiment() {
-    scoped_feature_list_.InitAndEnableFeature(
-        features::kAutofillCreditCardLocalCardMigration);
-  }
-
-  void DisableAutofillCreditCardLocalCardMigrationExperiment() {
-    scoped_feature_list_.InitAndDisableFeature(
-        features::kAutofillCreditCardLocalCardMigration);
-  }
-
   void FormsSeen(const std::vector<FormData>& forms) {
     autofill_manager_->OnFormsSeen(forms, base::TimeTicks());
   }
@@ -194,7 +185,8 @@
 // Having one local card on file and using it will not trigger migration.
 TEST_F(LocalCardMigrationManagerTest,
        MigrateCreditCard_UseLocalCardWithOneLocal) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -227,7 +219,8 @@
 // trigger migration.
 TEST_F(LocalCardMigrationManagerTest,
        MigrateCreditCard_UseNewCardWithAnyLocal) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -257,7 +250,8 @@
 // migration.
 TEST_F(LocalCardMigrationManagerTest,
        MigrateCreditCard_UseLocalCardWithMoreLocal) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -303,7 +297,8 @@
 // cards as long as the other local cards are not eligible for migration.
 TEST_F(LocalCardMigrationManagerTest,
        MigrateCreditCard_UseLocalCardWithInvalidLocal) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -336,7 +331,8 @@
 // will trigger migration.
 TEST_F(LocalCardMigrationManagerTest,
        MigrateCreditCard_UseServerCardWithOneValidLocal) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -385,7 +381,8 @@
 // cards as long as the other local cards are not eligible for migration.
 TEST_F(LocalCardMigrationManagerTest,
        MigrateCreditCard_UseServerCardWithNoneValidLocal) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -427,7 +424,8 @@
 // is off, will not trigger migration.
 TEST_F(LocalCardMigrationManagerTest, MigrateCreditCard_FeatureNotEnabled) {
   // Turn off the experiment flag.
-  DisableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndDisableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -454,20 +452,22 @@
 }
 
 // Do not trigger migration if user only signs in.
-TEST_F(LocalCardMigrationManagerTest, MigrateCreditCard_SignInOnly) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+TEST_F(LocalCardMigrationManagerTest, MigrateCreditCard_SignInOnlyWhenExpOff) {
+  scoped_feature_list_.InitWithFeatures(
+      // Enabled
+      {features::kAutofillCreditCardLocalCardMigration,
+       features::kAutofillEnableAccountWalletStorage},
+      // Disabled
+      {features::kAutofillEnableLocalCardMigrationForNonSyncUser});
 
-  // Make a non-primary account available with both a refresh token and cookie
-  // to be in Sync Transport for Wallet mode, in which case this account is
-  // signed in only without sync enabled.
-  sync_service_.SetIsAuthenticatedAccountPrimary(false);
-  sync_service_.SetActiveDataTypes(
-      syncer::ModelTypeSet(syncer::AUTOFILL_WALLET_DATA));
+  // Set the billing_customer_number Priority Preference to designate
+  // existence of a Payments account.
+  autofill_client_.GetPrefs()->SetDouble(prefs::kAutofillBillingCustomerNumber,
+                                         12345);
 
-  base::test::ScopedFeatureList scoped_features;
-  scoped_features.InitWithFeatures(
-      /*enabled_features=*/{features::kAutofillEnableAccountWalletStorage},
-      /*disabled_features=*/{});
+  // Mock Chrome Sync is disabled.
+  local_card_migration_manager_->ResetSyncState(
+      AutofillSyncSigninState::kSignedInAndWalletSyncTransportEnabled);
 
   // Add a local credit card whose |TypeAndLastFourDigits| matches what we will
   // enter below.
@@ -489,10 +489,50 @@
   EXPECT_FALSE(local_card_migration_manager_->LocalCardMigrationWasTriggered());
 }
 
+// Trigger migration if user only signs in and if experiment is enabled.
+TEST_F(LocalCardMigrationManagerTest, MigrateCreditCard_SignInOnlyWhenExpOn) {
+  scoped_feature_list_.InitWithFeatures(
+      // Enabled
+      {features::kAutofillCreditCardLocalCardMigration,
+       features::kAutofillEnableLocalCardMigrationForNonSyncUser,
+       features::kAutofillEnableAccountWalletStorage},
+      // Disabled
+      {});
+
+  // Set the billing_customer_number Priority Preference to designate
+  // existence of a Payments account.
+  autofill_client_.GetPrefs()->SetDouble(prefs::kAutofillBillingCustomerNumber,
+                                         12345);
+
+  // Mock Chrome Sync is disabled.
+  local_card_migration_manager_->ResetSyncState(
+      AutofillSyncSigninState::kSignedInAndWalletSyncTransportEnabled);
+
+  // Add a local credit card whose |TypeAndLastFourDigits| matches what we will
+  // enter below.
+  AddLocalCreditCard(personal_data_, "Flo Master", "4111111111111111", "11",
+                     test::NextYear().c_str(), "1", "guid1");
+  // Add another local credit card.
+  AddLocalCreditCard(personal_data_, "Flo Master", "5555555555554444", "11",
+                     test::NextYear().c_str(), "1", "guid2");
+
+  // Set up our credit card form data.
+  FormData credit_card_form;
+  test::CreateTestCreditCardFormData(&credit_card_form, true, false);
+  FormsSeen(std::vector<FormData>(1, credit_card_form));
+
+  // Edit the data, and submit.
+  EditCreditCardFrom(credit_card_form, "Flo Master", "4111111111111111", "11",
+                     test::NextYear().c_str(), "123");
+  FormSubmitted(credit_card_form);
+  EXPECT_TRUE(local_card_migration_manager_->LocalCardMigrationWasTriggered());
+}
+
 // Use one local card with more valid local cards available but billing customer
 // number is blank, will not trigger migration.
 TEST_F(LocalCardMigrationManagerTest, MigrateCreditCard_NoPaymentsAccount) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Add a local credit card whose |TypeAndLastFourDigits| matches what we will
   // enter below.
@@ -518,7 +558,8 @@
 // migratable.
 TEST_F(LocalCardMigrationManagerTest,
        MigrateCreditCard_LocalCardMatchMaskedServerCard) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -555,7 +596,8 @@
 // migratable.
 TEST_F(LocalCardMigrationManagerTest,
        MigrateCreditCard_LocalCardMatchFullServerCard) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -587,7 +629,8 @@
 
 // GetDetectedValues() should includes cardholder name if all cards have it.
 TEST_F(LocalCardMigrationManagerTest, GetDetectedValues_AllWithCardHolderName) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -619,7 +662,8 @@
 // a cardholder name.
 TEST_F(LocalCardMigrationManagerTest,
        GetDetectedValues_OneCardWithoutCardHolderName) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -650,7 +694,8 @@
 // account.
 TEST_F(LocalCardMigrationManagerTest,
        GetDetectedValues_IncludeGooglePaymentsAccount) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -681,7 +726,8 @@
 
 TEST_F(LocalCardMigrationManagerTest,
        MigrateCreditCard_ShouldAddMigrateCardsBillableServiceNumberInRequest) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -714,7 +760,8 @@
 
 TEST_F(LocalCardMigrationManagerTest,
        MigrateCreditCard_ShouldAddUploadCardSourceInRequest_CheckoutFlow) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -747,7 +794,8 @@
 
 TEST_F(LocalCardMigrationManagerTest,
        MigrateCreditCard_ShouldAddUploadCardSourceInRequest_SettingsPage) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -776,7 +824,8 @@
 // be triggered.
 TEST_F(LocalCardMigrationManagerTest,
        MigrateCreditCard_TriggerFromSettingsPage) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -815,7 +864,8 @@
 // prompt are both triggered.
 TEST_F(LocalCardMigrationManagerTest,
        MigrateCreditCard_TriggerFromSubmittedForm) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -846,7 +896,8 @@
 // prompt are not triggered as user previously rejected the prompt.
 TEST_F(LocalCardMigrationManagerTest,
        MigrateCreditCard_DontTriggerFromSubmittedForm) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -883,7 +934,8 @@
 // Verify that given the parsed response from the payments client, the migration
 // status is correctly set.
 TEST_F(LocalCardMigrationManagerTest, MigrateCreditCard_MigrationSuccess) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -924,7 +976,8 @@
 // status is correctly set.
 TEST_F(LocalCardMigrationManagerTest,
        MigrateCreditCard_MigrationTemporaryFailure) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -967,7 +1020,8 @@
 // status is correctly set.
 TEST_F(LocalCardMigrationManagerTest,
        MigrateCreditCard_MigrationPermanentFailure) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   // Set the billing_customer_number Priority Preference to designate
   // existence of a Payments account.
@@ -1008,7 +1062,8 @@
 
 // Verify selected cards are correctly passed to manager.
 TEST_F(LocalCardMigrationManagerTest, MigrateCreditCard_ToggleIsChosen) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
   AddLocalCreditCard(personal_data_, "Flo Master", "4111111111111111", "11",
                      test::NextYear().c_str(), "1", "guid1");
   AddLocalCreditCard(personal_data_, "Flo Master", "5454545454545454", "11",
@@ -1031,7 +1086,8 @@
 }
 
 TEST_F(LocalCardMigrationManagerTest, DeleteLocalCardViaMigrationDialog) {
-  EnableAutofillCreditCardLocalCardMigrationExperiment();
+  scoped_feature_list_.InitAndEnableFeature(
+      features::kAutofillCreditCardLocalCardMigration);
 
   AddLocalCreditCard(personal_data_, "Flo Master", "4111111111111111", "11",
                      test::NextYear().c_str(), "1", "guid1");
diff --git a/components/autofill/core/browser/personal_data_manager.h b/components/autofill/core/browser/personal_data_manager.h
index d1d364ee..df97465 100644
--- a/components/autofill/core/browser/personal_data_manager.h
+++ b/components/autofill/core/browser/personal_data_manager.h
@@ -126,7 +126,7 @@
   void OnAccountsCookieDeletedByUserAction() override;
 
   // Returns the current sync status.
-  AutofillSyncSigninState GetSyncSigninState() const;
+  virtual AutofillSyncSigninState GetSyncSigninState() const;
 
   // Adds a listener to be notified of PersonalDataManager events.
   virtual void AddObserver(PersonalDataManagerObserver* observer);
diff --git a/components/autofill/core/browser/test_local_card_migration_manager.cc b/components/autofill/core/browser/test_local_card_migration_manager.cc
index cc72aed..0438e64 100644
--- a/components/autofill/core/browser/test_local_card_migration_manager.cc
+++ b/components/autofill/core/browser/test_local_card_migration_manager.cc
@@ -8,7 +8,6 @@
 #include "components/autofill/core/browser/payments/test_payments_client.h"
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/autofill_prefs.h"
-#include "services/identity/public/cpp/identity_manager.h"
 
 namespace autofill {
 
@@ -16,11 +15,12 @@
     AutofillDriver* driver,
     AutofillClient* client,
     payments::TestPaymentsClient* payments_client,
-    PersonalDataManager* personal_data_manager)
+    TestPersonalDataManager* personal_data_manager)
     : LocalCardMigrationManager(client,
                                 payments_client,
                                 "en-US",
-                                personal_data_manager) {}
+                                personal_data_manager),
+      personal_data_manager_(personal_data_manager) {}
 
 TestLocalCardMigrationManager::~TestLocalCardMigrationManager() {}
 
@@ -28,10 +28,19 @@
   bool migration_experiment_enabled =
       features::GetLocalCardMigrationExperimentalFlag() !=
       features::LocalCardMigrationExperimentalFlag::kMigrationDisabled;
+
   bool has_google_payments_account =
       (static_cast<int64_t>(payments_client_->GetPrefService()->GetDouble(
            prefs::kAutofillBillingCustomerNumber)) != 0);
-  return migration_experiment_enabled && has_google_payments_account;
+
+  bool sync_feature_enabled =
+      (personal_data_manager_->GetSyncSigninState() ==
+       AutofillSyncSigninState::kSignedInAndSyncFeature);
+
+  return migration_experiment_enabled && has_google_payments_account &&
+         (sync_feature_enabled ||
+          base::FeatureList::IsEnabled(
+              features::kAutofillEnableLocalCardMigrationForNonSyncUser));
 }
 
 bool TestLocalCardMigrationManager::LocalCardMigrationWasTriggered() {
@@ -58,6 +67,11 @@
   LocalCardMigrationManager::OnUserAcceptedMainMigrationDialog(selected_cards);
 }
 
+void TestLocalCardMigrationManager::ResetSyncState(
+    AutofillSyncSigninState sync_state) {
+  personal_data_manager_->SetSyncAndSignInState(sync_state);
+}
+
 void TestLocalCardMigrationManager::OnDidGetUploadDetails(
     bool is_from_settings_page,
     AutofillClient::PaymentsRpcResult result,
diff --git a/components/autofill/core/browser/test_local_card_migration_manager.h b/components/autofill/core/browser/test_local_card_migration_manager.h
index 97e4518..fe5e6f8a 100644
--- a/components/autofill/core/browser/test_local_card_migration_manager.h
+++ b/components/autofill/core/browser/test_local_card_migration_manager.h
@@ -8,6 +8,8 @@
 #include <string>
 
 #include "components/autofill/core/browser/local_card_migration_manager.h"
+#include "components/autofill/core/browser/sync_utils.h"
+#include "components/autofill/core/browser/test_personal_data_manager.h"
 
 namespace autofill {
 
@@ -17,14 +19,13 @@
 
 class AutofillClient;
 class AutofillDriver;
-class PersonalDataManager;
 
 class TestLocalCardMigrationManager : public LocalCardMigrationManager {
  public:
   TestLocalCardMigrationManager(AutofillDriver* driver,
                                 AutofillClient* client,
                                 payments::TestPaymentsClient* payments_client,
-                                PersonalDataManager* personal_data_manager);
+                                TestPersonalDataManager* personal_data_manager);
   ~TestLocalCardMigrationManager() override;
 
   // Override the base function. Checks the existnece of billing customer number
@@ -50,6 +51,10 @@
   void OnUserAcceptedMainMigrationDialog(
       const std::vector<std::string>& selected_cards) override;
 
+  // Mock the Chrome Sync state in the LocalCardMigrationManager. If not set,
+  // default to AutofillSyncSigninState::kSignedInAndSyncFeature.
+  void ResetSyncState(AutofillSyncSigninState sync_state);
+
  private:
   void OnDidGetUploadDetails(
       bool is_from_settings_page,
@@ -63,6 +68,8 @@
 
   bool main_prompt_was_shown_ = false;
 
+  TestPersonalDataManager* personal_data_manager_;
+
   DISALLOW_COPY_AND_ASSIGN(TestLocalCardMigrationManager);
 };
 
diff --git a/components/autofill/core/browser/test_personal_data_manager.cc b/components/autofill/core/browser/test_personal_data_manager.cc
index 6115dad..209cf497 100644
--- a/components/autofill/core/browser/test_personal_data_manager.cc
+++ b/components/autofill/core/browser/test_personal_data_manager.cc
@@ -19,6 +19,10 @@
   sync_service_initialized_ = true;
 }
 
+AutofillSyncSigninState TestPersonalDataManager::GetSyncSigninState() const {
+  return sync_and_signin_state_;
+}
+
 void TestPersonalDataManager::RecordUseOf(const AutofillDataModel& data_model) {
   CreditCard* credit_card = GetCreditCardWithGUID(data_model.guid().c_str());
   if (credit_card)
diff --git a/components/autofill/core/browser/test_personal_data_manager.h b/components/autofill/core/browser/test_personal_data_manager.h
index 2ed5e71..8aedd182 100644
--- a/components/autofill/core/browser/test_personal_data_manager.h
+++ b/components/autofill/core/browser/test_personal_data_manager.h
@@ -28,6 +28,7 @@
   // for various tests, whether to skip calls to uncreated databases/services,
   // or to make things easier in general to toggle.
   void OnSyncServiceInitialized(syncer::SyncService* sync_service) override;
+  AutofillSyncSigninState GetSyncSigninState() const override;
   void RecordUseOf(const AutofillDataModel& data_model) override;
   std::string SaveImportedProfile(
       const AutofillProfile& imported_profile) override;
@@ -115,6 +116,10 @@
 
   void SetSyncFeatureEnabled(bool enabled) { sync_feature_enabled_ = enabled; }
 
+  void SetSyncAndSignInState(AutofillSyncSigninState sync_and_signin_state) {
+    sync_and_signin_state_ = sync_and_signin_state;
+  }
+
   void SetAccountInfoForPayments(const CoreAccountInfo& account_info) {
     account_info_ = account_info;
   }
@@ -129,6 +134,8 @@
   base::Optional<bool> autofill_credit_card_enabled_;
   base::Optional<bool> autofill_wallet_import_enabled_;
   bool sync_feature_enabled_ = false;
+  AutofillSyncSigninState sync_and_signin_state_ =
+      AutofillSyncSigninState::kSignedInAndSyncFeature;
   bool sync_service_initialized_ = false;
   CoreAccountInfo account_info_;
 
diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc
index 1715e1d..5bed4234 100644
--- a/components/autofill/core/common/autofill_features.cc
+++ b/components/autofill/core/common/autofill_features.cc
@@ -96,6 +96,12 @@
 const base::Feature kAutofillEnableIFrameSupportOniOS{
     "AutofillEnableIFrameSupportOniOS", base::FEATURE_ENABLED_BY_DEFAULT};
 
+// When enabled, enable local card migration flow for user who has signed in but
+// has not turned on sync.
+const base::Feature kAutofillEnableLocalCardMigrationForNonSyncUser{
+    "AutofillEnableLocalCardMigrationForNonSyncUser",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+
 // When enabled, no local copy of server card will be saved when upload
 // succeeds.
 const base::Feature kAutofillNoLocalSaveOnUploadSuccess{
diff --git a/components/autofill/core/common/autofill_features.h b/components/autofill/core/common/autofill_features.h
index e23fa2c0..a7a832b 100644
--- a/components/autofill/core/common/autofill_features.h
+++ b/components/autofill/core/common/autofill_features.h
@@ -39,6 +39,7 @@
 extern const base::Feature kAutofillEnableAccountWalletStorageUpload;
 extern const base::Feature kAutofillEnableCompanyName;
 extern const base::Feature kAutofillEnableIFrameSupportOniOS;
+extern const base::Feature kAutofillEnableLocalCardMigrationForNonSyncUser;
 extern const base::Feature kAutofillEnforceMinRequiredFieldsForHeuristics;
 extern const base::Feature kAutofillEnforceMinRequiredFieldsForQuery;
 extern const base::Feature kAutofillEnforceMinRequiredFieldsForUpload;
diff --git a/components/autofill_assistant_strings.grdp b/components/autofill_assistant_strings.grdp
index a4f0026..daadd1d 100644
--- a/components/autofill_assistant_strings.grdp
+++ b/components/autofill_assistant_strings.grdp
@@ -4,7 +4,9 @@
     <message name="IDS_AUTOFILL_ASSISTANT_DEFAULT_ERROR" desc="Text label that is shown when autofill assistant cannot help anymore, because something went wrong.">
       Sorry, something went wrong.
     </message>
-    <message name="IDS_AUTOFILL_ASSISTANT_GIVE_UP" desc="Text label that is shown when autofill assistant cannot help anymore, because of a user action." formatter_data="android_java">
+    <message name="IDS_AUTOFILL_ASSISTANT_GIVE_UP"
+             desc="Text label that is shown when autofill assistant cannot help anymore, because of a user action."
+             internal_comment="TODO(wnwen): Remove duplication in chrome/android/features/autofill_assistant/strings/android_chrome_autofill_assistant_strings.grd">
       Looks like you navigated off on your own, I’ll leave you to it. Have a nice day :)
     </message>
     <message name="IDS_AUTOFILL_ASSISTANT_MAYBE_GIVE_UP" desc="Text label shown next to an UNDO button when user action indicate they want to continue on their own.">
@@ -13,18 +15,9 @@
     <message name="IDS_AUTOFILL_ASSISTANT_LOADING" desc="Text label that is shown during the loading of the first page, right after being triggered.">
       Opening <ph name="SITE_NAME">$1<ex>google.com</ex></ph>…
     </message>
-    <message name="IDS_AUTOFILL_ASSISTANT_PAYMENT_INFO_CONFIRM" desc="Text on the payment request primary button to confirm payment information [CHAR-LIMIT=32]" formatter_data="android_java">
-      Continue
-    </message>
     <message name="IDS_AUTOFILL_ASSISTANT_DETAILS_DIFFER" desc="Shown as Status Message when details differ.">
       The screening is different from what you selected. Continue?
     </message>
-    <message name="IDS_AUTOFILL_ASSISTANT_FIRST_RUN_ACCESSIBILITY" desc="Accessibility description of Autofill Assistant first run screen is shown." formatter_data="android_java">
-      Google Assistant in Chrome first run screen is shown
-    </message>
-    <message name="IDS_AUTOFILL_ASSISTANT_AVAILABLE_ACCESSIBILITY" desc="Accessibility description of Autofill Assistant is available." formatter_data="android_java">
-      Google Assistant in Chrome is available near bottom of the screen
-    </message>
     <message name="IDS_AUTOFILL_ASSISTANT_CONTINUE_BUTTON" desc="Generic label for a button to continue to the next screen.">
       Continue
     </message>
diff --git a/components/browser_sync/profile_sync_service.cc b/components/browser_sync/profile_sync_service.cc
index 04e4836..787e2d2 100644
--- a/components/browser_sync/profile_sync_service.cc
+++ b/components/browser_sync/profile_sync_service.cc
@@ -538,8 +538,8 @@
 void ProfileSyncService::Shutdown() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  ShutdownImpl(syncer::BROWSER_SHUTDOWN);
   NotifyShutdown();
+  ShutdownImpl(syncer::BROWSER_SHUTDOWN);
 
   // All observers must be gone now: All KeyedServices should have unregistered
   // their observers already before, in their own Shutdown(), and all others
@@ -701,9 +701,12 @@
 
   if (!engine_initialized_) {
     switch (startup_controller_->GetState()) {
+        // TODO(crbug.com/935523): If the engine is allowed to start, then we
+        // should generally have kicked off the startup process already, so
+        // NOT_STARTED should be impossible. But we can temporarily be in this
+        // state between shutting down and starting up again (e.g. during the
+        // NotifyObservers() call in ShutdownImpl()).
       case syncer::StartupController::State::NOT_STARTED:
-        DCHECK(!engine_);
-        return TransportState::WAITING_FOR_START_REQUEST;
       case syncer::StartupController::State::STARTING_DEFERRED:
         DCHECK(!engine_);
         return TransportState::START_DEFERRED;
@@ -1523,8 +1526,6 @@
 
 void ProfileSyncService::OnSyncRequestedPrefChange(bool is_sync_requested) {
   if (is_sync_requested) {
-    NotifyObservers();
-
     // If the Sync engine was already initialized (probably running in transport
     // mode), just reconfigure.
     if (engine_initialized_) {
@@ -1534,6 +1535,8 @@
       // reasons remaining, in which case this will effectively do nothing.
       startup_controller_->TryStart(/*force_immediate=*/true);
     }
+
+    NotifyObservers();
   } else {
     // This will notify the observers.
     if (is_stopping_and_clearing_) {
diff --git a/components/browser_sync/profile_sync_service_startup_unittest.cc b/components/browser_sync/profile_sync_service_startup_unittest.cc
index 45a649b7..14d5e399 100644
--- a/components/browser_sync/profile_sync_service_startup_unittest.cc
+++ b/components/browser_sync/profile_sync_service_startup_unittest.cc
@@ -194,10 +194,8 @@
 
   SimulateTestUserSignin();
 
-  // Now we're signed in, so the engine can start. There's already a setup in
-  // progress, so we don't go into the WAITING_FOR_START_REQUEST state. Engine
-  // initialization is immediate in this test, so we also bypass the
-  // INITIALIZING state.
+  // Now we're signed in, so the engine can start. Engine initialization is
+  // immediate in this test, so we bypass the INITIALIZING state.
   EXPECT_TRUE(sync_service()->IsEngineInitialized());
   EXPECT_EQ(syncer::SyncService::DISABLE_REASON_NONE,
             sync_service()->GetDisableReasons());
diff --git a/components/exo/client_controlled_shell_surface.cc b/components/exo/client_controlled_shell_surface.cc
index fbc6997d9..184287f 100644
--- a/components/exo/client_controlled_shell_surface.cc
+++ b/components/exo/client_controlled_shell_surface.cc
@@ -923,12 +923,21 @@
     decorator_.reset();  // Remove rounded corners.
   }
 
+  bool wasPip = window_state->IsPip();
+
   if (client_controlled_state_->EnterNextState(window_state,
                                                pending_window_state_)) {
     client_controlled_state_->set_next_bounds_change_animation_type(
         animation_type);
   }
 
+  if (wasPip && !window_state->IsMinimized()) {
+    // As Android doesn't activate PIP tasks after they are expanded, we need
+    // to do it here explicitly.
+    // TODO(937738): Investigate if we can activate PIP windows inside commit.
+    window_state->Activate();
+  }
+
   return true;
 }
 
diff --git a/components/gwp_asan/client/guarded_page_allocator.cc b/components/gwp_asan/client/guarded_page_allocator.cc
index 82c9fa89..13fc2bf 100644
--- a/components/gwp_asan/client/guarded_page_allocator.cc
+++ b/components/gwp_asan/client/guarded_page_allocator.cc
@@ -115,7 +115,7 @@
   void* alloc = reinterpret_cast<void*>(free_page + offset);
 
   // Initialize slot metadata.
-  RecordAllocationInSlot(free_slot, size, alloc);
+  RecordAllocationMetadata(free_slot, size, alloc);
 
   return alloc;
 }
@@ -139,14 +139,14 @@
     // TODO(https://crbug.com/925447): The other thread may not be done writing
     // a stack trace so we could spin here until it's read; however, it's also
     // possible we are racing an allocation in the middle of
-    // RecordAllocationInSlot. For now it's possible a racy double free could
+    // RecordAllocationMetadata. For now it's possible a racy double free could
     // lead to a bad stack trace, but no internal allocator corruption.
     __builtin_trap();
   }
 
   // Record deallocation stack trace/thread id before marking the page
   // inaccessible in case a use-after-free occurs immediately.
-  RecordDeallocationInSlot(slot);
+  RecordDeallocationMetadata(slot);
   MarkPageInaccessible(reinterpret_cast<void*>(state_.GetPageAddr(addr)));
 
   FreeSlot(slot);
@@ -191,9 +191,9 @@
             free_slot_start_idx_);
 }
 
-void GuardedPageAllocator::RecordAllocationInSlot(size_t slot,
-                                                  size_t size,
-                                                  void* ptr) {
+void GuardedPageAllocator::RecordAllocationMetadata(size_t slot,
+                                                    size_t size,
+                                                    void* ptr) {
   slots_[slot].alloc_size = size;
   slots_[slot].alloc_ptr = reinterpret_cast<uintptr_t>(ptr);
 
@@ -212,7 +212,7 @@
   slots_[slot].deallocation_occurred = false;
 }
 
-void GuardedPageAllocator::RecordDeallocationInSlot(size_t slot) {
+void GuardedPageAllocator::RecordDeallocationMetadata(size_t slot) {
   void* trace[AllocatorState::kMaxStackFrames];
   size_t len =
       base::debug::CollectStackTrace(trace, AllocatorState::kMaxStackFrames);
diff --git a/components/gwp_asan/client/guarded_page_allocator.h b/components/gwp_asan/client/guarded_page_allocator.h
index 523b3b0b..a4ace98 100644
--- a/components/gwp_asan/client/guarded_page_allocator.h
+++ b/components/gwp_asan/client/guarded_page_allocator.h
@@ -101,8 +101,8 @@
   // encapsulates the logic for updating the stack traces and metadata for a
   // given slot.
   ALWAYS_INLINE
-  void RecordAllocationInSlot(size_t slot, size_t size, void* ptr);
-  ALWAYS_INLINE void RecordDeallocationInSlot(size_t slot);
+  void RecordAllocationMetadata(size_t slot, size_t size, void* ptr);
+  ALWAYS_INLINE void RecordDeallocationMetadata(size_t slot);
 
   // Allocator state shared with with the crash analyzer.
   AllocatorState state_;
diff --git a/components/gwp_asan/client/sampling_allocator_shims_unittest.cc b/components/gwp_asan/client/sampling_allocator_shims_unittest.cc
index 69e459d..f74fbf9 100644
--- a/components/gwp_asan/client/sampling_allocator_shims_unittest.cc
+++ b/components/gwp_asan/client/sampling_allocator_shims_unittest.cc
@@ -135,9 +135,10 @@
 
 #if defined(OS_POSIX)
   EXPECT_TRUE(allocationCheck(
-      [&] {
+      [&]() -> void* {
         void* ptr;
-        posix_memalign(&ptr, page_size, page_size);
+        if (posix_memalign(&ptr, page_size, page_size))
+          return nullptr;
         return ptr;
       },
       &free, &failures));
diff --git a/components/plugins/renderer/webview_plugin.cc b/components/plugins/renderer/webview_plugin.cc
index af53aae..509d298 100644
--- a/components/plugins/renderer/webview_plugin.cc
+++ b/components/plugins/renderer/webview_plugin.cc
@@ -275,6 +275,11 @@
       mojo::MakeRequest(&document_interface_broker).PassMessagePipe(), nullptr);
   // The created WebFrameWidget is owned by the |web_frame|.
   WebFrameWidget::CreateForMainFrame(this, web_frame);
+
+  // The WebFrame created here was already attached to the Page as its
+  // main frame, and the WebFrameWidget has been initialized, so we can call
+  // WebViewImpl's DidAttachLocalMainFrame().
+  web_view_->DidAttachLocalMainFrame(this);
 }
 
 WebViewPlugin::WebViewHelper::~WebViewHelper() {
diff --git a/components/policy/core/common/cloud/cloud_policy_manager.cc b/components/policy/core/common/cloud/cloud_policy_manager.cc
index bf860c85..bc7c924 100644
--- a/components/policy/core/common/cloud/cloud_policy_manager.cc
+++ b/components/policy/core/common/cloud/cloud_policy_manager.cc
@@ -121,6 +121,7 @@
 void CloudPolicyManager::CreateComponentCloudPolicyService(
     const std::string& policy_type,
     const base::FilePath& policy_cache_path,
+    PolicySource policy_source,
     CloudPolicyClient* client,
     SchemaRegistry* schema_registry) {
 #if !defined(OS_ANDROID) && !defined(OS_IOS)
@@ -149,7 +150,7 @@
   std::unique_ptr<ResourceCache> resource_cache(new ResourceCache(
       policy_cache_path, task_runner, /* max_cache_size */ base::nullopt));
   component_policy_service_.reset(new ComponentCloudPolicyService(
-      policy_type, this, schema_registry, core(), client,
+      policy_type, policy_source, this, schema_registry, core(), client,
       std::move(resource_cache), task_runner));
 #endif  // !defined(OS_ANDROID) && !defined(OS_IOS)
 }
diff --git a/components/policy/core/common/cloud/cloud_policy_manager.h b/components/policy/core/common/cloud/cloud_policy_manager.h
index c1bfa5be..285ff64 100644
--- a/components/policy/core/common/cloud/cloud_policy_manager.h
+++ b/components/policy/core/common/cloud/cloud_policy_manager.h
@@ -15,6 +15,7 @@
 #include "components/policy/core/common/cloud/cloud_policy_store.h"
 #include "components/policy/core/common/cloud/component_cloud_policy_service.h"
 #include "components/policy/core/common/configuration_policy_provider.h"
+#include "components/policy/core/common/policy_types.h"
 #include "components/policy/policy_export.h"
 #include "components/prefs/pref_member.h"
 #include "services/network/public/cpp/network_connection_tracker.h"
@@ -79,6 +80,7 @@
   void CreateComponentCloudPolicyService(
       const std::string& policy_type,
       const base::FilePath& policy_cache_path,
+      PolicySource policy_source,
       CloudPolicyClient* client,
       SchemaRegistry* schema_registry);
 
diff --git a/components/policy/core/common/cloud/component_cloud_policy_service.cc b/components/policy/core/common/cloud/component_cloud_policy_service.cc
index fd71e59..7852371 100644
--- a/components/policy/core/common/cloud/component_cloud_policy_service.cc
+++ b/components/policy/core/common/cloud/component_cloud_policy_service.cc
@@ -76,7 +76,8 @@
       scoped_refptr<base::SequencedTaskRunner> service_task_runner,
       std::unique_ptr<ResourceCache> cache,
       std::unique_ptr<ExternalPolicyDataFetcher> external_policy_data_fetcher,
-      const std::string& policy_type);
+      const std::string& policy_type,
+      PolicySource policy_source);
 
   ~Backend() override;
 
@@ -139,13 +140,14 @@
     scoped_refptr<base::SequencedTaskRunner> service_task_runner,
     std::unique_ptr<ResourceCache> cache,
     std::unique_ptr<ExternalPolicyDataFetcher> external_policy_data_fetcher,
-    const std::string& policy_type)
+    const std::string& policy_type,
+    PolicySource policy_source)
     : service_(service),
       task_runner_(task_runner),
       service_task_runner_(service_task_runner),
       cache_(std::move(cache)),
       external_policy_data_fetcher_(std::move(external_policy_data_fetcher)),
-      store_(this, cache_.get(), policy_type) {
+      store_(this, cache_.get(), policy_type, policy_source) {
   // This class is allowed to be instantiated on any thread.
   DETACH_FROM_SEQUENCE(sequence_checker_);
 }
@@ -264,6 +266,7 @@
 
 ComponentCloudPolicyService::ComponentCloudPolicyService(
     const std::string& policy_type,
+    PolicySource policy_source,
     Delegate* delegate,
     SchemaRegistry* schema_registry,
     CloudPolicyCore* core,
@@ -290,7 +293,7 @@
                   base::ThreadTaskRunnerHandle::Get(), std::move(cache),
                   external_policy_data_fetcher_backend_->CreateFrontend(
                       backend_task_runner_),
-                  policy_type));
+                  policy_type, policy_source));
 
   // Observe the schema registry for keeping |current_schema_map_| up to date.
   schema_registry_->AddObserver(this);
diff --git a/components/policy/core/common/cloud/component_cloud_policy_service.h b/components/policy/core/common/cloud/component_cloud_policy_service.h
index 7a1161a..c81ff703 100644
--- a/components/policy/core/common/cloud/component_cloud_policy_service.h
+++ b/components/policy/core/common/cloud/component_cloud_policy_service.h
@@ -19,6 +19,7 @@
 #include "components/policy/core/common/cloud/cloud_policy_store.h"
 #include "components/policy/core/common/policy_bundle.h"
 #include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_types.h"
 #include "components/policy/core/common/schema_registry.h"
 #include "components/policy/policy_export.h"
 
@@ -65,6 +66,11 @@
   // allowed values are: |dm_protocol::kChromeExtensionPolicyType|,
   // |dm_protocol::kChromeSigninExtensionPolicyType|.
   //
+  // |policy_source| specifies where the policy originates from, and can be used
+  // to configure precedence when the same components are configured by policies
+  // from different sources. It only accepts POLICY_SOURCE_CLOUD and
+  // POLICY_SOURCE_PRIORITY_CLOUD now.
+  //
   // The |delegate| is notified of updates to the downloaded policies and must
   // outlive this object.
   //
@@ -89,6 +95,7 @@
   // |backend_task_runner|, which must support file I/O.
   ComponentCloudPolicyService(
       const std::string& policy_type,
+      PolicySource policy_source,
       Delegate* delegate,
       SchemaRegistry* schema_registry,
       CloudPolicyCore* core,
diff --git a/components/policy/core/common/cloud/component_cloud_policy_service_stub.cc b/components/policy/core/common/cloud/component_cloud_policy_service_stub.cc
index bae768a..04b8668 100644
--- a/components/policy/core/common/cloud/component_cloud_policy_service_stub.cc
+++ b/components/policy/core/common/cloud/component_cloud_policy_service_stub.cc
@@ -12,6 +12,7 @@
 
 ComponentCloudPolicyService::ComponentCloudPolicyService(
     const std::string& policy_type,
+    PolicySource policy_source,
     Delegate* delegate,
     SchemaRegistry* schema_registry,
     CloudPolicyCore* core,
diff --git a/components/policy/core/common/cloud/component_cloud_policy_service_unittest.cc b/components/policy/core/common/cloud/component_cloud_policy_service_unittest.cc
index 072d979..496a64d6 100644
--- a/components/policy/core/common/cloud/component_cloud_policy_service_unittest.cc
+++ b/components/policy/core/common/cloud/component_cloud_policy_service_unittest.cc
@@ -142,8 +142,9 @@
         base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
             &loader_factory_));
     service_.reset(new ComponentCloudPolicyService(
-        dm_protocol::kChromeExtensionPolicyType, &delegate_, &registry_, &core_,
-        client_, std::move(owned_cache_), base::ThreadTaskRunnerHandle::Get()));
+        dm_protocol::kChromeExtensionPolicyType, POLICY_SOURCE_CLOUD,
+        &delegate_, &registry_, &core_, client_, std::move(owned_cache_),
+        base::ThreadTaskRunnerHandle::Get()));
 
     client_->SetDMToken(ComponentCloudPolicyBuilder::kFakeToken);
     EXPECT_EQ(1u, client_->types_to_fetch_.size());
diff --git a/components/policy/core/common/cloud/component_cloud_policy_store.cc b/components/policy/core/common/cloud/component_cloud_policy_store.cc
index af5b247..7c77c58 100644
--- a/components/policy/core/common/cloud/component_cloud_policy_store.cc
+++ b/components/policy/core/common/cloud/component_cloud_policy_store.cc
@@ -22,7 +22,6 @@
 #include "components/policy/core/common/cloud/resource_cache.h"
 #include "components/policy/core/common/external_data_fetcher.h"
 #include "components/policy/core/common/policy_map.h"
-#include "components/policy/core/common/policy_types.h"
 #include "components/policy/proto/chrome_extension_policy.pb.h"
 #include "components/policy/proto/device_management_backend.pb.h"
 #include "crypto/sha2.h"
@@ -87,14 +86,18 @@
 ComponentCloudPolicyStore::ComponentCloudPolicyStore(
     Delegate* delegate,
     ResourceCache* cache,
-    const std::string& policy_type)
+    const std::string& policy_type,
+    PolicySource policy_source)
     : delegate_(delegate),
       cache_(cache),
-      domain_constants_(GetDomainConstantsForType(policy_type)) {
+      domain_constants_(GetDomainConstantsForType(policy_type)),
+      policy_source_(policy_source) {
   // Allow the store to be created on a different thread than the thread that
   // will end up using it.
   DETACH_FROM_SEQUENCE(sequence_checker_);
   DCHECK(domain_constants_);
+  DCHECK(policy_source == POLICY_SOURCE_CLOUD ||
+         policy_source == POLICY_SOURCE_PRIORITY_CLOUD);
 }
 
 ComponentCloudPolicyStore::~ComponentCloudPolicyStore() {
@@ -433,7 +436,7 @@
       level = POLICY_LEVEL_RECOMMENDED;
     }
 
-    policy->Set(it.key(), level, domain_constants_->scope, POLICY_SOURCE_CLOUD,
+    policy->Set(it.key(), level, domain_constants_->scope, policy_source_,
                 std::move(value), nullptr);
   }
 
diff --git a/components/policy/core/common/cloud/component_cloud_policy_store.h b/components/policy/core/common/cloud/component_cloud_policy_store.h
index 679dbf6..b7404dc0 100644
--- a/components/policy/core/common/cloud/component_cloud_policy_store.h
+++ b/components/policy/core/common/cloud/component_cloud_policy_store.h
@@ -16,6 +16,7 @@
 #include "components/policy/core/common/cloud/resource_cache.h"
 #include "components/policy/core/common/policy_bundle.h"
 #include "components/policy/core/common/policy_namespace.h"
+#include "components/policy/core/common/policy_types.h"
 #include "components/policy/policy_export.h"
 
 namespace enterprise_management {
@@ -57,9 +58,14 @@
   // kChromeExtensionPolicyType, kChromeMachineLevelExtensionCloudPolicyType.
   // Please update component_cloud_policy_store.cc in case there is new policy
   // type added.
+  // |policy_source| specifies where the policy originates from, and can be used
+  // to configure precedence when the same components are configured by policies
+  // from different sources. It only accepts POLICY_SOURCE_CLOUD and
+  // POLICY_SOURCE_PRIORITY_CLOUD now.
   ComponentCloudPolicyStore(Delegate* delegate,
                             ResourceCache* cache,
-                            const std::string& policy_type);
+                            const std::string& policy_type,
+                            PolicySource policy_source);
   ~ComponentCloudPolicyStore();
 
   // Helper that returns true for PolicyDomains that can be managed by this
@@ -162,6 +168,8 @@
 
   const DomainConstants* domain_constants_;
 
+  const PolicySource policy_source_;
+
   SEQUENCE_CHECKER(sequence_checker_);
 
   DISALLOW_COPY_AND_ASSIGN(ComponentCloudPolicyStore);
diff --git a/components/policy/core/common/cloud/component_cloud_policy_store_unittest.cc b/components/policy/core/common/cloud/component_cloud_policy_store_unittest.cc
index ab3aa77..b067896 100644
--- a/components/policy/core/common/cloud/component_cloud_policy_store_unittest.cc
+++ b/components/policy/core/common/cloud/component_cloud_policy_store_unittest.cc
@@ -104,12 +104,14 @@
                            PolicyBuilder::kFakePublicKeyVersion);
   }
 
-  void SetupExpectBundleWithScope(const PolicyScope& scope) {
+  void SetupExpectBundleWithScope(
+      const PolicyScope& scope,
+      const PolicySource& source = POLICY_SOURCE_CLOUD) {
     PolicyMap& policy = expected_bundle_.Get(kTestPolicyNS);
     policy.Clear();
-    policy.Set("Name", POLICY_LEVEL_MANDATORY, scope, POLICY_SOURCE_CLOUD,
+    policy.Set("Name", POLICY_LEVEL_MANDATORY, scope, source,
                std::make_unique<base::Value>("disabled"), nullptr);
-    policy.Set("Second", POLICY_LEVEL_RECOMMENDED, scope, POLICY_SOURCE_CLOUD,
+    policy.Set("Second", POLICY_LEVEL_RECOMMENDED, scope, source,
                std::make_unique<base::Value>("maybe"), nullptr);
   }
 
@@ -128,14 +130,11 @@
     return builder_.GetBlob();
   }
 
-  std::unique_ptr<ComponentCloudPolicyStore> CreateStore() {
-    return CreateStoreWithPolicyType(dm_protocol::kChromeExtensionPolicyType);
-  }
-
-  std::unique_ptr<ComponentCloudPolicyStore> CreateStoreWithPolicyType(
-      const std::string& policy_type) {
+  std::unique_ptr<ComponentCloudPolicyStore> CreateStore(
+      const std::string& policy_type = dm_protocol::kChromeExtensionPolicyType,
+      const PolicySource& source = POLICY_SOURCE_CLOUD) {
     return std::make_unique<ComponentCloudPolicyStore>(
-        &store_delegate_, cache_.get(), policy_type);
+        &store_delegate_, cache_.get(), policy_type, source);
   }
 
   // Returns true if the policy exposed by the |store| is empty.
@@ -433,22 +432,21 @@
   PolicyNamespace ns_signin_extension(POLICY_DOMAIN_SIGNIN_EXTENSIONS,
                                       kTestExtension);
 
-  store_ = CreateStoreWithPolicyType(
-      dm_protocol::kChromeMachineLevelExtensionCloudPolicyType);
+  store_ =
+      CreateStore(dm_protocol::kChromeMachineLevelExtensionCloudPolicyType);
   EXPECT_FALSE(store_->ValidatePolicy(ns_chrome, CreateResponse(),
                                       nullptr /*policy_data*/,
                                       nullptr /*payload*/));
   EXPECT_FALSE(store_->ValidatePolicy(ns_signin_extension, CreateResponse(),
                                       nullptr, nullptr));
 
-  store_ =
-      CreateStoreWithPolicyType(dm_protocol::kChromeSigninExtensionPolicyType);
+  store_ = CreateStore(dm_protocol::kChromeSigninExtensionPolicyType);
   EXPECT_FALSE(
       store_->ValidatePolicy(ns_chrome, CreateResponse(), nullptr, nullptr));
   EXPECT_FALSE(
       store_->ValidatePolicy(ns_extension, CreateResponse(), nullptr, nullptr));
 
-  store_ = CreateStoreWithPolicyType(dm_protocol::kChromeExtensionPolicyType);
+  store_ = CreateStore(dm_protocol::kChromeExtensionPolicyType);
   EXPECT_FALSE(
       store_->ValidatePolicy(ns_chrome, CreateResponse(), nullptr, nullptr));
   EXPECT_FALSE(store_->ValidatePolicy(ns_signin_extension, CreateResponse(),
@@ -522,8 +520,8 @@
 }
 
 TEST_F(ComponentCloudPolicyStoreTest, StoreAndLoadMachineLevelUserPolicy) {
-  store_ = CreateStoreWithPolicyType(
-      dm_protocol::kChromeMachineLevelExtensionCloudPolicyType);
+  store_ =
+      CreateStore(dm_protocol::kChromeMachineLevelExtensionCloudPolicyType);
   store_->SetCredentials(PolicyBuilder::GetFakeAccountIdForTesting(),
                          PolicyBuilder::kFakeToken,
                          PolicyBuilder::kFakeDeviceId, public_key_,
@@ -536,8 +534,66 @@
 
   StoreTestPolicyWithNamespace(store_.get(), kTestPolicyNS);
 
-  another_store_ = CreateStoreWithPolicyType(
+  another_store_ =
+      CreateStore(dm_protocol::kChromeMachineLevelExtensionCloudPolicyType);
+  another_store_->SetCredentials(PolicyBuilder::GetFakeAccountIdForTesting(),
+                                 PolicyBuilder::kFakeToken,
+                                 PolicyBuilder::kFakeDeviceId, public_key_,
+                                 PolicyBuilder::kFakePublicKeyVersion);
+  another_store_->Load();
+  EXPECT_TRUE(another_store_->policy().Equals(expected_bundle_));
+  EXPECT_EQ(TestPolicyHash(), another_store_->GetCachedHash(kTestPolicyNS));
+}
+
+TEST_F(ComponentCloudPolicyStoreTest, StoreAndLoadPolicyWithCloudPriority) {
+  store_ = CreateStore(dm_protocol::kChromeMachineLevelExtensionCloudPolicyType,
+                       POLICY_SOURCE_PRIORITY_CLOUD);
+  store_->SetCredentials(PolicyBuilder::GetFakeAccountIdForTesting(),
+                         PolicyBuilder::kFakeToken,
+                         PolicyBuilder::kFakeDeviceId, public_key_,
+                         PolicyBuilder::kFakePublicKeyVersion);
+
+  builder_.policy_data().set_policy_type(
       dm_protocol::kChromeMachineLevelExtensionCloudPolicyType);
+  builder_.payload().set_secure_hash(TestPolicyHash());
+  SetupExpectBundleWithScope(POLICY_SCOPE_MACHINE,
+                             POLICY_SOURCE_PRIORITY_CLOUD);
+
+  StoreTestPolicyWithNamespace(store_.get(), kTestPolicyNS);
+
+  another_store_ =
+      CreateStore(dm_protocol::kChromeMachineLevelExtensionCloudPolicyType,
+                  POLICY_SOURCE_PRIORITY_CLOUD);
+  another_store_->SetCredentials(PolicyBuilder::GetFakeAccountIdForTesting(),
+                                 PolicyBuilder::kFakeToken,
+                                 PolicyBuilder::kFakeDeviceId, public_key_,
+                                 PolicyBuilder::kFakePublicKeyVersion);
+  another_store_->Load();
+  EXPECT_TRUE(another_store_->policy().Equals(expected_bundle_));
+  EXPECT_EQ(TestPolicyHash(), another_store_->GetCachedHash(kTestPolicyNS));
+}
+
+TEST_F(ComponentCloudPolicyStoreTest,
+       StoreAndLoadPolicyWithDifferentCloudPriority) {
+  store_ = CreateStore(dm_protocol::kChromeMachineLevelExtensionCloudPolicyType,
+                       POLICY_SOURCE_CLOUD);
+  store_->SetCredentials(PolicyBuilder::GetFakeAccountIdForTesting(),
+                         PolicyBuilder::kFakeToken,
+                         PolicyBuilder::kFakeDeviceId, public_key_,
+                         PolicyBuilder::kFakePublicKeyVersion);
+
+  builder_.policy_data().set_policy_type(
+      dm_protocol::kChromeMachineLevelExtensionCloudPolicyType);
+  builder_.payload().set_secure_hash(TestPolicyHash());
+  SetupExpectBundleWithScope(POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD);
+
+  StoreTestPolicyWithNamespace(store_.get(), kTestPolicyNS);
+
+  SetupExpectBundleWithScope(POLICY_SCOPE_MACHINE,
+                             POLICY_SOURCE_PRIORITY_CLOUD);
+  another_store_ =
+      CreateStore(dm_protocol::kChromeMachineLevelExtensionCloudPolicyType,
+                  POLICY_SOURCE_PRIORITY_CLOUD);
   another_store_->SetCredentials(PolicyBuilder::GetFakeAccountIdForTesting(),
                                  PolicyBuilder::kFakeToken,
                                  PolicyBuilder::kFakeDeviceId, public_key_,
diff --git a/components/policy/core/common/cloud/component_cloud_policy_updater_unittest.cc b/components/policy/core/common/cloud/component_cloud_policy_updater_unittest.cc
index d74686a..62449b52 100644
--- a/components/policy/core/common/cloud/component_cloud_policy_updater_unittest.cc
+++ b/components/policy/core/common/cloud/component_cloud_policy_updater_unittest.cc
@@ -121,7 +121,8 @@
                                            task_env_.GetMainThreadTaskRunner(),
                                            /* max_cache_size */ base::nullopt);
   store_ = std::make_unique<ComponentCloudPolicyStore>(
-      &store_delegate_, cache_.get(), dm_protocol::kChromeExtensionPolicyType);
+      &store_delegate_, cache_.get(), dm_protocol::kChromeExtensionPolicyType,
+      POLICY_SOURCE_CLOUD);
   store_->SetCredentials(PolicyBuilder::GetFakeAccountIdForTesting(),
                          PolicyBuilder::kFakeToken,
                          PolicyBuilder::kFakeDeviceId, public_key_,
diff --git a/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.cc b/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.cc
index deb1962..39cc7763 100644
--- a/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.cc
+++ b/components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.cc
@@ -50,8 +50,11 @@
 
   CreateComponentCloudPolicyService(
       dm_protocol::kChromeMachineLevelExtensionCloudPolicyType,
-      policy_dir_.Append(kComponentPolicyCache), client.get(),
-      schema_registry());
+      policy_dir_.Append(kComponentPolicyCache),
+      (local_state->GetBoolean(policy_prefs::kCloudPolicyOverridesMachinePolicy)
+           ? POLICY_SOURCE_PRIORITY_CLOUD
+           : POLICY_SOURCE_CLOUD),
+      client.get(), schema_registry());
   core()->Connect(std::move(client));
   core()->StartRefreshScheduler();
   core()->TrackRefreshDelayPref(local_state,
diff --git a/components/policy/core/common/cloud/user_cloud_policy_manager.cc b/components/policy/core/common/cloud/user_cloud_policy_manager.cc
index ea512ca..844de2b 100644
--- a/components/policy/core/common/cloud/user_cloud_policy_manager.cc
+++ b/components/policy/core/common/cloud/user_cloud_policy_manager.cc
@@ -20,6 +20,7 @@
 #include "components/policy/core/common/policy_pref_names.h"
 #include "components/policy/core/common/policy_types.h"
 #include "components/policy/policy_constants.h"
+#include "components/prefs/pref_service.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
 namespace em = enterprise_management;
@@ -71,9 +72,12 @@
   scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory =
       client->GetURLLoaderFactory();
 
-  CreateComponentCloudPolicyService(dm_protocol::kChromeExtensionPolicyType,
-                                    component_policy_cache_path_, client.get(),
-                                    schema_registry());
+  CreateComponentCloudPolicyService(
+      dm_protocol::kChromeExtensionPolicyType, component_policy_cache_path_,
+      (local_state->GetBoolean(policy_prefs::kCloudPolicyOverridesMachinePolicy)
+           ? POLICY_SOURCE_PRIORITY_CLOUD
+           : POLICY_SOURCE_CLOUD),
+      client.get(), schema_registry());
   core()->Connect(std::move(client));
   core()->StartRefreshScheduler();
   core()->TrackRefreshDelayPref(local_state,
diff --git a/components/signin/core/browser/fake_profile_oauth2_token_service.cc b/components/signin/core/browser/fake_profile_oauth2_token_service.cc
index 9e92a4e..4d2899e 100644
--- a/components/signin/core/browser/fake_profile_oauth2_token_service.cc
+++ b/components/signin/core/browser/fake_profile_oauth2_token_service.cc
@@ -112,12 +112,6 @@
                    GoogleServiceAuthError::AuthErrorNone(), token_response);
 }
 
-void FakeProfileOAuth2TokenService::UpdateAuthErrorForTesting(
-    const std::string& account_id,
-    const GoogleServiceAuthError& error) {
-  ProfileOAuth2TokenService::UpdateAuthError(account_id, error);
-}
-
 void FakeProfileOAuth2TokenService::CompleteRequests(
     const std::string& account_id,
     bool all_scopes,
diff --git a/components/signin/core/browser/fake_profile_oauth2_token_service.h b/components/signin/core/browser/fake_profile_oauth2_token_service.h
index a6e37a4..be71e20 100644
--- a/components/signin/core/browser/fake_profile_oauth2_token_service.h
+++ b/components/signin/core/browser/fake_profile_oauth2_token_service.h
@@ -99,10 +99,6 @@
     auto_post_fetch_response_on_message_loop_ = auto_post_response;
   }
 
-  // Calls ProfileOAuth2TokenService::UpdateAuthError(). Exposed for testing.
-  void UpdateAuthErrorForTesting(const std::string& account_id,
-                                 const GoogleServiceAuthError& error);
-
  protected:
   // OAuth2TokenService overrides.
   void CancelAllRequests() override;
diff --git a/components/signin/core/browser/profile_oauth2_token_service.h b/components/signin/core/browser/profile_oauth2_token_service.h
index c7c4ae6..55bd353 100644
--- a/components/signin/core/browser/profile_oauth2_token_service.h
+++ b/components/signin/core/browser/profile_oauth2_token_service.h
@@ -103,6 +103,12 @@
     all_credentials_loaded_ = loaded;
   }
 
+  // Exposes the ability to update auth errors to tests.
+  void UpdateAuthErrorForTesting(const std::string& account_id,
+                                 const GoogleServiceAuthError& error) {
+    UpdateAuthError(account_id, error);
+  }
+
  private:
   friend class identity::IdentityManager;
 
diff --git a/components/signin/core/browser/signin_manager_unittest.cc b/components/signin/core/browser/signin_manager_unittest.cc
index 0e1db82..be9ac36 100644
--- a/components/signin/core/browser/signin_manager_unittest.cc
+++ b/components/signin/core/browser/signin_manager_unittest.cc
@@ -22,12 +22,12 @@
 #include "components/signin/core/browser/account_tracker_service.h"
 #include "components/signin/core/browser/device_id_helper.h"
 #include "components/signin/core/browser/fake_account_fetcher_service.h"
-#include "components/signin/core/browser/fake_profile_oauth2_token_service.h"
 #include "components/signin/core/browser/gaia_cookie_manager_service.h"
 #include "components/signin/core/browser/profile_oauth2_token_service.h"
 #include "components/signin/core/browser/signin_pref_names.h"
 #include "components/signin/core/browser/test_signin_client.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "google_apis/gaia/fake_oauth2_token_service_delegate.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "net/cookies/cookie_monster.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -69,7 +69,8 @@
  public:
   SigninManagerTest()
       : test_signin_client_(&user_prefs_),
-        token_service_(&user_prefs_),
+        token_service_(&user_prefs_,
+                       std::make_unique<FakeOAuth2TokenServiceDelegate>()),
         cookie_manager_service_(&token_service_, &test_signin_client_),
         account_consistency_(signin::AccountConsistencyMethod::kDisabled) {
     AccountFetcherService::RegisterPrefs(user_prefs_.registry());
@@ -145,7 +146,7 @@
   sync_preferences::TestingPrefServiceSyncable user_prefs_;
   TestingPrefServiceSimple local_state_;
   TestSigninClient test_signin_client_;
-  FakeProfileOAuth2TokenService token_service_;
+  ProfileOAuth2TokenService token_service_;
   AccountTrackerService account_tracker_;
   GaiaCookieManagerService cookie_manager_service_;
   FakeAccountFetcherService account_fetcher_;
diff --git a/components/sync/driver/about_sync_util.cc b/components/sync/driver/about_sync_util.cc
index 19c69404..d5417d5 100644
--- a/components/sync/driver/about_sync_util.cc
+++ b/components/sync/driver/about_sync_util.cc
@@ -206,8 +206,6 @@
   switch (state) {
     case syncer::SyncService::TransportState::DISABLED:
       return "Disabled";
-    case syncer::SyncService::TransportState::WAITING_FOR_START_REQUEST:
-      return "Waiting for start request";
     case syncer::SyncService::TransportState::START_DEFERRED:
       return "Start deferred";
     case syncer::SyncService::TransportState::INITIALIZING:
diff --git a/components/sync/driver/sync_service.cc b/components/sync/driver/sync_service.cc
index 67a9818..2df9058 100644
--- a/components/sync/driver/sync_service.cc
+++ b/components/sync/driver/sync_service.cc
@@ -30,7 +30,6 @@
 bool SyncService::IsEngineInitialized() const {
   switch (GetTransportState()) {
     case TransportState::DISABLED:
-    case TransportState::WAITING_FOR_START_REQUEST:
     case TransportState::START_DEFERRED:
     case TransportState::INITIALIZING:
       return false;
@@ -49,7 +48,6 @@
   }
   switch (GetTransportState()) {
     case TransportState::DISABLED:
-    case TransportState::WAITING_FOR_START_REQUEST:
     case TransportState::START_DEFERRED:
     case TransportState::INITIALIZING:
     case TransportState::PENDING_DESIRED_CONFIGURATION:
diff --git a/components/sync/driver/sync_service.h b/components/sync/driver/sync_service.h
index dc143b7..5cb7f526 100644
--- a/components/sync/driver/sync_service.h
+++ b/components/sync/driver/sync_service.h
@@ -85,15 +85,6 @@
     // Sync is inactive, e.g. due to enterprise policy, or simply because there
     // is no authenticated user.
     DISABLED,
-    // Sync can start in principle, but nothing has prodded it to actually do it
-    // yet. Note that during subsequent browser startups, Sync starts
-    // automatically, i.e. no prod is necessary, but during the first start Sync
-    // does need a kick. This usually happens via starting (not finishing!) the
-    // initial setup, or via a call to SyncUserSettings::SetSyncRequested.
-    // TODO(crbug.com/839834): Check whether this state is necessary, or if Sync
-    // can just always start up if all conditions are fulfilled (that's what
-    // happens in practice anyway).
-    WAITING_FOR_START_REQUEST,
     // Sync's startup was deferred, so that it doesn't slow down browser
     // startup. Once the deferral time (usually 10s) expires, or something
     // requests immediate startup, Sync will actually start.
diff --git a/components/sync/driver/sync_service_utils.cc b/components/sync/driver/sync_service_utils.cc
index a03cea8e..ab43d9a3 100644
--- a/components/sync/driver/sync_service_utils.cc
+++ b/components/sync/driver/sync_service_utils.cc
@@ -45,7 +45,6 @@
     case SyncService::TransportState::DISABLED:
       return UploadState::NOT_ACTIVE;
 
-    case SyncService::TransportState::WAITING_FOR_START_REQUEST:
     case SyncService::TransportState::START_DEFERRED:
     case SyncService::TransportState::INITIALIZING:
     case SyncService::TransportState::PENDING_DESIRED_CONFIGURATION:
diff --git a/components/sync/driver/sync_service_utils_unittest.cc b/components/sync/driver/sync_service_utils_unittest.cc
index 2703b51..8f14e05 100644
--- a/components/sync/driver/sync_service_utils_unittest.cc
+++ b/components/sync/driver/sync_service_utils_unittest.cc
@@ -30,7 +30,7 @@
   // disabled anymore (though not necessarily active yet).
   service.SetDisableReasons(syncer::SyncService::DISABLE_REASON_NONE);
   service.SetTransportState(
-      syncer::SyncService::TransportState::WAITING_FOR_START_REQUEST);
+      syncer::SyncService::TransportState::START_DEFERRED);
 
   EXPECT_NE(UploadState::NOT_ACTIVE,
             GetUploadToGoogleState(&service, syncer::BOOKMARKS));
@@ -41,7 +41,7 @@
   TestSyncService service;
   service.SetDisableReasons(syncer::SyncService::DISABLE_REASON_NONE);
   service.SetTransportState(
-      syncer::SyncService::TransportState::WAITING_FOR_START_REQUEST);
+      syncer::SyncService::TransportState::START_DEFERRED);
   service.SetPreferredDataTypes(ProtocolTypes());
   service.SetActiveDataTypes(ProtocolTypes());
   service.SetEmptyLastCycleSnapshot();
diff --git a/components/sync/engine/sync_engine_switches.cc b/components/sync/engine/sync_engine_switches.cc
index d37b5f3..08c829c0 100644
--- a/components/sync/engine/sync_engine_switches.cc
+++ b/components/sync/engine/sync_engine_switches.cc
@@ -15,6 +15,6 @@
 // via scrypt when we receive a remote Nigori node that specifies it as the key
 // derivation method.
 const base::Feature kSyncUseScryptForNewCustomPassphrases{
-    "SyncUseScryptForNewCustomPassphrases", base::FEATURE_DISABLED_BY_DEFAULT};
+    "SyncUseScryptForNewCustomPassphrases", base::FEATURE_ENABLED_BY_DEFAULT};
 
 }  // namespace switches
diff --git a/components/sync/engine_impl/sync_encryption_handler_impl_unittest.cc b/components/sync/engine_impl/sync_encryption_handler_impl_unittest.cc
index 5ac7d81d..47d565f4 100644
--- a/components/sync/engine_impl/sync_encryption_handler_impl_unittest.cc
+++ b/components/sync/engine_impl/sync_encryption_handler_impl_unittest.cc
@@ -1826,9 +1826,9 @@
   VerifyPassphraseType(PassphraseType::CUSTOM_PASSPHRASE);
   EXPECT_TRUE(encryption_handler()->IsEncryptEverythingEnabled());
   EXPECT_FALSE(encryption_handler()->custom_passphrase_time().is_null());
-  VerifyMigratedNigoriWithTimestamp(migration_time,
-                                    PassphraseType::CUSTOM_PASSPHRASE, kNewKey,
-                                    {KeyDerivationParams::CreateForPbkdf2()});
+  VerifyMigratedNigoriWithTimestamp(
+      migration_time, PassphraseType::CUSTOM_PASSPHRASE, kNewKey,
+      {KeyDerivationParams::CreateForScrypt(kScryptSalt)});
 
   // Check that the cryptographer can decrypt the old key.
   sync_pb::EncryptedData old_encrypted;
@@ -1844,8 +1844,9 @@
   keystore_cryptographer.EncryptString("string", &keystore_encrypted);
   EXPECT_TRUE(GetCryptographer()->CanDecrypt(keystore_encrypted));
 
-  // Check the the cryptographer is encrypting with the new key.
-  KeyParams new_key = {KeyDerivationParams::CreateForPbkdf2(), kNewKey};
+  // Check that the cryptographer is encrypting with the new key.
+  KeyParams new_key = {KeyDerivationParams::CreateForScrypt(kScryptSalt),
+                       kNewKey};
   Cryptographer new_cryptographer(GetCryptographer()->encryptor());
   new_cryptographer.AddKey(new_key);
   sync_pb::EncryptedData new_encrypted;
@@ -1857,7 +1858,7 @@
   VerifyRestoreAfterExplicitPaspshrase(
       migration_time, kNewKey, captured_bootstrap_token, captured_nigori_state,
       PassphraseType::CUSTOM_PASSPHRASE,
-      {KeyDerivationParams::CreateForPbkdf2()});
+      {KeyDerivationParams::CreateForScrypt(kScryptSalt)});
 }
 
 // Test that if a client without a keystore key (e.g. one without keystore
@@ -1938,9 +1939,9 @@
   VerifyPassphraseType(PassphraseType::CUSTOM_PASSPHRASE);
   EXPECT_TRUE(encryption_handler()->IsEncryptEverythingEnabled());
   EXPECT_FALSE(encryption_handler()->custom_passphrase_time().is_null());
-  VerifyMigratedNigoriWithTimestamp(migration_time,
-                                    PassphraseType::CUSTOM_PASSPHRASE, kNewKey,
-                                    {KeyDerivationParams::CreateForPbkdf2()});
+  VerifyMigratedNigoriWithTimestamp(
+      migration_time, PassphraseType::CUSTOM_PASSPHRASE, kNewKey,
+      {KeyDerivationParams::CreateForScrypt(kScryptSalt)});
 
   // Check that the cryptographer can decrypt the old key.
   sync_pb::EncryptedData old_encrypted;
@@ -1955,8 +1956,9 @@
   keystore_cryptographer.EncryptString("string", &keystore_encrypted);
   EXPECT_TRUE(GetCryptographer()->CanDecrypt(keystore_encrypted));
 
-  // Check the the cryptographer is encrypting with the new key.
-  KeyParams new_key = {KeyDerivationParams::CreateForPbkdf2(), kNewKey};
+  // Check that the cryptographer is encrypting with the new key.
+  KeyParams new_key = {KeyDerivationParams::CreateForScrypt(kScryptSalt),
+                       kNewKey};
   Cryptographer new_cryptographer(GetCryptographer()->encryptor());
   new_cryptographer.AddKey(new_key);
   sync_pb::EncryptedData new_encrypted;
@@ -1968,7 +1970,7 @@
   VerifyRestoreAfterExplicitPaspshrase(
       migration_time, kNewKey, captured_bootstrap_token, captured_nigori_state,
       PassphraseType::CUSTOM_PASSPHRASE,
-      {KeyDerivationParams::CreateForPbkdf2()});
+      {KeyDerivationParams::CreateForScrypt(kScryptSalt)});
 }
 
 // Test that if a client without a keystore key (e.g. one without keystore
diff --git a/components/viz/service/display/gl_renderer.cc b/components/viz/service/display/gl_renderer.cc
index 758b764..9e0979a 100644
--- a/components/viz/service/display/gl_renderer.cc
+++ b/components/viz/service/display/gl_renderer.cc
@@ -1753,13 +1753,16 @@
 
   SkColor color = quad->color;
   float opacity = quad->shared_quad_state->opacity;
-  float alpha = (SkColorGetA(color) * (1.0f / 255.0f)) * opacity;
 
-  // Early out if alpha is small enough that quad doesn't contribute to output.
-  if (alpha < std::numeric_limits<float>::epsilon() &&
-      quad->ShouldDrawWithBlending() &&
-      quad->shared_quad_state->blend_mode == SkBlendMode::kSrcOver)
-    return;
+  // Early out if alpha is small enough that quad doesn't contribute to output,
+  // for kSrcOver blend mode.
+  if (quad->shared_quad_state->blend_mode == SkBlendMode::kSrcOver) {
+    float alpha = (SkColorGetA(color) * (1.0f / 255.0f)) * opacity;
+    if (alpha < std::numeric_limits<float>::epsilon() &&
+        quad->ShouldDrawWithBlending() &&
+        quad->shared_quad_state->blend_mode == SkBlendMode::kSrcOver)
+      return;
+  }
 
   gfx::Transform device_transform =
       current_frame()->window_matrix * current_frame()->projection_matrix *
diff --git a/components/viz/test/data/solid_color_empty_mask_with_effect.png b/components/viz/test/data/solid_color_empty_mask_with_effect.png
new file mode 100644
index 0000000..b59decea
--- /dev/null
+++ b/components/viz/test/data/solid_color_empty_mask_with_effect.png
Binary files differ
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index dd069199..d743ffc 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1266,8 +1266,8 @@
     "notifications/blink_notification_service_impl.h",
     "notifications/notification_database.cc",
     "notifications/notification_database.h",
-    "notifications/notification_database_data_conversions.cc",
-    "notifications/notification_database_data_conversions.h",
+    "notifications/notification_database_conversions.cc",
+    "notifications/notification_database_conversions.h",
     "notifications/notification_event_dispatcher_impl.cc",
     "notifications/notification_event_dispatcher_impl.h",
     "notifications/notification_id_generator.cc",
@@ -1625,8 +1625,6 @@
     "service_manager/service_manager_context.h",
     "service_worker/embedded_worker_instance.cc",
     "service_worker/embedded_worker_instance.h",
-    "service_worker/embedded_worker_registry.cc",
-    "service_worker/embedded_worker_registry.h",
     "service_worker/embedded_worker_status.h",
     "service_worker/payment_handler_support.cc",
     "service_worker/payment_handler_support.h",
diff --git a/content/browser/browser_context.cc b/content/browser/browser_context.cc
index 68e34ea..b2b3920 100644
--- a/content/browser/browser_context.cc
+++ b/content/browser/browser_context.cc
@@ -35,6 +35,7 @@
 #include "build/build_config.h"
 #include "content/browser/blob_storage/chrome_blob_storage_context.h"
 #include "content/browser/browsing_data/browsing_data_remover_impl.h"
+#include "content/browser/child_process_security_policy_impl.h"
 #include "content/browser/content_service_delegate_impl.h"
 #include "content/browser/download/download_manager_impl.h"
 #include "content/browser/indexed_db/indexed_db_context_impl.h"
@@ -562,6 +563,14 @@
       host->DisableKeepAliveRefCount();
     }
   }
+
+  // Clean up any isolated origins associated with this BrowserContext.  This
+  // should be safe now that all RenderProcessHosts are destroyed, since future
+  // navigations or security decisions shouldn't ever need to consult these
+  // isolated origins.
+  ChildProcessSecurityPolicyImpl* policy =
+      ChildProcessSecurityPolicyImpl::GetInstance();
+  policy->RemoveIsolatedOriginsForBrowserContext(*browser_context);
 }
 
 void BrowserContext::EnsureResourceContextInitialized(BrowserContext* context) {
diff --git a/content/browser/browser_process_sub_thread.cc b/content/browser/browser_process_sub_thread.cc
index a0546d1c..ed57071 100644
--- a/content/browser/browser_process_sub_thread.cc
+++ b/content/browser/browser_process_sub_thread.cc
@@ -5,6 +5,7 @@
 #include "content/browser/browser_process_sub_thread.h"
 
 #include "base/bind.h"
+#include "base/clang_coverage_buildflags.h"
 #include "base/compiler_specific.h"
 #include "base/debug/alias.h"
 #include "base/metrics/histogram_macros.h"
@@ -193,7 +194,13 @@
         service_manager::SANDBOX_TYPE_NETWORK) {
       // This ensures that cookies and cache are flushed to disk on shutdown.
       // https://crbug.com/841001
+#if BUILDFLAG(CLANG_COVERAGE)
+      // On coverage build, browser_tests runs 10x slower.
+      const int kMaxSecondsToWaitForNetworkProcess = 100;
+#else
       const int kMaxSecondsToWaitForNetworkProcess = 10;
+#endif
+
       ChildProcessHostImpl* child_process =
           static_cast<ChildProcessHostImpl*>(it.GetHost());
       auto& process = child_process->peer_process();
diff --git a/content/browser/browsing_instance.cc b/content/browser/browsing_instance.cc
index 1404997..82db89bb 100644
--- a/content/browser/browsing_instance.cc
+++ b/content/browser/browsing_instance.cc
@@ -22,7 +22,8 @@
 BrowsingInstance::BrowsingInstance(BrowserContext* browser_context)
     : browser_context_(browser_context),
       isolation_context_(
-          BrowsingInstanceId::FromUnsafeValue(next_browsing_instance_id_++)),
+          BrowsingInstanceId::FromUnsafeValue(next_browsing_instance_id_++),
+          BrowserOrResourceContext(browser_context)),
       active_contents_count_(0u),
       default_process_(nullptr) {
   DCHECK(browser_context);
diff --git a/content/browser/child_process_security_policy_impl.cc b/content/browser/child_process_security_policy_impl.cc
index 7530fb6c..b7a29716 100644
--- a/content/browser/child_process_security_policy_impl.cc
+++ b/content/browser/child_process_security_policy_impl.cc
@@ -452,8 +452,17 @@
 
 ChildProcessSecurityPolicyImpl::IsolatedOriginEntry::IsolatedOriginEntry(
     const url::Origin& origin,
-    BrowsingInstanceId min_browsing_instance_id)
-    : origin(origin), min_browsing_instance_id(min_browsing_instance_id) {}
+    BrowsingInstanceId min_browsing_instance_id,
+    BrowserContext* browser_context,
+    ResourceContext* resource_context)
+    : origin_(origin),
+      min_browsing_instance_id_(min_browsing_instance_id),
+      browser_context_(browser_context),
+      resource_context_(resource_context) {
+  // If there is a BrowserContext, there must also be a ResourceContext
+  // associated with this entry.
+  DCHECK_EQ(!browser_context, !resource_context);
+}
 
 ChildProcessSecurityPolicyImpl::IsolatedOriginEntry::IsolatedOriginEntry(
     const IsolatedOriginEntry& other) = default;
@@ -472,6 +481,29 @@
 ChildProcessSecurityPolicyImpl::IsolatedOriginEntry::~IsolatedOriginEntry() =
     default;
 
+bool ChildProcessSecurityPolicyImpl::IsolatedOriginEntry::
+    AppliesToAllBrowserContexts() const {
+  return !browser_context_;
+}
+
+bool ChildProcessSecurityPolicyImpl::IsolatedOriginEntry::MatchesProfile(
+    const BrowserOrResourceContext& browser_or_resource_context) const {
+  DCHECK(IsRunningOnExpectedThread());
+
+  // Globally isolated origins aren't associated with any particular profile
+  // and should apply to all profiles.
+  if (AppliesToAllBrowserContexts())
+    return true;
+
+  if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
+    return browser_context_ == browser_or_resource_context.ToBrowserContext();
+  } else if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
+    return resource_context_ == browser_or_resource_context.ToResourceContext();
+  }
+  NOTREACHED();
+  return false;
+}
+
 ChildProcessSecurityPolicyImpl::ChildProcessSecurityPolicyImpl() {
   // We know about these schemes and believe them to be safe.
   RegisterWebSafeScheme(url::kHttpScheme);
@@ -1280,7 +1312,7 @@
       BrowsingInstanceId browsing_instance_id =
           security_state->lowest_browsing_instance_id();
       expected_process_lock = SiteInstanceImpl::DetermineProcessLockURL(
-          context, IsolationContext(browsing_instance_id), url);
+          context, IsolationContext(browsing_instance_id, context), url);
     }
   }
 
@@ -1396,7 +1428,8 @@
 }
 
 void ChildProcessSecurityPolicyImpl::AddIsolatedOrigins(
-    std::vector<url::Origin> origins_to_add) {
+    std::vector<url::Origin> origins_to_add,
+    BrowserContext* browser_context) {
   // This can only be called from the UI thread, as it reads state that's only
   // available (and is only safe to be retrieved) on the UI thread, such as
   // BrowsingInstance IDs.
@@ -1441,27 +1474,60 @@
     BrowsingInstanceId min_browsing_instance_id =
         SiteInstanceImpl::NextBrowsingInstanceId();
 
-    // If the added origin already exists with a lower/same BrowsingInstance
-    // ID, don't re-add it.  Note that it's impossible for the origin to
-    // already be isolated with a higher ID, since NextBrowsingInstanceId()
-    // returns monotonically increasing IDs.
-    if (base::ContainsKey(isolated_origins_, key)) {
-      auto matching_origins = isolated_origins_[key];
-      auto it = std::find_if(matching_origins.begin(), matching_origins.end(),
-                             [origin](const IsolatedOriginEntry& entry) {
-                               return entry.origin == origin;
-                             });
-      if (it != matching_origins.end()) {
-        DCHECK_LE(it->min_browsing_instance_id, min_browsing_instance_id);
+    // Check if the origin to be added already exists, in which case it may not
+    // need to be added again.
+    bool should_add = true;
+    for (const auto& entry : isolated_origins_[key]) {
+      if (entry.origin() != origin)
         continue;
+
+      // If the added origin already exists for the same BrowserContext, don't
+      // re-add it. Note that in this case, it must necessarily have a
+      // lower/same BrowsingInstance ID: it's impossible for it to be
+      // isolated with a higher ID, since NextBrowsingInstanceId() returns
+      // monotonically increasing IDs.
+      if (entry.browser_context() == browser_context) {
+        DCHECK_LE(entry.min_browsing_instance_id(), min_browsing_instance_id);
+        should_add = false;
+        break;
       }
+
+      // Otherwise, allow the origin to be added again for a different profile
+      // (or globally for all profiles), possibly with a different
+      // BrowsingInstance ID cutoff.  Note that a particular origin might have
+      // multiple entries, each one for a different profile, so we must loop
+      // over all such existing entries before concluding that |origin| really
+      // needs to be added.
     }
 
-    IsolatedOriginEntry entry(std::move(origin), min_browsing_instance_id);
-    isolated_origins_[key].insert(std::move(entry));
+    if (should_add) {
+      ResourceContext* resource_context =
+          browser_context ? browser_context->GetResourceContext() : nullptr;
+      IsolatedOriginEntry entry(std::move(origin), min_browsing_instance_id,
+                                browser_context, resource_context);
+      isolated_origins_[key].insert(std::move(entry));
+    }
   }
 }
 
+void ChildProcessSecurityPolicyImpl::RemoveIsolatedOriginsForBrowserContext(
+    const BrowserContext& browser_context) {
+  base::AutoLock isolated_origins_lock(isolated_origins_lock_);
+
+  for (auto& iter : isolated_origins_) {
+    base::EraseIf(iter.second,
+                  [&browser_context](const IsolatedOriginEntry& entry) {
+                    // Remove if BrowserContext matches.
+                    return (entry.browser_context() == &browser_context);
+                  });
+  }
+
+  // Also remove map entries for site URLs which no longer have any
+  // IsolatedOriginEntries remaining.
+  base::EraseIf(isolated_origins_,
+                [](const auto& pair) { return pair.second.empty(); });
+}
+
 bool ChildProcessSecurityPolicyImpl::IsIsolatedOrigin(
     const IsolationContext& isolation_context,
     const url::Origin& origin) {
@@ -1490,6 +1556,8 @@
     const url::Origin& origin,
     const GURL& site_url,
     url::Origin* result) {
+  DCHECK(IsRunningOnExpectedThread());
+
   *result = url::Origin();
   base::AutoLock isolated_origins_lock(isolated_origins_lock_);
 
@@ -1527,15 +1595,21 @@
   bool found = false;
   if (it != isolated_origins_.end()) {
     for (const auto& isolated_origin_entry : it->second) {
+      // If this isolated origin applies only to a specific profile, don't
+      // use it for a different profile.
+      if (!isolated_origin_entry.MatchesProfile(
+              isolation_context.browser_or_resource_context()))
+        continue;
+
       bool matches_browsing_instance_id =
-          isolated_origin_entry.min_browsing_instance_id <=
+          isolated_origin_entry.min_browsing_instance_id() <=
           browsing_instance_id;
       if (matches_browsing_instance_id &&
           IsolatedOriginUtil::DoesOriginMatchIsolatedOrigin(
-              origin, isolated_origin_entry.origin)) {
+              origin, isolated_origin_entry.origin())) {
         if (!found || result->host().length() <
-                          isolated_origin_entry.origin.host().length()) {
-          *result = isolated_origin_entry.origin;
+                          isolated_origin_entry.origin().host().length()) {
+          *result = isolated_origin_entry.origin();
           found = true;
         }
       }
@@ -1552,7 +1626,7 @@
   base::EraseIf(isolated_origins_[key],
                 [&origin](const IsolatedOriginEntry& entry) {
                   // Remove if origin matches.
-                  return (entry.origin == origin);
+                  return (entry.origin() == origin);
                 });
   if (isolated_origins_[key].empty())
     isolated_origins_.erase(key);
diff --git a/content/browser/child_process_security_policy_impl.h b/content/browser/child_process_security_policy_impl.h
index 68b6a5e..3a8f25d 100644
--- a/content/browser/child_process_security_policy_impl.h
+++ b/content/browser/child_process_security_policy_impl.h
@@ -45,6 +45,7 @@
 
 class BrowserContext;
 class IsolationContext;
+class ResourceContext;
 class SiteInstance;
 
 class CONTENT_EXPORT ChildProcessSecurityPolicyImpl
@@ -320,7 +321,29 @@
   // associates it with each of the |origins|. If an origin had already been
   // isolated prior to calling this, it is ignored, and its threshold is not
   // updated.
-  void AddIsolatedOrigins(std::vector<url::Origin> origins);
+  //
+  // If |browser_context| is non-null, the new isolated origins added via this
+  // function will apply only within that BrowserContext.  If |browser_context|
+  // is null, the new isolated origins will apply globally in *all*
+  // BrowserContexts (but still subject to the BrowsingInstance ID cutoff in
+  // the previous paragraph).
+  //
+  // This function may be called again for the same origin but different
+  // |browser_context|. In that case, the origin will be isolated in all
+  // BrowserContexts for which this function has been called.  However,
+  // attempts to re-add an origin for the same |browser_context| will be
+  // ignored.
+  void AddIsolatedOrigins(std::vector<url::Origin> origins,
+                          BrowserContext* browser_context = nullptr);
+
+  // Remove all isolated origins associated with |browser_context|.  This is
+  // typically used when |browser_context| is being destroyed and assumes that
+  // no processes are running or will run for that profile; this makes the
+  // isolated origin removal safe.  Note that |browser_context| cannot be null;
+  // i.e., isolated origins that apply globally to all profiles cannot
+  // currently be removed, since that is not safe to do at runtime.
+  void RemoveIsolatedOriginsForBrowserContext(
+      const BrowserContext& browser_context);
 
   // Check whether |origin| requires origin-wide process isolation within
   // |isolation_context|.
@@ -369,6 +392,10 @@
   FRIEND_TEST_ALL_PREFIXES(ChildProcessSecurityPolicyTest, AddIsolatedOrigins);
   FRIEND_TEST_ALL_PREFIXES(ChildProcessSecurityPolicyTest,
                            DynamicIsolatedOrigins);
+  FRIEND_TEST_ALL_PREFIXES(ChildProcessSecurityPolicyTest,
+                           IsolatedOriginsForSpecificBrowserContexts);
+  FRIEND_TEST_ALL_PREFIXES(ChildProcessSecurityPolicyTest,
+                           IsolatedOriginsRemovedWhenBrowserContextDestroyed);
 
   class SecurityState;
 
@@ -376,12 +403,15 @@
   typedef std::map<int, std::unique_ptr<SecurityState>> SecurityStateMap;
   typedef std::map<storage::FileSystemType, int> FileSystemPermissionPolicyMap;
 
-  // This struct holds an isolated origin along with information such as which
-  // BrowsingInstances it applies to.  See |isolated_origins_| below for more
-  // details.
-  struct CONTENT_EXPORT IsolatedOriginEntry {
+  // This class holds an isolated origin along with information such as which
+  // BrowsingInstances and profile it applies to.  See |isolated_origins_|
+  // below for more details.
+  class CONTENT_EXPORT IsolatedOriginEntry {
+   public:
     IsolatedOriginEntry(const url::Origin& origin,
-                        BrowsingInstanceId min_browsing_instance_id);
+                        BrowsingInstanceId min_browsing_instance_id,
+                        BrowserContext* browser_context,
+                        ResourceContext* resource_context);
     // Copyable and movable.
     IsolatedOriginEntry(const IsolatedOriginEntry& other);
     IsolatedOriginEntry& operator=(const IsolatedOriginEntry& other);
@@ -391,23 +421,50 @@
 
     // Allow this class to be used as a key in STL.
     bool operator<(const IsolatedOriginEntry& other) const {
-      return std::tie(origin, min_browsing_instance_id) <
-             std::tie(other.origin, other.min_browsing_instance_id);
+      return std::tie(origin_, min_browsing_instance_id_, browser_context_,
+                      resource_context_) <
+             std::tie(other.origin_, other.min_browsing_instance_id_,
+                      other.browser_context_, other.resource_context_);
     }
 
     bool operator==(const IsolatedOriginEntry& other) const {
-      return origin == other.origin &&
-             min_browsing_instance_id == other.min_browsing_instance_id;
+      return origin_ == other.origin_ &&
+             min_browsing_instance_id_ == other.min_browsing_instance_id_ &&
+             browser_context_ == other.browser_context_ &&
+             resource_context_ == other.resource_context_;
     }
 
-    url::Origin origin;
-    BrowsingInstanceId min_browsing_instance_id;
+    // True if this isolated origin applies globally to all profiles.
+    bool AppliesToAllBrowserContexts() const;
+
+    // True if (1) this entry is associated with the same profile as
+    // |browser_or_resource_context|, or (2) this entry applies to all
+    // profiles.  May be used on UI or IO threads.
+    bool MatchesProfile(
+        const BrowserOrResourceContext& browser_or_resource_context) const;
+
+    const url::Origin& origin() const { return origin_; }
+
+    BrowsingInstanceId min_browsing_instance_id() const {
+      return min_browsing_instance_id_;
+    }
+
+    const BrowserContext* browser_context() const { return browser_context_; }
+
+   private:
+    url::Origin origin_;
+    BrowsingInstanceId min_browsing_instance_id_;
+
+    // Optional information about the profile where the isolated origin
+    // applies.  |browser_context_| may be used on the UI thread, and
+    // |resource_context_| may be used on the IO thread.  If these are null,
+    // then the isolated origin applies globally to all profiles.
+    BrowserContext* browser_context_;
+    ResourceContext* resource_context_;
+
     // TODO(alexmos): Track the source of each isolated origin entry, e.g., to
     // distinguish those that should be displayed to the user from those that
     // should not.  See https://crbug.com/920911.
-    //
-    // TODO(alexmos): Add a way to associate isolated origin entries with
-    // profiles.  See https://crbug.com/905513.
   };
 
   // Obtain an instance of ChildProcessSecurityPolicyImpl via GetInstance().
@@ -518,12 +575,25 @@
   // using the expensive DoesOriginMatchIsolatedOrigin() comparison is
   // typically small.
   //
-  // Each origin also stores information about which BrowsingInstances it
-  // applies to, in the form of a minimum BrowsingInstance ID.  This is looked
-  // up at the time the isolated origin is added.  The isolated origin will
-  // apply only to future BrowsingInstances, which will have IDs equal to or
-  // greater than the threshold ID (called |min_browsing_instance_id|) in each
-  // origin's IsolatedOriginEntry.
+  // Each origin entry stores information about:
+  //   1. Which BrowsingInstances it applies to, in the form of a minimum
+  //      BrowsingInstance ID.  This is looked up at the time the isolated
+  //      origin is added.  The isolated origin will apply only to future
+  //      BrowsingInstances, which will have IDs equal to or greater than the
+  //      threshold ID (called |min_browsing_instance_id|) in each origin's
+  //      IsolatedOriginEntry.
+  //   2. Optionally, which BrowserContext (profile) it applies to.  When the
+  //      |browser_context| field in the IsolatedOriginEntry is non-null, a
+  //      particular isolated origin entry only applies to that BrowserContext.
+  //      A ResourceContext, BrowserContext's representation on the IO thread,
+  //      is also stored in the entry to facilitate checks on the IO thread.
+  //      Note that the same origin may be isolated in different profiles,
+  //      possibly with different BrowsingInstance ID cut-offs.  For example:
+  //        https://foo.com -> { [https://test.foo.com profile1 4],
+  //                             [https://test.foo.com profile2 7] }
+  //      represents https://test.foo.com being isolated in profile1 starting
+  //      with BrowsingInstance ID 4, and also in profile2 starting with
+  //      BrowsingInstance ID 7.
   base::flat_map<GURL, base::flat_set<IsolatedOriginEntry>> isolated_origins_
       GUARDED_BY(isolated_origins_lock_);
 
diff --git a/content/browser/child_process_security_policy_unittest.cc b/content/browser/child_process_security_policy_unittest.cc
index 733f7273d..301cae01 100644
--- a/content/browser/child_process_security_policy_unittest.cc
+++ b/content/browser/child_process_security_policy_unittest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <algorithm>
 #include <set>
 #include <string>
 
@@ -100,8 +101,10 @@
                               const url::Origin& origin) {
     return std::pair<GURL, base::flat_set<IsolatedOriginEntry>>(
         SiteInstanceImpl::GetSiteForOrigin(origin),
-        {IsolatedOriginEntry(origin, BrowsingInstanceId::FromUnsafeValue(
-                                         min_browsing_instance_id))});
+        {IsolatedOriginEntry(
+            origin,
+            BrowsingInstanceId::FromUnsafeValue(min_browsing_instance_id),
+            nullptr, nullptr)});
   }
   // Converts |origin| -> (site_url, {entry})
   //     where site_url is created from |origin| and
@@ -123,20 +126,39 @@
     return std::pair<GURL, base::flat_set<IsolatedOriginEntry>>(
         SiteInstanceImpl::GetSiteForOrigin(origin1),
         {IsolatedOriginEntry(origin1,
-                             SiteInstanceImpl::NextBrowsingInstanceId()),
+                             SiteInstanceImpl::NextBrowsingInstanceId(),
+                             nullptr, nullptr),
          IsolatedOriginEntry(origin2,
-                             SiteInstanceImpl::NextBrowsingInstanceId())});
+                             SiteInstanceImpl::NextBrowsingInstanceId(),
+                             nullptr, nullptr)});
   }
 
-  bool IsIsolatedOrigin(int browsing_instance_id, const url::Origin& origin) {
+  bool IsIsolatedOrigin(BrowserContext* context,
+                        int browsing_instance_id,
+                        const url::Origin& origin) {
     ChildProcessSecurityPolicyImpl* p =
         ChildProcessSecurityPolicyImpl::GetInstance();
     return p->IsIsolatedOrigin(
         IsolationContext(
-            BrowsingInstanceId::FromUnsafeValue(browsing_instance_id)),
+            BrowsingInstanceId::FromUnsafeValue(browsing_instance_id), context),
         origin);
   }
 
+  // Returns the number of isolated origin entries for a particular origin.
+  // There may be more than one such entry if each is associated with a
+  // different profile.
+  int GetIsolatedOriginEntryCount(const url::Origin& origin) {
+    ChildProcessSecurityPolicyImpl* p =
+        ChildProcessSecurityPolicyImpl::GetInstance();
+    GURL key(SiteInstanceImpl::GetSiteForOrigin(origin));
+    base::AutoLock isolated_origins_lock(p->isolated_origins_lock_);
+    auto origins_for_key = p->isolated_origins_[key];
+    return std::count_if(origins_for_key.begin(), origins_for_key.end(),
+                         [origin](const IsolatedOriginEntry& entry) {
+                           return entry.origin() == origin;
+                         });
+  }
+
  protected:
   void RegisterTestScheme(const std::string& scheme) {
     test_browser_client_.AddScheme(scheme);
@@ -1407,9 +1429,9 @@
                                     GetIsolatedOriginEntry(initial_id, bar)));
 
   // Create a new BrowsingInstance.  Its ID will be |initial_id|.
-  std::unique_ptr<BrowserContext> browser_context(new TestBrowserContext());
-  scoped_refptr<SiteInstanceImpl> foo_instance = SiteInstanceImpl::CreateForURL(
-      browser_context.get(), GURL("https://foo.com/"));
+  TestBrowserContext context;
+  scoped_refptr<SiteInstanceImpl> foo_instance =
+      SiteInstanceImpl::CreateForURL(&context, GURL("https://foo.com/"));
   EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id),
             foo_instance->GetIsolationContext().browsing_instance_id());
   EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id + 1),
@@ -1433,8 +1455,8 @@
                          GetIsolatedOriginEntry(initial_id + 1, baz)));
 
   // Create another BrowsingInstance.
-  scoped_refptr<SiteInstanceImpl> bar_instance = SiteInstanceImpl::CreateForURL(
-      browser_context.get(), GURL("https://bar.com/"));
+  scoped_refptr<SiteInstanceImpl> bar_instance =
+      SiteInstanceImpl::CreateForURL(&context, GURL("https://bar.com/"));
   EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id + 1),
             bar_instance->GetIsolationContext().browsing_instance_id());
   EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id + 2),
@@ -1453,32 +1475,32 @@
   // the provided BrowsingInstance. foo and bar should apply in
   // BrowsingInstance ID |initial_id| and above, baz in IDs |initial_id + 1|
   // and above, and qux in |initial_id + 2| and above.
-  EXPECT_TRUE(IsIsolatedOrigin(initial_id, foo));
-  EXPECT_TRUE(IsIsolatedOrigin(initial_id, bar));
-  EXPECT_FALSE(IsIsolatedOrigin(initial_id, baz));
-  EXPECT_FALSE(IsIsolatedOrigin(initial_id, qux));
+  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id, foo));
+  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id, bar));
+  EXPECT_FALSE(IsIsolatedOrigin(&context, initial_id, baz));
+  EXPECT_FALSE(IsIsolatedOrigin(&context, initial_id, qux));
 
-  EXPECT_TRUE(IsIsolatedOrigin(initial_id + 1, foo));
-  EXPECT_TRUE(IsIsolatedOrigin(initial_id + 1, bar));
-  EXPECT_TRUE(IsIsolatedOrigin(initial_id + 1, baz));
-  EXPECT_FALSE(IsIsolatedOrigin(initial_id + 1, qux));
+  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 1, foo));
+  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 1, bar));
+  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 1, baz));
+  EXPECT_FALSE(IsIsolatedOrigin(&context, initial_id + 1, qux));
 
-  EXPECT_TRUE(IsIsolatedOrigin(initial_id + 2, foo));
-  EXPECT_TRUE(IsIsolatedOrigin(initial_id + 2, bar));
-  EXPECT_TRUE(IsIsolatedOrigin(initial_id + 2, baz));
-  EXPECT_TRUE(IsIsolatedOrigin(initial_id + 2, qux));
+  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 2, foo));
+  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 2, bar));
+  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 2, baz));
+  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 2, qux));
 
-  EXPECT_TRUE(IsIsolatedOrigin(initial_id + 42, foo));
-  EXPECT_TRUE(IsIsolatedOrigin(initial_id + 42, bar));
-  EXPECT_TRUE(IsIsolatedOrigin(initial_id + 42, baz));
-  EXPECT_TRUE(IsIsolatedOrigin(initial_id + 42, qux));
+  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 42, foo));
+  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 42, bar));
+  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 42, baz));
+  EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 42, qux));
 
-  // A default-constructed IsolationContext should return the latest available
-  // isolated origins.
-  EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(), foo));
-  EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(), bar));
-  EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(), baz));
-  EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(), qux));
+  // An IsolationContext constructed without a BrowsingInstance ID should
+  // return the latest available isolated origins.
+  EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(&context), foo));
+  EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(&context), bar));
+  EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(&context), baz));
+  EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(&context), qux));
 
   p->RemoveIsolatedOriginForTesting(foo);
   p->RemoveIsolatedOriginForTesting(bar);
@@ -1491,12 +1513,186 @@
 TEST_F(ChildProcessSecurityPolicyTest, IsIsolatedOriginWithEmptyHost) {
   ChildProcessSecurityPolicyImpl* p =
       ChildProcessSecurityPolicyImpl::GetInstance();
-  EXPECT_FALSE(
-      p->IsIsolatedOrigin(IsolationContext(), url::Origin::Create(GURL())));
-  EXPECT_FALSE(p->IsIsolatedOrigin(IsolationContext(),
+  TestBrowserContext context;
+  EXPECT_FALSE(p->IsIsolatedOrigin(IsolationContext(&context),
+                                   url::Origin::Create(GURL())));
+  EXPECT_FALSE(p->IsIsolatedOrigin(IsolationContext(&context),
                                    url::Origin::Create(GURL("file:///foo"))));
 }
 
+// Verifies the API for restricting isolated origins to a specific
+// BrowserContext (profile).  Namely, the same origin may be added for
+// different BrowserContexts, possibly with different BrowsingInstanceId
+// cutoffs.  Attempts to re-add an origin for the same profile should be
+// ignored.  Also, once an isolated origin is added globally for all profiles,
+// future attempts to re-add it (for any profile) should also be ignored.
+TEST_F(ChildProcessSecurityPolicyTest,
+       IsolatedOriginsForSpecificBrowserContexts) {
+  url::Origin foo = url::Origin::Create(GURL("https://foo.com/"));
+  url::Origin bar = url::Origin::Create(GURL("https://bar.com/"));
+  ChildProcessSecurityPolicyImpl* p =
+      ChildProcessSecurityPolicyImpl::GetInstance();
+
+  // Initially there should be no isolated origins.
+  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
+                     testing::IsEmpty());
+
+  // Save the next BrowsingInstance ID to be created.  Because unit tests run
+  // in batches, this isn't guaranteed to always be 1, for example if a
+  // previous test in the same batch had already created a SiteInstance and
+  // BrowsingInstance.
+  int initial_id(SiteInstanceImpl::NextBrowsingInstanceId().GetUnsafeValue());
+
+  // Isolate foo.com globally (for all BrowserContexts).
+  p->AddIsolatedOrigins({foo});
+
+  TestBrowserContext context1, context2;
+
+  // Isolate bar.com in |context1|.
+  p->AddIsolatedOrigins({bar}, &context1);
+
+  // bar.com should be isolated for |context1|, but not |context2|. foo.com
+  // should be isolated for all contexts.
+  EXPECT_TRUE(IsIsolatedOrigin(&context1, initial_id, foo));
+  EXPECT_TRUE(IsIsolatedOrigin(&context2, initial_id, foo));
+  EXPECT_TRUE(IsIsolatedOrigin(&context1, initial_id, bar));
+  EXPECT_FALSE(IsIsolatedOrigin(&context2, initial_id, bar));
+
+  // Create a new BrowsingInstance.  Its ID will be |initial_id|.
+  scoped_refptr<SiteInstanceImpl> foo_instance =
+      SiteInstanceImpl::CreateForURL(&context1, GURL("https://foo.com/"));
+  EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id),
+            foo_instance->GetIsolationContext().browsing_instance_id());
+  EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id + 1),
+            SiteInstanceImpl::NextBrowsingInstanceId());
+  EXPECT_EQ(&context1, foo_instance->GetIsolationContext()
+                           .browser_or_resource_context()
+                           .ToBrowserContext());
+
+  // Isolating foo.com in |context1| is allowed and should add a new
+  // IsolatedOriginEntry.  This wouldn't introduce any additional isolation,
+  // since foo.com is already isolated globally, but the new entry is
+  // important, e.g. for persisting profile-specific isolated origins across
+  // restarts.
+  EXPECT_EQ(1, GetIsolatedOriginEntryCount(foo));
+  p->AddIsolatedOrigins({foo}, &context1);
+  EXPECT_EQ(2, GetIsolatedOriginEntryCount(foo));
+  EXPECT_TRUE(IsIsolatedOrigin(&context1, initial_id, foo));
+  EXPECT_TRUE(IsIsolatedOrigin(&context2, initial_id, foo));
+
+  // Isolating bar.com in |context1| again should have no effect.
+  EXPECT_EQ(1, GetIsolatedOriginEntryCount(bar));
+  p->AddIsolatedOrigins({bar}, &context1);
+  EXPECT_EQ(1, GetIsolatedOriginEntryCount(bar));
+  EXPECT_TRUE(IsIsolatedOrigin(&context1, initial_id, bar));
+  EXPECT_FALSE(IsIsolatedOrigin(&context2, initial_id, bar));
+
+  // Isolate bar.com for |context2|, which should add a new
+  // IsolatedOriginEntry.  Verify that the isolation took effect for
+  // |initial_id + 1| (the current BrowsingInstance ID cutoff) only.
+  p->AddIsolatedOrigins({bar}, &context2);
+  EXPECT_EQ(2, GetIsolatedOriginEntryCount(bar));
+  EXPECT_FALSE(IsIsolatedOrigin(&context2, initial_id, bar));
+  EXPECT_TRUE(IsIsolatedOrigin(&context2, initial_id + 1, bar));
+
+  // Verify the bar.com is still isolated in |context1| starting with
+  // |initial_id|.
+  EXPECT_TRUE(IsIsolatedOrigin(&context1, initial_id, bar));
+  EXPECT_TRUE(IsIsolatedOrigin(&context1, initial_id + 1, bar));
+
+  // Create another BrowserContext; only foo.com should be isolated there.
+  TestBrowserContext context3;
+  EXPECT_TRUE(IsIsolatedOrigin(&context3, initial_id, foo));
+  EXPECT_TRUE(IsIsolatedOrigin(&context3, initial_id + 1, foo));
+  EXPECT_FALSE(IsIsolatedOrigin(&context3, initial_id, bar));
+  EXPECT_FALSE(IsIsolatedOrigin(&context3, initial_id + 1, bar));
+
+  // Now, add bar.com as a globally isolated origin.  This should make it apply
+  // to context3 as well, but only in initial_id + 1 (the current
+  // BrowsingInstance ID cutoff).
+  p->AddIsolatedOrigins({bar});
+  EXPECT_EQ(3, GetIsolatedOriginEntryCount(bar));
+  EXPECT_FALSE(IsIsolatedOrigin(&context3, initial_id, bar));
+  EXPECT_TRUE(IsIsolatedOrigin(&context3, initial_id + 1, bar));
+
+  // An attempt to re-add bar.com for a new profile should create a new
+  // IsolatedOriginEntry, though it wouldn't provide any additional isolation,
+  // since bar.com is already isolated globally.
+  TestBrowserContext context4;
+  p->AddIsolatedOrigins({bar}, &context4);
+  EXPECT_EQ(4, GetIsolatedOriginEntryCount(bar));
+
+  p->RemoveIsolatedOriginForTesting(foo);
+  p->RemoveIsolatedOriginForTesting(bar);
+}
+
+// This test ensures that isolated origins associated with a specific
+// BrowserContext are removed when that BrowserContext is destroyed.
+TEST_F(ChildProcessSecurityPolicyTest,
+       IsolatedOriginsRemovedWhenBrowserContextDestroyed) {
+  url::Origin foo = url::Origin::Create(GURL("https://foo.com/"));
+  url::Origin sub_foo = url::Origin::Create(GURL("https://sub.foo.com/"));
+  url::Origin bar = url::Origin::Create(GURL("https://bar.com/"));
+  url::Origin baz = url::Origin::Create(GURL("https://baz.com/"));
+  ChildProcessSecurityPolicyImpl* p =
+      ChildProcessSecurityPolicyImpl::GetInstance();
+
+  // Initially there should be no isolated origins.
+  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
+                     testing::IsEmpty());
+
+  // Save the next BrowsingInstance ID to be created.  Because unit tests run
+  // in batches, this isn't guaranteed to always be 1, for example if a
+  // previous test in the same batch had already created a SiteInstance and
+  // BrowsingInstance.
+  int initial_id(SiteInstanceImpl::NextBrowsingInstanceId().GetUnsafeValue());
+
+  std::unique_ptr<TestBrowserContext> context1(new TestBrowserContext());
+  std::unique_ptr<TestBrowserContext> context2(new TestBrowserContext());
+
+  // Isolate foo.com in |context1|.  Note that sub.foo.com should also be
+  // considered isolated in |context1|, since it's a subdomain of foo.com.
+  p->AddIsolatedOrigins({foo}, context1.get());
+  EXPECT_EQ(1, GetIsolatedOriginEntryCount(foo));
+  EXPECT_TRUE(IsIsolatedOrigin(context1.get(), initial_id, foo));
+  EXPECT_TRUE(IsIsolatedOrigin(context1.get(), initial_id, sub_foo));
+  EXPECT_FALSE(IsIsolatedOrigin(context2.get(), initial_id, foo));
+  EXPECT_FALSE(IsIsolatedOrigin(context2.get(), initial_id, sub_foo));
+
+  // Isolate sub.foo.com and bar.com in |context2|.
+  p->AddIsolatedOrigins({sub_foo, bar}, context2.get());
+  EXPECT_EQ(1, GetIsolatedOriginEntryCount(sub_foo));
+  EXPECT_EQ(1, GetIsolatedOriginEntryCount(bar));
+  EXPECT_TRUE(IsIsolatedOrigin(context2.get(), initial_id, sub_foo));
+  EXPECT_TRUE(IsIsolatedOrigin(context2.get(), initial_id, bar));
+  EXPECT_FALSE(IsIsolatedOrigin(context2.get(), initial_id, foo));
+
+  // Isolate baz.com in both BrowserContexts.
+  p->AddIsolatedOrigins({baz}, context1.get());
+  p->AddIsolatedOrigins({baz}, context2.get());
+
+  EXPECT_EQ(2, GetIsolatedOriginEntryCount(baz));
+  EXPECT_TRUE(IsIsolatedOrigin(context1.get(), initial_id, baz));
+  EXPECT_TRUE(IsIsolatedOrigin(context2.get(), initial_id, baz));
+
+  // Remove |context1|.  foo.com should no longer be in the isolated_origins_
+  // map, and the other origins should be isolated only in |context2|.
+  context1.reset();
+
+  EXPECT_EQ(0, GetIsolatedOriginEntryCount(foo));
+  EXPECT_EQ(1, GetIsolatedOriginEntryCount(sub_foo));
+  EXPECT_EQ(1, GetIsolatedOriginEntryCount(bar));
+  EXPECT_EQ(1, GetIsolatedOriginEntryCount(baz));
+  EXPECT_TRUE(IsIsolatedOrigin(context2.get(), initial_id, sub_foo));
+  EXPECT_TRUE(IsIsolatedOrigin(context2.get(), initial_id, bar));
+  EXPECT_TRUE(IsIsolatedOrigin(context2.get(), initial_id, baz));
+
+  // Remove |context2| and ensure the remaining entries are removed.
+  context2.reset();
+  LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
+                     testing::IsEmpty());
+}
+
 // Tests behavior of HasSecurityState() during race conditions that
 // can occur during Remove(). It verifies that SecurityState for a child ID is
 // preserved after a Remove() call until the task, that Remove() has posted to
@@ -1611,4 +1807,5 @@
   EXPECT_FALSE(ui_after_remove_complete);
   EXPECT_FALSE(io_after_remove_complete);
 }
+
 }  // namespace content
diff --git a/content/browser/devtools/devtools_stream_blob.cc b/content/browser/devtools/devtools_stream_blob.cc
index 2713584..2a720cd 100644
--- a/content/browser/devtools/devtools_stream_blob.cc
+++ b/content/browser/devtools/devtools_stream_blob.cc
@@ -211,7 +211,7 @@
     }
   }
   base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::IO},
+      FROM_HERE, {BrowserThread::UI},
       base::BindOnce(std::move(request->callback), std::move(data),
                      base64_encoded, status));
   if (!pending_reads_.empty())
diff --git a/content/browser/dom_storage/dom_storage_context_wrapper.cc b/content/browser/dom_storage/dom_storage_context_wrapper.cc
index 1dfcf25..ff06e2d4 100644
--- a/content/browser/dom_storage/dom_storage_context_wrapper.cc
+++ b/content/browser/dom_storage/dom_storage_context_wrapper.cc
@@ -431,36 +431,25 @@
 
 void DOMStorageContextWrapper::OpenLocalStorage(
     const url::Origin& origin,
-    blink::mojom::StorageAreaRequest request,
-    base::OnceClosure bind_done) {
+    blink::mojom::StorageAreaRequest request) {
   DCHECK(mojo_state_);
-
-  base::OnceClosure wrapped_done_callback = base::BindOnce(
-      base::IgnoreResult(&base::SequencedTaskRunner::PostTask),
-      base::SequencedTaskRunnerHandle::Get(), FROM_HERE, std::move(bind_done));
-
   // base::Unretained is safe here, because the mojo_state_ won't be deleted
   // until a ShutdownAndDelete task has been ran on the mojo_task_runner_, and
   // as soon as that task is posted, mojo_state_ is set to null, preventing
   // further tasks from being queued.
   mojo_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&LocalStorageContextMojo::OpenLocalStorage,
-                     base::Unretained(mojo_state_), origin, std::move(request),
-                     std::move(wrapped_done_callback)));
+      FROM_HERE, base::BindOnce(&LocalStorageContextMojo::OpenLocalStorage,
+                                base::Unretained(mojo_state_), origin,
+                                std::move(request)));
 }
 
 void DOMStorageContextWrapper::OpenSessionStorage(
     int process_id,
     const std::string& namespace_id,
     mojo::ReportBadMessageCallback bad_message_callback,
-    blink::mojom::SessionStorageNamespaceRequest request,
-    base::OnceClosure bind_done) {
+    blink::mojom::SessionStorageNamespaceRequest request) {
   if (!mojo_session_state_)
     return;
-  base::OnceClosure wrapped_done_callback = base::BindOnce(
-      base::IgnoreResult(&base::SequencedTaskRunner::PostTask),
-      base::SequencedTaskRunnerHandle::Get(), FROM_HERE, std::move(bind_done));
   // The bad message callback must be called on the same sequenced task runner
   // as the binding set. It cannot be called from our own mojo task runner.
   auto wrapped_bad_message_callback = base::BindOnce(
@@ -480,7 +469,7 @@
       base::BindOnce(&SessionStorageContextMojo::OpenSessionStorage,
                      base::Unretained(mojo_session_state_), process_id,
                      namespace_id, std::move(wrapped_bad_message_callback),
-                     std::move(request), std::move(wrapped_done_callback)));
+                     std::move(request)));
 }
 
 void DOMStorageContextWrapper::SetLocalStorageDatabaseForTesting(
diff --git a/content/browser/dom_storage/dom_storage_context_wrapper.h b/content/browser/dom_storage/dom_storage_context_wrapper.h
index 4e630f4..ded7b75a 100644
--- a/content/browser/dom_storage/dom_storage_context_wrapper.h
+++ b/content/browser/dom_storage/dom_storage_context_wrapper.h
@@ -88,13 +88,11 @@
 
   // See mojom::StoragePartitionService interface.
   void OpenLocalStorage(const url::Origin& origin,
-                        blink::mojom::StorageAreaRequest request,
-                        base::OnceClosure bind_done);
+                        blink::mojom::StorageAreaRequest request);
   void OpenSessionStorage(int process_id,
                           const std::string& namespace_id,
                           mojo::ReportBadMessageCallback bad_message_callback,
-                          blink::mojom::SessionStorageNamespaceRequest request,
-                          base::OnceClosure bind_done);
+                          blink::mojom::SessionStorageNamespaceRequest request);
 
   void SetLocalStorageDatabaseForTesting(
       leveldb::mojom::LevelDBDatabaseAssociatedPtr database);
diff --git a/content/browser/dom_storage/dom_storage_context_wrapper_unittest.cc b/content/browser/dom_storage/dom_storage_context_wrapper_unittest.cc
index 18f1dc5..71b1cc8 100644
--- a/content/browser/dom_storage/dom_storage_context_wrapper_unittest.cc
+++ b/content/browser/dom_storage/dom_storage_context_wrapper_unittest.cc
@@ -63,14 +63,13 @@
   blink::mojom::SessionStorageNamespacePtr ss_namespace_ptr;
   auto request = mojo::MakeRequest(&ss_namespace_ptr);
   bool called = false;
-
   // This call is invalid because |CreateSessionNamespace| was never called on
   // the SessionStorage context.
   context_->OpenSessionStorage(
       0, "nonexistant-namespace",
       base::BindLambdaForTesting(
           [&called](const std::string& message) { called = true; }),
-      std::move(request), base::DoNothing());
+      std::move(request));
   EXPECT_FALSE(called);
   fake_mojo_task_runner_->RunPendingTasks();
 
diff --git a/content/browser/dom_storage/local_storage_context_mojo.cc b/content/browser/dom_storage/local_storage_context_mojo.cc
index 6699f3f..5067dec 100644
--- a/content/browser/dom_storage/local_storage_context_mojo.cc
+++ b/content/browser/dom_storage/local_storage_context_mojo.cc
@@ -395,11 +395,10 @@
 
 void LocalStorageContextMojo::OpenLocalStorage(
     const url::Origin& origin,
-    blink::mojom::StorageAreaRequest request,
-    base::OnceClosure bind_done) {
+    blink::mojom::StorageAreaRequest request) {
   RunWhenConnected(base::BindOnce(&LocalStorageContextMojo::BindLocalStorage,
                                   weak_ptr_factory_.GetWeakPtr(), origin,
-                                  std::move(request), std::move(bind_done)));
+                                  std::move(request)));
 }
 
 void LocalStorageContextMojo::GetStorageUsage(
@@ -912,10 +911,8 @@
 // directly from that function, or through |on_database_open_callbacks_|.
 void LocalStorageContextMojo::BindLocalStorage(
     const url::Origin& origin,
-    blink::mojom::StorageAreaRequest request,
-    base::OnceClosure bind_done) {
+    blink::mojom::StorageAreaRequest request) {
   GetOrCreateStorageArea(origin)->Bind(std::move(request));
-  std::move(bind_done).Run();
 }
 
 LocalStorageContextMojo::StorageAreaHolder*
diff --git a/content/browser/dom_storage/local_storage_context_mojo.h b/content/browser/dom_storage/local_storage_context_mojo.h
index 5587e884..db50fab 100644
--- a/content/browser/dom_storage/local_storage_context_mojo.h
+++ b/content/browser/dom_storage/local_storage_context_mojo.h
@@ -57,8 +57,7 @@
       scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy);
 
   void OpenLocalStorage(const url::Origin& origin,
-                        blink::mojom::StorageAreaRequest request,
-                        base::OnceClosure bind_done);
+                        blink::mojom::StorageAreaRequest request);
   void GetStorageUsage(GetStorageUsageCallback callback);
   // |callback| is called when the deletion is sent to the database and
   // GetStorageUsage() will not return entries for |origin| anymore.
@@ -124,8 +123,7 @@
   // The (possibly delayed) implementation of OpenLocalStorage(). Can be called
   // directly from that function, or through |on_database_open_callbacks_|.
   void BindLocalStorage(const url::Origin& origin,
-                        blink::mojom::StorageAreaRequest request,
-                        base::OnceClosure bind_done);
+                        blink::mojom::StorageAreaRequest request);
   StorageAreaHolder* GetOrCreateStorageArea(const url::Origin& origin);
 
   // The (possibly delayed) implementation of GetStorageUsage(). Can be called
diff --git a/content/browser/dom_storage/local_storage_context_mojo_unittest.cc b/content/browser/dom_storage/local_storage_context_mojo_unittest.cc
index 56b9ba4..fee17f17 100644
--- a/content/browser/dom_storage/local_storage_context_mojo_unittest.cc
+++ b/content/browser/dom_storage/local_storage_context_mojo_unittest.cc
@@ -187,9 +187,8 @@
     const url::Origin kOrigin = url::Origin::Create(GURL("http://foobar.com"));
     blink::mojom::StorageAreaPtr area;
     blink::mojom::StorageAreaPtr dummy_area;  // To make sure values are cached.
-    context()->OpenLocalStorage(kOrigin, MakeRequest(&area), base::DoNothing());
-    context()->OpenLocalStorage(kOrigin, MakeRequest(&dummy_area),
-                                base::DoNothing());
+    context()->OpenLocalStorage(kOrigin, MakeRequest(&area));
+    context()->OpenLocalStorage(kOrigin, MakeRequest(&dummy_area));
     std::vector<uint8_t> result;
     bool success = test::GetSync(area.get(), key, &result);
     return success ? base::Optional<std::vector<uint8_t>>(result)
@@ -223,7 +222,7 @@
 
   blink::mojom::StorageAreaPtr area;
   context()->OpenLocalStorage(url::Origin::Create(GURL("http://foobar.com")),
-                              MakeRequest(&area), base::DoNothing());
+                              MakeRequest(&area));
   area->Put(key, value, base::nullopt, "source", base::DoNothing());
   area.reset();
 
@@ -242,11 +241,11 @@
   auto value = StdStringToUint8Vector("value");
 
   blink::mojom::StorageAreaPtr area;
-  context()->OpenLocalStorage(origin1, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin1, MakeRequest(&area));
   area->Put(key1, value, base::nullopt, "source", base::DoNothing());
   area.reset();
 
-  context()->OpenLocalStorage(origin2, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin2, MakeRequest(&area));
   area->Put(key2, value, base::nullopt, "source", base::DoNothing());
   area.reset();
 
@@ -262,9 +261,8 @@
   blink::mojom::StorageAreaPtr area;
   blink::mojom::StorageAreaPtr dummy_area;  // To make sure values are cached.
   const url::Origin kOrigin(url::Origin::Create(GURL("http://foobar.com")));
-  context()->OpenLocalStorage(kOrigin, MakeRequest(&area), base::DoNothing());
-  context()->OpenLocalStorage(kOrigin, MakeRequest(&dummy_area),
-                              base::DoNothing());
+  context()->OpenLocalStorage(kOrigin, MakeRequest(&area));
+  context()->OpenLocalStorage(kOrigin, MakeRequest(&dummy_area));
   area->Put(key, value, base::nullopt, "source", base::DoNothing());
   area.reset();
   dummy_area.reset();
@@ -292,7 +290,7 @@
   // Write some data to the DB.
   blink::mojom::StorageAreaPtr area;
   context()->OpenLocalStorage(url::Origin::Create(GURL("http://foobar.com")),
-                              MakeRequest(&area), base::DoNothing());
+                              MakeRequest(&area));
   area->Put(key, value, base::nullopt, "source", base::DoNothing());
   area.reset();
   base::RunLoop().RunUntilIdle();
@@ -305,7 +303,7 @@
   for (int i = 1; i <= 100; ++i) {
     context()->OpenLocalStorage(url::Origin::Create(GURL(base::StringPrintf(
                                     "http://example.com:%d", i))),
-                                MakeRequest(&area), base::DoNothing());
+                                MakeRequest(&area));
     area.reset();
   }
 
@@ -350,12 +348,12 @@
   base::Time before_write = base::Time::Now();
 
   blink::mojom::StorageAreaPtr area;
-  context()->OpenLocalStorage(origin1, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin1, MakeRequest(&area));
   area->Put(key1, value, base::nullopt, "source", base::DoNothing());
   area->Put(key2, value, base::nullopt, "source", base::DoNothing());
   area.reset();
 
-  context()->OpenLocalStorage(origin2, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin2, MakeRequest(&area));
   area->Put(key2, value, base::nullopt, "source", base::DoNothing());
   area.reset();
 
@@ -397,13 +395,13 @@
   auto value = StdStringToUint8Vector("value");
 
   blink::mojom::StorageAreaPtr area;
-  context()->OpenLocalStorage(origin1, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin1, MakeRequest(&area));
   area->Put(key, value, base::nullopt, "source", base::DoNothing());
   area.reset();
-  context()->OpenLocalStorage(origin2, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin2, MakeRequest(&area));
   area->Put(key, value, base::nullopt, "source", base::DoNothing());
   area.reset();
-  context()->OpenLocalStorage(origin1, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin1, MakeRequest(&area));
   area->Delete(key, value, "source", base::DoNothing());
   area.reset();
 
@@ -430,14 +428,14 @@
   auto value = StdStringToUint8Vector("value");
 
   blink::mojom::StorageAreaPtr area;
-  context()->OpenLocalStorage(origin1, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin1, MakeRequest(&area));
   area->Put(key, value, base::nullopt, "source", base::DoNothing());
   area.reset();
-  context()->OpenLocalStorage(origin2, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin2, MakeRequest(&area));
   area->Put(key, value, base::nullopt, "source", base::DoNothing());
   area.reset();
 
-  context()->OpenLocalStorage(origin1, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin1, MakeRequest(&area));
   area->DeleteAll("source", base::DoNothing());
   area.reset();
 
@@ -464,7 +462,7 @@
 
   {
     blink::mojom::StorageAreaPtr area;
-    context()->OpenLocalStorage(origin1, MakeRequest(&area), base::DoNothing());
+    context()->OpenLocalStorage(origin1, MakeRequest(&area));
     area->Put(key, value, base::nullopt, "source", base::DoNothing());
     area.reset();
   }
@@ -480,7 +478,7 @@
   // Check that local storage still works without a database.
   {
     blink::mojom::StorageAreaPtr area;
-    context()->OpenLocalStorage(origin1, MakeRequest(&area), base::DoNothing());
+    context()->OpenLocalStorage(origin1, MakeRequest(&area));
     area->Put(key, value, base::nullopt, "source", base::DoNothing());
     area.reset();
   }
@@ -505,11 +503,11 @@
   auto value = StdStringToUint8Vector("value");
 
   blink::mojom::StorageAreaPtr area;
-  context()->OpenLocalStorage(origin1, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin1, MakeRequest(&area));
   area->Put(key, value, base::nullopt, "source", base::DoNothing());
   area.reset();
 
-  context()->OpenLocalStorage(origin2, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin2, MakeRequest(&area));
   area->Put(key, value, base::nullopt, "source", base::DoNothing());
   area.reset();
 
@@ -540,11 +538,11 @@
   auto value = StdStringToUint8Vector("value");
 
   blink::mojom::StorageAreaPtr area;
-  context()->OpenLocalStorage(origin1, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin1, MakeRequest(&area));
   area->Put(key, value, base::nullopt, "source", base::DoNothing());
   area.reset();
 
-  context()->OpenLocalStorage(origin2, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin2, MakeRequest(&area));
   area->Put(key, value, base::nullopt, "source", base::DoNothing());
   area.reset();
 
@@ -553,7 +551,7 @@
   EXPECT_FALSE(mock_data().empty());
 
   TestLevelDBObserver observer;
-  context()->OpenLocalStorage(origin1, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin1, MakeRequest(&area));
   area->AddObserver(observer.Bind());
   base::RunLoop().RunUntilIdle();
 
@@ -584,11 +582,11 @@
   auto value = StdStringToUint8Vector("value");
 
   blink::mojom::StorageAreaPtr area;
-  context()->OpenLocalStorage(origin1, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin1, MakeRequest(&area));
   area->Put(key, value, base::nullopt, "source", base::DoNothing());
   area.reset();
 
-  context()->OpenLocalStorage(origin2, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin2, MakeRequest(&area));
   area->Put(key, value, base::nullopt, "source", base::DoNothing());
   area.reset();
 
@@ -597,7 +595,7 @@
   EXPECT_FALSE(mock_data().empty());
 
   TestLevelDBObserver observer;
-  context()->OpenLocalStorage(origin1, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin1, MakeRequest(&area));
   area->AddObserver(observer.Bind());
   area->Put(StdStringToUint8Vector("key2"), value, base::nullopt, "source",
             base::DoNothing());
@@ -647,10 +645,9 @@
 
   // Opening origin2 and accessing its data should not migrate anything.
   blink::mojom::StorageAreaPtr area;
-  context()->OpenLocalStorage(origin2, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin2, MakeRequest(&area));
   blink::mojom::StorageAreaPtr dummy_area;  // To make sure values are cached.
-  context()->OpenLocalStorage(origin2, MakeRequest(&dummy_area),
-                              base::DoNothing());
+  context()->OpenLocalStorage(origin2, MakeRequest(&dummy_area));
   area->Get(std::vector<uint8_t>(), base::DoNothing());
   area.reset();
   dummy_area.reset();
@@ -658,9 +655,8 @@
   EXPECT_TRUE(mock_data().empty());
 
   // Opening origin1 and accessing its data should migrate its storage.
-  context()->OpenLocalStorage(origin1, MakeRequest(&area), base::DoNothing());
-  context()->OpenLocalStorage(origin1, MakeRequest(&dummy_area),
-                              base::DoNothing());
+  context()->OpenLocalStorage(origin1, MakeRequest(&area));
+  context()->OpenLocalStorage(origin1, MakeRequest(&dummy_area));
   area->Get(std::vector<uint8_t>(), base::DoNothing());
   base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(mock_data().empty());
@@ -715,9 +711,9 @@
   blink::mojom::StorageAreaPtr area;
   blink::mojom::StorageAreaPtr dummy_area;  // To make sure values are cached.
   context()->OpenLocalStorage(url::Origin::Create(GURL("http://foobar.com")),
-                              MakeRequest(&area), base::DoNothing());
+                              MakeRequest(&area));
   context()->OpenLocalStorage(url::Origin::Create(GURL("http://foobar.com")),
-                              MakeRequest(&dummy_area), base::DoNothing());
+                              MakeRequest(&dummy_area));
 
   {
     std::vector<uint8_t> result;
@@ -753,12 +749,12 @@
   auto value = StdStringToUint8Vector("value");
 
   blink::mojom::StorageAreaPtr area;
-  context()->OpenLocalStorage(origin1, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin1, MakeRequest(&area));
   area->Put(key1, value, base::nullopt, "source", base::DoNothing());
   area->Put(key2, value, base::nullopt, "source", base::DoNothing());
   area.reset();
 
-  context()->OpenLocalStorage(origin2, MakeRequest(&area), base::DoNothing());
+  context()->OpenLocalStorage(origin2, MakeRequest(&area));
   area->Put(key2, value, base::nullopt, "source", base::DoNothing());
   area.reset();
 
@@ -795,7 +791,7 @@
     bool success = false;
     base::RunLoop run_loop;
     context->OpenLocalStorage(url::Origin::Create(GURL("http://foobar.com")),
-                              MakeRequest(&area), base::DoNothing());
+                              MakeRequest(&area));
     area->Put(key, value, base::nullopt, "source",
               test::MakeSuccessCallback(run_loop.QuitClosure(), &success));
     run_loop.Run();
@@ -809,7 +805,7 @@
                  std::vector<uint8_t>* result) {
     blink::mojom::StorageAreaPtr area;
     context->OpenLocalStorage(url::Origin::Create(GURL("http://foobar.com")),
-                              MakeRequest(&area), base::DoNothing());
+                              MakeRequest(&area));
 
     base::RunLoop run_loop;
     std::vector<blink::mojom::KeyValuePtr> data;
@@ -861,7 +857,7 @@
 
   blink::mojom::StorageAreaPtr area;
   context->OpenLocalStorage(url::Origin::Create(GURL("http://foobar.com")),
-                            MakeRequest(&area), base::DoNothing());
+                            MakeRequest(&area));
   DoTestPut(context, key, value);
   std::vector<uint8_t> result;
   EXPECT_TRUE(DoTestGet(context, key, &result));
@@ -891,7 +887,7 @@
 
   blink::mojom::StorageAreaPtr area;
   context->OpenLocalStorage(url::Origin::Create(GURL("http://foobar.com")),
-                            MakeRequest(&area), base::DoNothing());
+                            MakeRequest(&area));
 
   DoTestPut(context, key, value);
   std::vector<uint8_t> result;
@@ -1065,11 +1061,11 @@
     base::RunLoop loop;
     mock_leveldb_service.SetOnOpenCallback(loop.QuitClosure());
     context->OpenLocalStorage(url::Origin::Create(GURL("http://foobar.com")),
-                              MakeRequest(&area1), base::DoNothing());
+                              MakeRequest(&area1));
     context->OpenLocalStorage(url::Origin::Create(GURL("http://foobar.com")),
-                              MakeRequest(&area2), base::DoNothing());
+                              MakeRequest(&area2));
     context->OpenLocalStorage(url::Origin::Create(GURL("http://example.com")),
-                              MakeRequest(&area3), base::DoNothing());
+                              MakeRequest(&area3));
     loop.Run();
   }
 
@@ -1139,7 +1135,7 @@
 
   // Reconnect area1 to the database, and try to read a value.
   context->OpenLocalStorage(url::Origin::Create(GURL("http://foobar.com")),
-                            MakeRequest(&area1), base::DoNothing());
+                            MakeRequest(&area1));
   base::RunLoop delete_loop;
   bool success = true;
   TestLevelDBObserver observer3;
@@ -1211,7 +1207,7 @@
     base::RunLoop loop;
     mock_leveldb_service.SetOnOpenCallback(loop.QuitClosure());
     context->OpenLocalStorage(url::Origin::Create(GURL("http://foobar.com")),
-                              MakeRequest(&area), base::DoNothing());
+                              MakeRequest(&area));
     loop.Run();
   }
 
@@ -1279,7 +1275,7 @@
   // This time all should just keep getting written, and commit errors are
   // getting ignored.
   context->OpenLocalStorage(url::Origin::Create(GURL("http://foobar.com")),
-                            MakeRequest(&area), base::DoNothing());
+                            MakeRequest(&area));
   old_value = base::nullopt;
   for (int i = 0; i < 64; ++i) {
     base::RunLoop put_loop;
diff --git a/content/browser/dom_storage/session_storage_context_mojo.cc b/content/browser/dom_storage/session_storage_context_mojo.cc
index 433890d..d90d3da 100644
--- a/content/browser/dom_storage/session_storage_context_mojo.cc
+++ b/content/browser/dom_storage/session_storage_context_mojo.cc
@@ -128,14 +128,12 @@
     int process_id,
     const std::string& namespace_id,
     mojo::ReportBadMessageCallback bad_message_callback,
-    blink::mojom::SessionStorageNamespaceRequest request,
-    base::OnceClosure bind_done) {
+    blink::mojom::SessionStorageNamespaceRequest request) {
   if (connection_state_ != CONNECTION_FINISHED) {
     RunWhenConnected(
         base::BindOnce(&SessionStorageContextMojo::OpenSessionStorage,
                        weak_ptr_factory_.GetWeakPtr(), process_id, namespace_id,
-                       std::move(bad_message_callback), std::move(request),
-                       std::move(bind_done)));
+                       std::move(bad_message_callback), std::move(request)));
     return;
   }
   auto found = namespaces_.find(namespace_id);
@@ -152,7 +150,6 @@
 
   PurgeUnusedAreasIfNeeded();
   found->second->Bind(std::move(request), process_id);
-  std::move(bind_done).Run();
 
   size_t total_cache_size, unused_area_count;
   GetStatistics(&total_cache_size, &unused_area_count);
diff --git a/content/browser/dom_storage/session_storage_context_mojo.h b/content/browser/dom_storage/session_storage_context_mojo.h
index 33ba435..e1ebd6bf 100644
--- a/content/browser/dom_storage/session_storage_context_mojo.h
+++ b/content/browser/dom_storage/session_storage_context_mojo.h
@@ -85,8 +85,7 @@
   void OpenSessionStorage(int process_id,
                           const std::string& namespace_id,
                           mojo::ReportBadMessageCallback bad_message_callback,
-                          blink::mojom::SessionStorageNamespaceRequest request,
-                          base::OnceClosure bind_done);
+                          blink::mojom::SessionStorageNamespaceRequest request);
 
   void CreateSessionNamespace(const std::string& namespace_id);
   void CloneSessionNamespace(const std::string& namespace_id_to_clone,
diff --git a/content/browser/dom_storage/session_storage_context_mojo_unittest.cc b/content/browser/dom_storage/session_storage_context_mojo_unittest.cc
index 7d5e7005..1a6c235 100644
--- a/content/browser/dom_storage/session_storage_context_mojo_unittest.cc
+++ b/content/browser/dom_storage/session_storage_context_mojo_unittest.cc
@@ -121,9 +121,9 @@
                  const std::string& source) {
     context()->CreateSessionNamespace(namespace_id);
     blink::mojom::SessionStorageNamespacePtr ss_namespace;
-    context()->OpenSessionStorage(
-        kTestProcessId, namespace_id, GetBadMessageCallback(),
-        mojo::MakeRequest(&ss_namespace), base::DoNothing());
+    context()->OpenSessionStorage(kTestProcessId, namespace_id,
+                                  GetBadMessageCallback(),
+                                  mojo::MakeRequest(&ss_namespace));
     blink::mojom::StorageAreaAssociatedPtr leveldb;
     ss_namespace->OpenArea(origin, mojo::MakeRequest(&leveldb));
     EXPECT_TRUE(test::PutSync(
@@ -138,9 +138,9 @@
       base::StringPiece key) {
     context()->CreateSessionNamespace(namespace_id);
     blink::mojom::SessionStorageNamespacePtr ss_namespace;
-    context()->OpenSessionStorage(
-        kTestProcessId, namespace_id, GetBadMessageCallback(),
-        mojo::MakeRequest(&ss_namespace), base::DoNothing());
+    context()->OpenSessionStorage(kTestProcessId, namespace_id,
+                                  GetBadMessageCallback(),
+                                  mojo::MakeRequest(&ss_namespace));
     blink::mojom::StorageAreaAssociatedPtr leveldb;
     ss_namespace->OpenArea(origin, mojo::MakeRequest(&leveldb));
 
@@ -201,13 +201,13 @@
   context()->CreateSessionNamespace(namespace_id2);
 
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
   blink::mojom::SessionStorageNamespacePtr ss_namespace2;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id2, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace2), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id2,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace2));
 
   blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
   blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o2;
@@ -235,9 +235,9 @@
   context()->CreateSessionNamespace(namespace_id1);
 
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
 
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
@@ -263,9 +263,9 @@
 
   // This will re-open the context, and load the persisted namespace.
   context()->CreateSessionNamespace(namespace_id1);
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
 
   // The data from before should be here.
@@ -278,9 +278,9 @@
 
   // This will re-open the context, and the namespace should be empty.
   context()->CreateSessionNamespace(namespace_id1);
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
 
   // The data from before should not be here.
@@ -294,9 +294,9 @@
   url::Origin origin1 = url::Origin::Create(GURL("http://foobar.com"));
   context()->CreateSessionNamespace(namespace_id1);
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
 
@@ -315,9 +315,9 @@
 
   // Open the second namespace.
   blink::mojom::SessionStorageNamespacePtr ss_namespace2;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id2, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace2), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id2,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace2));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
   ss_namespace2->OpenArea(origin1, mojo::MakeRequest(&leveldb_n2_o1));
 
@@ -333,9 +333,9 @@
   url::Origin origin1 = url::Origin::Create(GURL("http://foobar.com"));
   context()->CreateSessionNamespace(namespace_id1);
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
 
@@ -355,9 +355,9 @@
 
   // Open the second namespace.
   blink::mojom::SessionStorageNamespacePtr ss_namespace2;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id2, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace2), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id2,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace2));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
   ss_namespace2->OpenArea(origin1, mojo::MakeRequest(&leveldb_n2_o1));
 
@@ -380,9 +380,9 @@
 
   // Re-open namespace 1, check that we don't have the extra data.
   context()->CreateSessionNamespace(namespace_id1);
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
 
   // We should only have the first value.
@@ -397,9 +397,9 @@
   url::Origin origin1 = url::Origin::Create(GURL("http://foobar.com"));
   context()->CreateSessionNamespace(namespace_id1);
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
 
@@ -411,9 +411,9 @@
   // Open the second namespace, ensure empty.
   {
     blink::mojom::SessionStorageNamespacePtr ss_namespace2;
-    context()->OpenSessionStorage(
-        kTestProcessId, namespace_id2, GetBadMessageCallback(),
-        mojo::MakeRequest(&ss_namespace2), base::DoNothing());
+    context()->OpenSessionStorage(kTestProcessId, namespace_id2,
+                                  GetBadMessageCallback(),
+                                  mojo::MakeRequest(&ss_namespace2));
     blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
     ss_namespace2->OpenArea(origin1, mojo::MakeRequest(&leveldb_n2_o1));
     std::vector<blink::mojom::KeyValuePtr> data;
@@ -436,9 +436,9 @@
   // Open the second namespace, ensure populated
   {
     blink::mojom::SessionStorageNamespacePtr ss_namespace2;
-    context()->OpenSessionStorage(
-        kTestProcessId, namespace_id2, GetBadMessageCallback(),
-        mojo::MakeRequest(&ss_namespace2), base::DoNothing());
+    context()->OpenSessionStorage(kTestProcessId, namespace_id2,
+                                  GetBadMessageCallback(),
+                                  mojo::MakeRequest(&ss_namespace2));
     blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
     ss_namespace2->OpenArea(origin1, mojo::MakeRequest(&leveldb_n2_o1));
     std::vector<blink::mojom::KeyValuePtr> data;
@@ -483,9 +483,9 @@
   context()->CreateSessionNamespace(namespace_id1);
 
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
   EXPECT_TRUE(test::PutSync(
@@ -517,9 +517,9 @@
   // Re-open the context, load the persisted namespace, and verify we still have
   // data.
   context()->CreateSessionNamespace(namespace_id1);
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
   std::vector<blink::mojom::KeyValuePtr> data;
   EXPECT_TRUE(test::GetAllSync(leveldb_n1_o1.get(), &data));
@@ -537,9 +537,9 @@
     loop.Run();
   }
   context()->CreateSessionNamespace(namespace_id1);
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
   EXPECT_TRUE(test::GetAllSync(leveldb_n1_o1.get(), &data));
   EXPECT_EQ(0ul, data.size());
@@ -644,9 +644,9 @@
   {
     base::RunLoop loop;
     fake_leveldb_service.SetOnOpenCallback(loop.QuitClosure());
-    context()->OpenSessionStorage(
-        kTestProcessId, namespace_id, GetBadMessageCallback(),
-        mojo::MakeRequest(&ss_namespace), base::DoNothing());
+    context()->OpenSessionStorage(kTestProcessId, namespace_id,
+                                  GetBadMessageCallback(),
+                                  mojo::MakeRequest(&ss_namespace));
     ss_namespace->OpenArea(origin1, mojo::MakeRequest(&area1));
     ss_namespace->OpenArea(origin2, mojo::MakeRequest(&area2));
     ss_namespace->OpenArea(origin3, mojo::MakeRequest(&area3));
@@ -718,9 +718,9 @@
   EXPECT_EQ(1u, fake_leveldb_service.destroy_requests().size());
 
   // Reconnect area1 to the database, and try to read a value.
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace));
   ss_namespace->OpenArea(origin1, mojo::MakeRequest(&area1));
 
   base::RunLoop delete_loop;
@@ -780,9 +780,9 @@
   {
     base::RunLoop loop;
     fake_leveldb_service.SetOnOpenCallback(loop.QuitClosure());
-    context()->OpenSessionStorage(
-        kTestProcessId, namespace_id, GetBadMessageCallback(),
-        mojo::MakeRequest(&ss_namespace), base::DoNothing());
+    context()->OpenSessionStorage(kTestProcessId, namespace_id,
+                                  GetBadMessageCallback(),
+                                  mojo::MakeRequest(&ss_namespace));
     ss_namespace->OpenArea(origin1, mojo::MakeRequest(&area));
     loop.Run();
   }
@@ -852,9 +852,9 @@
   // Reconnect a area to the database, and repeatedly write data to it again.
   // This time all should just keep getting written, and commit errors are
   // getting ignored.
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace));
   ss_namespace->OpenArea(origin1, mojo::MakeRequest(&area));
   old_value = base::nullopt;
   for (int i = 0; i < 64; ++i) {
@@ -892,9 +892,9 @@
   url::Origin origin1 = url::Origin::Create(GURL("http://foobar.com"));
   context()->CreateSessionNamespace(namespace_id1);
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
   // Put some data.
@@ -933,9 +933,9 @@
   // Put some data.
   context()->CreateSessionNamespace(namespace_id);
   blink::mojom::SessionStorageNamespacePtr ss_namespace;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace));
   blink::mojom::StorageAreaAssociatedPtr area;
   ss_namespace->OpenArea(origin, mojo::MakeRequest(&area));
   EXPECT_TRUE(test::PutSync(area.get(), key, value, base::nullopt, "source"));
@@ -950,9 +950,9 @@
   base::RunLoop().RunUntilIdle();
 
   context()->CreateSessionNamespace(namespace_id);
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace));
   ss_namespace->OpenArea(origin, mojo::MakeRequest(&area));
 
   // We can't access the data anymore.
@@ -976,9 +976,9 @@
   ShutdownContext();
 
   context()->CreateSessionNamespace(namespace_id);
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace));
   ss_namespace->OpenArea(origin, mojo::MakeRequest(&area));
 
   ASSERT_TRUE(test::GetAllSync(area.get(), &data));
@@ -992,9 +992,9 @@
   // First, test deleting data for a namespace that is open.
   context()->CreateSessionNamespace(namespace_id1);
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
 
@@ -1023,9 +1023,9 @@
   context()->DeleteStorage(origin1, namespace_id1, base::DoNothing());
 
   context()->CreateSessionNamespace(namespace_id1);
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
   data.clear();
   EXPECT_TRUE(test::GetAllSync(leveldb_n1_o1.get(), &data));
@@ -1039,9 +1039,9 @@
 
   context()->CreateSessionNamespace(namespace_id1);
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
   blink::mojom::StorageAreaAssociatedPtr leveldb;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb));
 
@@ -1067,9 +1067,9 @@
   // Now open many new wrappers (for different origins) to trigger clean up.
   for (int i = 1; i <= 100; ++i) {
     blink::mojom::SessionStorageNamespacePtr ss_namespace1;
-    context()->OpenSessionStorage(
-        kTestProcessId, namespace_id1, GetBadMessageCallback(),
-        mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+    context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                  GetBadMessageCallback(),
+                                  mojo::MakeRequest(&ss_namespace1));
     blink::mojom::StorageAreaAssociatedPtr leveldb;
     ss_namespace1->OpenArea(url::Origin::Create(GURL(base::StringPrintf(
                                 "http://example.com:%d", i))),
@@ -1080,9 +1080,9 @@
   }
 
   // And make sure caches were actually cleared.
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb));
   std::vector<blink::mojom::KeyValuePtr> data;
   ASSERT_TRUE(test::GetAllSync(leveldb.get(), &data));
@@ -1096,9 +1096,9 @@
   context()->CreateSessionNamespace(namespace_id1);
 
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
 
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
@@ -1121,9 +1121,9 @@
   // This will re-open the context, and load the persisted namespace, but it
   // should have been deleted due to our backing mode.
   context()->CreateSessionNamespace(namespace_id1);
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
 
   // The data from before should not be here, because the context clears disk
@@ -1141,17 +1141,17 @@
 
   context()->CreateSessionNamespace(namespace_id1);
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
 
   context()->CreateSessionNamespace(namespace_id2);
   blink::mojom::SessionStorageNamespacePtr ss_namespace2;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id2, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace2), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id2,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace2));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
   ss_namespace2->OpenArea(origin1, mojo::MakeRequest(&leveldb_n2_o1));
 
@@ -1198,9 +1198,9 @@
   url::Origin origin1 = url::Origin::Create(GURL("http://foobar.com"));
   context()->CreateSessionNamespace(namespace_id1);
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
 
@@ -1219,9 +1219,9 @@
 
   // Open the second namespace.
   blink::mojom::SessionStorageNamespacePtr ss_namespace2;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id2, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace2), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id2,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace2));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
   ss_namespace2->OpenArea(origin1, mojo::MakeRequest(&leveldb_n2_o1));
 
@@ -1237,9 +1237,9 @@
   url::Origin origin1 = url::Origin::Create(GURL("http://foobar.com"));
   context()->CreateSessionNamespace(namespace_id1);
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
 
@@ -1258,9 +1258,9 @@
 
   // Open the second namespace.
   blink::mojom::SessionStorageNamespacePtr ss_namespace2;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id2, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace2), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id2,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace2));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
   ss_namespace2->OpenArea(origin1, mojo::MakeRequest(&leveldb_n2_o1));
 
@@ -1276,9 +1276,9 @@
   url::Origin origin1 = url::Origin::Create(GURL("http://foobar.com"));
   context()->CreateSessionNamespace(namespace_id1);
   blink::mojom::SessionStorageNamespacePtr ss_namespace1;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id1, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace1), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id1,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace1));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n1_o1;
   ss_namespace1->OpenArea(origin1, mojo::MakeRequest(&leveldb_n1_o1));
 
@@ -1297,9 +1297,9 @@
 
   // Open the second namespace.
   blink::mojom::SessionStorageNamespacePtr ss_namespace2;
-  context()->OpenSessionStorage(
-      kTestProcessId, namespace_id2, GetBadMessageCallback(),
-      mojo::MakeRequest(&ss_namespace2), base::DoNothing());
+  context()->OpenSessionStorage(kTestProcessId, namespace_id2,
+                                GetBadMessageCallback(),
+                                mojo::MakeRequest(&ss_namespace2));
   blink::mojom::StorageAreaAssociatedPtr leveldb_n2_o1;
   ss_namespace2->OpenArea(origin1, mojo::MakeRequest(&leveldb_n2_o1));
 
diff --git a/content/browser/dom_storage/session_storage_namespace_impl_mojo_unittest.cc b/content/browser/dom_storage/session_storage_namespace_impl_mojo_unittest.cc
index a29283d4..64ae0a7 100644
--- a/content/browser/dom_storage/session_storage_namespace_impl_mojo_unittest.cc
+++ b/content/browser/dom_storage/session_storage_namespace_impl_mojo_unittest.cc
@@ -78,9 +78,11 @@
     security_policy->Add(kTestProcessIdOrigin3, &browser_context_);
     security_policy->AddIsolatedOrigins(
         {test_origin1_, test_origin2_, test_origin3_});
-    security_policy->LockToOrigin(IsolationContext(), kTestProcessIdOrigin1,
+    security_policy->LockToOrigin(IsolationContext(&browser_context_),
+                                  kTestProcessIdOrigin1,
                                   test_origin1_.GetURL());
-    security_policy->LockToOrigin(IsolationContext(), kTestProcessIdOrigin3,
+    security_policy->LockToOrigin(IsolationContext(&browser_context_),
+                                  kTestProcessIdOrigin3,
                                   test_origin3_.GetURL());
 
     mojo::core::SetDefaultProcessErrorCallback(
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 030d860..21f84cc3 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -1197,51 +1197,50 @@
 
 void RenderFrameHostImpl::ExecuteJavaScript(
     const base::string16& javascript) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   CHECK(CanExecuteJavaScript());
-  Send(new FrameMsg_JavaScriptExecuteRequest(routing_id_,
-                                             javascript,
-                                             0, false));
+  GetNavigationControl()->JavaScriptExecuteRequest(javascript, 0, false);
 }
 
 void RenderFrameHostImpl::ExecuteJavaScript(
      const base::string16& javascript,
      const JavaScriptResultCallback& callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   CHECK(CanExecuteJavaScript());
   int key = g_next_javascript_callback_id++;
-  Send(new FrameMsg_JavaScriptExecuteRequest(routing_id_,
-                                             javascript,
-                                             key, true));
+  GetNavigationControl()->JavaScriptExecuteRequest(javascript, key, true);
   javascript_callbacks_.emplace(key, callback);
 }
 
 void RenderFrameHostImpl::ExecuteJavaScriptForTests(
     const base::string16& javascript) {
-  Send(new FrameMsg_JavaScriptExecuteRequestForTests(routing_id_,
-                                                     javascript,
-                                                     0, false, false));
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  GetNavigationControl()->JavaScriptExecuteRequestForTests(javascript, 0, false,
+                                                           false);
 }
 
 void RenderFrameHostImpl::ExecuteJavaScriptForTests(
-     const base::string16& javascript,
-     const JavaScriptResultCallback& callback) {
+    const base::string16& javascript,
+    const JavaScriptResultCallback& callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   int key = g_next_javascript_callback_id++;
-  Send(new FrameMsg_JavaScriptExecuteRequestForTests(routing_id_, javascript,
-                                                     key, true, false));
+  GetNavigationControl()->JavaScriptExecuteRequestForTests(javascript, key,
+                                                           true, false);
   javascript_callbacks_.emplace(key, callback);
 }
 
-
 void RenderFrameHostImpl::ExecuteJavaScriptWithUserGestureForTests(
     const base::string16& javascript) {
-  Send(new FrameMsg_JavaScriptExecuteRequestForTests(routing_id_,
-                                                     javascript,
-                                                     0, false, true));
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  GetNavigationControl()->JavaScriptExecuteRequestForTests(javascript, 0, false,
+                                                           true);
 }
 
 void RenderFrameHostImpl::ExecuteJavaScriptInIsolatedWorld(
     const base::string16& javascript,
     const JavaScriptResultCallback& callback,
     int world_id) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (world_id <= ISOLATED_WORLD_ID_GLOBAL ||
       world_id > ISOLATED_WORLD_ID_MAX) {
     // Return if the world_id is not valid.
@@ -1257,8 +1256,8 @@
     javascript_callbacks_.emplace(key, callback);
   }
 
-  Send(new FrameMsg_JavaScriptExecuteRequestInIsolatedWorld(
-      routing_id_, javascript, key, request_reply, world_id));
+  GetNavigationControl()->JavaScriptExecuteRequestInIsolatedWorld(
+      javascript, key, request_reply, world_id);
 }
 
 void RenderFrameHostImpl::CopyImageAt(int x, int y) {
@@ -1388,8 +1387,6 @@
     IPC_MESSAGE_HANDLER(FrameHostMsg_BeforeUnload_ACK, OnBeforeUnloadACK)
     IPC_MESSAGE_HANDLER(FrameHostMsg_SwapOut_ACK, OnSwapOutACK)
     IPC_MESSAGE_HANDLER(FrameHostMsg_ContextMenu, OnContextMenu)
-    IPC_MESSAGE_HANDLER(FrameHostMsg_JavaScriptExecuteResponse,
-                        OnJavaScriptExecuteResponse)
     IPC_MESSAGE_HANDLER(FrameHostMsg_VisualStateResponse,
                         OnVisualStateResponse)
     IPC_MESSAGE_HANDLER_DELAY_REPLY(FrameHostMsg_RunJavaScriptDialog,
@@ -2640,18 +2637,11 @@
   delegate_->ShowContextMenu(this, validated_params);
 }
 
-void RenderFrameHostImpl::OnJavaScriptExecuteResponse(
-    int id, const base::ListValue& result) {
-  const base::Value* result_value;
-  if (!result.Get(0, &result_value)) {
-    // Programming error or rogue renderer.
-    NOTREACHED() << "Got bad arguments for OnJavaScriptExecuteResponse";
-    return;
-  }
-
+void RenderFrameHostImpl::JavaScriptExecuteResponse(int id,
+                                                    base::Value result) {
   auto it = javascript_callbacks_.find(id);
   if (it != javascript_callbacks_.end()) {
-    it->second.Run(result_value);
+    it->second.Run(&result);
     javascript_callbacks_.erase(it);
   } else {
     NOTREACHED() << "Received script response for unknown request";
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index 2e98ee46..8a1b64b9 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -105,10 +105,6 @@
 struct FrameHostMsg_ShowPopup_Params;
 #endif
 
-namespace base {
-class ListValue;
-}
-
 namespace blink {
 class AssociatedInterfaceProvider;
 class AssociatedInterfaceRegistry;
@@ -1046,7 +1042,6 @@
   void OnSwapOutACK();
   void OnRenderProcessGone(int status, int error_code);
   void OnContextMenu(const ContextMenuParams& params);
-  void OnJavaScriptExecuteResponse(int id, const base::ListValue& result);
   void OnVisualStateResponse(uint64_t id);
   void OnRunJavaScriptDialog(const base::string16& message,
                              const base::string16& default_prompt,
@@ -1149,6 +1144,7 @@
           validated_params,
       mojom::DidCommitProvisionalLoadInterfaceParamsPtr interface_params)
       override;
+  void JavaScriptExecuteResponse(int id, base::Value result) override;
 
   // This function mimics DidCommitProvisionalLoad but is a direct mojo
   // callback from NavigationClient::CommitNavigation.
diff --git a/content/browser/frame_host/webui_navigation_browsertest.cc b/content/browser/frame_host/webui_navigation_browsertest.cc
index b26315d3..aa8fedce 100644
--- a/content/browser/frame_host/webui_navigation_browsertest.cc
+++ b/content/browser/frame_host/webui_navigation_browsertest.cc
@@ -290,7 +290,7 @@
   GURL chrome_url = GURL(std::string(kChromeUIScheme) + "://" +
                          std::string(kChromeUIGpuHost));
   EXPECT_TRUE(SiteInstanceImpl::DoesSiteRequireDedicatedProcess(
-      browser_context, IsolationContext(), chrome_url));
+      browser_context, IsolationContext(browser_context), chrome_url));
 
   // Navigate to a WebUI page.
   EXPECT_TRUE(NavigateToURL(shell(), chrome_url));
@@ -311,7 +311,7 @@
   // Verify that the blob also requires a dedicated process and that it would
   // use the same site url as the original page.
   EXPECT_TRUE(SiteInstanceImpl::DoesSiteRequireDedicatedProcess(
-      browser_context, IsolationContext(), blob_url));
+      browser_context, IsolationContext(browser_context), blob_url));
   EXPECT_EQ(chrome_url, SiteInstance::GetSiteForURL(browser_context, blob_url));
 }
 
diff --git a/content/browser/indexed_db/indexed_db_database.cc b/content/browser/indexed_db/indexed_db_database.cc
index 1387bd6c..6b5787a5 100644
--- a/content/browser/indexed_db/indexed_db_database.cc
+++ b/content/browser/indexed_db/indexed_db_database.cc
@@ -14,6 +14,7 @@
 #include "base/bind.h"
 #include "base/logging.h"
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/stl_util.h"
@@ -32,6 +33,8 @@
 #include "content/browser/indexed_db/indexed_db_tracing.h"
 #include "content/browser/indexed_db/indexed_db_transaction.h"
 #include "content/browser/indexed_db/indexed_db_value.h"
+#include "content/browser/indexed_db/scopes/scope_lock.h"
+#include "content/browser/indexed_db/scopes/scopes_lock_manager.h"
 #include "content/public/common/content_switches.h"
 #include "ipc/ipc_channel.h"
 #include "storage/browser/blob/blob_data_handle.h"
@@ -124,7 +127,9 @@
  public:
   OpenRequest(scoped_refptr<IndexedDBDatabase> db,
               std::unique_ptr<IndexedDBPendingConnection> pending_connection)
-      : ConnectionRequest(db), pending_(std::move(pending_connection)) {}
+      : ConnectionRequest(db),
+        pending_(std::move(pending_connection)),
+        weak_factory_(this) {}
 
   void Perform() override {
     if (db_->metadata_.id == kInvalidId) {
@@ -200,7 +205,13 @@
     DCHECK_GT(new_version, old_version);
 
     if (db_->connections_.empty()) {
-      StartUpgrade();
+      std::vector<ScopesLockManager::ScopeLockRequest> lock_requests = {
+          {kDatabaseRangeLockLevel, GetDatabaseLockRange(db_->metadata_.id),
+           ScopesLockManager::LockType::kExclusive}};
+      db_->lock_manager_->AcquireLocks(
+          std::move(lock_requests),
+          base::BindOnce(&IndexedDBDatabase::OpenRequest::StartUpgrade,
+                         weak_factory_.GetWeakPtr()));
       return;
     }
 
@@ -234,13 +245,19 @@
     if (!db_->connections_.empty())
       return;
 
-    StartUpgrade();
+    std::vector<ScopesLockManager::ScopeLockRequest> lock_requests = {
+        {kDatabaseRangeLockLevel, GetDatabaseLockRange(db_->metadata_.id),
+         ScopesLockManager::LockType::kExclusive}};
+    db_->lock_manager_->AcquireLocks(
+        std::move(lock_requests),
+        base::BindOnce(&IndexedDBDatabase::OpenRequest::StartUpgrade,
+                       weak_factory_.GetWeakPtr()));
   }
 
   // Initiate the upgrade. The bulk of the work actually happens in
   // IndexedDBDatabase::VersionChangeOperation in order to kick the
   // transaction into the correct state.
-  void StartUpgrade() {
+  void StartUpgrade(std::vector<ScopeLock> locks) {
     connection_ = db_->CreateConnection(pending_->database_callbacks,
                                         pending_->child_process_id);
     DCHECK_EQ(db_->connections_.count(connection_.get()), 1UL);
@@ -252,11 +269,10 @@
         std::set<int64_t>(object_store_ids.begin(), object_store_ids.end()),
         blink::mojom::IDBTransactionMode::VersionChange,
         new IndexedDBBackingStore::Transaction(db_->backing_store()));
-    db_->RegisterAndScheduleTransaction(transaction);
-
     transaction->ScheduleTask(
         base::BindOnce(&IndexedDBDatabase::VersionChangeOperation, db_,
                        pending_->version, pending_->callbacks));
+    transaction->Start(std::move(locks));
   }
 
   // Called when the upgrade transaction has started executing.
@@ -296,6 +312,7 @@
   // transferred to the IndexedDBDispatcherHost via OnUpgradeNeeded.
   std::unique_ptr<IndexedDBConnection> connection_;
 
+  base::WeakPtrFactory<OpenRequest> weak_factory_;
   DISALLOW_COPY_AND_ASSIGN(OpenRequest);
 };
 
@@ -304,12 +321,18 @@
  public:
   DeleteRequest(scoped_refptr<IndexedDBDatabase> db,
                 scoped_refptr<IndexedDBCallbacks> callbacks)
-      : ConnectionRequest(db), callbacks_(callbacks) {}
+      : ConnectionRequest(db), callbacks_(callbacks), weak_factory_(this) {}
 
   void Perform() override {
     if (db_->connections_.empty()) {
       // No connections, so delete immediately.
-      DoDelete();
+      std::vector<ScopesLockManager::ScopeLockRequest> lock_requests = {
+          {kDatabaseRangeLockLevel, GetDatabaseLockRange(db_->metadata_.id),
+           ScopesLockManager::LockType::kExclusive}};
+      db_->lock_manager_->AcquireLocks(
+          std::move(lock_requests),
+          base::BindOnce(&IndexedDBDatabase::DeleteRequest::DoDelete,
+                         weak_factory_.GetWeakPtr()));
       return;
     }
 
@@ -328,10 +351,17 @@
   void OnConnectionClosed(IndexedDBConnection* connection) override {
     if (!db_->connections_.empty())
       return;
-    DoDelete();
+
+    std::vector<ScopesLockManager::ScopeLockRequest> lock_requests = {
+        {kDatabaseRangeLockLevel, GetDatabaseLockRange(db_->metadata_.id),
+         ScopesLockManager::LockType::kExclusive}};
+    db_->lock_manager_->AcquireLocks(
+        std::move(lock_requests),
+        base::BindOnce(&IndexedDBDatabase::DeleteRequest::DoDelete,
+                       weak_factory_.GetWeakPtr()));
   }
 
-  void DoDelete() {
+  void DoDelete(std::vector<ScopeLock> locks) {
     Status s;
     if (db_->backing_store_)
       s = db_->backing_store_->DeleteDatabase(db_->metadata_.name);
@@ -374,6 +404,7 @@
  private:
   scoped_refptr<IndexedDBCallbacks> callbacks_;
 
+  base::WeakPtrFactory<DeleteRequest> weak_factory_;
   DISALLOW_COPY_AND_ASSIGN(DeleteRequest);
 };
 
@@ -1806,18 +1837,28 @@
   return Status::OK();
 }
 
-void IndexedDBDatabase::TransactionFinished(IndexedDBTransaction* transaction,
-                                            bool committed) {
-  IDB_TRACE1("IndexedDBTransaction::TransactionFinished", "txn.id",
-             transaction->id());
+void IndexedDBDatabase::TransactionCreated() {
+  UMA_HISTOGRAM_COUNTS_1000(
+      "WebCore.IndexedDB.Database.OutstandingTransactionCount",
+      transaction_count_);
+  ++transaction_count_;
+}
+
+void IndexedDBDatabase::TransactionFinished(
+    blink::mojom::IDBTransactionMode mode,
+    bool committed) {
   --transaction_count_;
   DCHECK_GE(transaction_count_, 0);
 
+  // TODO(dmurph): To help remove this integration with IndexedDBDatabase, make
+  // a 'committed' listener closure on all transactions. Then the request can
+  // just listen for that.
+
   // This may be an unrelated transaction finishing while waiting for
   // connections to close, or the actual upgrade transaction from an active
   // request. Notify the active request if it's the latter.
   if (active_request_ &&
-      transaction->mode() == blink::mojom::IDBTransactionMode::VersionChange) {
+      mode == blink::mojom::IDBTransactionMode::VersionChange) {
     active_request_->UpgradeTransactionFinished(committed);
   }
 }
@@ -1861,11 +1902,6 @@
     IndexedDBTransaction* transaction) {
   IDB_TRACE1("IndexedDBDatabase::RegisterAndScheduleTransaction", "txn.id",
              transaction->id());
-
-  UMA_HISTOGRAM_COUNTS_1000(
-      "WebCore.IndexedDB.Database.OutstandingTransactionCount",
-      transaction_count_);
-  transaction_count_++;
   std::vector<ScopesLockManager::ScopeLockRequest> lock_requests;
   lock_requests.reserve(1 + transaction->scope().size());
   lock_requests.emplace_back(
diff --git a/content/browser/indexed_db/indexed_db_database.h b/content/browser/indexed_db/indexed_db_database.h
index d29251f9..f7f669d 100644
--- a/content/browser/indexed_db/indexed_db_database.h
+++ b/content/browser/indexed_db/indexed_db_database.h
@@ -103,8 +103,8 @@
                          int64_t object_store_id,
                          const base::string16& new_name);
 
-  // Returns a pointer to a newly created transaction. The object is owned
-  // by the connection.
+  // TODO(dmurph): Remove this method and have transactions be directly
+  // scheduled using the lock manager.
   void RegisterAndScheduleTransaction(IndexedDBTransaction* transaction);
   void Close(IndexedDBConnection* connection, bool forced);
   void ForceClose();
@@ -136,7 +136,9 @@
     return lock_manager_;
   }
 
-  void TransactionFinished(IndexedDBTransaction* transaction, bool committed);
+  void TransactionCreated();
+  void TransactionFinished(blink::mojom::IDBTransactionMode mode,
+                           bool committed);
 
   void AbortAllTransactionsForConnections();
 
diff --git a/content/browser/indexed_db/indexed_db_transaction.cc b/content/browser/indexed_db/indexed_db_transaction.cc
index 029fbb4..3db0dcc 100644
--- a/content/browser/indexed_db/indexed_db_transaction.cc
+++ b/content/browser/indexed_db/indexed_db_transaction.cc
@@ -119,6 +119,8 @@
   IDB_ASYNC_TRACE_BEGIN("IndexedDBTransaction::lifetime", this);
   callbacks_ = connection_->callbacks();
   database_ = connection_->database();
+  if (database_)
+    database_->TransactionCreated();
 
   diagnostics_.tasks_scheduled = 0;
   diagnostics_.tasks_completed = 0;
@@ -246,7 +248,7 @@
   if (callbacks_.get())
     callbacks_->OnAbort(*this, error);
 
-  database_->TransactionFinished(this, false);
+  database_->TransactionFinished(mode_, false);
 
   // RemoveTransaction will delete |this|.
   // Note: During force-close situations, the connection can be destroyed during
@@ -469,7 +471,7 @@
     if (!pending_observers_.empty() && connection_)
       connection_->ActivatePendingObservers(std::move(pending_observers_));
 
-    database_->TransactionFinished(this, true);
+    database_->TransactionFinished(mode_, true);
     // RemoveTransaction will delete |this|.
     connection_->RemoveTransaction(id_);
     return s;
@@ -488,7 +490,7 @@
                                  "Internal error committing transaction.");
     }
     callbacks_->OnAbort(*this, error);
-    database_->TransactionFinished(this, false);
+    database_->TransactionFinished(mode_, false);
   }
   return s;
 }
diff --git a/content/browser/isolated_origin_browsertest.cc b/content/browser/isolated_origin_browsertest.cc
index af5a765..0a7c48d 100644
--- a/content/browser/isolated_origin_browsertest.cc
+++ b/content/browser/isolated_origin_browsertest.cc
@@ -42,22 +42,32 @@
 
 namespace content {
 
-namespace {
+// This is a base class for all tests in this class.  It does not isolate any
+// origins and only provides common helper functions to the other test classes.
+class IsolatedOriginTestBase : public ContentBrowserTest {
+ public:
+  IsolatedOriginTestBase() {}
+  ~IsolatedOriginTestBase() override {}
 
-bool IsIsolatedOrigin(const url::Origin& origin) {
-  auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
-  // The tests that use this check globally applicable isolated origins, so
-  // it's fine to pass in a default IsolationContext.
-  return policy->IsIsolatedOrigin(IsolationContext(), origin);
-}
+  // Check if |origin| is an isolated origin.  This helper is used in tests
+  // that care only about globally applicable isolated origins (not restricted
+  // to a particular BrowsingInstance or profile).
+  bool IsIsolatedOrigin(const url::Origin& origin) {
+    auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
+    IsolationContext isolation_context(
+        shell()->web_contents()->GetBrowserContext());
+    return policy->IsIsolatedOrigin(isolation_context, origin);
+  }
 
-bool IsIsolatedOrigin(const GURL& url) {
-  return IsIsolatedOrigin(url::Origin::Create(url));
-}
+  bool IsIsolatedOrigin(const GURL& url) {
+    return IsIsolatedOrigin(url::Origin::Create(url));
+  }
 
-}  // namespace
+ private:
+  DISALLOW_COPY_AND_ASSIGN(IsolatedOriginTestBase);
+};
 
-class IsolatedOriginTest : public ContentBrowserTest {
+class IsolatedOriginTest : public IsolatedOriginTestBase {
  public:
   IsolatedOriginTest() {}
   ~IsolatedOriginTest() override {}
@@ -1067,10 +1077,9 @@
   // renderer process sending incorrect data to the browser process, so
   // security checks can be tested.
   void OpenLocalStorage(const url::Origin& origin,
-                        blink::mojom::StorageAreaRequest request,
-                        OpenLocalStorageCallback done) override {
-    GetForwardingInterface()->OpenLocalStorage(
-        origin_to_inject_, std::move(request), std::move(done));
+                        blink::mojom::StorageAreaRequest request) override {
+    GetForwardingInterface()->OpenLocalStorage(origin_to_inject_,
+                                               std::move(request));
   }
 
  private:
@@ -1143,7 +1152,7 @@
   EXPECT_EQ(bad_message::RPH_MOJO_PROCESS_ERROR, kill_waiter.Wait());
 }
 
-class IsolatedOriginFieldTrialTest : public ContentBrowserTest {
+class IsolatedOriginFieldTrialTest : public IsolatedOriginTestBase {
  public:
   IsolatedOriginFieldTrialTest() {
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
@@ -1202,7 +1211,7 @@
 // This is a regresion test for https://crbug.com/793350 - the long list of
 // origins to isolate used to be unnecessarily propagated to the renderer
 // process, trigerring a crash due to exceeding kZygoteMaxMessageLength.
-class IsolatedOriginLongListTest : public ContentBrowserTest {
+class IsolatedOriginLongListTest : public IsolatedOriginTestBase {
  public:
   IsolatedOriginLongListTest() {}
   ~IsolatedOriginLongListTest() override {}
@@ -1823,6 +1832,59 @@
   EXPECT_EQ("foo=bar", EvalJs(child, "document.cookie"));
 }
 
+// Checks that isolated origins can be added only for a specific profile,
+// and that they don't apply to other profiles.
+IN_PROC_BROWSER_TEST_F(DynamicIsolatedOriginTest, PerProfileIsolation) {
+  // This test is designed to run without strict site isolation.
+  if (AreAllSitesIsolatedForTesting())
+    return;
+
+  // Create a browser in a different profile.
+  BrowserContext* main_context = shell()->web_contents()->GetBrowserContext();
+  Shell* other_shell = CreateOffTheRecordBrowser();
+  BrowserContext* other_context =
+      other_shell->web_contents()->GetBrowserContext();
+  ASSERT_NE(main_context, other_context);
+
+  // Start on bar.com in both browsers.
+  GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title1.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), bar_url));
+  EXPECT_TRUE(NavigateToURL(other_shell, bar_url));
+
+  // Start isolating foo.com in |other_context| only.
+  GURL foo_url(
+      embedded_test_server()->GetURL("foo.com", "/page_with_iframe.html"));
+  auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
+  policy->AddIsolatedOrigins({url::Origin::Create(foo_url)}, other_context);
+
+  // Verify that foo.com is indeed isolated in |other_shell|, by navigating to
+  // it in a new BrowsingInstance and checking that a bar.com subframe becomes
+  // an OOPIF.
+  EXPECT_TRUE(NavigateToURL(other_shell, foo_url));
+  WebContentsImpl* other_contents =
+      static_cast<WebContentsImpl*>(other_shell->web_contents());
+  NavigateIframeToURL(other_contents, "test_iframe", bar_url);
+  FrameTreeNode* root = other_contents->GetFrameTree()->root();
+  FrameTreeNode* child = root->child_at(0);
+  EXPECT_EQ(child->current_url(), bar_url);
+  EXPECT_NE(root->current_frame_host()->GetSiteInstance(),
+            child->current_frame_host()->GetSiteInstance());
+  EXPECT_NE(root->current_frame_host()->GetProcess(),
+            child->current_frame_host()->GetProcess());
+
+  // Verify that foo.com is *not* isolated in the regular shell, due to a
+  // different profile.
+  EXPECT_TRUE(NavigateToURL(shell(), foo_url));
+  NavigateIframeToURL(web_contents(), "test_iframe", bar_url);
+  root = web_contents()->GetFrameTree()->root();
+  child = root->child_at(0);
+  EXPECT_EQ(child->current_url(), bar_url);
+  EXPECT_EQ(root->current_frame_host()->GetSiteInstance(),
+            child->current_frame_host()->GetSiteInstance());
+  EXPECT_EQ(root->current_frame_host()->GetProcess(),
+            child->current_frame_host()->GetProcess());
+}
+
 // This class allows intercepting the BroadcastChannelProvider::ConnectToChannel
 // method and changing the |origin| parameter before passing the call to the
 // real implementation of BroadcastChannelProvider.
diff --git a/content/browser/isolation_context.cc b/content/browser/isolation_context.cc
index a59fe32..0373b00 100644
--- a/content/browser/isolation_context.cc
+++ b/content/browser/isolation_context.cc
@@ -6,9 +6,22 @@
 
 namespace content {
 
-IsolationContext::IsolationContext() = default;
+IsolationContext::IsolationContext(BrowserContext* browser_context)
+    : browser_or_resource_context_(BrowserOrResourceContext(browser_context)) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
 
-IsolationContext::IsolationContext(BrowsingInstanceId browsing_instance_id)
-    : browsing_instance_id_(browsing_instance_id) {}
+IsolationContext::IsolationContext(BrowsingInstanceId browsing_instance_id,
+                                   BrowserContext* browser_context)
+    : IsolationContext(browsing_instance_id,
+                       BrowserOrResourceContext(browser_context)) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+IsolationContext::IsolationContext(
+    BrowsingInstanceId browsing_instance_id,
+    BrowserOrResourceContext browser_or_resource_context)
+    : browsing_instance_id_(browsing_instance_id),
+      browser_or_resource_context_(browser_or_resource_context) {}
 
 }  // namespace content
diff --git a/content/browser/isolation_context.h b/content/browser/isolation_context.h
index f4c4459..e91a20b 100644
--- a/content/browser/isolation_context.h
+++ b/content/browser/isolation_context.h
@@ -7,6 +7,7 @@
 
 #include "base/optional.h"
 #include "content/common/content_export.h"
+#include "content/public/browser/browser_or_resource_context.h"
 #include "gpu/command_buffer/common/id_type.h"
 
 namespace content {
@@ -22,17 +23,26 @@
 // BrowsingInstance are used. This object may be used on UI or IO threads.
 class CONTENT_EXPORT IsolationContext {
  public:
-  // A default-constructed IsolationContext is not associated with a specific
-  // BrowsingInstance.  Callers can use this when they don't know the current
-  // BrowsingInstance, or aren't associated with one.
+  // Normal use cases should create an IsolationContext associated with both a
+  // BrowsingInstance and a BrowserContext (profile).  The constructor that
+  // takes in a BrowserContext* may only be used on the UI thread; when
+  // creating this object on the IO thread, the BrowserOrResourceContext
+  // version should be used instead.
+  IsolationContext(BrowsingInstanceId browsing_instance_id,
+                   BrowserContext* browser_context);
+  IsolationContext(BrowsingInstanceId browsing_instance_id,
+                   BrowserOrResourceContext browser_or_resource_context);
+
+  // Also temporarily allow constructing an IsolationContext not associated
+  // with a specific BrowsingInstance.  Callers can use this when they don't
+  // know the current BrowsingInstance, or aren't associated with one.
   //
   // TODO(alexmos):  This is primarily used in tests, as well as in call sites
   // which do not yet plumb proper BrowsingInstance information.  Once the
   // remaining non-test call sites are removed or updated, this should become a
   // test-only API.
-  IsolationContext();
+  explicit IsolationContext(BrowserContext* browser_context);
 
-  explicit IsolationContext(BrowsingInstanceId browsing_instance_id);
   ~IsolationContext() = default;
 
   // Returns the BrowsingInstance ID associated with this isolation context.
@@ -48,13 +58,18 @@
     return browsing_instance_id_;
   }
 
+  // Return the BrowserOrResourceContext associated with this IsolationContext.
+  // This represents the profile associated with this IsolationContext, and can
+  // be used on both UI and IO threads.
+  const BrowserOrResourceContext& browser_or_resource_context() const {
+    return browser_or_resource_context_;
+  }
+
  private:
   // When non-null, associates this context with a particular BrowsingInstance.
   BrowsingInstanceId browsing_instance_id_;
 
-  // TODO(alexmos): Include BrowserContext information here as well.  Replace
-  // process model APIs that pass in both BrowserContext and IsolationContext
-  // to only pass in IsolationContext.
+  BrowserOrResourceContext browser_or_resource_context_;
 };
 
 }  // namespace content
diff --git a/content/browser/loader/cross_site_document_blocking_browsertest.cc b/content/browser/loader/cross_site_document_blocking_browsertest.cc
index 3d30b399..aa471f4 100644
--- a/content/browser/loader/cross_site_document_blocking_browsertest.cc
+++ b/content/browser/loader/cross_site_document_blocking_browsertest.cc
@@ -1440,7 +1440,8 @@
     // (the second server should have a different hostname because of the call
     // to SetSSLConfig with CERT_COMMON_NAME_IS_DOMAIN argument).
     ASSERT_FALSE(SiteInstanceImpl::IsSameWebSite(
-        shell()->web_contents()->GetBrowserContext(), IsolationContext(),
+        shell()->web_contents()->GetBrowserContext(),
+        IsolationContext(shell()->web_contents()->GetBrowserContext()),
         GetURLOnServiceWorkerServer("/"), GetURLOnCrossOriginServer("/"),
         true /* should_use_effective_urls */));
   }
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index e9cfe7d..161ab06 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -35,6 +35,7 @@
 #include "content/browser/resource_context_impl.h"
 #include "content/browser/service_worker/service_worker_navigation_handle.h"
 #include "content/browser/service_worker/service_worker_navigation_handle_core.h"
+#include "content/browser/service_worker/service_worker_provider_host.h"
 #include "content/browser/service_worker/service_worker_request_handler.h"
 #include "content/browser/storage_partition_impl.h"
 #include "content/browser/url_loader_factory_getter.h"
@@ -88,7 +89,6 @@
 #include "services/network/public/mojom/request_context_frame_type.mojom.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 #include "third_party/blink/public/common/mime_util/mime_util.h"
-#include "third_party/blink/public/common/service_worker/service_worker_utils.h"
 
 #if defined(OS_ANDROID)
 #include "content/browser/android/content_url_loader_factory.h"
@@ -128,16 +128,6 @@
 base::LazyInstance<NavigationURLLoaderImpl::URLLoaderFactoryInterceptor>::Leaky
     g_loader_factory_interceptor = LAZY_INSTANCE_INITIALIZER;
 
-// Returns true if interception by NavigationLoaderInterceptors is enabled.
-// Both ServiceWorkerServicification and SignedExchange require the loader
-// interception. So even if NetworkService is not enabled, returns true when one
-// of them is enabled.
-bool IsLoaderInterceptionEnabled() {
-  return base::FeatureList::IsEnabled(network::features::kNetworkService) ||
-         blink::ServiceWorkerUtils::IsServicificationEnabled() ||
-         signed_exchange_utils::IsSignedExchangeHandlingEnabled();
-}
-
 size_t GetCertificateChainsSizeInKB(const net::SSLInfo& ssl_info) {
   base::Pickle cert_pickle;
   ssl_info.cert->Persist(&cert_pickle);
@@ -268,13 +258,11 @@
   return new_request;
 }
 
-// Used only when NetworkService is disabled but IsLoaderInterceptionEnabled()
-// is true.
+// Used only when NetworkService is disabled.
 std::unique_ptr<NavigationRequestInfo> CreateNavigationRequestInfoForRedirect(
     const NavigationRequestInfo& previous_request_info,
     const network::ResourceRequest& updated_resource_request) {
   DCHECK(!base::FeatureList::IsEnabled(network::features::kNetworkService));
-  DCHECK(IsLoaderInterceptionEnabled());
 
   CommonNavigationParams new_common_params =
       previous_request_info.common_params;
@@ -444,7 +432,6 @@
   CreateDefaultRequestHandlerForNonNetworkService(
       net::URLRequestContextGetter* url_request_context_getter,
       storage::FileSystemContext* upload_file_system_context,
-      ServiceWorkerNavigationHandleCore* service_worker_navigation_handle_core,
       AppCacheNavigationHandleCore* appcache_handle_core,
       bool was_request_intercepted) const {
     DCHECK_CURRENTLY_ON(BrowserThread::IO);
@@ -458,14 +445,6 @@
         base::Unretained(upload_file_system_context),
         // If the request has already been intercepted, the request should not
         // be intercepted again.
-        // S13nServiceWorker: Requests are intercepted by S13nServiceWorker
-        // before the default request handler when needed, so we never need to
-        // pass |service_worker_navigation_handle_core| here.
-        base::Unretained(
-            blink::ServiceWorkerUtils::IsServicificationEnabled() ||
-                    was_request_intercepted
-                ? nullptr
-                : service_worker_navigation_handle_core),
         base::Unretained(was_request_intercepted ? nullptr
                                                  : appcache_handle_core));
   }
@@ -473,7 +452,6 @@
   void CreateNonNetworkServiceURLLoader(
       net::URLRequestContextGetter* url_request_context_getter,
       storage::FileSystemContext* upload_file_system_context,
-      ServiceWorkerNavigationHandleCore* service_worker_navigation_handle_core,
       AppCacheNavigationHandleCore* appcache_handle_core,
       const network::ResourceRequest& /* resource_request */,
       network::mojom::URLLoaderRequest url_loader,
@@ -516,22 +494,10 @@
           resource_context_, url_request_context_getter->GetURLRequestContext(),
           upload_file_system_context, *request_info_,
           std::move(navigation_ui_data_), std::move(url_loader_client),
-          std::move(url_loader), service_worker_navigation_handle_core,
+          std::move(url_loader),
+          nullptr /* service_worker_navigation_handle_core */,
           appcache_handle_core, options, resource_request_->priority,
           global_request_id_);
-
-      if (!blink::ServiceWorkerUtils::IsServicificationEnabled()) {
-        // Get the SWProviderHost for non-S13nSW path. For S13nSW path,
-        // |service_worker_provider_host_| must be set in
-        // CreateServiceWorkerInterceptor().
-        net::URLRequest* url_request = rdh->GetURLRequest(global_request_id_);
-        ServiceWorkerProviderHost* service_worker_provider_host =
-            ServiceWorkerRequestHandler::GetProviderHost(url_request);
-        if (service_worker_provider_host) {
-          service_worker_provider_host_ =
-              service_worker_provider_host->AsWeakPtr();
-        }
-      }
     }
 
     // TODO(arthursonzogni): Detect when the ResourceDispatcherHost didn't
@@ -573,7 +539,6 @@
         // |default_request_handler_factory_| could be called only from |this|.
         base::Unretained(this), base::Unretained(url_request_context_getter),
         base::Unretained(upload_file_system_context),
-        base::Unretained(service_worker_navigation_handle_core),
         base::Unretained(appcache_handle_core));
 
     StartInternal(request_info_.get(), service_worker_navigation_handle_core,
@@ -686,10 +651,8 @@
       return;
     }
 
-    // Set-up an interceptor for service workers if S13nSW is enabled and
-    // non-null |service_worker_navigation_handle_core| is given.
-    if (service_worker_navigation_handle_core &&
-        blink::ServiceWorkerUtils::IsServicificationEnabled()) {
+    // Set-up an interceptor for service workers.
+    if (service_worker_navigation_handle_core) {
       std::unique_ptr<NavigationLoaderInterceptor> service_worker_interceptor =
           CreateServiceWorkerInterceptor(*request_info,
                                          service_worker_navigation_handle_core);
@@ -766,7 +729,6 @@
 
   // This could be called multiple times to follow a chain of redirects.
   void Restart() {
-    DCHECK(IsLoaderInterceptionEnabled());
     // Clear |url_loader_| if it's not the default one (network). This allows
     // the restarted request to use a new loader, instead of, e.g., reusing the
     // AppCache or service worker loader. For an optimization, we keep and reuse
@@ -795,7 +757,6 @@
       NavigationLoaderInterceptor* interceptor,
       SingleRequestURLLoaderFactory::RequestHandler single_request_handler) {
     DCHECK_CURRENTLY_ON(BrowserThread::IO);
-    DCHECK(IsLoaderInterceptionEnabled());
     DCHECK(started_);
 
     if (single_request_handler) {
@@ -924,18 +885,12 @@
 
   scoped_refptr<network::SharedURLLoaderFactory>
   PrepareForNonInterceptedRequest(uint32_t* out_options) {
-    // If NetworkService is not enabled (which means we come here because one of
-    // the loader interceptors is enabled), use the default request handler
-    // instead of going through the NetworkService path.
+    // If NetworkService is not enabled, use the default request handler instead
+    // of going through the NetworkService path.
     if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) {
       DCHECK(!interceptors_.empty());
       DCHECK(default_request_handler_factory_);
-      // The only way to come here is to enable ServiceWorkerServicification or
-      // SignedExchange without NetworkService. We know that their request
-      // interceptors have already intercepted and decided not to handle the
-      // request.
-      DCHECK(blink::ServiceWorkerUtils::IsServicificationEnabled() ||
-             signed_exchange_utils::IsSignedExchangeHandlingEnabled());
+
       default_loader_used_ = true;
       // Update |request_info_| when following a redirect.
       if (url_chain_.size() > 0) {
@@ -1031,11 +986,6 @@
       common_params->method = redirect_info_.new_method;
     }
 
-    if (!IsLoaderInterceptionEnabled()) {
-      url_loader_->FollowRedirect(removed_headers, modified_headers);
-      return;
-    }
-
     // Update |resource_request_| and call Restart to give our |interceptors_| a
     // chance at handling the new location. If no interceptor wants to take
     // over, we'll use the existing url_loader to follow the redirect, see
@@ -1114,38 +1064,36 @@
 
     std::unique_ptr<NavigationData> cloned_navigation_data;
 
-    if (IsLoaderInterceptionEnabled()) {
-      bool must_download = download_utils::MustDownload(
-          url_, head.headers.get(), head.mime_type);
-      bool known_mime_type = blink::IsSupportedMimeType(head.mime_type);
+    bool must_download =
+        download_utils::MustDownload(url_, head.headers.get(), head.mime_type);
+    bool known_mime_type = blink::IsSupportedMimeType(head.mime_type);
 
 #if BUILDFLAG(ENABLE_PLUGINS)
-      if (!head.intercepted_by_plugin && !must_download && !known_mime_type) {
-        // No plugin throttles intercepted the response. Ask if the plugin
-        // registered to PluginService wants to handle the request.
-        CheckPluginAndContinueOnReceiveResponse(
-            head, std::move(url_loader_client_endpoints),
-            true /* is_download_if_not_handled_by_plugin */,
-            std::vector<WebPluginInfo>());
-        return;
-      }
+    if (!head.intercepted_by_plugin && !must_download && !known_mime_type) {
+      // No plugin throttles intercepted the response. Ask if the plugin
+      // registered to PluginService wants to handle the request.
+      CheckPluginAndContinueOnReceiveResponse(
+          head, std::move(url_loader_client_endpoints),
+          true /* is_download_if_not_handled_by_plugin */,
+          std::vector<WebPluginInfo>());
+      return;
+    }
 #endif
 
-      // When a plugin intercepted the response, we don't want to download it.
-      is_download =
-          !head.intercepted_by_plugin && (must_download || !known_mime_type);
-      is_stream = false;
+    // When a plugin intercepted the response, we don't want to download it.
+    is_download =
+        !head.intercepted_by_plugin && (must_download || !known_mime_type);
+    is_stream = false;
 
-      // If NetworkService is on, or an interceptor handled the request, the
-      // request doesn't use ResourceDispatcherHost so
-      // CallOnReceivedResponse and return here.
-      if (base::FeatureList::IsEnabled(network::features::kNetworkService) ||
-          !default_loader_used_) {
-        CallOnReceivedResponse(head, std::move(url_loader_client_endpoints),
-                               std::move(cloned_navigation_data), is_download,
-                               is_stream);
-        return;
-      }
+    // If NetworkService is on, or an interceptor handled the request, the
+    // request doesn't use ResourceDispatcherHost so
+    // CallOnReceivedResponse and return here.
+    if (base::FeatureList::IsEnabled(network::features::kNetworkService) ||
+        !default_loader_used_) {
+      CallOnReceivedResponse(head, std::move(url_loader_client_endpoints),
+                             std::move(cloned_navigation_data), is_download,
+                             is_stream);
+      return;
     }
 
     // NetworkService is off and an interceptor didn't handle the request,
@@ -1169,32 +1117,6 @@
         if (navigation_data)
           cloned_navigation_data = navigation_data->Clone();
       }
-
-      // non-S13nServiceWorker:
-      // This is similar to what is done in
-      // ServiceWorkerControlleeHandler::MaybeCreateSubresourceLoaderParams()
-      // (which is used when S13nServiceWorker is on). It takes the matching
-      // ControllerServiceWorkerInfo (if any) associated with the request. It
-      // will be sent to the renderer process and used to intercept requests.
-      ServiceWorkerProviderHost* sw_provider_host =
-          ServiceWorkerRequestHandler::GetProviderHost(url_request);
-      if (sw_provider_host && sw_provider_host->controller()) {
-        DCHECK(!blink::ServiceWorkerUtils::IsServicificationEnabled());
-        subresource_loader_params_ = SubresourceLoaderParams();
-        subresource_loader_params_->controller_service_worker_info =
-            blink::mojom::ControllerServiceWorkerInfo::New();
-        subresource_loader_params_->controller_service_worker_info->mode =
-            sw_provider_host->GetControllerMode();
-        base::WeakPtr<ServiceWorkerObjectHost> sw_object_host =
-            sw_provider_host->GetOrCreateServiceWorkerObjectHost(
-                sw_provider_host->controller());
-        if (sw_object_host) {
-          subresource_loader_params_->controller_service_worker_object_host =
-              sw_object_host;
-          subresource_loader_params_->controller_service_worker_info
-              ->object_info = sw_object_host->CreateIncompleteObjectInfo();
-        }
-      }
     } else {
       is_download = is_stream = false;
     }
@@ -1353,9 +1275,6 @@
   // different response. For e.g. AppCache may have fallback content.
   bool MaybeCreateLoaderForResponse(
       const network::ResourceResponseHead& response) {
-    if (!IsLoaderInterceptionEnabled())
-      return false;
-
     if (!default_loader_used_)
       return false;
 
@@ -1508,7 +1427,7 @@
   // Generator of a request handler for sending request to the network. This
   // captures all of parameters to create a
   // SingleRequestURLLoaderFactory::RequestHandler. Used only when
-  // NetworkService is disabled but IsLoaderInterceptionEnabled() is true.
+  // NetworkService is disabled.
   // Set |was_request_intercepted| to true if the request was intercepted by an
   // interceptor and the request is falling back to the network. In that case,
   // any interceptors won't intercept the request.
diff --git a/content/browser/media/capture/web_contents_audio_muter.cc b/content/browser/media/capture/web_contents_audio_muter.cc
index 77ce901..95fce2a 100644
--- a/content/browser/media/capture/web_contents_audio_muter.cc
+++ b/content/browser/media/capture/web_contents_audio_muter.cc
@@ -38,14 +38,15 @@
  public:
   explicit AudioDiscarder(const media::AudioParameters& params)
       : worker_(media::AudioManager::Get()->GetWorkerTaskRunner(), params),
+        fixed_data_delay_(
+            media::FakeAudioWorker::ComputeFakeOutputDelay(params)),
         audio_bus_(media::AudioBus::Create(params)) {}
 
   // AudioOutputStream implementation.
   bool Open() override { return true; }
   void Start(AudioSourceCallback* callback) override {
-    worker_.Start(base::Bind(&AudioDiscarder::FetchAudioData,
-                             base::Unretained(this),
-                             callback));
+    worker_.Start(base::BindRepeating(&AudioDiscarder::FetchAudioData,
+                                      base::Unretained(this), callback));
   }
   void Stop() override { worker_.Stop(); }
   void SetVolume(double volume) override {}
@@ -55,14 +56,19 @@
  private:
   ~AudioDiscarder() override {}
 
-  void FetchAudioData(AudioSourceCallback* callback) {
-    callback->OnMoreData(base::TimeDelta(), base::TimeTicks::Now(), 0,
+  void FetchAudioData(AudioSourceCallback* callback,
+                      base::TimeTicks ideal_time,
+                      base::TimeTicks now) {
+    // Real streams provide small tweaks to their delay values, alongside the
+    // current system time; and so the same is done here.
+    callback->OnMoreData(fixed_data_delay_ + (ideal_time - now), now, 0,
                          audio_bus_.get());
   }
 
   // Calls FetchAudioData() at regular intervals and discards the data.
   media::FakeAudioWorker worker_;
-  std::unique_ptr<media::AudioBus> audio_bus_;
+  const base::TimeDelta fixed_data_delay_;
+  const std::unique_ptr<media::AudioBus> audio_bus_;
 
   DISALLOW_COPY_AND_ASSIGN(AudioDiscarder);
 };
diff --git a/content/browser/notifications/BUILD.gn b/content/browser/notifications/BUILD.gn
index 0f6d5456..5898591ce 100644
--- a/content/browser/notifications/BUILD.gn
+++ b/content/browser/notifications/BUILD.gn
@@ -7,5 +7,6 @@
 proto_library("notification_proto") {
   sources = [
     "notification_database_data.proto",
+    "notification_database_resources.proto",
   ]
 }
diff --git a/content/browser/notifications/blink_notification_service_impl.cc b/content/browser/notifications/blink_notification_service_impl.cc
index 7c6b38b..22b3836 100644
--- a/content/browser/notifications/blink_notification_service_impl.cc
+++ b/content/browser/notifications/blink_notification_service_impl.cc
@@ -42,6 +42,27 @@
   return GetContentClient()->browser()->GetPlatformNotificationService();
 }
 
+bool FilterByTag(const std::string& filter_tag,
+                 const NotificationDatabaseData& database_data) {
+  // An empty filter tag matches all.
+  if (filter_tag.empty())
+    return true;
+  // Otherwise we need an exact match.
+  return filter_tag == database_data.notification_data.tag;
+}
+
+bool FilterByTriggered(bool include_triggered,
+                       const NotificationDatabaseData& database_data) {
+  // Including triggered matches all.
+  if (include_triggered)
+    return true;
+  // Notifications without a trigger always match.
+  if (!database_data.notification_data.show_trigger_timestamp.has_value())
+    return true;
+  // Otherwise it has to be triggered already.
+  return database_data.has_triggered;
+}
+
 }  // namespace
 
 using blink::mojom::PersistentNotificationError;
@@ -190,14 +211,17 @@
   // TODO(https://crbug.com/870258): Validate resources are not too big (either
   // here or in the mojo struct traits).
 
+  if (platform_notification_data.show_trigger_timestamp.has_value())
+    database_data.notification_resources = notification_resources;
+
   notification_context_->WriteNotificationData(
       next_persistent_id, service_worker_registration_id, origin_.GetURL(),
       database_data,
-      base::AdaptCallbackForRepeating(base::BindOnce(
+      base::BindOnce(
           &BlinkNotificationServiceImpl::DisplayPersistentNotificationWithId,
           weak_factory_for_ui_.GetWeakPtr(), service_worker_registration_id,
           platform_notification_data, notification_resources,
-          std::move(callback))));
+          std::move(callback)));
 }
 
 void BlinkNotificationServiceImpl::DisplayPersistentNotificationWithId(
@@ -300,8 +324,7 @@
 
   notification_context_->ReadAllNotificationDataForServiceWorkerRegistration(
       origin_.GetURL(), service_worker_registration_id,
-      base::AdaptCallbackForRepeating(
-          std::move(read_notification_data_callback)));
+      std::move(read_notification_data_callback));
 }
 
 void BlinkNotificationServiceImpl::DidGetNotifications(
@@ -316,9 +339,8 @@
   std::vector<blink::PlatformNotificationData> datas;
 
   for (const NotificationDatabaseData& database_data : notifications) {
-    // An empty filter tag matches all, else we need an exact match.
-    if (filter_tag.empty() ||
-        filter_tag == database_data.notification_data.tag) {
+    if (FilterByTag(filter_tag, database_data) &&
+        FilterByTriggered(include_triggered, database_data)) {
       ids.push_back(database_data.notification_id);
       datas.push_back(database_data.notification_data);
     }
diff --git a/content/browser/notifications/blink_notification_service_impl_unittest.cc b/content/browser/notifications/blink_notification_service_impl_unittest.cc
index 759e317..e0958af4 100644
--- a/content/browser/notifications/blink_notification_service_impl_unittest.cc
+++ b/content/browser/notifications/blink_notification_service_impl_unittest.cc
@@ -11,10 +11,12 @@
 #include "base/callback_helpers.h"
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/optional.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_simple_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
 #include "content/browser/notifications/blink_notification_service_impl.h"
 #include "content/browser/notifications/platform_notification_context_impl.h"
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
@@ -33,6 +35,7 @@
 #include "mojo/public/cpp/bindings/interface_request.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/notifications/notification_resources.h"
 #include "third_party/blink/public/platform/modules/notifications/notification_service.mojom.h"
 #include "third_party/blink/public/platform/modules/permissions/permission_status.mojom.h"
 #include "third_party/skia/include/core/SkBitmap.h"
@@ -248,6 +251,26 @@
     std::move(quit_closure).Run();
   }
 
+  void DidGetNotificationDataFromContext(
+      base::OnceClosure quit_closure,
+      bool success,
+      const std::vector<NotificationDatabaseData>& notification_datas) {
+    get_notifications_data_ = notification_datas;
+    std::move(quit_closure).Run();
+  }
+
+  void DidGetNotificationResourcesFromContext(
+      base::OnceClosure quit_closure,
+      bool success,
+      const blink::NotificationResources& notification_resources) {
+    if (success) {
+      get_notification_resources_ = notification_resources;
+    } else {
+      get_notification_resources_ = base::nullopt;
+    }
+    std::move(quit_closure).Run();
+  }
+
   void DidGetDisplayedNotifications(base::OnceClosure quit_closure,
                                     std::set<std::string> notification_ids,
                                     bool supports_synchronization) {
@@ -313,6 +336,41 @@
         .size();
   }
 
+  size_t CountScheduledNotificationsSync(int64_t service_worker_registration_id,
+                                         const std::string& filter_tag) {
+    return GetNotificationsSync(service_worker_registration_id, filter_tag,
+                                /* include_triggered= */ true)
+        .size();
+  }
+
+  std::vector<NotificationDatabaseData> GetNotificationDataFromContextSync(
+      int64_t service_worker_registration_id,
+      const std::string& filter_tag,
+      bool include_triggered) {
+    base::RunLoop run_loop;
+    notification_context_->ReadAllNotificationDataForServiceWorkerRegistration(
+        GURL(kTestOrigin), service_worker_registration_id,
+        base::AdaptCallbackForRepeating(
+            base::BindOnce(&BlinkNotificationServiceImplTest::
+                               DidGetNotificationDataFromContext,
+                           base::Unretained(this), run_loop.QuitClosure())));
+    run_loop.Run();
+    return get_notifications_data_;
+  }
+
+  base::Optional<blink::NotificationResources>
+  GetNotificationResourcesFromContextSync(const std::string& notification_id) {
+    base::RunLoop run_loop;
+    notification_context_->ReadNotificationResources(
+        notification_id, GURL(kTestOrigin),
+        base::AdaptCallbackForRepeating(
+            base::BindOnce(&BlinkNotificationServiceImplTest::
+                               DidGetNotificationResourcesFromContext,
+                           base::Unretained(this), run_loop.QuitClosure())));
+    run_loop.Run();
+    return get_notification_resources_;
+  }
+
   // Synchronous wrapper of
   // PlatformNotificationService::GetDisplayedNotifications
   std::set<std::string> GetDisplayedNotifications() {
@@ -385,6 +443,10 @@
 
   std::vector<std::string> get_notifications_callback_result_;
 
+  std::vector<NotificationDatabaseData> get_notifications_data_;
+
+  base::Optional<blink::NotificationResources> get_notification_resources_;
+
   bool read_notification_data_callback_result_ = false;
 
   DISALLOW_COPY_AND_ASSIGN(BlinkNotificationServiceImplTest);
@@ -715,4 +777,89 @@
   EXPECT_EQ(0u, CountDisplayedNotificationsSync(registration->id(), "tag"));
 }
 
+TEST_F(BlinkNotificationServiceImplTest, GetTriggeredNotificationsWithFilter) {
+  SetPermissionStatus(blink::mojom::PermissionStatus::GRANTED);
+
+  scoped_refptr<ServiceWorkerRegistration> registration;
+  RegisterServiceWorker(&registration);
+
+  base::Time timestamp = base::Time::Now() + base::TimeDelta::FromSeconds(10);
+  blink::PlatformNotificationData platform_notification_data;
+  platform_notification_data.tag = "tagA";
+  platform_notification_data.show_trigger_timestamp = timestamp;
+
+  blink::PlatformNotificationData other_platform_notification_data;
+  other_platform_notification_data.tag = "tagB";
+  other_platform_notification_data.show_trigger_timestamp = timestamp;
+
+  blink::PlatformNotificationData displayed_notification_data;
+  displayed_notification_data.tag = "tagC";
+
+  DisplayPersistentNotificationSync(registration->id(),
+                                    platform_notification_data,
+                                    blink::NotificationResources());
+
+  DisplayPersistentNotificationSync(registration->id(),
+                                    other_platform_notification_data,
+                                    blink::NotificationResources());
+
+  // Wait for service to receive all the Display calls.
+  RunAllTasksUntilIdle();
+
+  EXPECT_EQ(0u, CountDisplayedNotificationsSync(registration->id(), ""));
+  EXPECT_EQ(2u, CountScheduledNotificationsSync(registration->id(), ""));
+  EXPECT_EQ(1u, CountScheduledNotificationsSync(registration->id(), "tagA"));
+  EXPECT_EQ(1u, CountScheduledNotificationsSync(registration->id(), "tagB"));
+  EXPECT_EQ(0u, CountScheduledNotificationsSync(registration->id(), "tagC"));
+  EXPECT_EQ(0u, CountScheduledNotificationsSync(registration->id(), "tag"));
+}
+
+TEST_F(BlinkNotificationServiceImplTest, ResourcesStoredForTriggered) {
+  SetPermissionStatus(blink::mojom::PermissionStatus::GRANTED);
+
+  scoped_refptr<ServiceWorkerRegistration> registration;
+  RegisterServiceWorker(&registration);
+
+  base::Time timestamp = base::Time::Now() + base::TimeDelta::FromSeconds(10);
+  blink::PlatformNotificationData scheduled_notification_data;
+  scheduled_notification_data.tag = "tagA";
+  scheduled_notification_data.show_trigger_timestamp = timestamp;
+
+  blink::NotificationResources resources;
+  resources.notification_icon = CreateBitmap(10, 10, SK_ColorMAGENTA);
+
+  blink::PlatformNotificationData displayed_notification_data;
+  displayed_notification_data.tag = "tagB";
+
+  DisplayPersistentNotificationSync(registration->id(),
+                                    scheduled_notification_data, resources);
+
+  DisplayPersistentNotificationSync(registration->id(),
+                                    displayed_notification_data, resources);
+
+  // Wait for service to receive all the Display calls.
+  RunAllTasksUntilIdle();
+
+  auto notification_data =
+      GetNotificationDataFromContextSync(registration->id(), "", true);
+
+  EXPECT_EQ(2u, notification_data.size());
+
+  auto notification_a = notification_data[0].notification_data.tag == "tagA"
+                            ? notification_data[0]
+                            : notification_data[1];
+  auto notification_b = notification_data[0].notification_data.tag == "tagB"
+                            ? notification_data[0]
+                            : notification_data[1];
+  auto stored_resources_a =
+      GetNotificationResourcesFromContextSync(notification_a.notification_id);
+  auto stored_resources_b =
+      GetNotificationResourcesFromContextSync(notification_b.notification_id);
+
+  EXPECT_TRUE(stored_resources_a.has_value());
+  EXPECT_EQ(10, stored_resources_a.value().notification_icon.width());
+
+  EXPECT_FALSE(stored_resources_b.has_value());
+}
+
 }  // namespace content
diff --git a/content/browser/notifications/notification_database.cc b/content/browser/notifications/notification_database.cc
index 5994856..78e69f1 100644
--- a/content/browser/notifications/notification_database.cc
+++ b/content/browser/notifications/notification_database.cc
@@ -5,6 +5,7 @@
 #include "content/browser/notifications/notification_database.h"
 
 #include <string>
+#include <utility>
 
 #include "base/bind.h"
 #include "base/files/file_util.h"
@@ -12,12 +13,13 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/task/post_task.h"
-#include "content/browser/notifications/notification_database_data_conversions.h"
+#include "content/browser/notifications/notification_database_conversions.h"
 #include "content/common/service_worker/service_worker_types.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/notification_database_data.h"
 #include "storage/common/database/database_identifier.h"
+#include "third_party/blink/public/common/notifications/notification_resources.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
 #include "third_party/leveldatabase/env_chromium.h"
 #include "third_party/leveldatabase/leveldb_chrome.h"
@@ -33,15 +35,16 @@
 // value: String containing the NotificationDatabaseDataProto protocol buffer
 //        in serialized form.
 //
-// key: "NEXT_NOTIFICATION_ID"
-// value: Decimal string which fits into an int64_t, containing the next
-//        persistent notification ID.
+// key: "RESOURCES:" <origin identifier> '\x00' <notification_id>
+// value: String containing the NotificationDatabaseResourcesProto protocol
+//        buffer in serialized form.
 
 namespace content {
 namespace {
 
 // Keys of the fields defined in the database.
 const char kDataKeyPrefix[] = "DATA:";
+const char kResourcesKeyPrefix[] = "RESOURCES:";
 
 // Separates the components of compound keys.
 const char kNotificationKeySeparator = '\x00';
@@ -84,6 +87,25 @@
   return CreateDataPrefix(origin) + notification_id;
 }
 
+// Creates a prefix for the resources entries based on |origin|.
+std::string CreateResourcesPrefix(const GURL& origin) {
+  if (!origin.is_valid())
+    return kResourcesKeyPrefix;
+
+  return base::StringPrintf("%s%s%c", kResourcesKeyPrefix,
+                            storage::GetIdentifierFromOrigin(origin).c_str(),
+                            kNotificationKeySeparator);
+}
+
+// Creates the compound data key in which notification resources are stored.
+std::string CreateResourcesKey(const GURL& origin,
+                               const std::string& notification_id) {
+  DCHECK(origin.is_valid());
+  DCHECK(!notification_id.empty());
+
+  return CreateResourcesPrefix(origin) + notification_id;
+}
+
 // Deserializes data in |serialized_data| to |notification_database_data|.
 // Will return if the deserialization was successful.
 NotificationDatabase::Status DeserializedNotificationData(
@@ -99,6 +121,21 @@
   return NotificationDatabase::STATUS_ERROR_CORRUPTED;
 }
 
+// Deserializes resources in |serialized_resources| to |notification_resources|.
+// Will return if the deserialization was successful.
+NotificationDatabase::Status DeserializedNotificationResources(
+    const std::string& serialized_resources,
+    blink::NotificationResources* notification_resources) {
+  DCHECK(notification_resources);
+  if (DeserializeNotificationDatabaseResources(serialized_resources,
+                                               notification_resources)) {
+    return NotificationDatabase::STATUS_OK;
+  }
+
+  DLOG(ERROR) << "Unable to deserialize a notification's resources.";
+  return NotificationDatabase::STATUS_ERROR_CORRUPTED;
+}
+
 // Updates the time of the last click on the notification, and the first if
 // necessary.
 void UpdateNotificationTimestamps(NotificationDatabaseData* data) {
@@ -172,6 +209,28 @@
                                       notification_database_data);
 }
 
+NotificationDatabase::Status NotificationDatabase::ReadNotificationResources(
+    const std::string& notification_id,
+    const GURL& origin,
+    blink::NotificationResources* notification_resources) const {
+  DCHECK(sequence_checker_.CalledOnValidSequence());
+  DCHECK_EQ(State::INITIALIZED, state_);
+  DCHECK(!notification_id.empty());
+  DCHECK(origin.is_valid());
+  DCHECK(notification_resources);
+
+  std::string key = CreateResourcesKey(origin, notification_id);
+  std::string serialized_resources;
+
+  Status status = LevelDBStatusToNotificationDatabaseStatus(
+      db_->Get(leveldb::ReadOptions(), key, &serialized_resources));
+  if (status != STATUS_OK)
+    return status;
+
+  return DeserializedNotificationResources(serialized_resources,
+                                           notification_resources);
+}
+
 NotificationDatabase::Status
 NotificationDatabase::ReadNotificationDataAndRecordInteraction(
     const std::string& notification_id,
@@ -211,6 +270,13 @@
   return status;
 }
 
+NotificationDatabase::Status NotificationDatabase::ForEachNotificationData(
+    ReadAllNotificationsCallback callback) const {
+  return ForEachNotificationDataInternal(
+      GURL() /* origin */, blink::mojom::kInvalidServiceWorkerRegistrationId,
+      std::move(callback));
+}
+
 NotificationDatabase::Status NotificationDatabase::ReadAllNotificationData(
     std::vector<NotificationDatabaseData>* notification_data_vector) const {
   return ReadAllNotificationDataInternal(
@@ -256,6 +322,20 @@
   leveldb::WriteBatch batch;
   batch.Put(CreateDataKey(origin, notification_id), serialized_data);
 
+  if (notification_data.notification_resources.has_value()) {
+    std::string serialized_resources;
+    if (!SerializeNotificationDatabaseResources(
+            notification_data.notification_resources.value(),
+            &serialized_resources)) {
+      DLOG(ERROR) << "Unable to serialize resources for a notification "
+                  << "belonging to: " << origin;
+      return STATUS_ERROR_FAILED;
+    }
+
+    batch.Put(CreateResourcesKey(origin, notification_id),
+              serialized_resources);
+  }
+
   return LevelDBStatusToNotificationDatabaseStatus(
       db_->Write(leveldb::WriteOptions(), &batch));
 }
@@ -275,9 +355,13 @@
         FROM_HERE, {BrowserThread::UI},
         base::BindOnce(record_notification_to_ukm_callback_, data));
   }
-  std::string key = CreateDataKey(origin, notification_id);
+
+  leveldb::WriteBatch batch;
+  batch.Delete(CreateDataKey(origin, notification_id));
+  batch.Delete(CreateResourcesKey(origin, notification_id));
+
   return LevelDBStatusToNotificationDatabaseStatus(
-      db_->Delete(leveldb::WriteOptions(), key));
+      db_->Write(leveldb::WriteOptions(), &batch));
 }
 
 NotificationDatabase::Status
@@ -326,6 +410,21 @@
   DCHECK(sequence_checker_.CalledOnValidSequence());
   DCHECK(notification_data_vector);
 
+  return ForEachNotificationDataInternal(
+      origin, service_worker_registration_id,
+      base::BindRepeating(
+          [](std::vector<NotificationDatabaseData>* datas,
+             const NotificationDatabaseData& data) { datas->push_back(data); },
+          notification_data_vector));
+}
+
+NotificationDatabase::Status
+NotificationDatabase::ForEachNotificationDataInternal(
+    const GURL& origin,
+    int64_t service_worker_registration_id,
+    ReadAllNotificationsCallback callback) const {
+  DCHECK(sequence_checker_.CalledOnValidSequence());
+
   const std::string prefix = CreateDataPrefix(origin);
 
   leveldb::Slice prefix_slice(prefix);
@@ -349,7 +448,7 @@
       continue;
     }
 
-    notification_data_vector->push_back(notification_database_data);
+    callback.Run(notification_database_data);
   }
 
   return LevelDBStatusToNotificationDatabaseStatus(iter->status());
@@ -401,11 +500,13 @@
                          notification_database_data));
     }
 
-    batch.Delete(iter->key());
+    std::string notification_id = notification_database_data.notification_id;
+    DCHECK(!notification_id.empty());
 
-    DCHECK(!notification_database_data.notification_id.empty());
-    deleted_notification_ids->insert(
-        notification_database_data.notification_id);
+    batch.Delete(iter->key());
+    batch.Delete(CreateResourcesKey(origin, notification_id));
+
+    deleted_notification_ids->insert(notification_id);
   }
 
   if (deleted_notification_ids->empty())
diff --git a/content/browser/notifications/notification_database.h b/content/browser/notifications/notification_database.h
index 6ed4658..c6680a9 100644
--- a/content/browser/notifications/notification_database.h
+++ b/content/browser/notifications/notification_database.h
@@ -19,6 +19,10 @@
 
 class GURL;
 
+namespace blink {
+struct NotificationResources;
+}  // namespace blink
+
 namespace leveldb {
 class DB;
 class Env;
@@ -42,8 +46,10 @@
  public:
   using UkmCallback =
       base::RepeatingCallback<void(const NotificationDatabaseData&)>;
+  using ReadAllNotificationsCallback =
+      base::RepeatingCallback<void(const NotificationDatabaseData&)>;
 
-  // Result status codes for interations with the database. Will be used for
+  // Result status codes for interactions with the database. Will be used for
   // UMA, so the assigned ids must remain stable.
   enum Status {
     STATUS_OK = 0,
@@ -95,6 +101,14 @@
       const GURL& origin,
       NotificationDatabaseData* notification_data) const;
 
+  // Reads the notification resources for the notification identified by
+  // |notification_id| and belonging to |origin| from the database, and stores
+  // it in |*notification_resources|. Returns the status code.
+  Status ReadNotificationResources(
+      const std::string& notification_id,
+      const GURL& origin,
+      blink::NotificationResources* notification_resources) const;
+
   // This function is identical to ReadNotificationData above, but also records
   // an interaction with that notification in the database for UKM logging
   // purposes.
@@ -104,6 +118,10 @@
       PlatformNotificationContext::Interaction interaction,
       NotificationDatabaseData* notification_data);
 
+  // Iterates over all notification data for all origins from the database, and
+  // calls |callback| with each notification data. Returns the status code.
+  Status ForEachNotificationData(ReadAllNotificationsCallback callback) const;
+
   // Reads all notification data for all origins from the database, and appends
   // the data to |notification_data_vector|. Returns the status code.
   Status ReadAllNotificationData(
@@ -172,15 +190,22 @@
   // in the |next_persistent_notification_id_| member.
   Status ReadNextPersistentNotificationId();
 
+  // Iterates over all notifications and pushes matching ones onto
+  // |notification_data_vector|. See ForEachNotificationDataInternal for deails.
+  Status ReadAllNotificationDataInternal(
+      const GURL& origin,
+      int64_t service_worker_registration_id,
+      std::vector<NotificationDatabaseData>* notification_data_vector) const;
+
   // Reads all notification data with the given constraints. |origin| may be
   // empty to read all notification data from all origins. If |origin| is
   // set, but |service_worker_registration_id| is invalid, then all notification
   // data for |origin| will be read. If both are set, then all notification data
   // for the given |service_worker_registration_id| will be read.
-  Status ReadAllNotificationDataInternal(
+  Status ForEachNotificationDataInternal(
       const GURL& origin,
       int64_t service_worker_registration_id,
-      std::vector<NotificationDatabaseData>* notification_data_vector) const;
+      ReadAllNotificationsCallback callback) const;
 
   // Deletes all notification data with the given constraints. |origin| must
   // always be set - use Destroy() when the goal is to empty the database. If
diff --git a/content/browser/notifications/notification_database_data_conversions.cc b/content/browser/notifications/notification_database_conversions.cc
similarity index 69%
rename from content/browser/notifications/notification_database_data_conversions.cc
rename to content/browser/notifications/notification_database_conversions.cc
index d6b1332..e5314554 100644
--- a/content/browser/notifications/notification_database_data_conversions.cc
+++ b/content/browser/notifications/notification_database_conversions.cc
@@ -2,20 +2,48 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "content/browser/notifications/notification_database_data_conversions.h"
+#include "content/browser/notifications/notification_database_conversions.h"
 
 #include <stddef.h>
 
 #include <memory>
 
 #include "base/logging.h"
+#include "base/optional.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_restrictions.h"
 #include "base/time/time.h"
 #include "content/browser/notifications/notification_database_data.pb.h"
+#include "content/browser/notifications/notification_database_resources.pb.h"
 #include "content/public/browser/notification_database_data.h"
+#include "third_party/blink/public/common/notifications/notification_resources.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/codec/png_codec.h"
 
 namespace content {
 
+namespace {
+
+// static
+SkBitmap DeserializeImage(const std::string& image_data) {
+  base::AssertLongCPUWorkAllowed();
+  SkBitmap image;
+  gfx::PNGCodec::Decode(
+      reinterpret_cast<const unsigned char*>(image_data.data()),
+      image_data.length(), &image);
+  return image;
+}
+
+// static
+std::vector<unsigned char> SerializeImage(const SkBitmap& image) {
+  base::AssertLongCPUWorkAllowed();
+  std::vector<unsigned char> image_data;
+  gfx::PNGCodec::EncodeBGRASkBitmap(image, false, &image_data);
+  return image_data;
+}
+
+}  // namespace
+
 bool DeserializeNotificationDatabaseData(const std::string& input,
                                          NotificationDatabaseData* output) {
   DCHECK(output);
@@ -38,14 +66,20 @@
   if (message.has_time_until_close_millis()) {
     output->time_until_close_millis =
         base::TimeDelta::FromMilliseconds(message.time_until_close_millis());
+  } else {
+    output->time_until_close_millis = base::nullopt;
   }
   if (message.has_time_until_first_click_millis()) {
     output->time_until_first_click_millis = base::TimeDelta::FromMilliseconds(
         message.time_until_first_click_millis());
+  } else {
+    output->time_until_first_click_millis = base::nullopt;
   }
   if (message.has_time_until_last_click_millis()) {
     output->time_until_last_click_millis = base::TimeDelta::FromMilliseconds(
         message.time_until_last_click_millis());
+  } else {
+    output->time_until_last_click_millis = base::nullopt;
   }
 
   switch (message.closed_reason()) {
@@ -95,8 +129,9 @@
     notification_data->vibration_pattern.clear();
   }
 
-  notification_data->timestamp =
-      base::Time::FromInternalValue(payload.timestamp());
+  notification_data->timestamp = base::Time::FromDeltaSinceWindowsEpoch(
+      base::TimeDelta::FromMicroseconds(payload.timestamp()));
+
   notification_data->renotify = payload.renotify();
   notification_data->silent = payload.silent();
   notification_data->require_interaction = payload.require_interaction();
@@ -134,6 +169,19 @@
     notification_data->actions.push_back(action);
   }
 
+  if (payload.has_show_trigger_timestamp()) {
+    notification_data->show_trigger_timestamp =
+        base::Time::FromDeltaSinceWindowsEpoch(
+            base::TimeDelta::FromMicroseconds(
+                payload.show_trigger_timestamp()));
+  } else {
+    notification_data->show_trigger_timestamp = base::nullopt;
+  }
+
+  output->has_triggered = message.has_triggered();
+
+  output->notification_resources = base::nullopt;
+
   return true;
 }
 
@@ -141,8 +189,8 @@
                                        std::string* output) {
   DCHECK(output);
 
-  std::unique_ptr<NotificationDatabaseDataProto::NotificationData> payload(
-      new NotificationDatabaseDataProto::NotificationData());
+  auto payload =
+      std::make_unique<NotificationDatabaseDataProto::NotificationData>();
 
   const blink::PlatformNotificationData& notification_data =
       input.notification_data;
@@ -174,7 +222,8 @@
   for (size_t i = 0; i < notification_data.vibration_pattern.size(); ++i)
     payload->add_vibration_pattern(notification_data.vibration_pattern[i]);
 
-  payload->set_timestamp(notification_data.timestamp.ToInternalValue());
+  payload->set_timestamp(
+      notification_data.timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds());
   payload->set_renotify(notification_data.renotify);
   payload->set_silent(notification_data.silent);
   payload->set_require_interaction(notification_data.require_interaction);
@@ -212,6 +261,13 @@
     }
   }
 
+  if (notification_data.show_trigger_timestamp.has_value()) {
+    payload->set_show_trigger_timestamp(
+        notification_data.show_trigger_timestamp.value()
+            .ToDeltaSinceWindowsEpoch()
+            .InMicroseconds());
+  }
+
   NotificationDatabaseDataProto message;
   message.set_notification_id(input.notification_id);
   message.set_origin(input.origin.spec());
@@ -249,7 +305,71 @@
       break;
   }
 
+  message.set_has_triggered(input.has_triggered);
+
   return message.SerializeToString(output);
 }
 
+bool DeserializeNotificationDatabaseResources(
+    const std::string& serialized_resources,
+    blink::NotificationResources* output) {
+  DCHECK(output);
+
+  NotificationDatabaseResourcesProto message;
+  if (!message.ParseFromString(serialized_resources))
+    return false;
+
+  if (message.has_image())
+    output->image = DeserializeImage(message.image());
+  else
+    output->image = SkBitmap();
+
+  if (message.has_notification_icon())
+    output->notification_icon = DeserializeImage(message.notification_icon());
+  else
+    output->notification_icon = SkBitmap();
+
+  if (message.has_badge())
+    output->badge = DeserializeImage(message.badge());
+  else
+    output->badge = SkBitmap();
+
+  output->action_icons.clear();
+  for (int i = 0; i < message.action_icons_size(); ++i)
+    output->action_icons.push_back(DeserializeImage(message.action_icons(i)));
+
+  return true;
+}
+
+bool SerializeNotificationDatabaseResources(
+    const blink::NotificationResources& input,
+    std::string* serialized_resources) {
+  DCHECK(serialized_resources);
+
+  NotificationDatabaseResourcesProto message;
+
+  if (!input.image.isNull()) {
+    auto image_data = SerializeImage(input.image);
+    message.set_image(image_data.data(), image_data.size());
+  }
+  if (!input.notification_icon.isNull()) {
+    auto image_data = SerializeImage(input.notification_icon);
+    message.set_notification_icon(image_data.data(), image_data.size());
+  }
+  if (!input.badge.isNull()) {
+    auto image_data = SerializeImage(input.badge);
+    message.set_badge(image_data.data(), image_data.size());
+  }
+  for (const auto& image : input.action_icons) {
+    if (!image.isNull()) {
+      auto image_data = SerializeImage(image);
+      message.add_action_icons(image_data.data(), image_data.size());
+    } else {
+      message.add_action_icons();
+    }
+  }
+
+  return message.SerializeToString(serialized_resources);
+}
+
 }  // namespace content
diff --git a/content/browser/notifications/notification_database_conversions.h b/content/browser/notifications/notification_database_conversions.h
new file mode 100644
index 0000000..f72641a
--- /dev/null
+++ b/content/browser/notifications/notification_database_conversions.h
@@ -0,0 +1,47 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_NOTIFICATIONS_NOTIFICATION_DATABASE_CONVERSIONS_H_
+#define CONTENT_BROWSER_NOTIFICATIONS_NOTIFICATION_DATABASE_CONVERSIONS_H_
+
+#include <string>
+
+#include "content/common/content_export.h"
+
+namespace blink {
+struct NotificationResources;
+}  // namespace blink
+
+namespace content {
+
+struct NotificationDatabaseData;
+
+// Parses the serialized notification data |input| into a new object, |output|.
+// Returns whether the serialized |input| could be deserialized successfully.
+CONTENT_EXPORT bool DeserializeNotificationDatabaseData(
+    const std::string& input,
+    NotificationDatabaseData* output);
+
+// Serializes the contents of |input| into the string |output|. Returns whether
+// the notification data could be serialized successfully.
+CONTENT_EXPORT bool SerializeNotificationDatabaseData(
+    const NotificationDatabaseData& input,
+    std::string* output);
+
+// Parses the serialized notification resources |input| into a new object,
+// |output|. Returns whether the serialized |input| could be deserialized
+// successfully.
+CONTENT_EXPORT bool DeserializeNotificationDatabaseResources(
+    const std::string& input,
+    blink::NotificationResources* output);
+
+// Serializes the contents of |input| into the string |output|. Returns whether
+// the notification resources could be serialized successfully.
+CONTENT_EXPORT bool SerializeNotificationDatabaseResources(
+    const blink::NotificationResources& input,
+    std::string* output);
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_NOTIFICATIONS_NOTIFICATION_DATABASE_CONVERSIONS_H_
diff --git a/content/browser/notifications/notification_database_data_unittest.cc b/content/browser/notifications/notification_database_conversions_unittest.cc
similarity index 69%
rename from content/browser/notifications/notification_database_data_unittest.cc
rename to content/browser/notifications/notification_database_conversions_unittest.cc
index 72d7363..fbd1bb42 100644
--- a/content/browser/notifications/notification_database_data_unittest.cc
+++ b/content/browser/notifications/notification_database_conversions_unittest.cc
@@ -11,15 +11,29 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
+#include "content/browser/notifications/notification_database_conversions.h"
 #include "content/browser/notifications/notification_database_data.pb.h"
-#include "content/browser/notifications/notification_database_data_conversions.h"
+#include "content/browser/notifications/notification_database_resources.pb.h"
 #include "content/public/browser/notification_database_data.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/notifications/notification_resources.h"
 #include "third_party/blink/public/platform/modules/notifications/web_notification_constants.h"
+#include "third_party/skia/include/core/SkBitmap.h"
 
 namespace content {
 
+namespace {
+
+SkBitmap CreateBitmap(int width, int height, SkColor color) {
+  SkBitmap bitmap;
+  bitmap.allocN32Pixels(width, height);
+  bitmap.eraseColor(color);
+  return bitmap;
+}
+
+}  // namespace
+
 const char kNotificationId[] = "my-notification";
 const int64_t kServiceWorkerRegistrationId = 9001;
 const bool kReplacedExistingNotification = true;
@@ -44,8 +58,10 @@
 const int kNotificationVibrationPattern[] = {100, 200, 300};
 const double kNotificationTimestamp = 621046800.;
 const unsigned char kNotificationData[] = {0xdf, 0xff, 0x0, 0x0, 0xff, 0xdf};
+const double kShowTriggerTimestamp = 621086800.;
+const bool kHasTriggered = true;
 
-TEST(NotificationDatabaseDataTest, SerializeAndDeserializeData) {
+TEST(NotificationDatabaseConversionsTest, SerializeAndDeserializeData) {
   std::vector<int> vibration_pattern(
       kNotificationVibrationPattern,
       kNotificationVibrationPattern +
@@ -69,6 +85,8 @@
   notification_data.renotify = true;
   notification_data.silent = true;
   notification_data.require_interaction = true;
+  notification_data.show_trigger_timestamp =
+      base::Time::FromJsTime(kShowTriggerTimestamp);
   notification_data.data = developer_data;
   for (size_t i = 0; i < blink::kWebNotificationMaxActions; i++) {
     blink::PlatformNotificationAction notification_action;
@@ -97,6 +115,7 @@
   database_data.time_until_close_millis =
       base::TimeDelta::FromMilliseconds(kTimeUntilCloseMillis);
   database_data.closed_reason = NotificationDatabaseData::ClosedReason::USER;
+  database_data.has_triggered = kHasTriggered;
   std::string serialized_data;
 
   // Serialize the data in |notification_data| to the string |serialized_data|.
@@ -127,6 +146,7 @@
   EXPECT_EQ(database_data.time_until_close_millis,
             copied_data.time_until_close_millis);
   EXPECT_EQ(database_data.closed_reason, copied_data.closed_reason);
+  EXPECT_EQ(database_data.has_triggered, copied_data.has_triggered);
 
   const blink::PlatformNotificationData& copied_notification_data =
       copied_data.notification_data;
@@ -148,6 +168,8 @@
   EXPECT_EQ(notification_data.silent, copied_notification_data.silent);
   EXPECT_EQ(notification_data.require_interaction,
             copied_notification_data.require_interaction);
+  EXPECT_EQ(notification_data.show_trigger_timestamp,
+            copied_notification_data.show_trigger_timestamp);
 
   ASSERT_EQ(developer_data.size(), copied_notification_data.data.size());
   for (size_t i = 0; i < developer_data.size(); ++i)
@@ -170,7 +192,7 @@
   }
 }
 
-TEST(NotificationDatabaseDataTest, ActionDeserializationIsNotAdditive) {
+TEST(NotificationDatabaseConversionsTest, ActionDeserializationIsNotAdditive) {
   NotificationDatabaseData database_data;
 
   for (size_t i = 0; i < blink::kWebNotificationMaxActions; ++i)
@@ -198,7 +220,7 @@
             blink::kWebNotificationMaxActions);
 }
 
-TEST(NotificationDatabaseDataTest, SerializeAndDeserializeActionTypes) {
+TEST(NotificationDatabaseConversionsTest, SerializeAndDeserializeActionTypes) {
   blink::PlatformNotificationActionType action_types[] = {
       blink::PLATFORM_NOTIFICATION_ACTION_TYPE_BUTTON,
       blink::PLATFORM_NOTIFICATION_ACTION_TYPE_TEXT};
@@ -225,7 +247,7 @@
   }
 }
 
-TEST(NotificationDatabaseDataTest, SerializeAndDeserializeDirections) {
+TEST(NotificationDatabaseConversionsTest, SerializeAndDeserializeDirections) {
   blink::mojom::NotificationDirection directions[] = {
       blink::mojom::NotificationDirection::LEFT_TO_RIGHT,
       blink::mojom::NotificationDirection::RIGHT_TO_LEFT,
@@ -250,7 +272,8 @@
   }
 }
 
-TEST(NotificationDatabaseDataTest, SerializeAndDeserializeClosedReasons) {
+TEST(NotificationDatabaseConversionsTest,
+     SerializeAndDeserializeClosedReasons) {
   NotificationDatabaseData::ClosedReason closed_reasons[] = {
       NotificationDatabaseData::ClosedReason::USER,
       NotificationDatabaseData::ClosedReason::DEVELOPER,
@@ -272,7 +295,8 @@
   }
 }
 
-TEST(NotificationDatabaseDataTest, SerializeAndDeserializeNullPlaceholder) {
+TEST(NotificationDatabaseConversionsTest,
+     SerializeAndDeserializeNullPlaceholder) {
   blink::PlatformNotificationAction action;
   action.type = kNotificationActionType;
   action.placeholder = base::NullableString16();  // null string.
@@ -294,4 +318,100 @@
   EXPECT_TRUE(copied_data.notification_data.actions[0].placeholder.is_null());
 }
 
+TEST(NotificationDatabaseConversionsTest,
+     SerializeAndDeserializeNullShowTriggerTimestamp) {
+  blink::PlatformNotificationData notification_data;
+
+  // explicitly empty timestamp
+  notification_data.show_trigger_timestamp = base::nullopt;
+
+  NotificationDatabaseData database_data;
+  database_data.notification_data = notification_data;
+
+  std::string serialized_data;
+  ASSERT_TRUE(
+      SerializeNotificationDatabaseData(database_data, &serialized_data));
+
+  NotificationDatabaseData copied_data;
+  ASSERT_TRUE(
+      DeserializeNotificationDatabaseData(serialized_data, &copied_data));
+
+  EXPECT_FALSE(
+      copied_data.notification_data.show_trigger_timestamp.has_value());
+}
+
+TEST(NotificationDatabaseConversionsTest, OptionalFieldsGetCleared) {
+  NotificationDatabaseData data_without_fields;
+  NotificationDatabaseData data_with_fields;
+
+  data_with_fields.time_until_close_millis = base::TimeDelta::FromSeconds(1);
+  data_with_fields.time_until_first_click_millis =
+      base::TimeDelta::FromSeconds(2);
+  data_with_fields.time_until_last_click_millis =
+      base::TimeDelta::FromSeconds(3);
+  data_with_fields.notification_resources = blink::NotificationResources();
+
+  std::string serialized_data;
+  NotificationDatabaseData copied_database_data;
+
+  // Serialize the |data_with_fields| to the string |serialized_data|,
+  // and then deserialize it again immediately to |copied_database_data|.
+  ASSERT_TRUE(
+      SerializeNotificationDatabaseData(data_with_fields, &serialized_data));
+  ASSERT_TRUE(DeserializeNotificationDatabaseData(serialized_data,
+                                                  &copied_database_data));
+
+  EXPECT_EQ(base::TimeDelta::FromSeconds(1),
+            copied_database_data.time_until_close_millis);
+  EXPECT_EQ(base::TimeDelta::FromSeconds(2),
+            copied_database_data.time_until_first_click_millis);
+  EXPECT_EQ(base::TimeDelta::FromSeconds(3),
+            copied_database_data.time_until_last_click_millis);
+  EXPECT_FALSE(copied_database_data.notification_resources.has_value());
+
+  // Deserialize the |data_without_fields| in the same |copied_database_data|.
+  // The optional fields should now be gone.
+  ASSERT_TRUE(
+      SerializeNotificationDatabaseData(data_without_fields, &serialized_data));
+  ASSERT_TRUE(DeserializeNotificationDatabaseData(serialized_data,
+                                                  &copied_database_data));
+
+  EXPECT_FALSE(copied_database_data.time_until_close_millis.has_value());
+  EXPECT_FALSE(copied_database_data.time_until_first_click_millis.has_value());
+  EXPECT_FALSE(copied_database_data.time_until_last_click_millis.has_value());
+  EXPECT_FALSE(copied_database_data.notification_resources.has_value());
+}
+
+TEST(NotificationDatabaseConversionsTest,
+     SerializeAndDeserializeNotificationResources) {
+  blink::NotificationResources notification_resources;
+
+  notification_resources.notification_icon = CreateBitmap(10, 10, SK_ColorBLUE);
+  notification_resources.image = CreateBitmap(20, 20, SK_ColorGREEN);
+  notification_resources.badge = CreateBitmap(30, 30, SK_ColorRED);
+
+  notification_resources.action_icons.push_back(
+      CreateBitmap(40, 40, SK_ColorYELLOW));
+  notification_resources.action_icons.push_back(
+      CreateBitmap(41, 41, SK_ColorCYAN));
+  notification_resources.action_icons.push_back(
+      CreateBitmap(42, 42, SK_ColorMAGENTA));
+
+  std::string serialized_resources;
+  ASSERT_TRUE(SerializeNotificationDatabaseResources(notification_resources,
+                                                     &serialized_resources));
+
+  blink::NotificationResources copied_resources;
+  ASSERT_TRUE(DeserializeNotificationDatabaseResources(serialized_resources,
+                                                       &copied_resources));
+
+  EXPECT_EQ(10, copied_resources.notification_icon.width());
+  EXPECT_EQ(20, copied_resources.image.width());
+  EXPECT_EQ(30, copied_resources.badge.width());
+  EXPECT_EQ(3u, copied_resources.action_icons.size());
+  EXPECT_EQ(40, copied_resources.action_icons[0].width());
+  EXPECT_EQ(41, copied_resources.action_icons[1].width());
+  EXPECT_EQ(42, copied_resources.action_icons[2].width());
+}
+
 }  // namespace content
diff --git a/content/browser/notifications/notification_database_data.proto b/content/browser/notifications/notification_database_data.proto
index e521c88..21f448c 100644
--- a/content/browser/notifications/notification_database_data.proto
+++ b/content/browser/notifications/notification_database_data.proto
@@ -11,7 +11,7 @@
 // Stores information about a Web Notification. This message is the protocol
 // buffer meant to serialize the content::NotificationDatabaseData structure.
 //
-// Next tag: 14
+// Next tag: 15
 message NotificationDatabaseDataProto {
   enum ClosedReason {
     USER = 0;
@@ -55,7 +55,7 @@
   // Actual data payload of the notification. This message is the protocol
   // buffer meant to serialize the blink::PlatformNotificationData structure.
   //
-  // Next tag: 16
+  // Next tag: 17
   message NotificationData {
     enum Direction {
       LEFT_TO_RIGHT = 0;
@@ -78,7 +78,13 @@
     optional bool require_interaction = 11;
     optional bytes data = 8;
     repeated NotificationAction actions = 10;
+    // Stored as offset from the windows epoch in microseconds.
+    optional int64 show_trigger_timestamp = 16;
   }
 
   optional NotificationData notification_data = 4;
+
+  // Keeps track if a notification with a |show_trigger_timestamp| has been
+  // displayed already.
+  optional bool has_triggered = 14;
 }
diff --git a/content/browser/notifications/notification_database_data_conversions.h b/content/browser/notifications/notification_database_data_conversions.h
deleted file mode 100644
index 84475b5..0000000
--- a/content/browser/notifications/notification_database_data_conversions.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_BROWSER_NOTIFICATIONS_NOTIFICATION_DATABASE_DATA_CONVERSIONS_H_
-#define CONTENT_BROWSER_NOTIFICATIONS_NOTIFICATION_DATABASE_DATA_CONVERSIONS_H_
-
-#include <string>
-
-#include "content/common/content_export.h"
-
-namespace content {
-
-struct NotificationDatabaseData;
-
-// Parses the serialized notification data |input| into a new object, |output|.
-// Returns whether the serialized |input| could be deserialized successfully.
-CONTENT_EXPORT bool DeserializeNotificationDatabaseData(
-    const std::string& input,
-    NotificationDatabaseData* output);
-
-// Serializes the contents of |input| into the string |output|. Returns whether
-// the notification data could be serialized successfully.
-CONTENT_EXPORT bool SerializeNotificationDatabaseData(
-    const NotificationDatabaseData& input,
-    std::string* output);
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_NOTIFICATIONS_NOTIFICATION_DATABASE_DATA_CONVERSIONS_H_
diff --git a/content/browser/notifications/notification_database_resources.proto b/content/browser/notifications/notification_database_resources.proto
new file mode 100644
index 0000000..5aecf00
--- /dev/null
+++ b/content/browser/notifications/notification_database_resources.proto
@@ -0,0 +1,21 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package content;
+
+// Stores resources of a Web Notification. This message is the protocol buffer
+// meant to serialize the blink::NotificationResources structure.
+// The images are stored as serialized PNGs.
+//
+// Next tag: 5
+message NotificationDatabaseResourcesProto {
+  optional bytes image = 1;
+  optional bytes notification_icon = 2;
+  optional bytes badge = 3;
+  repeated bytes action_icons = 4;
+}
diff --git a/content/browser/notifications/notification_database_unittest.cc b/content/browser/notifications/notification_database_unittest.cc
index 3544a5f..aabfbae 100644
--- a/content/browser/notifications/notification_database_unittest.cc
+++ b/content/browser/notifications/notification_database_unittest.cc
@@ -15,6 +15,7 @@
 #include "content/public/browser/notification_database_data.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/notifications/notification_resources.h"
 #include "third_party/blink/public/common/notifications/platform_notification_data.h"
 #include "third_party/leveldatabase/src/include/leveldb/db.h"
 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
@@ -330,6 +331,93 @@
   EXPECT_EQ(notification_data.silent, read_notification_data.silent);
 }
 
+TEST_F(NotificationDatabaseTest, ReadInvalidNotificationResources) {
+  std::unique_ptr<NotificationDatabase> database(CreateDatabaseInMemory());
+  ASSERT_EQ(NotificationDatabase::STATUS_OK,
+            database->Open(true /* create_if_missing */));
+
+  blink::NotificationResources database_resources;
+
+  // Reading the notification resources for a notification that does not exist
+  // should return the ERROR_NOT_FOUND status code.
+  EXPECT_EQ(NotificationDatabase::STATUS_ERROR_NOT_FOUND,
+            database->ReadNotificationResources(
+                "bad-id", GURL("https://chrome.com"), &database_resources));
+}
+
+TEST_F(NotificationDatabaseTest, ReadNotificationResourcesDifferentOrigin) {
+  std::unique_ptr<NotificationDatabase> database(CreateDatabaseInMemory());
+  ASSERT_EQ(NotificationDatabase::STATUS_OK,
+            database->Open(true /* create_if_missing */));
+
+  GURL origin("https://example.com");
+
+  NotificationDatabaseData database_data;
+  blink::NotificationResources database_resources;
+  database_data.notification_id = GenerateNotificationId();
+  database_data.notification_data.title = base::UTF8ToUTF16("My Notification");
+  database_data.notification_resources = blink::NotificationResources();
+
+  ASSERT_EQ(NotificationDatabase::STATUS_OK,
+            database->WriteNotificationData(origin, database_data));
+
+  // Reading the notification resources from the database when given a different
+  // origin should return the ERROR_NOT_FOUND status code.
+  EXPECT_EQ(NotificationDatabase::STATUS_ERROR_NOT_FOUND,
+            database->ReadNotificationResources(database_data.notification_id,
+                                                GURL("https://chrome.com"),
+                                                &database_resources));
+
+  // However, reading the notification from the database with the same origin
+  // should return STATUS_OK and the associated notification data.
+  EXPECT_EQ(NotificationDatabase::STATUS_OK,
+            database->ReadNotificationResources(database_data.notification_id,
+                                                origin, &database_resources));
+}
+
+TEST_F(NotificationDatabaseTest, ReadNotificationResourcesReflection) {
+  std::unique_ptr<NotificationDatabase> database(CreateDatabaseInMemory());
+  ASSERT_EQ(NotificationDatabase::STATUS_OK,
+            database->Open(true /* create_if_missing */));
+
+  GURL origin("https://example.com");
+
+  blink::NotificationResources notification_resources;
+  NotificationDatabaseData database_data;
+  database_data.notification_id = GenerateNotificationId();
+  database_data.origin = origin;
+  database_data.service_worker_registration_id = 42;
+  database_data.notification_resources = notification_resources;
+
+  // Write the constructed notification to the database, and then immediately
+  // read it back from the database again as well.
+  ASSERT_EQ(NotificationDatabase::STATUS_OK,
+            database->WriteNotificationData(origin, database_data));
+
+  NotificationDatabaseData read_database_data;
+  ASSERT_EQ(NotificationDatabase::STATUS_OK,
+            database->ReadNotificationData(database_data.notification_id,
+                                           origin, &read_database_data));
+
+  // Verify that all members retrieved from the database are exactly the same
+  // as the ones that were written to it. This tests the serialization behavior.
+
+  EXPECT_EQ(database_data.notification_id, read_database_data.notification_id);
+
+  EXPECT_EQ(database_data.origin, read_database_data.origin);
+  EXPECT_EQ(database_data.service_worker_registration_id,
+            read_database_data.service_worker_registration_id);
+
+  // We do not populate the resources when reading from the database.
+  EXPECT_FALSE(read_database_data.notification_resources.has_value());
+
+  blink::NotificationResources read_notification_resources;
+  EXPECT_EQ(
+      NotificationDatabase::STATUS_OK,
+      database->ReadNotificationResources(database_data.notification_id, origin,
+                                          &read_notification_resources));
+}
+
 TEST_F(NotificationDatabaseTest, ReadWriteMultipleNotificationData) {
   std::unique_ptr<NotificationDatabase> database(CreateDatabaseInMemory());
   ASSERT_EQ(NotificationDatabase::STATUS_OK,
@@ -465,6 +553,37 @@
       database->ReadNotificationData(notification_id, origin, &database_data));
 }
 
+TEST_F(NotificationDatabaseTest, DeleteNotificationResourcesSameOrigin) {
+  std::unique_ptr<NotificationDatabase> database(CreateDatabaseInMemory());
+  ASSERT_EQ(NotificationDatabase::STATUS_OK,
+            database->Open(true /* create_if_missing */));
+
+  const std::string notification_id = GenerateNotificationId();
+
+  blink::NotificationResources notification_resources;
+  NotificationDatabaseData database_data;
+  database_data.notification_id = notification_id;
+  database_data.notification_resources = notification_resources;
+
+  GURL origin("https://example.com");
+
+  ASSERT_EQ(NotificationDatabase::STATUS_OK,
+            database->WriteNotificationData(origin, database_data));
+
+  // Reading notification resources after writing should succeed.
+  EXPECT_EQ(NotificationDatabase::STATUS_OK,
+            database->ReadNotificationResources(notification_id, origin,
+                                                &notification_resources));
+
+  // Delete the notification which was just written to the database, and verify
+  // that reading the resources again will fail.
+  EXPECT_EQ(NotificationDatabase::STATUS_OK,
+            database->DeleteNotificationData(notification_id, origin));
+  EXPECT_EQ(NotificationDatabase::STATUS_ERROR_NOT_FOUND,
+            database->ReadNotificationResources(notification_id, origin,
+                                                &notification_resources));
+}
+
 TEST_F(NotificationDatabaseTest, DeleteNotificationDataDifferentOrigin) {
   std::unique_ptr<NotificationDatabase> database(CreateDatabaseInMemory());
   ASSERT_EQ(NotificationDatabase::STATUS_OK,
diff --git a/content/browser/notifications/notification_storage.cc b/content/browser/notifications/notification_storage.cc
index d627bd9..e60e4d9 100644
--- a/content/browser/notifications/notification_storage.cc
+++ b/content/browser/notifications/notification_storage.cc
@@ -9,7 +9,7 @@
 #include "base/bind.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/time/time.h"
-#include "content/browser/notifications/notification_database_data_conversions.h"
+#include "content/browser/notifications/notification_database_conversions.h"
 
 namespace content {
 
diff --git a/content/browser/notifications/platform_notification_context_impl.cc b/content/browser/notifications/platform_notification_context_impl.cc
index d78bd800..693aa19 100644
--- a/content/browser/notifications/platform_notification_context_impl.cc
+++ b/content/browser/notifications/platform_notification_context_impl.cc
@@ -21,6 +21,7 @@
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/notification_database_data.h"
 #include "content/public/browser/platform_notification_service.h"
+#include "third_party/blink/public/common/notifications/notification_resources.h"
 
 namespace content {
 namespace {
@@ -63,14 +64,14 @@
     return;
   }
 
+  ukm_callback_ = base::BindRepeating(
+      &PlatformNotificationService::RecordNotificationUkmEvent,
+      base::Unretained(service), browser_context_);
+
   service->GetDisplayedNotifications(
       browser_context_,
       base::BindOnce(&PlatformNotificationContextImpl::DidGetNotifications,
                      this));
-
-  ukm_callback_ = base::BindRepeating(
-      &PlatformNotificationService::RecordNotificationUkmEvent,
-      base::Unretained(service), browser_context_);
 }
 
 void PlatformNotificationContextImpl::DidGetNotifications(
@@ -83,13 +84,10 @@
   // because flakiness may cause a platform to inform Chrome of a notification
   // that has since been closed, or because the platform does not support
   // notifications that exceed the lifetime of the browser process.
-
-  // TODO(peter): Synchronizing the actual notifications will be done when the
-  // persistent notification ids are stable. For M44 we need to support the
-  // case where there may be no notifications after a Chrome restart.
-
-  if (supports_synchronization && displayed_notifications.empty()) {
-    prune_database_on_open_ = true;
+  if (supports_synchronization) {
+    LazyInitialize(
+        base::BindOnce(&PlatformNotificationContextImpl::DoSyncNotificationData,
+                       this, std::move(displayed_notifications)));
   }
 
   // |service_worker_context_| may be NULL in tests.
@@ -97,6 +95,33 @@
     service_worker_context_->AddObserver(this);
 }
 
+void PlatformNotificationContextImpl::DoSyncNotificationData(
+    std::set<std::string> displayed_notifications,
+    bool initialized) {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  if (!initialized)
+    return;
+
+  // Iterate over all notifications and delete all expired ones. Passing |this|
+  // as Unretained is safe as this is synchronous.
+  NotificationDatabase::Status status =
+      database_->ForEachNotificationData(base::BindRepeating(
+          &PlatformNotificationContextImpl::DoHandleSyncNotification,
+          base::Unretained(this), displayed_notifications));
+
+  // Blow away the database if reading data failed due to corruption.
+  if (status == NotificationDatabase::STATUS_ERROR_CORRUPTED)
+    DestroyDatabase();
+}
+
+void PlatformNotificationContextImpl::DoHandleSyncNotification(
+    const std::set<std::string>& displayed_notifications,
+    const NotificationDatabaseData& data) {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  if (!displayed_notifications.count(data.notification_id))
+    database_->DeleteNotificationData(data.notification_id, data.origin);
+}
+
 void PlatformNotificationContextImpl::Shutdown() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
@@ -178,6 +203,55 @@
                      NotificationDatabaseData()));
 }
 
+void PlatformNotificationContextImpl::ReadNotificationResources(
+    const std::string& notification_id,
+    const GURL& origin,
+    ReadResourcesResultCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  LazyInitialize(base::BindOnce(
+      &PlatformNotificationContextImpl::DoReadNotificationResources, this,
+      notification_id, origin, std::move(callback)));
+}
+
+void PlatformNotificationContextImpl::DoReadNotificationResources(
+    const std::string& notification_id,
+    const GURL& origin,
+    ReadResourcesResultCallback callback,
+    bool initialized) {
+  DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  if (!initialized) {
+    base::PostTaskWithTraits(
+        FROM_HERE, {BrowserThread::UI, base::TaskPriority::USER_VISIBLE},
+        base::BindOnce(std::move(callback), /* success= */ false,
+                       blink::NotificationResources()));
+    return;
+  }
+
+  blink::NotificationResources notification_resources;
+  NotificationDatabase::Status status = database_->ReadNotificationResources(
+      notification_id, origin, &notification_resources);
+
+  UMA_HISTOGRAM_ENUMERATION("Notifications.Database.ReadResult", status,
+                            NotificationDatabase::STATUS_COUNT);
+
+  if (status == NotificationDatabase::STATUS_OK) {
+    base::PostTaskWithTraits(
+        FROM_HERE, {BrowserThread::UI, base::TaskPriority::USER_VISIBLE},
+        base::BindOnce(std::move(callback), /* success= */ true,
+                       notification_resources));
+    return;
+  }
+
+  // Blow away the database if reading data failed due to corruption.
+  if (status == NotificationDatabase::STATUS_ERROR_CORRUPTED)
+    DestroyDatabase();
+
+  base::PostTaskWithTraits(
+      FROM_HERE, {BrowserThread::UI, base::TaskPriority::USER_VISIBLE},
+      base::BindOnce(std::move(callback), /* success= */ false,
+                     blink::NotificationResources()));
+}
+
 void PlatformNotificationContextImpl::
     SynchronizeDisplayedNotificationsForServiceWorkerRegistration(
         const GURL& origin,
@@ -498,18 +572,6 @@
   UMA_HISTOGRAM_ENUMERATION("Notifications.Database.OpenResult", status,
                             NotificationDatabase::STATUS_COUNT);
 
-  // TODO(peter): Do finer-grained synchronization here.
-  if (prune_database_on_open_) {
-    prune_database_on_open_ = false;
-    DestroyDatabase();
-
-    database_.reset(new NotificationDatabase(GetDatabasePath(), ukm_callback_));
-    status = database_->Open(/* create_if_missing= */ true);
-
-    // TODO(peter): Find the appropriate UMA to cover in regards to
-    // synchronizing notifications after the implementation is complete.
-  }
-
   // When the database could not be opened due to corruption, destroy it, blow
   // away the contents of the directory and try re-opening the database.
   if (status == NotificationDatabase::STATUS_ERROR_CORRUPTED) {
diff --git a/content/browser/notifications/platform_notification_context_impl.h b/content/browser/notifications/platform_notification_context_impl.h
index 09fd40b..268e9ff 100644
--- a/content/browser/notifications/platform_notification_context_impl.h
+++ b/content/browser/notifications/platform_notification_context_impl.h
@@ -80,6 +80,9 @@
       const GURL& origin,
       Interaction interaction,
       ReadResultCallback callback) override;
+  void ReadNotificationResources(const std::string& notification_id,
+                                 const GURL& origin,
+                                 ReadResourcesResultCallback callback) override;
   void WriteNotificationData(int64_t persistent_notification_id,
                              int64_t service_worker_registration_id,
                              const GURL& origin,
@@ -128,6 +131,25 @@
                               ReadResultCallback callback,
                               bool initialized);
 
+  // Actually reads the notification resources from the database. Must only be
+  // called on the |task_runner_| thread. |callback| will be invoked on the
+  // UI thread when the operation has completed.
+  void DoReadNotificationResources(const std::string& notification_id,
+                                   const GURL& origin,
+                                   ReadResourcesResultCallback callback,
+                                   bool initialized);
+
+  // Synchronize displayed notifications. This removes all non-displayed
+  // notifications from the database.
+  void DoSyncNotificationData(std::set<std::string> displayed_notifications,
+                              bool initialized);
+
+  // Checks if the given notification is still valid, otherwise deletes it from
+  // the database.
+  void DoHandleSyncNotification(
+      const std::set<std::string>& displayed_notifications,
+      const NotificationDatabaseData& data);
+
   // Updates the database (and the result callback) based on
   // |displayed_notifications| if |supports_synchronization|.
   void SynchronizeDisplayedNotificationsForServiceWorkerRegistration(
@@ -197,9 +219,6 @@
 
   NotificationIdGenerator notification_id_generator_;
 
-  // Indicates whether the database should be pruned when it's opened.
-  bool prune_database_on_open_ = false;
-
   // The notification services are owned by the platform context, and will be
   // removed when either this class is destroyed or the Mojo pipe disconnects.
   std::vector<std::unique_ptr<BlinkNotificationServiceImpl>> services_;
diff --git a/content/browser/notifications/platform_notification_context_unittest.cc b/content/browser/notifications/platform_notification_context_unittest.cc
index 16c294c3..95ac6c16 100644
--- a/content/browser/notifications/platform_notification_context_unittest.cc
+++ b/content/browser/notifications/platform_notification_context_unittest.cc
@@ -104,9 +104,8 @@
   CreatePlatformNotificationContext() {
     auto context = base::MakeRefCounted<PlatformNotificationContextImpl>(
         base::FilePath(), &browser_context_, nullptr);
-    context->Initialize();
-
     OverrideTaskRunnerForTesting(context.get());
+    context->Initialize();
     return context;
   }
 
@@ -328,9 +327,8 @@
       new PlatformNotificationContextImpl(
           base::FilePath(), browser_context(),
           embedded_worker_test_helper->context_wrapper()));
-  notification_context->Initialize();
-
   OverrideTaskRunnerForTesting(notification_context.get());
+  notification_context->Initialize();
 
   GURL origin("https://example.com");
   GURL script_url("https://example.com/worker.js");
@@ -524,6 +522,8 @@
 
   scoped_refptr<PlatformNotificationContextImpl> context =
       CreatePlatformNotificationContext();
+  // Let PlatformNotificationContext synchronize displayed notifications.
+  base::RunLoop().RunUntilIdle();
 
   GURL origin("https://example.com");
   NotificationDatabaseData notification_database_data;
diff --git a/content/browser/renderer_host/web_database_host_impl_unittest.cc b/content/browser/renderer_host/web_database_host_impl_unittest.cc
index d0dc218b..7f7c77bb 100644
--- a/content/browser/renderer_host/web_database_host_impl_unittest.cc
+++ b/content/browser/renderer_host/web_database_host_impl_unittest.cc
@@ -91,6 +91,7 @@
 
   WebDatabaseHostImpl* host() { return host_.get(); }
   int process_id() const { return render_process_host_->GetID(); }
+  BrowserContext* browser_context() { return &browser_context_; }
 
  private:
   TestBrowserThreadBundle thread_bundle_;
@@ -113,8 +114,9 @@
 
   auto* security_policy = ChildProcessSecurityPolicyImpl::GetInstance();
   security_policy->AddIsolatedOrigins({correct_origin, incorrect_origin});
-  security_policy->LockToOrigin(IsolationContext(), process_id(),
-                                correct_origin.GetURL());
+
+  security_policy->LockToOrigin(IsolationContext(browser_context()),
+                                process_id(), correct_origin.GetURL());
   ASSERT_TRUE(
       security_policy->CanAccessDataForOrigin(process_id(), correct_origin));
   ASSERT_FALSE(
@@ -197,8 +199,8 @@
 
   auto* security_policy = ChildProcessSecurityPolicyImpl::GetInstance();
   security_policy->AddIsolatedOrigins({correct_origin, incorrect_origin});
-  security_policy->LockToOrigin(IsolationContext(), process_id(),
-                                correct_origin.GetURL());
+  security_policy->LockToOrigin(IsolationContext(browser_context()),
+                                process_id(), correct_origin.GetURL());
 
   bool success_callback_was_called = false;
   auto success_callback = base::BindLambdaForTesting(
diff --git a/content/browser/service_worker/embedded_worker_instance.cc b/content/browser/service_worker/embedded_worker_instance.cc
index 51f5000..b8fc921 100644
--- a/content/browser/service_worker/embedded_worker_instance.cc
+++ b/content/browser/service_worker/embedded_worker_instance.cc
@@ -19,7 +19,6 @@
 #include "content/browser/bad_message.h"
 #include "content/browser/devtools/service_worker_devtools_manager.h"
 #include "content/browser/renderer_host/render_process_host_impl.h"
-#include "content/browser/service_worker/embedded_worker_registry.h"
 #include "content/browser/service_worker/embedded_worker_status.h"
 #include "content/browser/service_worker/service_worker_content_settings_proxy_impl.h"
 #include "content/browser/service_worker/service_worker_context_core.h"
@@ -686,8 +685,6 @@
 EmbeddedWorkerInstance::~EmbeddedWorkerInstance() {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   devtools_proxy_.reset();
-  if (registry_->GetWorker(embedded_worker_id_))
-    registry_->RemoveWorker(embedded_worker_id_);
   ReleaseProcess();
 }
 
@@ -773,13 +770,10 @@
 }
 
 EmbeddedWorkerInstance::EmbeddedWorkerInstance(
-    base::WeakPtr<ServiceWorkerContextCore> context,
-    ServiceWorkerVersion* owner_version,
-    int embedded_worker_id)
-    : context_(context),
-      registry_(context->embedded_worker_registry()),
+    ServiceWorkerVersion* owner_version)
+    : context_(owner_version->context()),
       owner_version_(owner_version),
-      embedded_worker_id_(embedded_worker_id),
+      embedded_worker_id_(context_->GetNextEmbeddedWorkerId()),
       status_(EmbeddedWorkerStatus::STOPPED),
       starting_phase_(NOT_STARTING),
       restart_count_(0),
@@ -790,6 +784,7 @@
       foreground_notified_(false),
       weak_factory_(this) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK(context_);
 }
 
 void EmbeddedWorkerInstance::OnProcessAllocated(
diff --git a/content/browser/service_worker/embedded_worker_instance.h b/content/browser/service_worker/embedded_worker_instance.h
index 79be4bbe..cb9b99b 100644
--- a/content/browser/service_worker/embedded_worker_instance.h
+++ b/content/browser/service_worker/embedded_worker_instance.h
@@ -10,7 +10,6 @@
 #include <memory>
 #include <string>
 
-#include "base/callback.h"
 #include "base/callback_forward.h"
 #include "base/gtest_prod_util.h"
 #include "base/logging.h"
@@ -37,7 +36,6 @@
 
 namespace content {
 
-class EmbeddedWorkerRegistry;
 class ServiceWorkerContentSettingsProxyImpl;
 class ServiceWorkerContextCore;
 class ServiceWorkerVersion;
@@ -120,6 +118,7 @@
         const GURL& source_url) {}
   };
 
+  explicit EmbeddedWorkerInstance(ServiceWorkerVersion* owner_version);
   ~EmbeddedWorkerInstance() override;
 
   // Starts the worker. It is invalid to call this when the worker is not in
@@ -236,7 +235,6 @@
   class ScopedLifetimeTracker;
   class StartTask;
   class WorkerProcessHandle;
-  friend class EmbeddedWorkerRegistry;
   friend class EmbeddedWorkerInstanceTest;
   FRIEND_TEST_ALL_PREFIXES(EmbeddedWorkerInstanceTest, StartAndStop);
   FRIEND_TEST_ALL_PREFIXES(EmbeddedWorkerInstanceTest, DetachDuringStart);
@@ -245,12 +243,6 @@
                                ServiceWorkerNewScriptLoaderTest,
                            AccessedNetwork);
 
-  // Constructor is called via EmbeddedWorkerRegistry::CreateWorker().
-  // This instance holds a ref of |registry|.
-  EmbeddedWorkerInstance(base::WeakPtr<ServiceWorkerContextCore> context,
-                         ServiceWorkerVersion* owner_version,
-                         int embedded_worker_id);
-
   // Called back from StartTask after a process is allocated on the UI thread.
   void OnProcessAllocated(std::unique_ptr<WorkerProcessHandle> handle,
                           ServiceWorkerMetrics::StartSituation start_situation);
@@ -314,10 +306,9 @@
   void NotifyForegroundServiceWorkerRemoved();
 
   base::WeakPtr<ServiceWorkerContextCore> context_;
-  scoped_refptr<EmbeddedWorkerRegistry> registry_;
   ServiceWorkerVersion* owner_version_;
 
-  // Unique within an EmbeddedWorkerRegistry.
+  // Unique within a ServiceWorkerContextCore.
   const int embedded_worker_id_;
 
   EmbeddedWorkerStatus status_;
diff --git a/content/browser/service_worker/embedded_worker_instance_unittest.cc b/content/browser/service_worker/embedded_worker_instance_unittest.cc
index 3ccfae1..9357e7a 100644
--- a/content/browser/service_worker/embedded_worker_instance_unittest.cc
+++ b/content/browser/service_worker/embedded_worker_instance_unittest.cc
@@ -16,7 +16,6 @@
 #include "base/stl_util.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
-#include "content/browser/service_worker/embedded_worker_registry.h"
 #include "content/browser/service_worker/embedded_worker_status.h"
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
 #include "content/browser/service_worker/fake_embedded_worker_instance_client.h"
@@ -207,11 +206,6 @@
 
   ServiceWorkerContextCore* context() { return helper_->context(); }
 
-  EmbeddedWorkerRegistry* embedded_worker_registry() {
-    DCHECK(context());
-    return context()->embedded_worker_registry();
-  }
-
   // Mojo endpoints.
   std::vector<blink::mojom::ServiceWorkerPtr> service_workers_;
   std::vector<blink::mojom::ControllerServiceWorkerPtr> controllers_;
@@ -234,8 +228,7 @@
   const GURL url("http://example.com/worker.js");
 
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
-  std::unique_ptr<EmbeddedWorkerInstance> worker =
-      embedded_worker_registry()->CreateWorker(pair.second.get());
+  auto worker = std::make_unique<EmbeddedWorkerInstance>(pair.second.get());
   EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
   worker->AddObserver(this);
 
@@ -274,8 +267,7 @@
 
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
   const int64_t service_worker_version_id = pair.second->version_id();
-  std::unique_ptr<EmbeddedWorkerInstance> worker =
-      embedded_worker_registry()->CreateWorker(pair.second.get());
+  auto worker = std::make_unique<EmbeddedWorkerInstance>(pair.second.get());
   EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
 
   {
@@ -311,8 +303,7 @@
   const GURL url("http://example.com/worker.js");
 
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
-  std::unique_ptr<EmbeddedWorkerInstance> worker =
-      embedded_worker_registry()->CreateWorker(pair.second.get());
+  auto worker = std::make_unique<EmbeddedWorkerInstance>(pair.second.get());
   EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
 
   // Start the worker and then call StopIfNotAttachedToDevTools().
@@ -348,8 +339,7 @@
   const GURL url("http://example.com/worker.js");
 
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
-  std::unique_ptr<EmbeddedWorkerInstance> worker =
-      embedded_worker_registry()->CreateWorker(pair.second.get());
+  auto worker = std::make_unique<EmbeddedWorkerInstance>(pair.second.get());
   worker->AddObserver(this);
 
   // Run the start worker sequence and detach during process allocation.
@@ -377,8 +367,7 @@
   const GURL url("http://example.com/worker.js");
 
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
-  std::unique_ptr<EmbeddedWorkerInstance> worker =
-      embedded_worker_registry()->CreateWorker(pair.second.get());
+  auto worker = std::make_unique<EmbeddedWorkerInstance>(pair.second.get());
   worker->AddObserver(this);
 
   auto* client = helper_->AddNewPendingInstanceClient<
@@ -407,8 +396,7 @@
   const GURL url("http://example.com/worker.js");
 
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
-  std::unique_ptr<EmbeddedWorkerInstance> worker =
-      embedded_worker_registry()->CreateWorker(pair.second.get());
+  auto worker = std::make_unique<EmbeddedWorkerInstance>(pair.second.get());
   worker->AddObserver(this);
 
   // Stop the start worker sequence before a process is allocated.
@@ -471,8 +459,7 @@
           helper_.get(), &was_resume_after_download_called));
 
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
-  std::unique_ptr<EmbeddedWorkerInstance> worker =
-      embedded_worker_registry()->CreateWorker(pair.second.get());
+  auto worker = std::make_unique<EmbeddedWorkerInstance>(pair.second.get());
   worker->AddObserver(this);
 
   // Run the start worker sequence until pause after download.
@@ -500,8 +487,7 @@
   const GURL url("http://example.com/worker.js");
 
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
-  std::unique_ptr<EmbeddedWorkerInstance> worker =
-      embedded_worker_registry()->CreateWorker(pair.second.get());
+  auto worker = std::make_unique<EmbeddedWorkerInstance>(pair.second.get());
   worker->AddObserver(this);
 
   auto* client = helper_->AddNewPendingInstanceClient<
@@ -544,8 +530,7 @@
   const GURL url("http://example.com/worker.js");
 
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
-  std::unique_ptr<EmbeddedWorkerInstance> worker =
-      embedded_worker_registry()->CreateWorker(pair.second.get());
+  auto worker = std::make_unique<EmbeddedWorkerInstance>(pair.second.get());
 
   // Start the worker.
   StartWorker(worker.get(), CreateStartParams(pair.second));
@@ -564,8 +549,7 @@
   helper_->AddPendingInstanceClient(nullptr);
 
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
-  std::unique_ptr<EmbeddedWorkerInstance> worker =
-      embedded_worker_registry()->CreateWorker(pair.second.get());
+  auto worker = std::make_unique<EmbeddedWorkerInstance>(pair.second.get());
   worker->AddObserver(this);
 
   // Attempt to start the worker. From the browser process's point of view, the
@@ -594,8 +578,7 @@
   const GURL url("http://example.com/worker.js");
 
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
-  std::unique_ptr<EmbeddedWorkerInstance> worker =
-      embedded_worker_registry()->CreateWorker(pair.second.get());
+  auto worker = std::make_unique<EmbeddedWorkerInstance>(pair.second.get());
   worker->AddObserver(this);
 
   // Attempt to start the worker.
@@ -653,8 +636,7 @@
   const GURL scope("http://example.com/");
   const GURL url("http://example.com/worker.js");
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
-  std::unique_ptr<EmbeddedWorkerInstance> worker =
-      embedded_worker_registry()->CreateWorker(pair.second.get());
+  auto worker = std::make_unique<EmbeddedWorkerInstance>(pair.second.get());
   worker->AddObserver(this);
 
   auto* client =
@@ -722,8 +704,7 @@
   const GURL url("http://example.com/worker.js");
 
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
-  std::unique_ptr<EmbeddedWorkerInstance> worker =
-      embedded_worker_registry()->CreateWorker(pair.second.get());
+  auto worker = std::make_unique<EmbeddedWorkerInstance>(pair.second.get());
 
   // First, test a worker without pause after download.
   {
@@ -780,8 +761,7 @@
   const GURL url("http://example.com/worker.js");
 
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
-  std::unique_ptr<EmbeddedWorkerInstance> worker =
-      embedded_worker_registry()->CreateWorker(pair.second.get());
+  auto worker = std::make_unique<EmbeddedWorkerInstance>(pair.second.get());
 
   // First, test a worker without pause after download.
   {
@@ -852,8 +832,7 @@
   const GURL scope("http://example.com/");
   const GURL url("http://example.com/worker.js");
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
-  std::unique_ptr<EmbeddedWorkerInstance> worker =
-      embedded_worker_registry()->CreateWorker(pair.second.get());
+  auto worker = std::make_unique<EmbeddedWorkerInstance>(pair.second.get());
   EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, worker->status());
   worker->AddObserver(this);
 
@@ -876,8 +855,7 @@
   const GURL url("http://example.com/worker.js");
 
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
-  std::unique_ptr<EmbeddedWorkerInstance> worker =
-      embedded_worker_registry()->CreateWorker(pair.second.get());
+  auto worker = std::make_unique<EmbeddedWorkerInstance>(pair.second.get());
 
   base::HistogramTester metrics;
 
@@ -901,8 +879,7 @@
   const GURL url("http://example.com/worker.js");
 
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
-  std::unique_ptr<EmbeddedWorkerInstance> worker =
-      embedded_worker_registry()->CreateWorker(pair.second.get());
+  auto worker = std::make_unique<EmbeddedWorkerInstance>(pair.second.get());
 
   base::HistogramTester metrics;
 
@@ -932,8 +909,7 @@
   const GURL url("http://example.com/worker.js");
 
   RegistrationAndVersionPair pair = PrepareRegistrationAndVersion(scope, url);
-  std::unique_ptr<EmbeddedWorkerInstance> worker =
-      embedded_worker_registry()->CreateWorker(pair.second.get());
+  auto worker = std::make_unique<EmbeddedWorkerInstance>(pair.second.get());
 
   base::HistogramTester metrics;
 
diff --git a/content/browser/service_worker/embedded_worker_registry.cc b/content/browser/service_worker/embedded_worker_registry.cc
deleted file mode 100644
index f74289d..0000000
--- a/content/browser/service_worker/embedded_worker_registry.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/browser/service_worker/embedded_worker_registry.h"
-
-#include "content/browser/service_worker/embedded_worker_instance.h"
-#include "content/browser/service_worker/service_worker_context_core.h"
-
-namespace content {
-
-// static
-scoped_refptr<EmbeddedWorkerRegistry> EmbeddedWorkerRegistry::Create(
-    const base::WeakPtr<ServiceWorkerContextCore>& context) {
-  return base::WrapRefCounted(new EmbeddedWorkerRegistry(context, 0));
-}
-
-// static
-scoped_refptr<EmbeddedWorkerRegistry> EmbeddedWorkerRegistry::Create(
-    const base::WeakPtr<ServiceWorkerContextCore>& context,
-    EmbeddedWorkerRegistry* old_registry) {
-  scoped_refptr<EmbeddedWorkerRegistry> registry =
-      new EmbeddedWorkerRegistry(
-          context,
-          old_registry->next_embedded_worker_id_);
-  return registry;
-}
-
-std::unique_ptr<EmbeddedWorkerInstance> EmbeddedWorkerRegistry::CreateWorker(
-    ServiceWorkerVersion* owner_version) {
-  std::unique_ptr<EmbeddedWorkerInstance> worker(new EmbeddedWorkerInstance(
-      context_, owner_version, next_embedded_worker_id_));
-  worker_map_[next_embedded_worker_id_++] = worker.get();
-  return worker;
-}
-
-EmbeddedWorkerInstance* EmbeddedWorkerRegistry::GetWorker(
-    int embedded_worker_id) {
-  auto found = worker_map_.find(embedded_worker_id);
-  if (found == worker_map_.end())
-    return nullptr;
-  return found->second;
-}
-
-EmbeddedWorkerRegistry::EmbeddedWorkerRegistry(
-    const base::WeakPtr<ServiceWorkerContextCore>& context,
-    int initial_embedded_worker_id)
-    : context_(context),
-      next_embedded_worker_id_(initial_embedded_worker_id),
-      initial_embedded_worker_id_(initial_embedded_worker_id) {
-}
-
-EmbeddedWorkerRegistry::~EmbeddedWorkerRegistry() = default;
-
-void EmbeddedWorkerRegistry::RemoveWorker(int embedded_worker_id) {
-  DCHECK(base::ContainsKey(worker_map_, embedded_worker_id));
-  worker_map_.erase(embedded_worker_id);
-}
-
-}  // namespace content
diff --git a/content/browser/service_worker/embedded_worker_registry.h b/content/browser/service_worker/embedded_worker_registry.h
deleted file mode 100644
index a9b4d8b..0000000
--- a/content/browser/service_worker/embedded_worker_registry.h
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_BROWSER_SERVICE_WORKER_EMBEDDED_WORKER_REGISTRY_H_
-#define CONTENT_BROWSER_SERVICE_WORKER_EMBEDDED_WORKER_REGISTRY_H_
-
-#include <map>
-#include <memory>
-
-#include "base/gtest_prod_util.h"
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/weak_ptr.h"
-#include "content/common/content_export.h"
-
-namespace content {
-
-class EmbeddedWorkerInstance;
-class ServiceWorkerContextCore;
-class ServiceWorkerVersion;
-
-// Hangs off ServiceWorkerContextCore (its reference is also held by each
-// EmbeddedWorkerInstance).  Operated only on IO thread.
-class CONTENT_EXPORT EmbeddedWorkerRegistry
-    : public base::RefCounted<EmbeddedWorkerRegistry> {
- public:
-  static scoped_refptr<EmbeddedWorkerRegistry> Create(
-      const base::WeakPtr<ServiceWorkerContextCore>& context);
-
-  // Used for DeleteAndStartOver. Creates a new registry which takes over
-  // |next_embedded_worker_id_| and |process_sender_map_| from |old_registry|.
-  static scoped_refptr<EmbeddedWorkerRegistry> Create(
-      const base::WeakPtr<ServiceWorkerContextCore>& context,
-      EmbeddedWorkerRegistry* old_registry);
-
-  // Creates and removes a new worker instance entry for bookkeeping.
-  // This doesn't actually start or stop the worker.
-  std::unique_ptr<EmbeddedWorkerInstance> CreateWorker(
-      ServiceWorkerVersion* owner_version);
-
-  // Returns an embedded worker instance for given |embedded_worker_id|.
-  EmbeddedWorkerInstance* GetWorker(int embedded_worker_id);
-
- private:
-  friend class base::RefCounted<EmbeddedWorkerRegistry>;
-  friend class EmbeddedWorkerInstance;
-  friend class EmbeddedWorkerInstanceTest;
-  FRIEND_TEST_ALL_PREFIXES(EmbeddedWorkerInstanceTest,
-                           RemoveWorkerInSharedProcess);
-
-  using WorkerInstanceMap = std::map<int, EmbeddedWorkerInstance*>;
-
-  EmbeddedWorkerRegistry(
-      const base::WeakPtr<ServiceWorkerContextCore>& context,
-      int initial_embedded_worker_id);
-  ~EmbeddedWorkerRegistry();
-
-  // RemoveWorker is called when EmbeddedWorkerInstance is destructed.
-  void RemoveWorker(int embedded_worker_id);
-
-  base::WeakPtr<ServiceWorkerContextCore> context_;
-
-  WorkerInstanceMap worker_map_;
-
-  int next_embedded_worker_id_;
-  const int initial_embedded_worker_id_;
-
-  DISALLOW_COPY_AND_ASSIGN(EmbeddedWorkerRegistry);
-};
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_SERVICE_WORKER_EMBEDDED_WORKER_REGISTRY_H_
diff --git a/content/browser/service_worker/service_worker_browsertest.cc b/content/browser/service_worker/service_worker_browsertest.cc
index a87fb9c..1836ca82 100644
--- a/content/browser/service_worker/service_worker_browsertest.cc
+++ b/content/browser/service_worker/service_worker_browsertest.cc
@@ -36,7 +36,6 @@
 #include "content/browser/cache_storage/cache_storage_context_impl.h"
 #include "content/browser/cache_storage/cache_storage_manager.h"
 #include "content/browser/service_worker/embedded_worker_instance.h"
-#include "content/browser/service_worker/embedded_worker_registry.h"
 #include "content/browser/service_worker/embedded_worker_status.h"
 #include "content/browser/service_worker/service_worker_context_core.h"
 #include "content/browser/service_worker/service_worker_context_core_observer.h"
diff --git a/content/browser/service_worker/service_worker_context_core.cc b/content/browser/service_worker/service_worker_context_core.cc
index dc2a1f6f..fe1e4e4 100644
--- a/content/browser/service_worker/service_worker_context_core.cc
+++ b/content/browser/service_worker/service_worker_context_core.cc
@@ -21,7 +21,6 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "content/browser/frame_host/render_frame_host_impl.h"
 #include "content/browser/log_console_message.h"
-#include "content/browser/service_worker/embedded_worker_registry.h"
 #include "content/browser/service_worker/embedded_worker_status.h"
 #include "content/browser/service_worker/service_worker_consts.h"
 #include "content/browser/service_worker/service_worker_context_core_observer.h"
@@ -297,7 +296,6 @@
   storage_ = ServiceWorkerStorage::Create(
       user_data_directory, AsWeakPtr(), std::move(database_task_runner),
       quota_manager_proxy, special_storage_policy);
-  embedded_worker_registry_ = EmbeddedWorkerRegistry::Create(AsWeakPtr());
   job_coordinator_ = std::make_unique<ServiceWorkerJobCoordinator>(AsWeakPtr());
 }
 
@@ -311,15 +309,13 @@
       was_service_worker_registered_(
           old_context->was_service_worker_registered_),
       observer_list_(old_context->observer_list_),
+      next_embedded_worker_id_(old_context->next_embedded_worker_id_),
       weak_factory_(this) {
   DCHECK(observer_list_);
 
   // These get a WeakPtr from |weak_factory_|, so must be set after
   // |weak_factory_| is initialized.
   storage_ = ServiceWorkerStorage::Create(AsWeakPtr(), old_context->storage());
-  embedded_worker_registry_ = EmbeddedWorkerRegistry::Create(
-      AsWeakPtr(),
-      old_context->embedded_worker_registry());
   job_coordinator_ = std::make_unique<ServiceWorkerJobCoordinator>(AsWeakPtr());
 }
 
@@ -541,6 +537,10 @@
   }
 }
 
+int ServiceWorkerContextCore::GetNextEmbeddedWorkerId() {
+  return next_embedded_worker_id_++;
+}
+
 ServiceWorkerContextCore::ProviderMap*
 ServiceWorkerContextCore::GetProviderMapForProcess(int process_id) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
diff --git a/content/browser/service_worker/service_worker_context_core.h b/content/browser/service_worker/service_worker_context_core.h
index 1ffb335..1da46ff 100644
--- a/content/browser/service_worker/service_worker_context_core.h
+++ b/content/browser/service_worker/service_worker_context_core.h
@@ -40,7 +40,6 @@
 
 namespace content {
 
-class EmbeddedWorkerRegistry;
 class ServiceWorkerContextCoreObserver;
 class ServiceWorkerContextWrapper;
 class ServiceWorkerJobCoordinator;
@@ -157,9 +156,6 @@
   ServiceWorkerContextWrapper* wrapper() const { return wrapper_; }
   ServiceWorkerStorage* storage() { return storage_.get(); }
   ServiceWorkerProcessManager* process_manager();
-  EmbeddedWorkerRegistry* embedded_worker_registry() {
-    return embedded_worker_registry_.get();
-  }
   ServiceWorkerJobCoordinator* job_coordinator() {
     return job_coordinator_.get();
   }
@@ -294,6 +290,8 @@
     return weak_factory_.GetWeakPtr();
   }
 
+  int GetNextEmbeddedWorkerId();
+
  private:
   friend class ServiceWorkerContextCoreTest;
   FRIEND_TEST_ALL_PREFIXES(ServiceWorkerContextCoreTest, FailureInfo);
@@ -354,7 +352,6 @@
   std::unique_ptr<ProviderByClientUUIDMap> provider_by_uuid_;
 
   std::unique_ptr<ServiceWorkerStorage> storage_;
-  scoped_refptr<EmbeddedWorkerRegistry> embedded_worker_registry_;
   std::unique_ptr<ServiceWorkerJobCoordinator> job_coordinator_;
   std::map<int64_t, ServiceWorkerRegistration*> live_registrations_;
   std::map<int64_t, ServiceWorkerVersion*> live_versions_;
@@ -374,6 +371,8 @@
       base::ObserverListThreadSafe<ServiceWorkerContextCoreObserver>;
   const scoped_refptr<ServiceWorkerContextObserverList> observer_list_;
 
+  int next_embedded_worker_id_ = 0;
+
   base::WeakPtrFactory<ServiceWorkerContextCore> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerContextCore);
diff --git a/content/browser/service_worker/service_worker_context_unittest.cc b/content/browser/service_worker/service_worker_context_unittest.cc
index 1a23625..ae6b513 100644
--- a/content/browser/service_worker/service_worker_context_unittest.cc
+++ b/content/browser/service_worker/service_worker_context_unittest.cc
@@ -11,7 +11,6 @@
 #include "base/logging.h"
 #include "base/run_loop.h"
 #include "base/time/time.h"
-#include "content/browser/service_worker/embedded_worker_registry.h"
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
 #include "content/browser/service_worker/fake_embedded_worker_instance_client.h"
 #include "content/browser/service_worker/service_worker_context_core.h"
diff --git a/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc b/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc
index f545233..c575f7a 100644
--- a/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc
+++ b/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc
@@ -16,7 +16,6 @@
 #include "base/task/post_task.h"
 #include "base/time/time.h"
 #include "content/browser/service_worker/embedded_worker_instance.h"
-#include "content/browser/service_worker/embedded_worker_registry.h"
 #include "content/browser/service_worker/embedded_worker_status.h"
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
 #include "content/browser/service_worker/service_worker_context_core.h"
diff --git a/content/browser/service_worker/service_worker_job_unittest.cc b/content/browser/service_worker/service_worker_job_unittest.cc
index b4f80ea0..4ca41eb 100644
--- a/content/browser/service_worker/service_worker_job_unittest.cc
+++ b/content/browser/service_worker/service_worker_job_unittest.cc
@@ -14,7 +14,6 @@
 #include "base/stl_util.h"
 #include "base/test/test_simple_task_runner.h"
 #include "base/time/time.h"
-#include "content/browser/service_worker/embedded_worker_registry.h"
 #include "content/browser/service_worker/embedded_worker_status.h"
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
 #include "content/browser/service_worker/fake_embedded_worker_instance_client.h"
diff --git a/content/browser/service_worker/service_worker_object_host_unittest.cc b/content/browser/service_worker/service_worker_object_host_unittest.cc
index 34318811..527a8f153 100644
--- a/content/browser/service_worker/service_worker_object_host_unittest.cc
+++ b/content/browser/service_worker/service_worker_object_host_unittest.cc
@@ -10,7 +10,6 @@
 #include "base/macros.h"
 #include "base/run_loop.h"
 #include "base/test/simple_test_tick_clock.h"
-#include "content/browser/service_worker/embedded_worker_registry.h"
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
 #include "content/browser/service_worker/fake_embedded_worker_instance_client.h"
 #include "content/browser/service_worker/service_worker_context_core.h"
diff --git a/content/browser/service_worker/service_worker_version.cc b/content/browser/service_worker/service_worker_version.cc
index 56b725f..c6d61cc8 100644
--- a/content/browser/service_worker/service_worker_version.cc
+++ b/content/browser/service_worker/service_worker_version.cc
@@ -26,7 +26,6 @@
 #include "base/time/default_tick_clock.h"
 #include "content/browser/bad_message.h"
 #include "content/browser/child_process_security_policy_impl.h"
-#include "content/browser/service_worker/embedded_worker_registry.h"
 #include "content/browser/service_worker/payment_handler_support.h"
 #include "content/browser/service_worker/service_worker_context_core.h"
 #include "content/browser/service_worker/service_worker_context_wrapper.h"
@@ -250,7 +249,7 @@
   DCHECK(context_);
   DCHECK(registration);
   DCHECK(script_url_.is_valid());
-  embedded_worker_ = context_->embedded_worker_registry()->CreateWorker(this);
+  embedded_worker_ = std::make_unique<EmbeddedWorkerInstance>(this);
   embedded_worker_->AddObserver(this);
   context_->AddLiveVersion(this);
 }
@@ -1750,7 +1749,7 @@
     scoped_refptr<ServiceWorkerVersion> protect_this(this);
     embedded_worker_->RemoveObserver(this);
     embedded_worker_->Detach();
-    embedded_worker_ = context_->embedded_worker_registry()->CreateWorker(this);
+    embedded_worker_ = std::make_unique<EmbeddedWorkerInstance>(this);
     embedded_worker_->AddObserver(this);
 
     // Call OnStoppedInternal to fail callbacks and possibly restart.
diff --git a/content/browser/service_worker/service_worker_version_unittest.cc b/content/browser/service_worker/service_worker_version_unittest.cc
index aed22e8..e2537e9 100644
--- a/content/browser/service_worker/service_worker_version_unittest.cc
+++ b/content/browser/service_worker/service_worker_version_unittest.cc
@@ -18,7 +18,6 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/simple_test_tick_clock.h"
 #include "base/time/time.h"
-#include "content/browser/service_worker/embedded_worker_registry.h"
 #include "content/browser/service_worker/embedded_worker_status.h"
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
 #include "content/browser/service_worker/fake_embedded_worker_instance_client.h"
diff --git a/content/browser/site_instance_impl.cc b/content/browser/site_instance_impl.cc
index eb202b8..bbeebe7 100644
--- a/content/browser/site_instance_impl.cc
+++ b/content/browser/site_instance_impl.cc
@@ -481,7 +481,8 @@
   // IsolationContext to be passed in, and this implementation should just
   // become SiteInstanceImpl::GetSiteForURL.
   return SiteInstanceImpl::GetSiteForURL(
-      BrowserOrResourceContext(browser_context), IsolationContext(), url,
+      BrowserOrResourceContext(browser_context),
+      IsolationContext(browser_context), url,
       true /* should_use_effective_urls */);
 }
 
diff --git a/content/browser/site_instance_impl.h b/content/browser/site_instance_impl.h
index da5a0ef..1acc3ef 100644
--- a/content/browser/site_instance_impl.h
+++ b/content/browser/site_instance_impl.h
@@ -137,6 +137,10 @@
   // |should_use_effective_urls| defaults to true and specifies whether to
   // resolve |url| to an effective URL (via
   // ContentBrowserClient::GetEffectiveURL()) before determining the site.
+  //
+  // TODO(alexmos): |isolation_context| now also carries a
+  // BrowserOrResourceContext, so |context| should be removed here and in
+  // similar functions.
   static GURL GetSiteForURL(const BrowserOrResourceContext& context,
                             const IsolationContext& isolation_context,
                             const GURL& url,
diff --git a/content/browser/site_instance_impl_unittest.cc b/content/browser/site_instance_impl_unittest.cc
index a70a674..33f16dd 100644
--- a/content/browser/site_instance_impl_unittest.cc
+++ b/content/browser/site_instance_impl_unittest.cc
@@ -54,20 +54,11 @@
 bool IsSameWebSite(BrowserContext* context,
                    const GURL& url1,
                    const GURL& url2) {
-  return SiteInstanceImpl::IsSameWebSite(context, IsolationContext(), url1,
-                                         url2,
+  return SiteInstanceImpl::IsSameWebSite(context, IsolationContext(context),
+                                         url1, url2,
                                          true /* should_use_effective_urls */);
 }
 
-bool IsIsolatedOrigin(const GURL& url) {
-  // It's fine to use an IsolationContext without an associated
-  // BrowsingInstance, since this helper is used by tests that deal with
-  // globally isolated origins.
-  IsolationContext isolation_context;
-  auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
-  return policy->IsIsolatedOrigin(isolation_context, url::Origin::Create(url));
-}
-
 }  // namespace
 
 const char kPrivilegedScheme[] = "privileged";
@@ -172,8 +163,21 @@
 
   SiteInstanceTestBrowserClient* browser_client() { return &browser_client_; }
 
+  bool IsIsolatedOrigin(const GURL& url) {
+    // It's fine to use an IsolationContext without an associated
+    // BrowsingInstance, since this helper is used by tests that deal with
+    // globally isolated origins.
+    IsolationContext isolation_context(&context_);
+    auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
+    return policy->IsIsolatedOrigin(isolation_context,
+                                    url::Origin::Create(url));
+  }
+
+  BrowserContext* context() { return &context_; }
+
  private:
   TestBrowserThreadBundle test_browser_thread_bundle_;
+  TestBrowserContext context_;
 
   SiteInstanceTestBrowserClient browser_client_;
   ContentBrowserClient* old_browser_client_;
@@ -433,7 +437,7 @@
   ContentBrowserClient* regular_client =
       SetBrowserClientForTesting(&modified_client);
   std::unique_ptr<TestBrowserContext> browser_context(new TestBrowserContext());
-  IsolationContext isolation_context;
+  IsolationContext isolation_context(browser_context.get());
 
   // Sanity check that GetSiteForURL's |use_effective_urls| option works
   // properly.  When it's true, the site URL should include both the effective
@@ -904,10 +908,9 @@
   GURL isolated_bar_url("http://isolated.bar.com");
 
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
-  TestBrowserContext context;
 
   EXPECT_FALSE(IsIsolatedOrigin(isolated_foo_url));
-  EXPECT_TRUE(IsSameWebSite(&context, foo_url, isolated_foo_url));
+  EXPECT_TRUE(IsSameWebSite(context(), foo_url, isolated_foo_url));
 
   policy->AddIsolatedOrigins({url::Origin::Create(isolated_foo_url)});
   EXPECT_TRUE(IsIsolatedOrigin(isolated_foo_url));
@@ -928,47 +931,48 @@
 
   // IsSameWebSite should compare origins rather than sites if either URL is an
   // isolated origin.
-  EXPECT_FALSE(IsSameWebSite(&context, foo_url, isolated_foo_url));
-  EXPECT_FALSE(IsSameWebSite(&context, isolated_foo_url, foo_url));
-  EXPECT_FALSE(IsSameWebSite(&context, isolated_foo_url, isolated_bar_url));
-  EXPECT_TRUE(IsSameWebSite(&context, isolated_foo_url, isolated_foo_url));
+  EXPECT_FALSE(IsSameWebSite(context(), foo_url, isolated_foo_url));
+  EXPECT_FALSE(IsSameWebSite(context(), isolated_foo_url, foo_url));
+  EXPECT_FALSE(IsSameWebSite(context(), isolated_foo_url, isolated_bar_url));
+  EXPECT_TRUE(IsSameWebSite(context(), isolated_foo_url, isolated_foo_url));
 
   // Ensure blob and filesystem URLs with isolated origins are compared
   // correctly.
   GURL isolated_blob_foo_url("blob:http://isolated.foo.com/uuid");
-  EXPECT_TRUE(IsSameWebSite(&context, isolated_foo_url, isolated_blob_foo_url));
+  EXPECT_TRUE(
+      IsSameWebSite(context(), isolated_foo_url, isolated_blob_foo_url));
   GURL isolated_filesystem_foo_url("filesystem:http://isolated.foo.com/bar/");
   EXPECT_TRUE(
-      IsSameWebSite(&context, isolated_foo_url, isolated_filesystem_foo_url));
+      IsSameWebSite(context(), isolated_foo_url, isolated_filesystem_foo_url));
 
   // The site URL for an isolated origin should be the full origin rather than
   // eTLD+1.
-  IsolationContext isolation_context;
+  IsolationContext isolation_context(context());
   EXPECT_EQ(isolated_foo_url,
-            SiteInstanceImpl::GetSiteForURL(&context, isolation_context,
+            SiteInstanceImpl::GetSiteForURL(context(), isolation_context,
                                             isolated_foo_url));
   EXPECT_EQ(isolated_foo_url, SiteInstanceImpl::GetSiteForURL(
-                                  &context, isolation_context,
+                                  context(), isolation_context,
                                   GURL("http://isolated.foo.com:12345")));
   EXPECT_EQ(isolated_bar_url,
-            SiteInstanceImpl::GetSiteForURL(&context, isolation_context,
+            SiteInstanceImpl::GetSiteForURL(context(), isolation_context,
                                             isolated_bar_url));
   EXPECT_EQ(isolated_foo_url,
-            SiteInstanceImpl::GetSiteForURL(&context, isolation_context,
+            SiteInstanceImpl::GetSiteForURL(context(), isolation_context,
                                             isolated_blob_foo_url));
   EXPECT_EQ(isolated_foo_url,
-            SiteInstanceImpl::GetSiteForURL(&context, isolation_context,
+            SiteInstanceImpl::GetSiteForURL(context(), isolation_context,
                                             isolated_filesystem_foo_url));
 
   // Isolated origins always require a dedicated process.
   EXPECT_TRUE(SiteInstanceImpl::DoesSiteRequireDedicatedProcess(
-      &context, isolation_context, isolated_foo_url));
+      context(), isolation_context, isolated_foo_url));
   EXPECT_TRUE(SiteInstanceImpl::DoesSiteRequireDedicatedProcess(
-      &context, isolation_context, isolated_bar_url));
+      context(), isolation_context, isolated_bar_url));
   EXPECT_TRUE(SiteInstanceImpl::DoesSiteRequireDedicatedProcess(
-      &context, isolation_context, isolated_blob_foo_url));
+      context(), isolation_context, isolated_blob_foo_url));
   EXPECT_TRUE(SiteInstanceImpl::DoesSiteRequireDedicatedProcess(
-      &context, isolation_context, isolated_filesystem_foo_url));
+      context(), isolation_context, isolated_filesystem_foo_url));
 
   // Cleanup.
   policy->RemoveIsolatedOriginForTesting(url::Origin::Create(isolated_foo_url));
@@ -980,7 +984,6 @@
   GURL isolated_foo_with_port("http://isolated.foo.com:12345");
 
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
-  TestBrowserContext context;
 
   {
     base::test::MockLog mock_log;
@@ -998,12 +1001,12 @@
   EXPECT_TRUE(IsIsolatedOrigin(isolated_foo_url));
   EXPECT_TRUE(IsIsolatedOrigin(isolated_foo_with_port));
 
-  IsolationContext isolation_context;
+  IsolationContext isolation_context(context());
   EXPECT_EQ(isolated_foo_url,
-            SiteInstanceImpl::GetSiteForURL(&context, isolation_context,
+            SiteInstanceImpl::GetSiteForURL(context(), isolation_context,
                                             isolated_foo_url));
   EXPECT_EQ(isolated_foo_url,
-            SiteInstanceImpl::GetSiteForURL(&context, isolation_context,
+            SiteInstanceImpl::GetSiteForURL(context(), isolation_context,
                                             isolated_foo_with_port));
 
   // Cleanup.
@@ -1054,7 +1057,6 @@
   GURL isolated_url("http://isolated.com");
   GURL foo_isolated_url("http://foo.isolated.com");
 
-  TestBrowserContext context;
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
   policy->AddIsolatedOrigins({url::Origin::Create(isolated_url)});
 
@@ -1074,15 +1076,15 @@
   // A new SiteInstance created for a subdomain on an isolated origin
   // should use the isolated origin's host and not its own host as the site
   // URL.
-  IsolationContext isolation_context;
+  IsolationContext isolation_context(context());
   EXPECT_EQ(isolated_url, SiteInstanceImpl::GetSiteForURL(
-                              &context, isolation_context, foo_isolated_url));
+                              context(), isolation_context, foo_isolated_url));
 
   EXPECT_TRUE(SiteInstanceImpl::DoesSiteRequireDedicatedProcess(
-      &context, isolation_context, foo_isolated_url));
+      context(), isolation_context, foo_isolated_url));
 
-  EXPECT_TRUE(IsSameWebSite(&context, isolated_url, foo_isolated_url));
-  EXPECT_TRUE(IsSameWebSite(&context, foo_isolated_url, isolated_url));
+  EXPECT_TRUE(IsSameWebSite(context(), isolated_url, foo_isolated_url));
+  EXPECT_TRUE(IsSameWebSite(context(), foo_isolated_url, isolated_url));
 
   // Don't try to match subdomains on IP addresses.
   GURL isolated_ip("http://127.0.0.1");
@@ -1101,7 +1103,6 @@
   GURL bar_isolated_foo_url("http://bar.isolated.foo.com");
   GURL baz_isolated_foo_url("http://baz.isolated.foo.com");
 
-  TestBrowserContext context;
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
   policy->AddIsolatedOrigins({url::Origin::Create(isolated_foo_url)});
 
@@ -1110,40 +1111,40 @@
   EXPECT_TRUE(IsIsolatedOrigin(bar_isolated_foo_url));
   EXPECT_TRUE(IsIsolatedOrigin(baz_isolated_foo_url));
 
-  IsolationContext isolation_context;
+  IsolationContext isolation_context(context());
   EXPECT_EQ(foo_url, SiteInstanceImpl::GetSiteForURL(
-                         &context, isolation_context, foo_url));
+                         context(), isolation_context, foo_url));
   EXPECT_EQ(isolated_foo_url,
-            SiteInstanceImpl::GetSiteForURL(&context, isolation_context,
+            SiteInstanceImpl::GetSiteForURL(context(), isolation_context,
                                             isolated_foo_url));
   EXPECT_EQ(isolated_foo_url,
-            SiteInstanceImpl::GetSiteForURL(&context, isolation_context,
+            SiteInstanceImpl::GetSiteForURL(context(), isolation_context,
                                             bar_isolated_foo_url));
   EXPECT_EQ(isolated_foo_url,
-            SiteInstanceImpl::GetSiteForURL(&context, isolation_context,
+            SiteInstanceImpl::GetSiteForURL(context(), isolation_context,
                                             baz_isolated_foo_url));
 
   if (!AreAllSitesIsolatedForTesting()) {
     EXPECT_FALSE(SiteInstanceImpl::DoesSiteRequireDedicatedProcess(
-        &context, isolation_context, foo_url));
+        context(), isolation_context, foo_url));
   }
   EXPECT_TRUE(SiteInstanceImpl::DoesSiteRequireDedicatedProcess(
-      &context, isolation_context, isolated_foo_url));
+      context(), isolation_context, isolated_foo_url));
   EXPECT_TRUE(SiteInstanceImpl::DoesSiteRequireDedicatedProcess(
-      &context, isolation_context, bar_isolated_foo_url));
+      context(), isolation_context, bar_isolated_foo_url));
   EXPECT_TRUE(SiteInstanceImpl::DoesSiteRequireDedicatedProcess(
-      &context, isolation_context, baz_isolated_foo_url));
+      context(), isolation_context, baz_isolated_foo_url));
 
-  EXPECT_FALSE(IsSameWebSite(&context, foo_url, isolated_foo_url));
-  EXPECT_FALSE(IsSameWebSite(&context, isolated_foo_url, foo_url));
-  EXPECT_FALSE(IsSameWebSite(&context, foo_url, bar_isolated_foo_url));
-  EXPECT_FALSE(IsSameWebSite(&context, bar_isolated_foo_url, foo_url));
-  EXPECT_TRUE(IsSameWebSite(&context, bar_isolated_foo_url, isolated_foo_url));
-  EXPECT_TRUE(IsSameWebSite(&context, isolated_foo_url, bar_isolated_foo_url));
+  EXPECT_FALSE(IsSameWebSite(context(), foo_url, isolated_foo_url));
+  EXPECT_FALSE(IsSameWebSite(context(), isolated_foo_url, foo_url));
+  EXPECT_FALSE(IsSameWebSite(context(), foo_url, bar_isolated_foo_url));
+  EXPECT_FALSE(IsSameWebSite(context(), bar_isolated_foo_url, foo_url));
+  EXPECT_TRUE(IsSameWebSite(context(), bar_isolated_foo_url, isolated_foo_url));
+  EXPECT_TRUE(IsSameWebSite(context(), isolated_foo_url, bar_isolated_foo_url));
   EXPECT_TRUE(
-      IsSameWebSite(&context, bar_isolated_foo_url, baz_isolated_foo_url));
+      IsSameWebSite(context(), bar_isolated_foo_url, baz_isolated_foo_url));
   EXPECT_TRUE(
-      IsSameWebSite(&context, baz_isolated_foo_url, bar_isolated_foo_url));
+      IsSameWebSite(context(), baz_isolated_foo_url, bar_isolated_foo_url));
 
   // Cleanup.
   policy->RemoveIsolatedOriginForTesting(url::Origin::Create(isolated_foo_url));
@@ -1155,8 +1156,7 @@
   GURL baz_bar_foo_url("http://baz.bar.foo.com");
   GURL qux_baz_bar_foo_url("http://qux.baz.bar.foo.com");
 
-  TestBrowserContext context;
-  IsolationContext isolation_context;
+  IsolationContext isolation_context(context());
   auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
   policy->AddIsolatedOrigins(
       {url::Origin::Create(foo_url), url::Origin::Create(baz_bar_foo_url)});
@@ -1167,32 +1167,33 @@
   EXPECT_TRUE(IsIsolatedOrigin(qux_baz_bar_foo_url));
 
   EXPECT_EQ(foo_url, SiteInstanceImpl::GetSiteForURL(
-                         &context, isolation_context, foo_url));
+                         context(), isolation_context, foo_url));
   EXPECT_EQ(foo_url, SiteInstanceImpl::GetSiteForURL(
-                         &context, isolation_context, bar_foo_url));
-  EXPECT_EQ(baz_bar_foo_url, SiteInstanceImpl::GetSiteForURL(
-                                 &context, isolation_context, baz_bar_foo_url));
+                         context(), isolation_context, bar_foo_url));
   EXPECT_EQ(baz_bar_foo_url,
-            SiteInstanceImpl::GetSiteForURL(&context, isolation_context,
+            SiteInstanceImpl::GetSiteForURL(context(), isolation_context,
+                                            baz_bar_foo_url));
+  EXPECT_EQ(baz_bar_foo_url,
+            SiteInstanceImpl::GetSiteForURL(context(), isolation_context,
                                             qux_baz_bar_foo_url));
 
   EXPECT_TRUE(SiteInstanceImpl::DoesSiteRequireDedicatedProcess(
-      &context, isolation_context, foo_url));
+      context(), isolation_context, foo_url));
   EXPECT_TRUE(SiteInstanceImpl::DoesSiteRequireDedicatedProcess(
-      &context, isolation_context, bar_foo_url));
+      context(), isolation_context, bar_foo_url));
   EXPECT_TRUE(SiteInstanceImpl::DoesSiteRequireDedicatedProcess(
-      &context, isolation_context, baz_bar_foo_url));
+      context(), isolation_context, baz_bar_foo_url));
   EXPECT_TRUE(SiteInstanceImpl::DoesSiteRequireDedicatedProcess(
-      &context, isolation_context, qux_baz_bar_foo_url));
+      context(), isolation_context, qux_baz_bar_foo_url));
 
-  EXPECT_TRUE(IsSameWebSite(&context, foo_url, bar_foo_url));
-  EXPECT_FALSE(IsSameWebSite(&context, foo_url, baz_bar_foo_url));
-  EXPECT_FALSE(IsSameWebSite(&context, foo_url, qux_baz_bar_foo_url));
+  EXPECT_TRUE(IsSameWebSite(context(), foo_url, bar_foo_url));
+  EXPECT_FALSE(IsSameWebSite(context(), foo_url, baz_bar_foo_url));
+  EXPECT_FALSE(IsSameWebSite(context(), foo_url, qux_baz_bar_foo_url));
 
-  EXPECT_FALSE(IsSameWebSite(&context, bar_foo_url, baz_bar_foo_url));
-  EXPECT_FALSE(IsSameWebSite(&context, bar_foo_url, qux_baz_bar_foo_url));
+  EXPECT_FALSE(IsSameWebSite(context(), bar_foo_url, baz_bar_foo_url));
+  EXPECT_FALSE(IsSameWebSite(context(), bar_foo_url, qux_baz_bar_foo_url));
 
-  EXPECT_TRUE(IsSameWebSite(&context, baz_bar_foo_url, qux_baz_bar_foo_url));
+  EXPECT_TRUE(IsSameWebSite(context(), baz_bar_foo_url, qux_baz_bar_foo_url));
 
   // Cleanup.
   policy->RemoveIsolatedOriginForTesting(url::Origin::Create(foo_url));
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index 77d9e9d..c85dd4b 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -938,8 +938,7 @@
 
 void StoragePartitionImpl::OpenLocalStorage(
     const url::Origin& origin,
-    blink::mojom::StorageAreaRequest request,
-    OpenLocalStorageCallback callback) {
+    blink::mojom::StorageAreaRequest request) {
   int process_id = bindings_.dispatch_context();
   if (!ChildProcessSecurityPolicyImpl::GetInstance()->CanAccessDataForOrigin(
           process_id, origin)) {
@@ -947,18 +946,16 @@
     bindings_.ReportBadMessage("Access denied for localStorage request");
     return;
   }
-  dom_storage_context_->OpenLocalStorage(origin, std::move(request),
-                                         std::move(callback));
+  dom_storage_context_->OpenLocalStorage(origin, std::move(request));
 }
 
 void StoragePartitionImpl::OpenSessionStorage(
     const std::string& namespace_id,
-    blink::mojom::SessionStorageNamespaceRequest request,
-    OpenSessionStorageCallback callback) {
+    blink::mojom::SessionStorageNamespaceRequest request) {
   int process_id = bindings_.dispatch_context();
-  dom_storage_context_->OpenSessionStorage(
-      process_id, namespace_id, bindings_.GetBadMessageCallback(),
-      std::move(request), std::move(callback));
+  dom_storage_context_->OpenSessionStorage(process_id, namespace_id,
+                                           bindings_.GetBadMessageCallback(),
+                                           std::move(request));
 }
 
 void StoragePartitionImpl::OnCanSendReportingReports(
diff --git a/content/browser/storage_partition_impl.h b/content/browser/storage_partition_impl.h
index 464734fe..84aa3aa2 100644
--- a/content/browser/storage_partition_impl.h
+++ b/content/browser/storage_partition_impl.h
@@ -156,11 +156,10 @@
 
   // blink::mojom::StoragePartitionService interface.
   void OpenLocalStorage(const url::Origin& origin,
-                        blink::mojom::StorageAreaRequest request,
-                        OpenLocalStorageCallback callback) override;
-  void OpenSessionStorage(const std::string& namespace_id,
-                          blink::mojom::SessionStorageNamespaceRequest request,
-                          OpenSessionStorageCallback callback) override;
+                        blink::mojom::StorageAreaRequest request) override;
+  void OpenSessionStorage(
+      const std::string& namespace_id,
+      blink::mojom::SessionStorageNamespaceRequest request) override;
 
   // network::mojom::NetworkContextClient interface.
   void OnCanSendReportingReports(
diff --git a/content/browser/tracing/background_tracing_manager_browsertest.cc b/content/browser/tracing/background_tracing_manager_browsertest.cc
index c36572c..db59711 100644
--- a/content/browser/tracing/background_tracing_manager_browsertest.cc
+++ b/content/browser/tracing/background_tracing_manager_browsertest.cc
@@ -400,31 +400,12 @@
   EXPECT_TRUE(trace_receiver_helper.trace_received());
 }
 
-namespace {
-
-bool IsTraceEventArgsWhitelisted(
-    const char* category_group_name,
-    const char* event_name,
-    base::trace_event::ArgumentNameFilterPredicate* arg_filter) {
-  if (base::MatchPattern(category_group_name, "benchmark") &&
-      base::MatchPattern(event_name, "whitelisted")) {
-    return true;
-  }
-
-  return false;
-}
-
-}  // namespace
-
 // This tests that non-whitelisted args get stripped if required.
 IN_PROC_BROWSER_TEST_F(BackgroundTracingManagerBrowserTest,
-                       NoWhitelistedArgsStripped) {
+                       NotWhitelistedArgsStripped) {
   TestTraceReceiverHelper trace_receiver_helper;
   TestBackgroundTracingHelper background_tracing_helper;
 
-  TraceLog::GetInstance()->SetArgumentFilterPredicate(
-      base::Bind(&IsTraceEventArgsWhitelisted));
-
   std::unique_ptr<BackgroundTracingConfig> config = CreatePreemptiveConfig();
 
   content::BackgroundTracingManager::TriggerHandle handle =
@@ -440,8 +421,8 @@
   background_tracing_helper.WaitForTracingEnabled();
 
   {
-    TRACE_EVENT1("benchmark", "whitelisted", "find_this", 1);
-    TRACE_EVENT1("benchmark", "not_whitelisted", "this_not_found", 1);
+    TRACE_EVENT1("benchmark", "TestWhitelist", "test_whitelist", "abc");
+    TRACE_EVENT1("benchmark", "TestNotWhitelist", "test_not_whitelist", "abc");
   }
 
   TestTriggerHelper trigger_helper;
@@ -454,8 +435,9 @@
 
   EXPECT_TRUE(trace_receiver_helper.trace_received());
   EXPECT_TRUE(trace_receiver_helper.TraceHasMatchingString("{"));
-  EXPECT_TRUE(trace_receiver_helper.TraceHasMatchingString("find_this"));
-  EXPECT_FALSE(trace_receiver_helper.TraceHasMatchingString("this_not_found"));
+  EXPECT_TRUE(trace_receiver_helper.TraceHasMatchingString("test_whitelist"));
+  EXPECT_FALSE(
+      trace_receiver_helper.TraceHasMatchingString("test_not_whitelist"));
 }
 
 // This tests that browser metadata gets included in the trace.
@@ -464,9 +446,6 @@
   TestBackgroundTracingHelper background_tracing_helper;
   TestTraceReceiverHelper trace_receiver_helper;
 
-  TraceLog::GetInstance()->SetArgumentFilterPredicate(
-      base::BindRepeating(&IsTraceEventArgsWhitelisted));
-
   std::unique_ptr<BackgroundTracingConfig> config = CreatePreemptiveConfig();
 
   content::BackgroundTracingManager::TriggerHandle handle =
@@ -506,9 +485,6 @@
   TestBackgroundTracingHelper background_tracing_helper;
   TestTraceReceiverHelper trace_receiver_helper;
 
-  TraceLog::GetInstance()->SetArgumentFilterPredicate(
-      base::Bind(&IsTraceEventArgsWhitelisted));
-
   std::unique_ptr<BackgroundTracingConfig> config = CreatePreemptiveConfig();
 
   content::BackgroundTracingManager::TriggerHandle handle =
@@ -1494,8 +1470,6 @@
   BackgroundStartupTracingObserver::GetInstance()
       ->SetPreferenceManagerForTesting(std::move(preferences_moved));
   preferences->SetBackgroundStartupTracingEnabled(true);
-  TraceLog::GetInstance()->SetArgumentFilterPredicate(
-      base::BindRepeating(&IsTraceEventArgsWhitelisted));
 
   base::DictionaryValue dict;
   std::unique_ptr<base::ListValue> rules_list(new base::ListValue());
diff --git a/content/browser/tracing/tracing_controller_browsertest.cc b/content/browser/tracing/tracing_controller_browsertest.cc
index d3f2cd5..e46520c 100644
--- a/content/browser/tracing/tracing_controller_browsertest.cc
+++ b/content/browser/tracing/tracing_controller_browsertest.cc
@@ -38,33 +38,6 @@
 
 namespace {
 
-const char* kMetadataWhitelist[] = {
-  "cpu-brand",
-  "network-type",
-  "os-name",
-  "user-agent"
-};
-
-bool IsMetadataWhitelisted(const std::string& metadata_name) {
-  for (auto* key : kMetadataWhitelist) {
-    if (base::MatchPattern(metadata_name, key)) {
-      return true;
-    }
-  }
-  return false;
-}
-
-bool IsTraceEventArgsWhitelisted(
-    const char* category_group_name,
-    const char* event_name,
-    base::trace_event::ArgumentNameFilterPredicate* arg_filter) {
-  if (base::MatchPattern(category_group_name, "benchmark") &&
-      base::MatchPattern(event_name, "whitelisted")) {
-    return true;
-  }
-  return false;
-}
-
 bool KeyEquals(const base::Value* value,
                const char* key_name,
                const char* expected) {
@@ -125,9 +98,6 @@
       scoped_refptr<network::SharedURLLoaderFactory>) override {
     return nullptr;
   }
-  MetadataFilterPredicate GetMetadataFilterPredicate() override {
-    return base::BindRepeating(IsMetadataWhitelisted);
-  }
 };
 
 class TracingControllerTest : public ContentBrowserTest {
@@ -250,9 +220,6 @@
 
     Navigate(shell());
 
-    base::trace_event::TraceLog::GetInstance()->SetArgumentFilterPredicate(
-        base::Bind(&IsTraceEventArgsWhitelisted));
-
     TracingControllerImpl* controller = TracingControllerImpl::GetInstance();
     tracing::TraceEventAgent::GetInstance()->AddMetadataGeneratorFunction(
         base::Bind(&TracingControllerTest::GenerateMetadataDict,
@@ -452,7 +419,7 @@
   EXPECT_TRUE(KeyNotEquals(metadata_json, "user-agent", "__stripped__"));
 
   // The following field is not whitelisted and is supposed to be stripped.
-  EXPECT_TRUE(KeyEquals(metadata_json, "chrome-bitness", "__stripped__"));
+  EXPECT_TRUE(KeyEquals(metadata_json, "v8-version", "__stripped__"));
 
   // TODO(770017): This test is currently broken since metadata filtering is
   // only done in |TracingControllerImpl::GenerateMetadataDict()|. Metadata
diff --git a/content/browser/tracing/tracing_controller_impl.cc b/content/browser/tracing/tracing_controller_impl.cc
index 5dc71d8b..7608c71 100644
--- a/content/browser/tracing/tracing_controller_impl.cc
+++ b/content/browser/tracing/tracing_controller_impl.cc
@@ -306,10 +306,10 @@
   // TODO(crbug.com/737049): The central controller doesn't know about
   // metadata filters, so we temporarily filter here as the controller is
   // what assembles the full trace data.
-  MetadataFilterPredicate metadata_filter;
+  base::trace_event::MetadataFilterPredicate metadata_filter;
   if (trace_config_ && trace_config_->IsArgumentFilterEnabled()) {
-    if (delegate_)
-      metadata_filter = delegate_->GetMetadataFilterPredicate();
+    metadata_filter = base::trace_event::TraceLog::GetInstance()
+                          ->GetMetadataFilterPredicate();
   }
 
   if (!metadata_filter.is_null()) {
@@ -480,10 +480,10 @@
 void TracingControllerImpl::OnMetadataAvailable(base::Value metadata) {
   DCHECK(!filtered_metadata_);
   is_metadata_available_ = true;
-  MetadataFilterPredicate metadata_filter;
+  base::trace_event::MetadataFilterPredicate metadata_filter;
   if (trace_config_->IsArgumentFilterEnabled()) {
-    if (delegate_)
-      metadata_filter = delegate_->GetMetadataFilterPredicate();
+    metadata_filter = base::trace_event::TraceLog::GetInstance()
+                          ->GetMetadataFilterPredicate();
   }
   if (metadata_filter.is_null()) {
     filtered_metadata_ = base::DictionaryValue::From(
diff --git a/content/common/frame.mojom b/content/common/frame.mojom
index f660470..16a3099 100644
--- a/content/common/frame.mojom
+++ b/content/common/frame.mojom
@@ -14,6 +14,7 @@
 import "content/public/common/window_container_type.mojom";
 import "mojo/public/mojom/base/string16.mojom";
 import "mojo/public/mojom/base/unguessable_token.mojom";
+import "mojo/public/mojom/base/values.mojom";
 import "services/network/public/mojom/url_loader.mojom";
 import "services/network/public/mojom/url_loader_factory.mojom";
 import "services/service_manager/public/mojom/interface_provider.mojom";
@@ -172,6 +173,37 @@
   // navigation.
   BindDevToolsAgent(associated blink.mojom.DevToolsAgentHost agent_host,
                     associated blink.mojom.DevToolsAgent& agent);
+
+  // Request for the renderer to execute JavaScript in the frame's context.
+  //
+  // |javascript| is the string containing the JavaScript to be executed in the
+  // target frame's context.
+  //
+  // If |notify_result| is true the result is sent back to the browser using
+  // the message FrameHost::JavaScriptExecuteResponse.
+  // FrameHost::JavaScriptExecuteResponse is passed the ID parameter so that the
+  // host can uniquely identify the request.
+  //
+  // TODO(hajimehoshi): This requires navigate association to keep the message
+  // order with other navigation-related messages. Fix this and move this to a
+  // non-navigate-related interface if possible.
+  JavaScriptExecuteRequest(mojo_base.mojom.String16 javascript,
+                           int32 id,
+                           bool notify_result);
+
+  // ONLY FOR TESTS: Same as above but adds a fake UserGestureindicator around
+  // execution. (crbug.com/408426)
+  JavaScriptExecuteRequestForTests(mojo_base.mojom.String16 javascript,
+                                   int32 id,
+                                   bool notify_result,
+                                   bool has_user_gesture);
+
+  // Same as JavaScriptExecuteRequest above except the script is run in the
+  // isolated world specified by the fourth parameter.
+  JavaScriptExecuteRequestInIsolatedWorld(mojo_base.mojom.String16 javascript,
+                                          int32 id,
+                                          bool notify_result,
+                                          int32 world_id);
 };
 
 // Implemented by the frame (e.g. renderer processes).
@@ -397,4 +429,10 @@
   // were initiated by a gesture too, otherwise the navigation may be blocked.
   [EnableIf=is_android]
   UpdateUserGestureCarryoverInfo();
+
+  // Response for JavaScriptExecuteRequest, sent when a reply was requested.
+  // The ID is the parameter supplied to
+  // Frame::JavaScriptExecuteRequest. The result is the value returned by the
+  // script, one of Null, Boolean, Integer, Real, or String.
+  JavaScriptExecuteResponse(int32 id, mojo_base.mojom.Value result);
 };
diff --git a/content/common/frame_messages.h b/content/common/frame_messages.h
index 58645639..f1b940d 100644
--- a/content/common/frame_messages.h
+++ b/content/common/frame_messages.h
@@ -870,36 +870,6 @@
                     content::ConsoleMessageLevel /* level */,
                     std::string /* message */)
 
-// Request for the renderer to execute JavaScript in the frame's context.
-//
-// javascript is the string containing the JavaScript to be executed in the
-// target frame's context.
-//
-// If the third parameter is true the result is sent back to the browser using
-// the message FrameHostMsg_JavaScriptExecuteResponse.
-// FrameHostMsg_JavaScriptExecuteResponse is passed the ID parameter so that the
-// host can uniquely identify the request.
-IPC_MESSAGE_ROUTED3(FrameMsg_JavaScriptExecuteRequest,
-                    base::string16,  /* javascript */
-                    int,  /* ID */
-                    bool  /* if true, a reply is requested */)
-
-// ONLY FOR TESTS: Same as above but adds a fake UserGestureindicator around
-// execution. (crbug.com/408426)
-IPC_MESSAGE_ROUTED4(FrameMsg_JavaScriptExecuteRequestForTests,
-                    base::string16,  /* javascript */
-                    int,  /* ID */
-                    bool, /* if true, a reply is requested */
-                    bool  /* if true, a user gesture indicator is created */)
-
-// Same as FrameMsg_JavaScriptExecuteRequest above except the script is
-// run in the isolated world specified by the fourth parameter.
-IPC_MESSAGE_ROUTED4(FrameMsg_JavaScriptExecuteRequestInIsolatedWorld,
-                    base::string16, /* javascript */
-                    int, /* ID */
-                    bool, /* if true, a reply is requested */
-                    int /* world_id */)
-
 // Tells the renderer to reload the frame, optionally bypassing the cache while
 // doing so.
 IPC_MESSAGE_ROUTED1(FrameMsg_Reload,
@@ -1511,15 +1481,6 @@
                     uint32_t /* the offset of the text in the document */,
                     gfx::Range /* selection range in the document */)
 
-// Response for FrameMsg_JavaScriptExecuteRequest, sent when a reply was
-// requested. The ID is the parameter supplied to
-// FrameMsg_JavaScriptExecuteRequest. The result has the value returned by the
-// script as its only element, one of Null, Boolean, Integer, Real, Date, or
-// String.
-IPC_MESSAGE_ROUTED2(FrameHostMsg_JavaScriptExecuteResponse,
-                    int  /* id */,
-                    base::ListValue  /* result */)
-
 // A request to run a JavaScript dialog.
 IPC_SYNC_MESSAGE_ROUTED3_2(FrameHostMsg_RunJavaScriptDialog,
                            base::string16 /* in - alert message */,
diff --git a/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java b/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java
index 174e4e3..7978411 100644
--- a/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/selection/SelectionPopupControllerImpl.java
@@ -18,6 +18,7 @@
 import android.graphics.Rect;
 import android.os.Build;
 import android.provider.Browser;
+import android.provider.Settings;
 import android.support.annotation.Nullable;
 import android.text.Spanned;
 import android.text.TextUtils;
@@ -34,7 +35,6 @@
 import android.view.WindowManager;
 import android.view.textclassifier.TextClassifier;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.BuildInfo;
 import org.chromium.base.Log;
 import org.chromium.base.UserData;
@@ -364,7 +364,7 @@
 
         if (hasSelection()) {
             // Device is not provisioned, don't trigger SelectionClient logic at all.
-            boolean blockSelectionClient = !ApiCompatibilityUtils.isDeviceProvisioned(mContext);
+            boolean blockSelectionClient = !isDeviceProvisioned(mContext);
 
             // Disable SelectionClient logic if it's incognito.
             blockSelectionClient |= isIncognito();
@@ -1566,6 +1566,13 @@
         return client == null ? null : client.getCustomTextClassifier();
     }
 
+    private static boolean isDeviceProvisioned(Context context) {
+        if (context == null || context.getContentResolver() == null) return true;
+        return Settings.Global.getInt(
+                       context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0)
+                != 0;
+    }
+
     @CalledByNative
     private void nativeSelectionPopupControllerDestroyed() {
         mNativeSelectionPopupController = 0;
diff --git a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java
index 1bd0c48..9d4316a 100644
--- a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java
@@ -560,6 +560,7 @@
 
     @Override
     public void evaluateJavaScript(String script, JavaScriptCallback callback) {
+        ThreadUtils.assertOnUiThread();
         if (isDestroyed() || script == null) return;
         nativeEvaluateJavaScript(mNativeWebContentsAndroid, script, callback);
     }
@@ -567,6 +568,7 @@
     @Override
     @VisibleForTesting
     public void evaluateJavaScriptForTests(String script, JavaScriptCallback callback) {
+        ThreadUtils.assertOnUiThread();
         if (script == null) return;
         checkNotDestroyed();
         nativeEvaluateJavaScriptForTests(mNativeWebContentsAndroid, script, callback);
diff --git a/content/public/browser/notification_database_data.h b/content/public/browser/notification_database_data.h
index d5823b1..4cb6253 100644
--- a/content/public/browser/notification_database_data.h
+++ b/content/public/browser/notification_database_data.h
@@ -11,6 +11,7 @@
 #include "base/optional.h"
 #include "base/time/time.h"
 #include "content/common/content_export.h"
+#include "third_party/blink/public/common/notifications/notification_resources.h"
 #include "third_party/blink/public/common/notifications/platform_notification_data.h"
 #include "url/gurl.h"
 
@@ -53,6 +54,14 @@
   // Platform data of the notification that's being stored.
   blink::PlatformNotificationData notification_data;
 
+  // Flag if this notification has been triggered by its |showTrigger|.
+  bool has_triggered = false;
+
+  // Notification resources to allow showing scheduled notifications. This is
+  // only used to store resources in the NotificationDatabase and is not
+  // deserialized when reading from the database.
+  base::Optional<blink::NotificationResources> notification_resources;
+
   // Boolean for if this current notification is replacing an existing
   // notification.
   bool replaced_existing_notification = false;
diff --git a/content/public/browser/platform_notification_context.h b/content/public/browser/platform_notification_context.h
index c8017eb..276ca40c 100644
--- a/content/public/browser/platform_notification_context.h
+++ b/content/public/browser/platform_notification_context.h
@@ -15,6 +15,10 @@
 
 class GURL;
 
+namespace blink {
+struct NotificationResources;
+}  // namespace blink
+
 namespace content {
 
 // Represents the storage context for persistent Web Notifications, specific to
@@ -28,6 +32,10 @@
       base::OnceCallback<void(bool /* success */,
                               const NotificationDatabaseData&)>;
 
+  using ReadResourcesResultCallback =
+      base::OnceCallback<void(bool /* success */,
+                              const blink::NotificationResources&)>;
+
   using ReadAllResultCallback =
       base::OnceCallback<void(bool /* success */,
                               const std::vector<NotificationDatabaseData>&)>;
@@ -64,6 +72,14 @@
       Interaction interaction,
       ReadResultCallback callback) = 0;
 
+  // Reads the resources associated with |notification_id| belonging to |origin|
+  // from the database. |callback| will be invoked with the success status
+  // and a reference to the notification resources when completed.
+  virtual void ReadNotificationResources(
+      const std::string& notification_id,
+      const GURL& origin,
+      ReadResourcesResultCallback callback) = 0;
+
   // Reads all data associated with |service_worker_registration_id| belonging
   // to |origin| from the database. |callback| will be invoked with the success
   // status and a vector with all read notification data when completed.
diff --git a/content/public/browser/tracing_delegate.cc b/content/public/browser/tracing_delegate.cc
index 5766af6..c216979 100644
--- a/content/public/browser/tracing_delegate.cc
+++ b/content/public/browser/tracing_delegate.cc
@@ -28,8 +28,4 @@
   return nullptr;
 }
 
-MetadataFilterPredicate TracingDelegate::GetMetadataFilterPredicate() {
-  return MetadataFilterPredicate();
-}
-
 }  // namespace content
diff --git a/content/public/browser/tracing_delegate.h b/content/public/browser/tracing_delegate.h
index 82af200..0aede03 100644
--- a/content/public/browser/tracing_delegate.h
+++ b/content/public/browser/tracing_delegate.h
@@ -23,9 +23,6 @@
 class BackgroundTracingConfig;
 class TraceUploader;
 
-typedef base::Callback<bool(const std::string& metadata_name)>
-    MetadataFilterPredicate;
-
 // This can be implemented by the embedder to provide functionality for the
 // about://tracing WebUI.
 class CONTENT_EXPORT TracingDelegate {
@@ -49,8 +46,6 @@
 
   // Used to add any additional metadata to traces.
   virtual std::unique_ptr<base::DictionaryValue> GenerateMetadataDict();
-
-  virtual MetadataFilterPredicate GetMetadataFilterPredicate();
 };
 
 }  // namespace content
diff --git a/content/renderer/accessibility/ax_image_annotator.cc b/content/renderer/accessibility/ax_image_annotator.cc
index f30ec9e4..c4ac434 100644
--- a/content/renderer/accessibility/ax_image_annotator.cc
+++ b/content/renderer/accessibility/ax_image_annotator.cc
@@ -49,6 +49,15 @@
   return std::string();
 }
 
+ax::mojom::ImageAnnotationStatus AXImageAnnotator::GetImageAnnotationStatus(
+    blink::WebAXObject& image) const {
+  DCHECK(!image.IsDetached());
+  const auto lookup = image_annotations_.find(image.AxID());
+  if (lookup != image_annotations_.end())
+    return lookup->second.status();
+  return ax::mojom::ImageAnnotationStatus::kNone;
+}
+
 bool AXImageAnnotator::HasAnnotationInCache(blink::WebAXObject& image) const {
   DCHECK(!image.IsDetached());
   if (!HasImageInCache(image))
@@ -157,6 +166,7 @@
 AXImageAnnotator::ImageInfo::ImageInfo(const blink::WebAXObject& image)
     : image_processor_(
           base::BindRepeating(&AXImageAnnotator::GetImageData, image)),
+      status_(ax::mojom::ImageAnnotationStatus::kAnnotationPending),
       annotation_(base::nullopt) {}
 
 AXImageAnnotator::ImageInfo::~ImageInfo() = default;
@@ -167,7 +177,24 @@
 }
 
 bool AXImageAnnotator::ImageInfo::HasAnnotation() const {
-  return annotation_.has_value();
+  switch (status_) {
+    case ax::mojom::ImageAnnotationStatus::kNone:
+    case ax::mojom::ImageAnnotationStatus::kIneligibleForAnnotation:
+    // The user hasn't requested an annotation yet, or a previously pending
+    // annotation request had been cancelled.
+    case ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation:
+    case ax::mojom::ImageAnnotationStatus::kAnnotationPending:
+      return false;
+    case ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded:
+      DCHECK(annotation_.has_value());
+      return true;
+    case ax::mojom::ImageAnnotationStatus::kAnnotationEmpty:
+    // Image has been classified as adult content.
+    case ax::mojom::ImageAnnotationStatus::kAnnotationAdult:
+    case ax::mojom::ImageAnnotationStatus::kAnnotationProcessFailed:
+      DCHECK(!annotation_.has_value());
+      return true;
+  }
 }
 
 // static
@@ -186,16 +213,43 @@
 void AXImageAnnotator::OnImageAnnotated(
     const blink::WebAXObject& image,
     image_annotation::mojom::AnnotateImageResultPtr result) {
-  if (image.IsDetached())
-    return;
   if (!base::ContainsKey(image_annotations_, image.AxID()))
     return;
-  // TODO(nektar): Set the image annotation status on this image to Error.
-  if (result->is_error_code())
+
+  if (image.IsDetached()) {
+    image_annotations_.at(image.AxID())
+        .set_status(ax::mojom::ImageAnnotationStatus::kIneligibleForAnnotation);
+    // We should not mark dirty a detached object.
     return;
+  }
+
+  if (result->is_error_code()) {
+    DLOG(WARNING) << "Image annotation error.";
+    switch (result->get_error_code()) {
+      case image_annotation::mojom::AnnotateImageError::kCanceled:
+        image_annotations_.at(image.AxID())
+            .set_status(
+                ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation);
+        break;
+      case image_annotation::mojom::AnnotateImageError::kFailure:
+        image_annotations_.at(image.AxID())
+            .set_status(
+                ax::mojom::ImageAnnotationStatus::kAnnotationProcessFailed);
+        break;
+      case image_annotation::mojom::AnnotateImageError::kAdult:
+        image_annotations_.at(image.AxID())
+            .set_status(ax::mojom::ImageAnnotationStatus::kAnnotationAdult);
+        break;
+    }
+    render_accessibility_->MarkWebAXObjectDirty(image, false /* subtree */);
+    return;
+  }
 
   if (!result->is_annotations()) {
     DLOG(WARNING) << "No image annotation results.";
+    image_annotations_.at(image.AxID())
+        .set_status(ax::mojom::ImageAnnotationStatus::kAnnotationEmpty);
+    render_accessibility_->MarkWebAXObjectDirty(image, false /* subtree */);
     return;
   }
 
@@ -229,9 +283,15 @@
     }
   }
 
-  if (contextualized_strings.empty())
+  if (contextualized_strings.empty()) {
+    image_annotations_.at(image.AxID())
+        .set_status(ax::mojom::ImageAnnotationStatus::kAnnotationEmpty);
+    render_accessibility_->MarkWebAXObjectDirty(image, false /* subtree */);
     return;
+  }
 
+  image_annotations_.at(image.AxID())
+      .set_status(ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded);
   // TODO(accessibility): join two sentences together in a more i18n-friendly
   // way. Since this is intended for a screen reader, though, a period
   // probably works in almost all languages.
diff --git a/content/renderer/accessibility/ax_image_annotator.h b/content/renderer/accessibility/ax_image_annotator.h
index bd4207a..0b8ae0b 100644
--- a/content/renderer/accessibility/ax_image_annotator.h
+++ b/content/renderer/accessibility/ax_image_annotator.h
@@ -18,6 +18,7 @@
 #include "services/image_annotation/public/cpp/image_processor.h"
 #include "services/image_annotation/public/mojom/image_annotation.mojom.h"
 #include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/accessibility/ax_enums.mojom.h"
 
 namespace blink {
 
@@ -42,6 +43,8 @@
   void Destroy();
 
   std::string GetImageAnnotation(blink::WebAXObject& image) const;
+  ax::mojom::ImageAnnotationStatus GetImageAnnotationStatus(
+      blink::WebAXObject& image) const;
   bool HasAnnotationInCache(blink::WebAXObject& image) const;
   bool HasImageInCache(const blink::WebAXObject& image) const;
 
@@ -59,8 +62,14 @@
     image_annotation::mojom::ImageProcessorPtr GetImageProcessor();
     bool HasAnnotation() const;
 
+    ax::mojom::ImageAnnotationStatus status() const { return status_; }
+
+    void set_status(ax::mojom::ImageAnnotationStatus status) {
+      DCHECK_NE(status, ax::mojom::ImageAnnotationStatus::kNone);
+      status_ = status;
+    }
+
     std::string annotation() const {
-      DCHECK(annotation_.has_value());
       return annotation_.value_or("");
     }
 
@@ -68,6 +77,7 @@
 
    private:
     image_annotation::ImageProcessor image_processor_;
+    ax::mojom::ImageAnnotationStatus status_;
     base::Optional<std::string> annotation_;
   };
 
diff --git a/content/renderer/accessibility/blink_ax_tree_source.cc b/content/renderer/accessibility/blink_ax_tree_source.cc
index 9fe319bd0..784c728 100644
--- a/content/renderer/accessibility/blink_ax_tree_source.cc
+++ b/content/renderer/accessibility/blink_ax_tree_source.cc
@@ -1062,6 +1062,8 @@
   // unloaded images where the size is unknown.
   if (dst->relative_bounds.bounds.width() < kMinImageAnnotationWidth ||
       dst->relative_bounds.bounds.height() < kMinImageAnnotationHeight) {
+    dst->SetImageAnnotationStatus(
+        ax::mojom::ImageAnnotationStatus::kIneligibleForAnnotation);
     return;
   }
 
@@ -1075,7 +1077,7 @@
     dst->AddStringAttribute(ax::mojom::StringAttribute::kImageAnnotation,
                             image_annotator_->GetImageAnnotation(src));
     dst->SetImageAnnotationStatus(
-        ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded);
+        image_annotator_->GetImageAnnotationStatus(src));
   } else if (image_annotator_->HasImageInCache(src)) {
     image_annotator_->OnImageUpdated(src);
     dst->SetImageAnnotationStatus(
diff --git a/content/renderer/dom_storage/local_storage_cached_area.cc b/content/renderer/dom_storage/local_storage_cached_area.cc
index 1b406b32..c04e0e8 100644
--- a/content/renderer/dom_storage/local_storage_cached_area.cc
+++ b/content/renderer/dom_storage/local_storage_cached_area.cc
@@ -109,7 +109,7 @@
   DCHECK(namespace_id_.empty());
   blink::mojom::StorageAreaPtrInfo wrapper_ptr_info;
   storage_partition_service->OpenLocalStorage(
-      origin_, mojo::MakeRequest(&wrapper_ptr_info), base::DoNothing());
+      origin_, mojo::MakeRequest(&wrapper_ptr_info));
   leveldb_.Bind(std::move(wrapper_ptr_info),
                 main_thread_scheduler->IPCTaskRunner());
   blink::mojom::StorageAreaObserverAssociatedPtrInfo ptr_info;
diff --git a/content/renderer/dom_storage/local_storage_cached_areas.cc b/content/renderer/dom_storage/local_storage_cached_areas.cc
index 79e455e..32dc5d9 100644
--- a/content/renderer/dom_storage/local_storage_cached_areas.cc
+++ b/content/renderer/dom_storage/local_storage_cached_areas.cc
@@ -70,8 +70,7 @@
             .first;
     storage_partition_service_->OpenSessionStorage(
         source_namespace,
-        mojo::MakeRequest(&namespace_it->second.session_storage_namespace),
-        base::DoNothing());
+        mojo::MakeRequest(&namespace_it->second.session_storage_namespace));
   }
   DCHECK(namespace_it->second.session_storage_namespace);
   namespace_it->second.session_storage_namespace->Clone(destination_namespace);
@@ -148,8 +147,7 @@
       if (!dom_namespace->session_storage_namespace) {
         storage_partition_service_->OpenSessionStorage(
             namespace_id,
-            mojo::MakeRequest(&dom_namespace->session_storage_namespace),
-            base::DoNothing());
+            mojo::MakeRequest(&dom_namespace->session_storage_namespace));
       }
       result = base::MakeRefCounted<LocalStorageCachedArea>(
           namespace_id, origin, dom_namespace->session_storage_namespace.get(),
diff --git a/content/renderer/dom_storage/mock_leveldb_wrapper.cc b/content/renderer/dom_storage/mock_leveldb_wrapper.cc
index 03817120..4f7dead 100644
--- a/content/renderer/dom_storage/mock_leveldb_wrapper.cc
+++ b/content/renderer/dom_storage/mock_leveldb_wrapper.cc
@@ -41,20 +41,16 @@
 
 void MockLevelDBWrapper::OpenLocalStorage(
     const url::Origin& origin,
-    blink::mojom::StorageAreaRequest database,
-    OpenLocalStorageCallback done) {
+    blink::mojom::StorageAreaRequest database) {
   bindings_.AddBinding(this, std::move(database));
-  std::move(done).Run();
 }
 
 void MockLevelDBWrapper::OpenSessionStorage(
     const std::string& namespace_id,
-    blink::mojom::SessionStorageNamespaceRequest request,
-    OpenSessionStorageCallback done) {
+    blink::mojom::SessionStorageNamespaceRequest request) {
   namespace_bindings_.AddBinding(
       std::make_unique<MockSessionStorageNamespace>(namespace_id, this),
       std::move(request));
-  std::move(done).Run();
 }
 
 void MockLevelDBWrapper::AddObserver(
diff --git a/content/renderer/dom_storage/mock_leveldb_wrapper.h b/content/renderer/dom_storage/mock_leveldb_wrapper.h
index 32df0552..2a2d4103 100644
--- a/content/renderer/dom_storage/mock_leveldb_wrapper.h
+++ b/content/renderer/dom_storage/mock_leveldb_wrapper.h
@@ -26,11 +26,10 @@
 
   // StoragePartitionService implementation:
   void OpenLocalStorage(const url::Origin& origin,
-                        blink::mojom::StorageAreaRequest database,
-                        OpenLocalStorageCallback done) override;
-  void OpenSessionStorage(const std::string& namespace_id,
-                          blink::mojom::SessionStorageNamespaceRequest request,
-                          OpenSessionStorageCallback done) override;
+                        blink::mojom::StorageAreaRequest database) override;
+  void OpenSessionStorage(
+      const std::string& namespace_id,
+      blink::mojom::SessionStorageNamespaceRequest request) override;
 
   // StorageArea implementation:
   void AddObserver(
diff --git a/content/renderer/loader/web_url_loader_impl_unittest.cc b/content/renderer/loader/web_url_loader_impl_unittest.cc
index 28b97e7..40dd7a9 100644
--- a/content/renderer/loader/web_url_loader_impl_unittest.cc
+++ b/content/renderer/loader/web_url_loader_impl_unittest.cc
@@ -209,8 +209,7 @@
     return true;
   }
 
-  void DidSendData(unsigned long long bytesSent,
-                   unsigned long long totalBytesToBeSent) override {
+  void DidSendData(uint64_t bytesSent, uint64_t totalBytesToBeSent) override {
     EXPECT_TRUE(loader_);
   }
 
diff --git a/content/renderer/pepper/pepper_url_loader_host.cc b/content/renderer/pepper/pepper_url_loader_host.cc
index f6323896..10023df 100644
--- a/content/renderer/pepper/pepper_url_loader_host.cc
+++ b/content/renderer/pepper/pepper_url_loader_host.cc
@@ -134,9 +134,8 @@
   return true;
 }
 
-void PepperURLLoaderHost::DidSendData(
-    unsigned long long bytes_sent,
-    unsigned long long total_bytes_to_be_sent) {
+void PepperURLLoaderHost::DidSendData(uint64_t bytes_sent,
+                                      uint64_t total_bytes_to_be_sent) {
   // TODO(darin): Bounds check input?
   bytes_sent_ = static_cast<int64_t>(bytes_sent);
   total_bytes_to_be_sent_ = static_cast<int64_t>(total_bytes_to_be_sent);
@@ -151,7 +150,7 @@
   SaveResponse(response);
 }
 
-void PepperURLLoaderHost::DidDownloadData(unsigned long long data_length) {
+void PepperURLLoaderHost::DidDownloadData(uint64_t data_length) {
   bytes_received_ += data_length;
   UpdateProgress();
 }
diff --git a/content/renderer/pepper/pepper_url_loader_host.h b/content/renderer/pepper/pepper_url_loader_host.h
index f383c8b..57975dd9 100644
--- a/content/renderer/pepper/pepper_url_loader_host.h
+++ b/content/renderer/pepper/pepper_url_loader_host.h
@@ -46,10 +46,10 @@
   // blink::WebAssociatedURLLoaderClient implementation.
   bool WillFollowRedirect(const blink::WebURL& new_url,
                           const blink::WebURLResponse& redir_response) override;
-  void DidSendData(unsigned long long bytes_sent,
-                   unsigned long long total_bytes_to_be_sent) override;
+  void DidSendData(uint64_t bytes_sent,
+                   uint64_t total_bytes_to_be_sent) override;
   void DidReceiveResponse(const blink::WebURLResponse& response) override;
-  void DidDownloadData(unsigned long long data_length) override;
+  void DidDownloadData(uint64_t data_length) override;
   void DidReceiveData(const char* data, int data_length) override;
   void DidFinishLoading() override;
   void DidFail(const blink::WebURLError& error) override;
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 1bf750a4..63ff53f 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -2128,12 +2128,6 @@
     IPC_MESSAGE_HANDLER(FrameMsg_CopyImageAt, OnCopyImageAt)
     IPC_MESSAGE_HANDLER(FrameMsg_SaveImageAt, OnSaveImageAt)
     IPC_MESSAGE_HANDLER(FrameMsg_AddMessageToConsole, OnAddMessageToConsole)
-    IPC_MESSAGE_HANDLER(FrameMsg_JavaScriptExecuteRequest,
-                        OnJavaScriptExecuteRequest)
-    IPC_MESSAGE_HANDLER(FrameMsg_JavaScriptExecuteRequestForTests,
-                        OnJavaScriptExecuteRequestForTests)
-    IPC_MESSAGE_HANDLER(FrameMsg_JavaScriptExecuteRequestInIsolatedWorld,
-                        OnJavaScriptExecuteRequestInIsolatedWorld)
     IPC_MESSAGE_HANDLER(FrameMsg_VisualStateRequest,
                         OnVisualStateRequest)
     IPC_MESSAGE_HANDLER(FrameMsg_Reload, OnReload)
@@ -2431,11 +2425,10 @@
   AddMessageToConsole(level, message);
 }
 
-void RenderFrameImpl::OnJavaScriptExecuteRequest(
-    const base::string16& jscript,
-    int id,
-    bool notify_result) {
-  TRACE_EVENT_INSTANT0("test_tracing", "OnJavaScriptExecuteRequest",
+void RenderFrameImpl::JavaScriptExecuteRequest(const base::string16& jscript,
+                                               int id,
+                                               bool notify_result) {
+  TRACE_EVENT_INSTANT0("test_tracing", "JavaScriptExecuteRequest",
                        TRACE_EVENT_SCOPE_THREAD);
 
   v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
@@ -2445,12 +2438,12 @@
   HandleJavascriptExecutionResult(jscript, id, notify_result, result);
 }
 
-void RenderFrameImpl::OnJavaScriptExecuteRequestForTests(
+void RenderFrameImpl::JavaScriptExecuteRequestForTests(
     const base::string16& jscript,
     int id,
     bool notify_result,
     bool has_user_gesture) {
-  TRACE_EVENT_INSTANT0("test_tracing", "OnJavaScriptExecuteRequestForTests",
+  TRACE_EVENT_INSTANT0("test_tracing", "JavaScriptExecuteRequestForTests",
                        TRACE_EVENT_SCOPE_THREAD);
 
   // A bunch of tests expect to run code in the context of a user gesture, which
@@ -2464,13 +2457,13 @@
   HandleJavascriptExecutionResult(jscript, id, notify_result, result);
 }
 
-void RenderFrameImpl::OnJavaScriptExecuteRequestInIsolatedWorld(
+void RenderFrameImpl::JavaScriptExecuteRequestInIsolatedWorld(
     const base::string16& jscript,
     int id,
     bool notify_result,
     int world_id) {
   TRACE_EVENT_INSTANT0("test_tracing",
-                       "OnJavaScriptExecuteRequestInIsolatedWorld",
+                       "JavaScriptExecuteRequestInIsolatedWorld",
                        TRACE_EVENT_SCOPE_THREAD);
 
   if (world_id <= ISOLATED_WORLD_ID_GLOBAL ||
@@ -2484,7 +2477,7 @@
   v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
   WebScriptSource script = WebScriptSource(WebString::FromUTF16(jscript));
   JavaScriptIsolatedWorldRequest* request = new JavaScriptIsolatedWorldRequest(
-      id, notify_result, routing_id_, weak_factory_.GetWeakPtr());
+      id, notify_result, weak_factory_.GetWeakPtr());
   frame_->RequestExecuteScriptInIsolatedWorld(
       world_id, &script, 1, false, WebLocalFrame::kSynchronous, request);
 }
@@ -2492,11 +2485,9 @@
 RenderFrameImpl::JavaScriptIsolatedWorldRequest::JavaScriptIsolatedWorldRequest(
     int id,
     bool notify_result,
-    int routing_id,
     base::WeakPtr<RenderFrameImpl> render_frame_impl)
     : id_(id),
       notify_result_(notify_result),
-      routing_id_(routing_id),
       render_frame_impl_(render_frame_impl) {
 }
 
@@ -2511,7 +2502,7 @@
   }
 
   if (notify_result_) {
-    base::ListValue list;
+    base::Value value;
     if (!result.empty()) {
       // It's safe to always use the main world context when converting
       // here. V8ValueConverterImpl shouldn't actually care about the
@@ -2523,17 +2514,14 @@
       V8ValueConverterImpl converter;
       converter.SetDateAllowed(true);
       converter.SetRegExpAllowed(true);
-      for (const auto& value : result) {
-        std::unique_ptr<base::Value> result_value(
-            converter.FromV8Value(value, context));
-        list.Append(result_value ? std::move(result_value)
-                                 : std::make_unique<base::Value>());
+      std::unique_ptr<base::Value> new_value =
+          converter.FromV8Value(*result.begin(), context);
+      if (new_value) {
+        value = std::move(*new_value);
       }
-    } else {
-      list.Set(0, std::make_unique<base::Value>());
     }
-    render_frame_impl_.get()->Send(
-        new FrameHostMsg_JavaScriptExecuteResponse(routing_id_, id_, list));
+    render_frame_impl_->GetFrameHost()->JavaScriptExecuteResponse(
+        id_, std::move(value));
   }
 
   delete this;
@@ -2545,21 +2533,20 @@
     bool notify_result,
     v8::Local<v8::Value> result) {
   if (notify_result) {
-    base::ListValue list;
+    base::Value value;
     if (!result.IsEmpty()) {
       v8::Local<v8::Context> context = frame_->MainWorldScriptContext();
       v8::Context::Scope context_scope(context);
       V8ValueConverterImpl converter;
       converter.SetDateAllowed(true);
       converter.SetRegExpAllowed(true);
-      std::unique_ptr<base::Value> result_value(
-          converter.FromV8Value(result, context));
-      list.Set(0, result_value ? std::move(result_value)
-                               : std::make_unique<base::Value>());
-    } else {
-      list.Set(0, std::make_unique<base::Value>());
+      std::unique_ptr<base::Value> new_value =
+          converter.FromV8Value(result, context);
+      if (new_value) {
+        value = std::move(*new_value);
+      }
     }
-    Send(new FrameHostMsg_JavaScriptExecuteResponse(routing_id_, id, list));
+    GetFrameHost()->JavaScriptExecuteResponse(id, std::move(value));
   }
 }
 
@@ -3032,7 +3019,7 @@
 }
 
 void RenderFrameImpl::ExecuteJavaScript(const base::string16& javascript) {
-  OnJavaScriptExecuteRequest(javascript, 0, false);
+  JavaScriptExecuteRequest(javascript, 0, false);
 }
 
 void RenderFrameImpl::BindLocalInterface(
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index d0df613..634e9d0 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -620,6 +620,18 @@
       blink::mojom::DevToolsAgentHostAssociatedPtrInfo host,
       blink::mojom::DevToolsAgentAssociatedRequest request) override;
 
+  void JavaScriptExecuteRequest(const base::string16& javascript,
+                                int id,
+                                bool notify_result) override;
+  void JavaScriptExecuteRequestForTests(const base::string16& javascript,
+                                        int id,
+                                        bool notify_result,
+                                        bool has_user_gesture) override;
+  void JavaScriptExecuteRequestInIsolatedWorld(const base::string16& jscript,
+                                               int id,
+                                               bool notify_result,
+                                               int world_id) override;
+
   // mojom::FullscreenVideoElementHandler implementation:
   void RequestFullscreenVideoElement() override;
 
@@ -984,7 +996,6 @@
     JavaScriptIsolatedWorldRequest(
         int id,
         bool notify_result,
-        int routing_id,
         base::WeakPtr<RenderFrameImpl> render_frame_impl);
     void Completed(
         const blink::WebVector<v8::Local<v8::Value>>& result) override;
@@ -994,7 +1005,6 @@
 
     int id_;
     bool notify_result_;
-    int routing_id_;
     base::WeakPtr<RenderFrameImpl> render_frame_impl_;
 
     DISALLOW_COPY_AND_ASSIGN(JavaScriptIsolatedWorldRequest);
@@ -1086,17 +1096,6 @@
   void OnSaveImageAt(int x, int y);
   void OnAddMessageToConsole(ConsoleMessageLevel level,
                              const std::string& message);
-  void OnJavaScriptExecuteRequest(const base::string16& javascript,
-                                  int id,
-                                  bool notify_result);
-  void OnJavaScriptExecuteRequestForTests(const base::string16& javascript,
-                                          int id,
-                                          bool notify_result,
-                                          bool has_user_gesture);
-  void OnJavaScriptExecuteRequestInIsolatedWorld(const base::string16& jscript,
-                                                 int id,
-                                                 bool notify_result,
-                                                 int world_id);
   void OnVisualStateRequest(uint64_t key);
   void OnReload(bool bypass_cache);
   void OnReloadLoFiImages();
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 0ea01f6c..33a601f8 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1555,7 +1555,7 @@
     "../browser/network_service_client_unittest.cc",
     "../browser/notification_service_impl_unittest.cc",
     "../browser/notifications/blink_notification_service_impl_unittest.cc",
-    "../browser/notifications/notification_database_data_unittest.cc",
+    "../browser/notifications/notification_database_conversions_unittest.cc",
     "../browser/notifications/notification_database_unittest.cc",
     "../browser/notifications/notification_event_dispatcher_impl_unittest.cc",
     "../browser/notifications/notification_id_generator_unittest.cc",
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
index 412ad009..875e3b307 100644
--- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -318,8 +318,6 @@
     # Passthrough command decoder
     self.Fail('conformance/misc/webgl-specific-stencil-settings.html',
         ['passthrough'], bug=844349)
-    self.Fail('conformance2/extensions/ext-float-blend.html',
-        ['passthrough'], bug=930993)
 
     # Passthrough command decoder / OpenGL
     self.Fail('conformance2/misc/uninitialized-test-2.html',
diff --git a/content/test/test_render_frame.cc b/content/test/test_render_frame.cc
index 6f60b98..f3dddb5 100644
--- a/content/test/test_render_frame.cc
+++ b/content/test/test_render_frame.cc
@@ -113,6 +113,8 @@
     last_commit_params_ = std::move(params);
   }
 
+  void JavaScriptExecuteResponse(int id, base::Value result) override {}
+
   void BeginNavigation(const CommonNavigationParams& common_params,
                        mojom::BeginNavigationParamsPtr begin_params,
                        blink::mojom::BlobURLTokenPtr blob_url_token,
diff --git a/docs/mojo_ipc_conversion.md b/docs/mojo_ipc_conversion.md
index 7bf5838..7bd22bf 100644
--- a/docs/mojo_ipc_conversion.md
+++ b/docs/mojo_ipc_conversion.md
@@ -265,6 +265,8 @@
   DoTheThing(string name) => (bool success);
 };
 ```
+See [Receiving responses](/mojo/public/cpp/bindings/README.md#receiving-responses)
+for more information.
 
 ## Repurposing `IPC::ParamTraits` and `IPC_STRUCT*` Invocations
 
diff --git a/extensions/browser/api/web_request/web_request_api.cc b/extensions/browser/api/web_request/web_request_api.cc
index d9b7eb5..896df6fb 100644
--- a/extensions/browser/api/web_request/web_request_api.cc
+++ b/extensions/browser/api/web_request/web_request_api.cc
@@ -1589,22 +1589,21 @@
     const std::string& event_name,
     const std::string& sub_event_name,
     uint64_t request_id,
+    int embedder_process_id,
+    int web_view_instance_id,
     EventResponse* response) {
-  int extra_info_spec = 0;
-  // TODO(robwu): This ignores WebViews.
   Listeners& listeners = listeners_[browser_context][event_name];
-  EventListener::ID id(browser_context, extension_id, sub_event_name, 0, 0);
-  for (auto it = listeners.begin(); it != listeners.end(); ++it) {
-    // TODO(karandeepb): Investigate if this ever matches more than one
-    // listener.
-    if ((*it)->id.LooselyMatches(id)) {
-      (*it)->blocked_requests.erase(request_id);
-      extra_info_spec |= (*it)->extra_info_spec;
-    }
-  }
+  EventListener::ID id(browser_context, extension_id, sub_event_name,
+                       embedder_process_id, web_view_instance_id);
+  EventListener* listener = FindEventListenerInContainer(id, listeners);
 
+  // This might happen, for example, if the extension has been unloaded.
+  if (!listener)
+    return;
+
+  listener->blocked_requests.erase(request_id);
   DecrementBlockCount(browser_context, extension_id, event_name, request_id,
-                      response, extra_info_spec);
+                      response, listener->extra_info_spec);
 }
 
 bool ExtensionWebRequestEventRouter::AddEventListener(
@@ -1646,6 +1645,10 @@
   RecordAddEventListenerUMAs(extra_info_spec);
 
   listeners_[browser_context][event_name].push_back(std::move(listener));
+
+  if (extra_info_spec & ExtraInfoSpec::EXTRA_HEADERS)
+    extra_headers_listener_count_[browser_context]++;
+
   return true;
 }
 
@@ -1693,10 +1696,17 @@
         strict ? listener->id == id : listener->id.LooselyMatches(id);
     if (matches) {
       // Unblock any request that this event listener may have been blocking.
-      for (uint64_t blocked_request_id : listener->blocked_requests)
+      for (uint64_t blocked_request_id : listener->blocked_requests) {
         DecrementBlockCount(
             listener->id.browser_context, listener->id.extension_id, event_name,
             blocked_request_id, nullptr, 0 /* extra_info_spec */);
+      }
+
+      if (listener->extra_info_spec & ExtraInfoSpec::EXTRA_HEADERS) {
+        extra_headers_listener_count_[listener->id.browser_context]--;
+        DCHECK_GE(extra_headers_listener_count_[listener->id.browser_context],
+                  0);
+      }
 
       listeners.erase(it);
       helpers::ClearCacheOnNavigation();
@@ -1779,14 +1789,7 @@
 
 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;
+  return extra_headers_listener_count_[browser_context] > 0;
 }
 
 bool ExtensionWebRequestEventRouter::IsPageLoad(
@@ -2587,14 +2590,12 @@
     const std::string& event_name,
     const std::string& sub_event_name,
     uint64_t request_id,
+    int embedder_process_id,
+    int web_view_instance_id,
     std::unique_ptr<ExtensionWebRequestEventRouter::EventResponse> response) {
   ExtensionWebRequestEventRouter::GetInstance()->OnEventHandled(
-      profile_id(),
-      extension_id_safe(),
-      event_name,
-      sub_event_name,
-      request_id,
-      response.release());
+      profile_id(), extension_id_safe(), event_name, sub_event_name, request_id,
+      embedder_process_id, web_view_instance_id, response.release());
 }
 
 ExtensionFunction::ResponseAction
@@ -2610,11 +2611,16 @@
   uint64_t request_id;
   EXTENSION_FUNCTION_VALIDATE(base::StringToUint64(request_id_str,
                                                    &request_id));
+  int web_view_instance_id = 0;
+  EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(3, &web_view_instance_id));
+
+  base::WeakPtr<IOThreadExtensionMessageFilter> ipc_sender = ipc_sender_weak();
+  int embedder_process_id = ipc_sender ? ipc_sender->render_process_id() : 0;
 
   std::unique_ptr<ExtensionWebRequestEventRouter::EventResponse> response;
-  if (HasOptionalArgument(3)) {
+  if (HasOptionalArgument(4)) {
     base::DictionaryValue* value = NULL;
-    EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(3, &value));
+    EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(4, &value));
 
     if (!value->empty()) {
       base::Time install_time =
@@ -2631,14 +2637,16 @@
          value->HasKey(keys::kAuthCredentialsKey) ||
          value->HasKey("requestHeaders") ||
          value->HasKey("responseHeaders"))) {
-      OnError(event_name, sub_event_name, request_id, std::move(response));
+      OnError(event_name, sub_event_name, request_id, embedder_process_id,
+              web_view_instance_id, std::move(response));
       return RespondNow(Error(keys::kInvalidPublicSessionBlockingResponse));
     }
 
     if (value->HasKey("cancel")) {
       // Don't allow cancel mixed with other keys.
       if (value->size() != 1) {
-        OnError(event_name, sub_event_name, request_id, std::move(response));
+        OnError(event_name, sub_event_name, request_id, embedder_process_id,
+                web_view_instance_id, std::move(response));
         return RespondNow(Error(keys::kInvalidBlockingResponse));
       }
 
@@ -2653,7 +2661,8 @@
                                                    &new_url_str));
       response->new_url = GURL(new_url_str);
       if (!response->new_url.is_valid()) {
-        OnError(event_name, sub_event_name, request_id, std::move(response));
+        OnError(event_name, sub_event_name, request_id, embedder_process_id,
+                web_view_instance_id, std::move(response));
         return RespondNow(Error(keys::kInvalidRedirectUrl, new_url_str));
       }
     }
@@ -2663,7 +2672,8 @@
     if (has_request_headers || has_response_headers) {
       if (has_request_headers && has_response_headers) {
         // Allow only one of the keys, not both.
-        OnError(event_name, sub_event_name, request_id, std::move(response));
+        OnError(event_name, sub_event_name, request_id, embedder_process_id,
+                web_view_instance_id, std::move(response));
         return RespondNow(Error(keys::kInvalidHeaderKeyCombination));
       }
 
@@ -2689,15 +2699,18 @@
         if (!FromHeaderDictionary(header_value, &name, &value)) {
           std::string serialized_header;
           base::JSONWriter::Write(*header_value, &serialized_header);
-          OnError(event_name, sub_event_name, request_id, std::move(response));
+          OnError(event_name, sub_event_name, request_id, embedder_process_id,
+                  web_view_instance_id, std::move(response));
           return RespondNow(Error(keys::kInvalidHeader, serialized_header));
         }
         if (!net::HttpUtil::IsValidHeaderName(name)) {
-          OnError(event_name, sub_event_name, request_id, std::move(response));
+          OnError(event_name, sub_event_name, request_id, embedder_process_id,
+                  web_view_instance_id, std::move(response));
           return RespondNow(Error(keys::kInvalidHeaderName));
         }
         if (!net::HttpUtil::IsValidHeaderValue(value)) {
-          OnError(event_name, sub_event_name, request_id, std::move(response));
+          OnError(event_name, sub_event_name, request_id, embedder_process_id,
+                  web_view_instance_id, std::move(response));
           return RespondNow(Error(keys::kInvalidHeaderValue, name));
         }
         if (has_request_headers)
@@ -2728,7 +2741,7 @@
 
   ExtensionWebRequestEventRouter::GetInstance()->OnEventHandled(
       profile_id(), extension_id_safe(), event_name, sub_event_name, request_id,
-      response.release());
+      embedder_process_id, web_view_instance_id, response.release());
 
   return RespondNow(NoArguments());
 }
diff --git a/extensions/browser/api/web_request/web_request_api.h b/extensions/browser/api/web_request/web_request_api.h
index febce14..6f26f7e 100644
--- a/extensions/browser/api/web_request/web_request_api.h
+++ b/extensions/browser/api/web_request/web_request_api.h
@@ -446,6 +446,8 @@
                       const std::string& event_name,
                       const std::string& sub_event_name,
                       uint64_t request_id,
+                      int embedder_process_id,
+                      int web_view_instance_id,
                       EventResponse* response);
 
   // Adds a listener to the given event. |event_name| specifies the event being
@@ -560,6 +562,7 @@
   using Listeners = std::vector<std::unique_ptr<EventListener>>;
   using ListenerMapForBrowserContext = std::map<std::string, Listeners>;
   using ListenerMap = std::map<void*, ListenerMapForBrowserContext>;
+  using ExtraHeadersListenerCountMap = std::map<void*, int>;
   using BlockedRequestMap = std::map<uint64_t, BlockedRequest>;
   // Map of request_id -> bit vector of EventTypes already signaled
   using SignaledRequestMap = std::map<uint64_t, int>;
@@ -713,10 +716,16 @@
   size_t GetListenerCountForTesting(void* browser_context,
                                     const std::string& event_name);
 
+  // TODO(karandeepb): The below code should be refactored to have a single map
+  // to store per-browser-context data.
+
   // A map for each browser_context that maps an event name to a set of
   // extensions that are listening to that event.
   ListenerMap listeners_;
 
+  // Count of listeners per browser context which request extra headers.
+  ExtraHeadersListenerCountMap extra_headers_listener_count_;
+
   // A map of network requests that are waiting for at least one event handler
   // to respond.
   BlockedRequestMap blocked_requests_;
@@ -790,6 +799,8 @@
       const std::string& event_name,
       const std::string& sub_event_name,
       uint64_t request_id,
+      int embedder_process_id,
+      int web_view_instance_id,
       std::unique_ptr<ExtensionWebRequestEventRouter::EventResponse> response);
 
   // ExtensionFunction:
diff --git a/extensions/browser/guest_view/web_view/web_view_apitest.cc b/extensions/browser/guest_view/web_view/web_view_apitest.cc
index 945f8c5d..31b0123 100644
--- a/extensions/browser/guest_view/web_view/web_view_apitest.cc
+++ b/extensions/browser/guest_view/web_view/web_view_apitest.cc
@@ -45,6 +45,7 @@
 #include "extensions/test/extension_test_message_listener.h"
 #include "extensions/test/result_catcher.h"
 #include "net/base/filename_util.h"
+#include "net/test/embedded_test_server/default_handlers.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
@@ -238,6 +239,8 @@
           kUserAgentRedirectResponsePath,
           embedded_test_server()->GetURL(kRedirectResponseFullPath)));
 
+  net::test_server::RegisterDefaultHandlers(embedded_test_server());
+
   embedded_test_server()->StartAcceptingConnections();
 }
 
@@ -794,6 +797,13 @@
   StopTestServer();
 }
 
+IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestWebRequestAPIWithExtraHeaders) {
+  std::string app_location = "web_view/apitest";
+  StartTestServer(app_location);
+  RunTest("testWebRequestAPIWithExtraHeaders", app_location);
+  StopTestServer();
+}
+
 IN_PROC_BROWSER_TEST_F(WebViewAPITest, TestLoadEventsSameDocumentNavigation) {
   std::string app_location = "web_view/apitest";
   StartTestServer(app_location);
diff --git a/extensions/common/api/web_request_internal.json b/extensions/common/api/web_request_internal.json
index e158e4ed..69d3d2b0 100644
--- a/extensions/common/api/web_request_internal.json
+++ b/extensions/common/api/web_request_internal.json
@@ -52,6 +52,7 @@
           {"type": "string", "name": "eventName"},
           {"type": "string", "name": "subEventName"},
           {"type": "string", "name": "requestId"},
+          {"type": "integer", "name": "webViewInstanceId"},
           {
             "$ref": "webRequest.BlockingResponse",
             "optional": true,
diff --git a/extensions/renderer/resources/web_request_event.js b/extensions/renderer/resources/web_request_event.js
index 8685ab8..71e473e 100644
--- a/extensions/renderer/resources/web_request_event.js
+++ b/extensions/renderer/resources/web_request_event.js
@@ -99,27 +99,29 @@
   var subEventCallback = cb;
   if (opt_extraInfo && $Array.indexOf(opt_extraInfo, 'blocking') >= 0) {
     var eventName = this.eventName;
+    var webViewInstanceId = this.webViewInstanceId;
     subEventCallback = function() {
       var requestId = arguments[0].requestId;
       try {
         var result = $Function.apply(cb, null, arguments);
         webRequestInternal.eventHandled(
-            eventName, subEventName, requestId, result);
+            eventName, subEventName, requestId, webViewInstanceId, result);
       } catch (e) {
         webRequestInternal.eventHandled(
-            eventName, subEventName, requestId);
+            eventName, subEventName, requestId, webViewInstanceId);
         throw e;
       }
     };
   } else if (
       opt_extraInfo && $Array.indexOf(opt_extraInfo, 'asyncBlocking') >= 0) {
     var eventName = this.eventName;
+    var webViewInstanceId = this.webViewInstanceId;
     subEventCallback = function() {
       var details = arguments[0];
       var requestId = details.requestId;
       var handledCallback = function(response) {
         webRequestInternal.eventHandled(
-            eventName, subEventName, requestId, response);
+            eventName, subEventName, requestId, webViewInstanceId, response);
       };
       $Function.apply(cb, null, [details, handledCallback]);
     };
diff --git a/extensions/test/data/web_view/apitest/main.js b/extensions/test/data/web_view/apitest/main.js
index d970de5..0394384 100644
--- a/extensions/test/data/web_view/apitest/main.js
+++ b/extensions/test/data/web_view/apitest/main.js
@@ -1744,6 +1744,67 @@
   document.body.appendChild(webview);
 }
 
+// Tests web request api support for "extraHeaders" with webviews. Regression
+// test for crbug.com/938095.
+function testWebRequestAPIWithExtraHeaders() {
+  var echoCookieUrl = embedder.baseGuestURL + '/echoheader?Cookie';
+  var setCookieUrl = embedder.baseGuestURL + '/set-cookie?foo=bar';
+
+  var webview = new WebView();
+
+  var requestFilter = {urls: [echoCookieUrl]};
+  webview.request.onBeforeSendHeaders.addListener(function(details) {
+    var cookieHeader = details.requestHeaders.find(function(header) {
+      return header.name.toLowerCase() == 'cookie';
+    });
+    embedder.test.assertTrue(cookieHeader);
+    embedder.test.assertEq('foo=bar', cookieHeader.value);
+    // Modify the Cookie header.
+    cookieHeader.value = 'foo=new_value';
+    return {requestHeaders: details.requestHeaders};
+  }, requestFilter, ['requestHeaders', 'blocking', 'extraHeaders']);
+
+  var onSendHeadersSeen = false;
+  webview.request.onSendHeaders.addListener(function(details) {
+    var cookieHeader = details.requestHeaders.find(function(header) {
+      return header.name.toLowerCase() == 'cookie';
+    });
+    embedder.test.assertTrue(cookieHeader);
+    onSendHeadersSeen = true;
+    // Verify the Cookie header was modified.
+    chrome.test.assertEq('foo=new_value', cookieHeader.value);
+  }, requestFilter, ['requestHeaders', 'extraHeaders']);
+
+  var listener = function() {
+    webview.removeEventListener('loadstop', listener);
+
+    webview.addEventListener('loadstart', function(e) {
+      embedder.test.assertTrue(e.isTopLevel);
+      embedder.test.assertEq(echoCookieUrl, e.url);
+    });
+
+    webview.addEventListener('loadstop', function() {
+      embedder.test.assertTrue(onSendHeadersSeen);
+
+      // Ensure the header was modified.
+      webview.executeScript(
+          {code: 'document.body.innerText'}, function(results) {
+            embedder.test.assertEq('foo=new_value', results[0]);
+            embedder.test.succeed();
+          });
+    });
+
+    // Now load a url to echo the cookie header.
+    webview.src = echoCookieUrl;
+  };
+  webview.addEventListener('loadstop', listener);
+
+  // Load a URL to set a cookie so that the Cookie header is set for future
+  // requests.
+  webview.src = setCookieUrl;
+  document.body.appendChild(webview);
+}
+
 function testWebRequestAPIExistence() {
   var regularEventsToCheck = [
     // Declarative WebRequest API.
@@ -1926,6 +1987,7 @@
   'testTerminateAfterExit': testTerminateAfterExit,
   'testWebRequestAPI': testWebRequestAPI,
   'testWebRequestAPIWithHeaders': testWebRequestAPIWithHeaders,
+  'testWebRequestAPIWithExtraHeaders': testWebRequestAPIWithExtraHeaders,
   'testWebRequestAPIExistence': testWebRequestAPIExistence,
   'testWebRequestAPIGoogleProperty': testWebRequestAPIGoogleProperty,
   'testCaptureVisibleRegion': testCaptureVisibleRegion,
diff --git a/gpu/command_buffer/common/gles2_cmd_utils.cc b/gpu/command_buffer/common/gles2_cmd_utils.cc
index e3a0a8ec..10cb229 100644
--- a/gpu/command_buffer/common/gles2_cmd_utils.cc
+++ b/gpu/command_buffer/common/gles2_cmd_utils.cc
@@ -1759,6 +1759,19 @@
 }
 
 // static
+bool GLES2Util::IsFloat32Format(uint32_t internal_format) {
+  switch (internal_format) {
+    case GL_R32F:
+    case GL_RG32F:
+    case GL_RGB32F:
+    case GL_RGBA32F:
+      return true;
+    default:
+      return false;
+  }
+}
+
+// static
 uint32_t GLES2Util::ConvertToSizedFormat(uint32_t format, uint32_t type) {
   switch (format) {
     case GL_RGB:
diff --git a/gpu/command_buffer/common/gles2_cmd_utils.h b/gpu/command_buffer/common/gles2_cmd_utils.h
index 25a28052..7ec260b 100644
--- a/gpu/command_buffer/common/gles2_cmd_utils.h
+++ b/gpu/command_buffer/common/gles2_cmd_utils.h
@@ -218,6 +218,7 @@
   static bool IsSignedIntegerFormat(uint32_t internal_format);
   static bool IsIntegerFormat(uint32_t internal_format);
   static bool IsFloatFormat(uint32_t internal_format);
+  static bool IsFloat32Format(uint32_t internal_format);
   static uint32_t ConvertToSizedFormat(uint32_t format, uint32_t type);
   static bool IsSizedColorFormat(uint32_t internal_format);
 
diff --git a/gpu/command_buffer/service/framebuffer_manager.cc b/gpu/command_buffer/service/framebuffer_manager.cc
index a76f5caf..dab94578 100644
--- a/gpu/command_buffer/service/framebuffer_manager.cc
+++ b/gpu/command_buffer/service/framebuffer_manager.cc
@@ -375,14 +375,14 @@
   DCHECK(result.second);
 }
 
-Framebuffer::Framebuffer(
-    FramebufferManager* manager, GLuint service_id)
+Framebuffer::Framebuffer(FramebufferManager* manager, GLuint service_id)
     : manager_(manager),
       deleted_(false),
       service_id_(service_id),
       has_been_bound_(false),
       framebuffer_complete_state_count_id_(0),
       draw_buffer_type_mask_(0u),
+      draw_buffer_float32_mask_(0u),
       draw_buffer_bound_mask_(0u),
       adjusted_draw_buffer_bound_mask_(0u),
       read_buffer_(GL_COLOR_ATTACHMENT0) {
@@ -639,16 +639,8 @@
   return attachments_.find(GL_STENCIL_ATTACHMENT) != attachments_.end();
 }
 
-bool Framebuffer::HasFloatColorAttachment() const {
-  for (AttachmentMap::const_iterator it = attachments_.begin();
-       it != attachments_.end(); ++it) {
-    if (it->first != GL_DEPTH_ATTACHMENT &&
-        it->first != GL_STENCIL_ATTACHMENT &&
-        it->second->texture_type() == GL_FLOAT) {
-      return true;
-    }
-  }
-  return false;
+bool Framebuffer::HasActiveFloat32ColorAttachment() const {
+  return draw_buffer_float32_mask_ != 0u;
 }
 
 GLenum Framebuffer::GetReadBufferInternalFormat() const {
@@ -955,6 +947,7 @@
 
 void Framebuffer::UpdateDrawBufferMasks() {
   draw_buffer_type_mask_ = 0u;
+  draw_buffer_float32_mask_ = 0u;
   draw_buffer_bound_mask_ = 0u;
   for (uint32_t index = 0; index < manager_->max_color_attachments_; ++index) {
     GLenum draw_buffer = draw_buffers_[index];
@@ -976,6 +969,10 @@
     size_t shift_bits = index * 2;
     draw_buffer_type_mask_ |= base_type << shift_bits;
     draw_buffer_bound_mask_ |= 0x3 << shift_bits;
+
+    if (GLES2Util::IsFloat32Format(internal_format)) {
+      draw_buffer_float32_mask_ |= 0x3 << shift_bits;
+    }
   }
 }
 
diff --git a/gpu/command_buffer/service/framebuffer_manager.h b/gpu/command_buffer/service/framebuffer_manager.h
index f463279..f48538e 100644
--- a/gpu/command_buffer/service/framebuffer_manager.h
+++ b/gpu/command_buffer/service/framebuffer_manager.h
@@ -155,7 +155,7 @@
   bool HasColorAttachment(int index) const;
   bool HasDepthAttachment() const;
   bool HasStencilAttachment() const;
-  bool HasFloatColorAttachment() const;
+  bool HasActiveFloat32ColorAttachment() const;
   GLenum GetDepthFormat() const;
   GLenum GetStencilFormat() const;
   GLenum GetDrawBufferInternalFormat() const;
@@ -296,6 +296,9 @@
   // We have up to 16 draw buffers, each is encoded into 2 bits, total 32 bits:
   // the lowest 2 bits for draw buffer 0, the highest 2 bits for draw buffer 15.
   uint32_t draw_buffer_type_mask_;
+  // Same layout as above, 0x03 if it's 32bit float color attachment, 0x00 if
+  // not
+  uint32_t draw_buffer_float32_mask_;
   // Same layout as above, 2 bits per draw buffer, 0x03 if a draw buffer has a
   // bound image, 0x00 if not.
   uint32_t draw_buffer_bound_mask_;
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index 2cace03..d7197fe 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -4959,7 +4959,7 @@
     // only is true when called by DoMultiDrawArrays or DoMultiDrawElements
     if (framebuffer && state_.GetEnabled(GL_BLEND) &&
         !features().ext_float_blend) {
-      if (framebuffer->HasFloatColorAttachment()) {
+      if (framebuffer->HasActiveFloat32ColorAttachment()) {
         LOCAL_SET_GL_ERROR(GL_INVALID_OPERATION, func_name,
                            "GL_BLEND with floating-point color attachments "
                            "requires the EXT_float_blend extension");
diff --git a/gpu/ipc/service/direct_composition_surface_win.cc b/gpu/ipc/service/direct_composition_surface_win.cc
index d5ad98b..8154366 100644
--- a/gpu/ipc/service/direct_composition_surface_win.cc
+++ b/gpu/ipc/service/direct_composition_surface_win.cc
@@ -1656,6 +1656,8 @@
     bool video_needs_commit = false;
     if (!video_swap_chain->PresentToSwapChain(*pending_overlays_[i],
                                               &video_needs_commit)) {
+      DLOG(ERROR) << "PresentToSwapChain failed";
+      DCHECK(false);
       return false;
     }
     needs_commit = needs_commit || video_needs_commit;
@@ -1691,6 +1693,7 @@
     HRESULT hr = dcomp_device_->Commit();
     if (FAILED(hr)) {
       DLOG(ERROR) << "Commit failed with error 0x" << std::hex << hr;
+      DCHECK(false);
       return false;
     }
   }
@@ -1950,7 +1953,8 @@
     succeeded = false;
 
   DCLayerTree::BackbufferInfo backbuffer_info = {
-      root_surface_->swap_chain().Get(), root_surface_->dcomp_surface().Get(),
+      root_surface_->swap_chain().Get(),
+      root_surface_->dcomp_surface().Get(),
       root_surface_->dcomp_surface_serial(),
   };
   if (!layer_tree_->CommitAndClearPendingOverlays(std::move(backbuffer_info)))
diff --git a/infra/config/cr-buildbucket.cfg b/infra/config/cr-buildbucket.cfg
index 9a141e0..84f1854 100644
--- a/infra/config/cr-buildbucket.cfg
+++ b/infra/config/cr-buildbucket.cfg
@@ -1557,6 +1557,7 @@
       dimensions: "os:Windows-10"
       dimensions: "cpu:x86"
       mixins: "fyi-ci"
+      mixins: "goma-j150"
     }
 
     builders {
diff --git a/ios/chrome/browser/app_launcher/BUILD.gn b/ios/chrome/browser/app_launcher/BUILD.gn
index 21d636b..3d59ccb 100644
--- a/ios/chrome/browser/app_launcher/BUILD.gn
+++ b/ios/chrome/browser/app_launcher/BUILD.gn
@@ -20,8 +20,8 @@
     "//base",
     "//components/reading_list/core:core",
     "//ios/chrome/browser",
+    "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/reading_list",
-    "//ios/chrome/browser/tabs",
     "//ios/chrome/browser/u2f",
     "//ios/web/public",
     "//url",
@@ -56,7 +56,6 @@
     "//ios/chrome/browser",
     "//ios/chrome/browser/browser_state:test_support",
     "//ios/chrome/browser/reading_list",
-    "//ios/chrome/browser/tabs",
     "//ios/chrome/browser/u2f",
     "//ios/chrome/browser/web:tab_id_tab_helper",
     "//ios/chrome/browser/web:web_internal",
diff --git a/ios/chrome/browser/app_launcher/app_launcher_tab_helper.mm b/ios/chrome/browser/app_launcher/app_launcher_tab_helper.mm
index 21a5fbb..745582f6 100644
--- a/ios/chrome/browser/app_launcher/app_launcher_tab_helper.mm
+++ b/ios/chrome/browser/app_launcher/app_launcher_tab_helper.mm
@@ -13,10 +13,9 @@
 #import "ios/chrome/browser/app_launcher/app_launcher_abuse_detector.h"
 #include "ios/chrome/browser/app_launcher/app_launcher_flags.h"
 #import "ios/chrome/browser/app_launcher/app_launcher_tab_helper_delegate.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/chrome_url_util.h"
 #include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
-#import "ios/chrome/browser/tabs/legacy_tab_helper.h"
-#import "ios/chrome/browser/tabs/tab.h"
 #import "ios/chrome/browser/u2f/u2f_tab_helper.h"
 #import "ios/web/public/navigation_item.h"
 #import "ios/web/public/navigation_manager.h"
@@ -216,7 +215,8 @@
   bool is_link_transition = ui::PageTransitionTypeIncludingQualifiersIs(
       request_info.transition_type, ui::PAGE_TRANSITION_LINK);
 
-  Tab* tab = LegacyTabHelper::GetTabForWebState(web_state_);
+  ios::ChromeBrowserState* browser_state =
+      ios::ChromeBrowserState::FromBrowserState(web_state_->GetBrowserState());
 
   if (base::FeatureList::IsEnabled(kAppLauncherRefresh)) {
     if (!is_link_transition && original_pending_url.is_valid()) {
@@ -224,7 +224,7 @@
       // was a redirection, the |source_url| may not have been reported to
       // ReadingListWebStateObserver. Report it to mark as read if needed.
       ReadingListModel* model =
-          ReadingListModelFactory::GetForBrowserState(tab.browserState);
+          ReadingListModelFactory::GetForBrowserState(browser_state);
       if (model && model->loaded())
         model->SetReadStatus(original_pending_url, true);
     }
@@ -244,7 +244,7 @@
     // entry as read if needed.
     if (original_pending_url.is_valid()) {
       ReadingListModel* model =
-          ReadingListModelFactory::GetForBrowserState(tab.browserState);
+          ReadingListModelFactory::GetForBrowserState(browser_state);
       if (model && model->loaded())
         model->SetReadStatus(original_pending_url, true);
     }
diff --git a/ios/chrome/browser/app_launcher/app_launcher_tab_helper_unittest.mm b/ios/chrome/browser/app_launcher/app_launcher_tab_helper_unittest.mm
index 27e66f6d..21e0a57c 100644
--- a/ios/chrome/browser/app_launcher/app_launcher_tab_helper_unittest.mm
+++ b/ios/chrome/browser/app_launcher/app_launcher_tab_helper_unittest.mm
@@ -19,7 +19,6 @@
 #include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
 #import "ios/chrome/browser/chrome_url_util.h"
 #include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
-#import "ios/chrome/browser/tabs/legacy_tab_helper.h"
 #import "ios/chrome/browser/u2f/u2f_tab_helper.h"
 #import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/web/public/test/fakes/test_navigation_manager.h"
@@ -143,7 +142,6 @@
         chrome_browser_state_.get(),
         base::BindRepeating(&BuildReadingListModel));
     TabIdTabHelper::CreateForWebState(&web_state_);
-    LegacyTabHelper::CreateForWebState(&web_state_);
     is_reading_list_initialized_ = true;
   }
 
@@ -312,7 +310,6 @@
 TEST_F(AppLauncherTabHelperTest, U2FUrls) {
   // Add required tab helpers for the U2F check.
   TabIdTabHelper::CreateForWebState(&web_state_);
-  LegacyTabHelper::CreateForWebState(&web_state_);
   std::unique_ptr<web::NavigationItem> item = web::NavigationItem::Create();
 
   // "u2f-x-callback" scheme should only be created by the browser. External
diff --git a/ios/chrome/browser/tabs/tab.h b/ios/chrome/browser/tabs/tab.h
index 7162082..4a3c9d3 100644
--- a/ios/chrome/browser/tabs/tab.h
+++ b/ios/chrome/browser/tabs/tab.h
@@ -16,7 +16,6 @@
 
 @class AutofillController;
 @class CastController;
-@class ExternalAppLauncher;
 class GURL;
 @class OpenInController;
 @class OverscrollActionsController;
@@ -27,10 +26,6 @@
 @protocol TabDialogDelegate;
 @class Tab;
 
-namespace ios {
-class ChromeBrowserState;
-}
-
 namespace web {
 class WebState;
 }
@@ -69,9 +64,6 @@
 // loaded.
 @interface Tab : NSObject
 
-// Browser state associated with this Tab.
-@property(nonatomic, readonly) ios::ChromeBrowserState* browserState;
-
 // The Webstate associated with this Tab.
 @property(nonatomic, readonly) web::WebState* webState;
 
diff --git a/ios/chrome/browser/tabs/tab.mm b/ios/chrome/browser/tabs/tab.mm
index 727fe254..919a94df 100644
--- a/ios/chrome/browser/tabs/tab.mm
+++ b/ios/chrome/browser/tabs/tab.mm
@@ -116,6 +116,7 @@
 NSString* const kTabUrlKey = @"url";
 
 @interface Tab ()<CRWWebStateObserver> {
+  // Browser state associated with this Tab.
   ios::ChromeBrowserState* _browserState;
 
   OpenInController* _openInController;
@@ -142,7 +143,6 @@
 
 @implementation Tab
 
-@synthesize browserState = _browserState;
 @synthesize overscrollActionsController = _overscrollActionsController;
 @synthesize overscrollActionsControllerDelegate =
     overscrollActionsControllerDelegate_;
@@ -207,7 +207,7 @@
   [_overscrollActionsController setStyle:style];
   [_overscrollActionsController
       setDelegate:overscrollActionsControllerDelegate];
-  [_overscrollActionsController setBrowserState:self.browserState];
+  [_overscrollActionsController setBrowserState:_browserState];
   overscrollActionsControllerDelegate_ = overscrollActionsControllerDelegate;
 }
 
diff --git a/ios/chrome/browser/tabs/tab_unittest.mm b/ios/chrome/browser/tabs/tab_unittest.mm
index 19a0cce..55d4ab0 100644
--- a/ios/chrome/browser/tabs/tab_unittest.mm
+++ b/ios/chrome/browser/tabs/tab_unittest.mm
@@ -443,7 +443,7 @@
 
 TEST_P(TabTest, ClosingWebStateDoesNotRemoveSnapshot) {
   id partialMock = OCMPartialMock(
-      SnapshotCacheFactory::GetForBrowserState(tab_.browserState));
+      SnapshotCacheFactory::GetForBrowserState(chrome_browser_state_.get()));
   NSString* tab_id = TabIdTabHelper::FromWebState(tab_.webState)->tab_id();
   SnapshotTabHelper::CreateForWebState(tab_.webState, tab_id);
   [[partialMock reject] removeImageWithSessionID:tab_id];
@@ -461,7 +461,7 @@
 
 TEST_P(TabTest, CallingRemoveSnapshotRemovesSnapshot) {
   id partialMock = OCMPartialMock(
-      SnapshotCacheFactory::GetForBrowserState(tab_.browserState));
+      SnapshotCacheFactory::GetForBrowserState(chrome_browser_state_.get()));
   NSString* tab_id = TabIdTabHelper::FromWebState(tab_.webState)->tab_id();
 
   SnapshotTabHelper::CreateForWebState(tab_.webState, tab_id);
diff --git a/ios/chrome/browser/translate/BUILD.gn b/ios/chrome/browser/translate/BUILD.gn
index 0de815d..d5d6217bb 100644
--- a/ios/chrome/browser/translate/BUILD.gn
+++ b/ios/chrome/browser/translate/BUILD.gn
@@ -123,3 +123,32 @@
   ]
   libs = [ "XCTest.framework" ]
 }
+
+source_set("external_url_eg_tests") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  testonly = true
+  sources = [
+    "legacy_translate_infobar_egtest.mm",
+  ]
+  deps = [
+    ":translate",
+    "//base",
+    "//base/test:test_support",
+    "//components/language/ios/browser",
+    "//components/strings",
+    "//components/translate/core/browser",
+    "//components/translate/core/common",
+    "//components/translate/ios/browser",
+    "//ios/chrome/browser/browser_state",
+    "//ios/chrome/browser/ui/translate:translate_ui",
+    "//ios/chrome/test/app:test_support",
+    "//ios/chrome/test/earl_grey:test_support",
+    "//ios/third_party/earl_grey:earl_grey+link",
+    "//ios/web:earl_grey_test_support",
+    "//ios/web/public/test",
+    "//ios/web/public/test/http_server",
+    "//net",
+    "//ui/base",
+  ]
+  libs = [ "XCTest.framework" ]
+}
diff --git a/ios/chrome/browser/translate/DEPS b/ios/chrome/browser/translate/DEPS
index 9ca844d..3b733086 100644
--- a/ios/chrome/browser/translate/DEPS
+++ b/ios/chrome/browser/translate/DEPS
@@ -1,7 +1,10 @@
 specific_include_rules = {
   # web::HttpServer is deprecated in favor of net::EmbeddedTestServer.
-  # TODO:(crbug.com/891834) Remove this exception.
+  # TODO:(crbug.com/891834) Remove these exceptions.
   "translate_egtest\.mm": [
     "+ios/web/public/test/http_server",
   ],
+  "legacy_translate_infobar_egtest\.mm": [
+    "+ios/web/public/test/http_server",
+  ],
 }
diff --git a/ios/chrome/browser/translate/legacy_translate_infobar_egtest.mm b/ios/chrome/browser/translate/legacy_translate_infobar_egtest.mm
new file mode 100644
index 0000000..24a03bf3
--- /dev/null
+++ b/ios/chrome/browser/translate/legacy_translate_infobar_egtest.mm
@@ -0,0 +1,522 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <XCTest/XCTest.h>
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#import "base/test/ios/wait_util.h"
+#include "base/test/scoped_feature_list.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/translate/core/browser/translate_download_manager.h"
+#include "components/translate/core/browser/translate_manager.h"
+#include "components/translate/core/browser/translate_pref_names.h"
+#include "components/translate/core/browser/translate_prefs.h"
+#include "components/translate/core/common/translate_constants.h"
+#include "components/translate/core/common/translate_switches.h"
+#include "components/translate/ios/browser/ios_translate_driver.h"
+#import "components/translate/ios/browser/js_translate_manager.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#include "ios/chrome/browser/translate/chrome_ios_translate_client.h"
+#include "ios/chrome/browser/ui/translate/language_selection_view_controller.h"
+#import "ios/chrome/test/app/chrome_test_util.h"
+#include "ios/chrome/test/app/navigation_test_util.h"
+#import "ios/chrome/test/app/tab_test_util.h"
+#import "ios/chrome/test/app/web_view_interaction_test_util.h"
+#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
+#import "ios/chrome/test/earl_grey/chrome_matchers.h"
+#import "ios/chrome/test/earl_grey/chrome_test_case.h"
+#import "ios/web/public/test/earl_grey/js_test_util.h"
+#include "ios/web/public/test/http_server/data_response_provider.h"
+#import "ios/web/public/test/http_server/http_server.h"
+#include "ios/web/public/test/http_server/http_server_util.h"
+#include "net/base/url_util.h"
+#include "ui/base/l10n/l10n_util.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+
+// Some text in French language.
+const char kFrenchText[] =
+    "Des yeux qui font baisser les miens. Un rire qui se perd sur sa bouche."
+    "Voilà le portrait sans retouches de l'homme auquel j'appartiens "
+    "Quand il me prend dans ses bras Il me parle tout bas "
+    "Je vois la vie en rose Il me dit des mots d'amour "
+    "Des mots de tous les jours Et ça me fait quelque chose "
+    "Il est entré dans mon cœur Une part de bonheur Dont je connais la cause "
+    "C'est lui pour moi, moi pour lui, dans la vie "
+    "Il me l'a dit, l'a juré, pour la vie Et dès que je l'aperçois "
+    "Alors je sens en moi, Mon cœur qui bat Des nuits d'amour à plus finir "
+    "Un grand bonheur qui prend sa place Les ennuis, les chagrins s'effacent "
+    "Heureux, heureux à en mourir Quand il me prend dans ses bras "
+    "Il me parle tout bas Je vois la vie en rose Il me dit des mots d'amour "
+    "Des mots de tous les jours Et ça me fait quelque chose "
+    "Il est entré dans mon cœur Une part de bonheur Dont je connais la cause "
+    "C'est toi pour moi, moi pour toi, dans la vie "
+    "Tu me l'as dit, l'as juré, pour la vie Et dès que je t'aperçois "
+    "Alors je sens en moi Mon cœur qui bat";
+
+// Various HTML tags.
+const char kHtmlAttribute[] = "<html>";
+
+// Various link components.
+// TODO(crbug.com/729195): Re-write the hardcoded address.
+const char kHttpServerDomain[] = "127.0.0.1";
+const char kLanguagePath[] = "/languagepath/";
+const char kLinkPath[] = "/linkpath/";
+const char kSubresourcePath[] = "/subresourcepath/";
+const char kSomeLanguageUrl[] = "http://languagepath/?http=es";
+const char kFrenchPagePath[] = "/frenchpage/";
+const char kFrenchPageWithLinkPath[] = "/frenchpagewithlink/";
+const char kTranslateScriptPath[] = "/translatescript/";
+const char kTranslateScript[] = "Fake Translate Script";
+
+// Body text for /languagepath/.
+const char kLanguagePathText[] = "Some text here.";
+
+// Builds a HTML document with a French text and the given |html| and |meta|
+// tags.
+std::string GetFrenchPageHtml(const std::string& html_tag,
+                              const std::string& meta_tags) {
+  return html_tag + meta_tags + "<body>" + kFrenchText + "</body></html>";
+}
+
+// Returns the label of the "Always translate" switch in the Translate infobar.
+NSString* GetTranslateInfobarSwitchLabel(const std::string& language) {
+  return base::SysUTF16ToNSString(l10n_util::GetStringFUTF16(
+      IDS_TRANSLATE_INFOBAR_ALWAYS_TRANSLATE, base::UTF8ToUTF16(language)));
+}
+
+// Returns a matcher for the button with label "Cancel" in the language picker.
+// The language picker uses the system accessibility labels, thus no IDS_CANCEL.
+id<GREYMatcher> LanguagePickerCancelButton() {
+  return grey_accessibilityID(kLanguagePickerCancelButtonId);
+}
+
+// Returns a matcher for the button with label "Done" in the language picker.
+// The language picker uses the system accessibility labels, thus no IDS_DONE.
+id<GREYMatcher> LanguagePickerDoneButton() {
+  return grey_accessibilityID(kLanguagePickerDoneButtonId);
+}
+
+#pragma mark - TestResponseProvider
+
+// A ResponseProvider that provides html responses of texts in different
+// languages or links.
+class TestResponseProvider : public web::DataResponseProvider {
+ public:
+  // TestResponseProvider implementation.
+  bool CanHandleRequest(const Request& request) override;
+  void GetResponseHeadersAndBody(
+      const Request& request,
+      scoped_refptr<net::HttpResponseHeaders>* headers,
+      std::string* response_body) override;
+
+ private:
+  // Generates a page with a HTTP "Content-Language" header and "httpEquiv" meta
+  // tag.
+  // The URL in |request| has two parameters, "http" and "meta", that can be
+  // used to set the values of the header and the meta tag. For example:
+  // http://someurl?http=en&meta=fr generates a page with a "en" HTTP header and
+  // a "fr" meta tag.
+  void GetLanguageResponse(const Request& request,
+                           scoped_refptr<net::HttpResponseHeaders>* headers,
+                           std::string* response_body);
+};
+
+bool TestResponseProvider::CanHandleRequest(const Request& request) {
+  const GURL& url = request.url;
+  return url.host() == kHttpServerDomain &&
+         (url.path() == kLanguagePath || url.path() == kLinkPath ||
+          url.path() == kSubresourcePath || url.path() == kFrenchPagePath ||
+          url.path() == kFrenchPageWithLinkPath ||
+          url.path() == kTranslateScriptPath);
+}
+
+void TestResponseProvider::GetResponseHeadersAndBody(
+    const Request& request,
+    scoped_refptr<net::HttpResponseHeaders>* headers,
+    std::string* response_body) {
+  const GURL& url = request.url;
+  *headers = web::ResponseProvider::GetDefaultResponseHeaders();
+  if (url.path() == kLanguagePath) {
+    // HTTP header and meta tag read from parameters.
+    return GetLanguageResponse(request, headers, response_body);
+  } else if (url.path() == kSubresourcePath) {
+    // Different "Content-Language" headers in the main page and subresource.
+    (*headers)->AddHeader("Content-Language: fr");
+    *response_body = base::StringPrintf(
+        "<html><body><img src=%s></body></html>", kSomeLanguageUrl);
+    return;
+  } else if (url.path() == kLinkPath) {
+    // Link to a page with "Content Language" headers.
+    GURL url = web::test::HttpServer::MakeUrl(kSomeLanguageUrl);
+    *response_body = base::StringPrintf(
+        "<html><body><a href='%s' id='click'>Click</a></body></html>",
+        url.spec().c_str());
+    return;
+  } else if (url.path() == kFrenchPagePath) {
+    *response_body =
+        base::StringPrintf("<html><body>%s</body></html>", kFrenchText);
+    return;
+  } else if (url.path() == kFrenchPageWithLinkPath) {
+    GURL page_path_url = web::test::HttpServer::MakeUrl(
+        base::StringPrintf("http://%s", kFrenchPagePath));
+    *response_body = base::StringPrintf(
+        "<html><body>%s<br /><a href='%s' id='link'>link</a></body></html>",
+        kFrenchText, page_path_url.spec().c_str());
+    return;
+  } else if (url.path() == kTranslateScriptPath) {
+    *response_body = kTranslateScript;
+    return;
+  }
+  NOTREACHED();
+}
+
+void TestResponseProvider::GetLanguageResponse(
+    const Request& request,
+    scoped_refptr<net::HttpResponseHeaders>* headers,
+    std::string* response_body) {
+  const GURL& url = request.url;
+  // HTTP headers.
+  std::string http;
+  net::GetValueForKeyInQuery(url, "http", &http);
+  if (!http.empty())
+    (*headers)->AddHeader(std::string("Content-Language: ") + http);
+  // Response body.
+  std::string meta;
+  net::GetValueForKeyInQuery(url, "meta", &meta);
+  *response_body = "<html>";
+  if (!meta.empty()) {
+    *response_body += "<head>"
+                      "<meta http-equiv='content-language' content='" +
+                      meta +
+                      "'>"
+                      "</head>";
+  }
+  *response_body +=
+      base::StringPrintf("<html><body>%s</body></html>", kLanguagePathText);
+}
+
+}  // namespace
+
+using base::test::ios::kWaitForJSCompletionTimeout;
+using base::test::ios::WaitUntilConditionOrTimeout;
+using chrome_test_util::ButtonWithAccessibilityLabel;
+using chrome_test_util::ButtonWithAccessibilityLabelId;
+using chrome_test_util::CloseButton;
+using chrome_test_util::TapWebViewElementWithId;
+using translate::LanguageDetectionController;
+
+#pragma mark - MockTranslateScriptManager
+
+// Mock javascript translate manager that does not use the translate servers.
+// Translating the page just adds a 'Translated' button to the page, without
+// changing the text.
+@interface MockTranslateScriptManager : JsTranslateManager {
+  web::WebState* _webState;  // weak
+}
+
+- (instancetype)initWithWebState:(web::WebState*)webState;
+
+@end
+
+@implementation MockTranslateScriptManager
+
+- (instancetype)initWithWebState:(web::WebState*)webState {
+  if ((self = [super init])) {
+    _webState = webState;
+  }
+  return self;
+}
+
+- (void)setScript:(NSString*)script {
+}
+
+- (void)startTranslationFrom:(const std::string&)source
+                          to:(const std::string&)target {
+  // Add a button with the 'Translated' label to the web page.
+  // The test can check it to determine if this method has been called.
+  _webState->ExecuteJavaScript(base::UTF8ToUTF16(
+      "myButton = document.createElement('button');"
+      "myButton.appendChild(document.createTextNode('Translated'));"
+      "document.body.appendChild(myButton);"));
+}
+
+- (void)inject {
+  // Prevent the actual script from being injected and instead just invoke host
+  // with 'translate.ready' followed by 'translate.status'.
+  _webState->ExecuteJavaScript(
+      base::UTF8ToUTF16("__gCrWeb.message.invokeOnHost({"
+                        "  'command': 'translate.ready',"
+                        "  'errorCode': 0,"
+                        "  'loadTime': 0,"
+                        "  'readyTime': 0});"));
+  _webState->ExecuteJavaScript(
+      base::UTF8ToUTF16("__gCrWeb.message.invokeOnHost({"
+                        "  'command': 'translate.status',"
+                        "  'errorCode': 0,"
+                        "  'originalPageLanguage': 'fr',"
+                        "  'translationTime': 0});"));
+}
+
+@end
+
+#pragma mark - TranslateTestCase
+
+// Tests for translate.
+@interface TranslateTestCase : ChromeTestCase
+
+@end
+
+@implementation TranslateTestCase
+
++ (void)setUp {
+  [super setUp];
+  if (base::FeatureList::IsEnabled(translate::kCompactTranslateInfobarIOS)) {
+    // translate::kCompactTranslateInfobarIOS feature is enabled. You need
+    // to pass --disable-features=CompactTranslateInfobarIOS command line
+    // argument in order to run this test.
+    DCHECK(false);
+  }
+}
+
+- (void)setUp {
+  [super setUp];
+  // Reset translate prefs to default.
+  std::unique_ptr<translate::TranslatePrefs> translatePrefs(
+      ChromeIOSTranslateClient::CreateTranslatePrefs(
+          chrome_test_util::GetOriginalBrowserState()->GetPrefs()));
+  translatePrefs->ResetToDefaults();
+}
+
+- (void)tearDown {
+  // Reset translate prefs to default.
+  std::unique_ptr<translate::TranslatePrefs> translatePrefs(
+      ChromeIOSTranslateClient::CreateTranslatePrefs(
+          chrome_test_util::GetOriginalBrowserState()->GetPrefs()));
+  translatePrefs->ResetToDefaults();
+
+  // Do not allow offering translate in builds without an API key.
+  translate::TranslateManager::SetIgnoreMissingKeyForTesting(false);
+  [super tearDown];
+}
+
+#pragma mark - Test Cases
+
+// Tests that the language detection infobar is displayed.
+- (void)testLanguageDetectionInfobar {
+  const GURL URL =
+      web::test::HttpServer::MakeUrl("http://scenarioLanguageDetectionInfobar");
+  std::map<GURL, std::string> responses;
+  // A page with French text.
+  responses[URL] = GetFrenchPageHtml(kHtmlAttribute, "");
+  web::test::SetUpSimpleHttpServer(responses);
+  [ChromeEarlGrey loadURL:URL];
+
+  // Check that the "Before Translate" infobar is displayed.
+  [[EarlGrey selectElementWithMatcher:ButtonWithAccessibilityLabel(@"English")]
+      assertWithMatcher:grey_notNil()];
+  [[EarlGrey selectElementWithMatcher:ButtonWithAccessibilityLabelId(
+                                          IDS_TRANSLATE_INFOBAR_ACCEPT)]
+      assertWithMatcher:grey_notNil()];
+  [[EarlGrey selectElementWithMatcher:CloseButton()]
+      assertWithMatcher:grey_notNil()];
+
+  // Open the language picker.
+  NSString* kFrench = @"French";
+  [[EarlGrey selectElementWithMatcher:ButtonWithAccessibilityLabel(kFrench)]
+      performAction:grey_tap()];
+
+  [[EarlGrey selectElementWithMatcher:LanguagePickerCancelButton()]
+      assertWithMatcher:grey_notNil()];
+
+  // Change the language using the picker.
+  NSString* const kPickedLanguage = @"Finnish";
+  id<GREYMatcher> languageMatcher = grey_allOf(
+      chrome_test_util::StaticTextWithAccessibilityLabel(kPickedLanguage),
+      grey_sufficientlyVisible(), nil);
+  [[EarlGrey selectElementWithMatcher:languageMatcher]
+      performAction:grey_tap()];
+
+  [[EarlGrey selectElementWithMatcher:LanguagePickerDoneButton()]
+      performAction:grey_tap()];
+  [[EarlGrey
+      selectElementWithMatcher:ButtonWithAccessibilityLabel(kPickedLanguage)]
+      assertWithMatcher:grey_notNil()];
+  [[EarlGrey selectElementWithMatcher:ButtonWithAccessibilityLabel(kFrench)]
+      assertWithMatcher:grey_nil()];
+
+  // Deny the translation, and check that the infobar is dismissed.
+  [[EarlGrey selectElementWithMatcher:ButtonWithAccessibilityLabelId(
+                                          IDS_TRANSLATE_INFOBAR_DENY)]
+      performAction:grey_tap()];
+  [[EarlGrey
+      selectElementWithMatcher:ButtonWithAccessibilityLabel(kPickedLanguage)]
+      assertWithMatcher:grey_nil()];
+}
+
+// Tests that the Translate infobar is displayed after translation.
+- (void)testTranslateInfobar {
+  const GURL URL =
+      web::test::HttpServer::MakeUrl("http://scenarioTranslateInfobar");
+  std::map<GURL, std::string> responses;
+  // A page with some text.
+  responses[URL] = "<html><body>Hello world!</body></html>";
+  web::test::SetUpSimpleHttpServer(responses);
+
+  // Assert that Spanish to English translation is disabled.
+  std::unique_ptr<translate::TranslatePrefs> translatePrefs(
+      ChromeIOSTranslateClient::CreateTranslatePrefs(
+          chrome_test_util::GetOriginalBrowserState()->GetPrefs()));
+  GREYAssert(!translatePrefs->IsLanguagePairWhitelisted("es", "en"),
+             @"Translate Spanish is enabled");
+  // Increase accepted translation count for Spanish
+  for (int i = 0; i < 3; i++) {
+    translatePrefs->IncrementTranslationAcceptedCount("es");
+  }
+
+  // Open a new webpage.
+  [ChromeEarlGrey loadURL:URL];
+  [self simulateTranslationFromSpanishToEnglish];
+
+  // Check that the "Always Translate" switch is displayed in the infobar.
+  NSString* switchLabel = GetTranslateInfobarSwitchLabel("Spanish");
+  [[EarlGrey selectElementWithMatcher:ButtonWithAccessibilityLabel(switchLabel)]
+      assertWithMatcher:grey_sufficientlyVisible()];
+
+  // Toggle "Always Translate" and check the preference.
+  [[EarlGrey selectElementWithMatcher:ButtonWithAccessibilityLabel(switchLabel)]
+      performAction:grey_tap()];
+  id<GREYMatcher> switchOn =
+      grey_allOf(ButtonWithAccessibilityLabel(switchLabel),
+                 grey_accessibilityValue(@"1"), nil);
+  [[EarlGrey selectElementWithMatcher:switchOn]
+      assertWithMatcher:grey_notNil()];
+
+  // Assert that Spanish to English translation is not enabled after tapping
+  // the switch (should only be saved when "Done" button is tapped).
+  GREYAssert(!translatePrefs->IsLanguagePairWhitelisted("es", "en"),
+             @"Translate Spanish is disabled");
+
+  // Tap the "Done" button to save the preference.
+  [[EarlGrey selectElementWithMatcher:ButtonWithAccessibilityLabel(@"Done")]
+      performAction:grey_tap()];
+
+  // Assert that Spanish to English translation is enabled.
+  GREYAssert(translatePrefs->IsLanguagePairWhitelisted("es", "en"),
+             @"Translate Spanish is disabled");
+}
+
+// Tests that the "Always Translate" switch is not shown in incognito mode.
+- (void)testIncognitoTranslateInfobar {
+  const GURL URL =
+      web::test::HttpServer::MakeUrl("http://scenarioTranslateInfobar");
+  std::map<GURL, std::string> responses;
+  // A page with some text.
+  responses[URL] = "<html><body>Hello world!</body></html>";
+  web::test::SetUpSimpleHttpServer(responses);
+
+  // Increased accepted translation count for Spanish.
+  std::unique_ptr<translate::TranslatePrefs> translatePrefs(
+      ChromeIOSTranslateClient::CreateTranslatePrefs(
+          chrome_test_util::GetOriginalBrowserState()->GetPrefs()));
+  for (int i = 0; i < 3; i++) {
+    translatePrefs->IncrementTranslationAcceptedCount("es");
+  }
+
+  // Do a translation in incognito
+  [ChromeEarlGrey openNewIncognitoTab];
+  [ChromeEarlGrey loadURL:URL];
+  [self simulateTranslationFromSpanishToEnglish];
+  // Check that the infobar does not contain the "Always Translate" switch.
+  NSString* switchLabel = GetTranslateInfobarSwitchLabel("Spanish");
+  [[EarlGrey selectElementWithMatcher:ButtonWithAccessibilityLabel(switchLabel)]
+      assertWithMatcher:grey_nil()];
+}
+
+// Tests that translation occurs automatically on second navigation to an
+// already translated page.
+- (void)testAutoTranslateInfobar {
+  std::unique_ptr<web::DataResponseProvider> provider(new TestResponseProvider);
+  web::test::SetUpHttpServer(std::move(provider));
+
+  // Set up the mock translate script manager.
+  ChromeIOSTranslateClient* client = ChromeIOSTranslateClient::FromWebState(
+      chrome_test_util::GetCurrentWebState());
+  translate::IOSTranslateDriver* driver =
+      static_cast<translate::IOSTranslateDriver*>(client->GetTranslateDriver());
+  MockTranslateScriptManager* jsTranslateManager =
+      [[MockTranslateScriptManager alloc]
+          initWithWebState:chrome_test_util::GetCurrentWebState()];
+  driver->translate_controller()->SetJsTranslateManagerForTesting(
+      jsTranslateManager);
+
+  // Set up a fake URL for the translate script, to avoid hitting real servers.
+  base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess();
+  GURL translateScriptURL = web::test::HttpServer::MakeUrl(
+      base::StringPrintf("http://%s", kTranslateScriptPath));
+  command_line.AppendSwitchASCII(translate::switches::kTranslateScriptURL,
+                                 translateScriptURL.spec().c_str());
+
+  // Translate the page with the link.
+  GURL frenchPageURL = web::test::HttpServer::MakeUrl(
+      base::StringPrintf("http://%s", kFrenchPageWithLinkPath));
+  [ChromeEarlGrey loadURL:frenchPageURL];
+  [[EarlGrey selectElementWithMatcher:ButtonWithAccessibilityLabelId(
+                                          IDS_TRANSLATE_INFOBAR_ACCEPT)]
+      performAction:grey_tap()];
+
+  // Check that the translation happened.
+  [ChromeEarlGrey waitForWebViewContainingText:"Translated"];
+
+  // Click on the link.
+  [ChromeEarlGrey tapWebViewElementWithID:@"link"];
+  [ChromeEarlGrey waitForWebViewNotContainingText:"link"];
+
+  GURL frenchPagePathURL = web::test::HttpServer::MakeUrl(
+      base::StringPrintf("http://%s", kFrenchPagePath));
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::OmniboxText(
+                                          frenchPagePathURL.GetContent())]
+      assertWithMatcher:grey_notNil()];
+
+  // Check that the auto-translation happened.
+  [ChromeEarlGrey waitForWebViewContainingText:"Translated"];
+}
+
+#pragma mark - Utility methods
+
+// Simulates translation from Spanish to English, and asserts that the translate
+// InfoBar is shown.
+- (void)simulateTranslationFromSpanishToEnglish {
+  // Simulate translation.
+  ChromeIOSTranslateClient* client = ChromeIOSTranslateClient::FromWebState(
+      chrome_test_util::GetCurrentWebState());
+  client->GetTranslateManager()->PageTranslated(
+      "es", "en", translate::TranslateErrors::NONE);
+
+  // The infobar is presented with an animation. Wait for the "Done" button
+  // to become visibile before considering the animation as complete.
+  [ChromeEarlGrey waitForElementWithMatcherSufficientlyVisible:
+                      ButtonWithAccessibilityLabelId(IDS_DONE)];
+
+  // Assert that the infobar is visible.
+  [[EarlGrey selectElementWithMatcher:ButtonWithAccessibilityLabelId(IDS_DONE)]
+      assertWithMatcher:grey_sufficientlyVisible()];
+  [[EarlGrey selectElementWithMatcher:ButtonWithAccessibilityLabelId(
+                                          IDS_TRANSLATE_INFOBAR_REVERT)]
+      assertWithMatcher:grey_sufficientlyVisible()];
+  [[EarlGrey selectElementWithMatcher:CloseButton()]
+      assertWithMatcher:grey_sufficientlyVisible()];
+}
+
+@end
diff --git a/ios/chrome/browser/translate/translate_egtest.mm b/ios/chrome/browser/translate/translate_egtest.mm
index f4724555..23b44ae 100644
--- a/ios/chrome/browser/translate/translate_egtest.mm
+++ b/ios/chrome/browser/translate/translate_egtest.mm
@@ -111,24 +111,6 @@
   return html_tag + meta_tags + "<body>" + kFrenchText + "</body></html>";
 }
 
-// Returns the label of the "Always translate" switch in the Translate infobar.
-NSString* GetTranslateInfobarSwitchLabel(const std::string& language) {
-  return base::SysUTF16ToNSString(l10n_util::GetStringFUTF16(
-      IDS_TRANSLATE_INFOBAR_ALWAYS_TRANSLATE, base::UTF8ToUTF16(language)));
-}
-
-// Returns a matcher for the button with label "Cancel" in the language picker.
-// The language picker uses the system accessibility labels, thus no IDS_CANCEL.
-id<GREYMatcher> LanguagePickerCancelButton() {
-  return grey_accessibilityID(kLanguagePickerCancelButtonId);
-}
-
-// Returns a matcher for the button with label "Done" in the language picker.
-// The language picker uses the system accessibility labels, thus no IDS_DONE.
-id<GREYMatcher> LanguagePickerDoneButton() {
-  return grey_accessibilityID(kLanguagePickerDoneButtonId);
-}
-
 // Assigns the testing callback for the current WebState's language detection
 // helper.
 void SetTestingLanguageDetectionCallback(
@@ -623,225 +605,6 @@
              @"A language has been detected");
 }
 
-// Tests that the language detection infobar is displayed.
-- (void)testLanguageDetectionInfobar {
-  // The translate machinery will not auto-fire without API keys, unless that
-  // behavior is overridden for testing.
-  translate::TranslateManager::SetIgnoreMissingKeyForTesting(true);
-
-  // Reset translate prefs to default.
-  std::unique_ptr<translate::TranslatePrefs> translatePrefs(
-      ChromeIOSTranslateClient::CreateTranslatePrefs(
-          chrome_test_util::GetOriginalBrowserState()->GetPrefs()));
-  translatePrefs->ResetToDefaults();
-
-  const GURL URL =
-      web::test::HttpServer::MakeUrl("http://scenarioLanguageDetectionInfobar");
-  std::map<GURL, std::string> responses;
-  // A page with French text.
-  responses[URL] = GetFrenchPageHtml(kHtmlAttribute, "");
-  web::test::SetUpSimpleHttpServer(responses);
-  [ChromeEarlGrey loadURL:URL];
-
-  // Check that the "Before Translate" infobar is displayed.
-  [[EarlGrey
-      selectElementWithMatcher:chrome_test_util::ButtonWithAccessibilityLabel(
-                                   @"English")]
-      assertWithMatcher:grey_notNil()];
-  [[EarlGrey
-      selectElementWithMatcher:chrome_test_util::ButtonWithAccessibilityLabelId(
-                                   IDS_TRANSLATE_INFOBAR_ACCEPT)]
-      assertWithMatcher:grey_notNil()];
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::CloseButton()]
-      assertWithMatcher:grey_notNil()];
-
-  // Open the language picker.
-  NSString* kFrench = @"French";
-  [[EarlGrey
-      selectElementWithMatcher:chrome_test_util::ButtonWithAccessibilityLabel(
-                                   kFrench)] performAction:grey_tap()];
-
-  [[EarlGrey selectElementWithMatcher:LanguagePickerCancelButton()]
-      assertWithMatcher:grey_notNil()];
-
-  // Change the language using the picker.
-  NSString* const kPickedLanguage = @"Finnish";
-  id<GREYMatcher> languageMatcher = grey_allOf(
-      chrome_test_util::StaticTextWithAccessibilityLabel(kPickedLanguage),
-      grey_sufficientlyVisible(), nil);
-  [[EarlGrey selectElementWithMatcher:languageMatcher]
-      performAction:grey_tap()];
-
-  [[EarlGrey selectElementWithMatcher:LanguagePickerDoneButton()]
-      performAction:grey_tap()];
-  [[EarlGrey
-      selectElementWithMatcher:chrome_test_util::ButtonWithAccessibilityLabel(
-                                   kPickedLanguage)]
-      assertWithMatcher:grey_notNil()];
-  [[EarlGrey
-      selectElementWithMatcher:chrome_test_util::ButtonWithAccessibilityLabel(
-                                   kFrench)] assertWithMatcher:grey_nil()];
-
-  // Deny the translation, and check that the infobar is dismissed.
-  [[EarlGrey
-      selectElementWithMatcher:chrome_test_util::ButtonWithAccessibilityLabelId(
-                                   IDS_TRANSLATE_INFOBAR_DENY)]
-      performAction:grey_tap()];
-  [[EarlGrey
-      selectElementWithMatcher:chrome_test_util::ButtonWithAccessibilityLabel(
-                                   kPickedLanguage)]
-      assertWithMatcher:grey_nil()];
-}
-
-// Tests that the Translate infobar is displayed after translation.
-- (void)testTranslateInfobar {
-  const GURL URL =
-      web::test::HttpServer::MakeUrl("http://scenarioTranslateInfobar");
-  std::map<GURL, std::string> responses;
-  // A page with some text.
-  responses[URL] = "<html><body>Hello world!</body></html>";
-  web::test::SetUpSimpleHttpServer(responses);
-
-  // Reset translate prefs to default.
-  std::unique_ptr<translate::TranslatePrefs> translatePrefs(
-      ChromeIOSTranslateClient::CreateTranslatePrefs(
-          chrome_test_util::GetOriginalBrowserState()->GetPrefs()));
-  translatePrefs->ResetToDefaults();
-
-  // Assert that Spanish to English translation is disabled.
-  GREYAssert(!translatePrefs->IsLanguagePairWhitelisted("es", "en"),
-             @"Translate Spanish is enabled");
-  // Increase accepted translation count for Spanish
-  for (int i = 0; i < 3; i++) {
-    translatePrefs->IncrementTranslationAcceptedCount("es");
-  }
-
-  // Open a new webpage.
-  [ChromeEarlGrey loadURL:URL];
-  [self simulateTranslationFromSpanishToEnglish];
-
-  // Check that the "Always Translate" switch is displayed in the infobar.
-  NSString* switchLabel = GetTranslateInfobarSwitchLabel("Spanish");
-  [[EarlGrey
-      selectElementWithMatcher:chrome_test_util::ButtonWithAccessibilityLabel(
-                                   switchLabel)]
-      assertWithMatcher:grey_sufficientlyVisible()];
-
-  // Toggle "Always Translate" and check the preference.
-  [[EarlGrey
-      selectElementWithMatcher:chrome_test_util::ButtonWithAccessibilityLabel(
-                                   switchLabel)] performAction:grey_tap()];
-  id<GREYMatcher> switchOn =
-      grey_allOf(chrome_test_util::ButtonWithAccessibilityLabel(switchLabel),
-                 grey_accessibilityValue(@"1"), nil);
-  [[EarlGrey selectElementWithMatcher:switchOn]
-      assertWithMatcher:grey_notNil()];
-
-  // Assert that Spanish to English translation is not enabled after tapping
-  // the switch (should only be saved when "Done" button is tapped).
-  GREYAssert(!translatePrefs->IsLanguagePairWhitelisted("es", "en"),
-             @"Translate Spanish is disabled");
-
-  // Tap the "Done" button to save the preference.
-  [[EarlGrey
-      selectElementWithMatcher:chrome_test_util::ButtonWithAccessibilityLabel(
-                                   @"Done")] performAction:grey_tap()];
-
-  // Assert that Spanish to English translation is enabled.
-  GREYAssert(translatePrefs->IsLanguagePairWhitelisted("es", "en"),
-             @"Translate Spanish is disabled");
-}
-
-// Tests that the "Always Translate" switch is not shown in incognito mode.
-- (void)testIncognitoTranslateInfobar {
-  const GURL URL =
-      web::test::HttpServer::MakeUrl("http://scenarioTranslateInfobar");
-  std::map<GURL, std::string> responses;
-  // A page with some text.
-  responses[URL] = "<html><body>Hello world!</body></html>";
-  web::test::SetUpSimpleHttpServer(responses);
-
-  // Reset translate prefs to default.
-  std::unique_ptr<translate::TranslatePrefs> translatePrefs(
-      ChromeIOSTranslateClient::CreateTranslatePrefs(
-          chrome_test_util::GetOriginalBrowserState()->GetPrefs()));
-  translatePrefs->ResetToDefaults();
-  // Increased accepted translation count for Spanish.
-  for (int i = 0; i < 3; i++) {
-    translatePrefs->IncrementTranslationAcceptedCount("es");
-  }
-
-  // Do a translation in incognito
-  [ChromeEarlGrey openNewIncognitoTab];
-  [ChromeEarlGrey loadURL:URL];
-  [self simulateTranslationFromSpanishToEnglish];
-  // Check that the infobar does not contain the "Always Translate" switch.
-  NSString* switchLabel = GetTranslateInfobarSwitchLabel("Spanish");
-  [[EarlGrey
-      selectElementWithMatcher:chrome_test_util::ButtonWithAccessibilityLabel(
-                                   switchLabel)] assertWithMatcher:grey_nil()];
-}
-
-// Tests that translation occurs automatically on second navigation to an
-// already translated page.
-- (void)testAutoTranslate {
-  // The translate machinery will not auto-fire without API keys, unless that
-  // behavior is overridden for testing.
-  translate::TranslateManager::SetIgnoreMissingKeyForTesting(true);
-
-  std::unique_ptr<web::DataResponseProvider> provider(new TestResponseProvider);
-  web::test::SetUpHttpServer(std::move(provider));
-
-  // Reset translate prefs to default.
-  std::unique_ptr<translate::TranslatePrefs> translatePrefs(
-      ChromeIOSTranslateClient::CreateTranslatePrefs(
-          chrome_test_util::GetOriginalBrowserState()->GetPrefs()));
-  translatePrefs->ResetToDefaults();
-
-  // Set up the mock translate script manager.
-  ChromeIOSTranslateClient* client = ChromeIOSTranslateClient::FromWebState(
-      chrome_test_util::GetCurrentWebState());
-  translate::IOSTranslateDriver* driver =
-      static_cast<translate::IOSTranslateDriver*>(client->GetTranslateDriver());
-  MockTranslateScriptManager* jsTranslateManager =
-      [[MockTranslateScriptManager alloc]
-          initWithWebState:chrome_test_util::GetCurrentWebState()];
-  driver->translate_controller()->SetJsTranslateManagerForTesting(
-      jsTranslateManager);
-
-  // Set up a fake URL for the translate script, to avoid hitting real servers.
-  base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess();
-  GURL translateScriptURL = web::test::HttpServer::MakeUrl(
-      base::StringPrintf("http://%s", kTranslateScriptPath));
-  command_line.AppendSwitchASCII(translate::switches::kTranslateScriptURL,
-                                 translateScriptURL.spec().c_str());
-
-  // Translate the page with the link.
-  GURL frenchPageURL = web::test::HttpServer::MakeUrl(
-      base::StringPrintf("http://%s", kFrenchPageWithLinkPath));
-  [ChromeEarlGrey loadURL:frenchPageURL];
-  [[EarlGrey
-      selectElementWithMatcher:chrome_test_util::ButtonWithAccessibilityLabelId(
-                                   IDS_TRANSLATE_INFOBAR_ACCEPT)]
-      performAction:grey_tap()];
-
-  // Check that the translation happened.
-  [ChromeEarlGrey waitForWebViewContainingText:"Translated"];
-
-  // Click on the link.
-  [ChromeEarlGrey tapWebViewElementWithID:@"link"];
-  [ChromeEarlGrey waitForWebViewNotContainingText:"link"];
-
-  GURL frenchPagePathURL = web::test::HttpServer::MakeUrl(
-      base::StringPrintf("http://%s", kFrenchPagePath));
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::OmniboxText(
-                                          frenchPagePathURL.GetContent())]
-      assertWithMatcher:grey_notNil()];
-
-  // Check that the auto-translation happened.
-  [ChromeEarlGrey waitForWebViewContainingText:"Translated"];
-}
-
 #pragma mark - Utility methods
 
 // Waits until a language has been detected and checks the language details.
@@ -880,32 +643,4 @@
              adoptedLanguageError);
 }
 
-// Simulates translation from Spanish to English, and asserts that the translate
-// InfoBar is shown.
-- (void)simulateTranslationFromSpanishToEnglish {
-  // Simulate translation.
-  ChromeIOSTranslateClient* client = ChromeIOSTranslateClient::FromWebState(
-      chrome_test_util::GetCurrentWebState());
-  client->GetTranslateManager()->PageTranslated(
-      "es", "en", translate::TranslateErrors::NONE);
-
-  // The infobar is presented with an animation. Wait for the "Done" button
-  // to become visibile before considering the animation as complete.
-  [ChromeEarlGrey
-      waitForElementWithMatcherSufficientlyVisible:
-          chrome_test_util::ButtonWithAccessibilityLabelId(IDS_DONE)];
-
-  // Assert that the infobar is visible.
-  [[EarlGrey
-      selectElementWithMatcher:chrome_test_util::ButtonWithAccessibilityLabelId(
-                                   IDS_DONE)]
-      assertWithMatcher:grey_sufficientlyVisible()];
-  [[EarlGrey
-      selectElementWithMatcher:chrome_test_util::ButtonWithAccessibilityLabelId(
-                                   IDS_TRANSLATE_INFOBAR_REVERT)]
-      assertWithMatcher:grey_sufficientlyVisible()];
-  [[EarlGrey selectElementWithMatcher:chrome_test_util::CloseButton()]
-      assertWithMatcher:grey_sufficientlyVisible()];
-}
-
 @end
diff --git a/ios/chrome/browser/ui/activity_services/share_to_data_builder_unittest.mm b/ios/chrome/browser/ui/activity_services/share_to_data_builder_unittest.mm
index e8fffd6..f32bae3d 100644
--- a/ios/chrome/browser/ui/activity_services/share_to_data_builder_unittest.mm
+++ b/ios/chrome/browser/ui/activity_services/share_to_data_builder_unittest.mm
@@ -102,10 +102,6 @@
 
     tab_ = [[ShareToDataBuilderTestTabMock alloc]
         initWithWebState:std::move(web_state)];
-    OCMockObject* tab_mock = static_cast<OCMockObject*>(tab_);
-
-    ios::ChromeBrowserState* ptr = chrome_browser_state_.get();
-    [[[tab_mock stub] andReturnValue:OCMOCK_VALUE(ptr)] browserState];
   }
 
   void TearDown() override {
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_view_ios.h b/ios/chrome/browser/ui/omnibox/omnibox_view_ios.h
index 0599ed1..44752c36 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_view_ios.h
+++ b/ios/chrome/browser/ui/omnibox/omnibox_view_ios.h
@@ -149,8 +149,8 @@
   void EmphasizeURLComponents() override;
 
  private:
-  void SetEmphasis(bool emphasize, const gfx::Range& range) override{};
-  void UpdateSchemeStyle(const gfx::Range& scheme_range) override{};
+  void SetEmphasis(bool emphasize, const gfx::Range& range) override {}
+  void UpdateSchemeStyle(const gfx::Range& scheme_range) override {}
 
   // Calculates text attributes according to |display_text| and
   // returns them in an autoreleased object.
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_image_item.h b/ios/chrome/browser/ui/table_view/cells/table_view_image_item.h
index 4c5efe3..51e3853 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_image_item.h
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_image_item.h
@@ -15,7 +15,7 @@
 
 // The image in the cell. If nil, won't be added to the view hierarchy.
 @property(nonatomic, readwrite, strong) UIImage* image;
-// The title label in the cell.
+// The text label in the cell.
 @property(nonatomic, readwrite, copy) NSString* title;
 // UIColor for the cell's textLabel. ChromeTableViewStyler's |cellTitleColor|
 // takes precedence over black color, but not over |textColor|.
@@ -34,13 +34,14 @@
 
 @end
 
-// TableViewImageCell contains a favicon, a title, and an optional chevron.
+// TableViewImageCell contains a favicon, a text, an optional detail text and an
+// optional chevron.
 @interface TableViewImageCell : TableViewCell
 
 // The cell favicon imageView.
 @property(nonatomic, readonly, strong) UIImageView* imageView;
-// The cell title.
-@property(nonatomic, readonly, strong) UILabel* titleLabel;
+// The cell text.
+@property(nonatomic, readonly, strong) UILabel* textLabel;
 // The cell detail text.
 @property(nonatomic, readonly, strong) UILabel* detailTextLabel;
 
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_image_item.mm b/ios/chrome/browser/ui/table_view/cells/table_view_image_item.mm
index 89a26ea9..b5213393 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_image_item.mm
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_image_item.mm
@@ -42,17 +42,17 @@
     cell.imageView.hidden = YES;
   }
 
-  cell.titleLabel.text = self.title;
+  cell.textLabel.text = self.title;
   cell.detailTextLabel.text = self.detailText;
   UIColor* cellBackgroundColor = styler.cellBackgroundColor
                                      ? styler.cellBackgroundColor
                                      : styler.tableViewBackgroundColor;
   cell.imageView.backgroundColor = cellBackgroundColor;
-  cell.titleLabel.backgroundColor = cellBackgroundColor;
+  cell.textLabel.backgroundColor = cellBackgroundColor;
   if (self.textColor) {
-    cell.titleLabel.textColor = self.textColor;
+    cell.textLabel.textColor = self.textColor;
   } else if (styler.cellTitleColor) {
-    cell.titleLabel.textColor = styler.cellTitleColor;
+    cell.textLabel.textColor = styler.cellTitleColor;
   } else {
     cell.textLabel.textColor = UIColor.blackColor;
   }
@@ -72,6 +72,7 @@
 
 // These properties overrides the ones from UITableViewCell, so this @synthesize
 // cannot be removed.
+@synthesize textLabel = _textLabel;
 @synthesize detailTextLabel = _detailTextLabel;
 @synthesize imageView = _imageView;
 
@@ -87,10 +88,10 @@
                                   forAxis:UILayoutConstraintAxisHorizontal];
 
     // Set font size using dynamic type.
-    _titleLabel = [[UILabel alloc] init];
-    _titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
-    _titleLabel.adjustsFontForContentSizeCategory = YES;
-    [_titleLabel
+    _textLabel = [[UILabel alloc] init];
+    _textLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
+    _textLabel.adjustsFontForContentSizeCategory = YES;
+    [_textLabel
         setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
                                         forAxis:
                                             UILayoutConstraintAxisHorizontal];
@@ -101,7 +102,7 @@
     _detailTextLabel.numberOfLines = 0;
 
     UIStackView* verticalStack = [[UIStackView alloc]
-        initWithArrangedSubviews:@[ _titleLabel, _detailTextLabel ]];
+        initWithArrangedSubviews:@[ _textLabel, _detailTextLabel ]];
     verticalStack.translatesAutoresizingMaskIntoConstraints = NO;
     verticalStack.axis = UILayoutConstraintAxisVertical;
     verticalStack.spacing = 0;
@@ -185,10 +186,10 @@
 
 - (NSString*)accessibilityLabel {
   if (self.detailTextLabel.text) {
-    return [NSString stringWithFormat:@"%@, %@", self.titleLabel.text,
+    return [NSString stringWithFormat:@"%@, %@", self.textLabel.text,
                                       self.detailTextLabel.text];
   }
-  return self.titleLabel.text;
+  return self.textLabel.text;
 }
 
 @end
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_image_item_unittest.mm b/ios/chrome/browser/ui/table_view/cells/table_view_image_item_unittest.mm
index f75fbdd..fd81282 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_image_item_unittest.mm
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_image_item_unittest.mm
@@ -39,7 +39,7 @@
   EXPECT_FALSE(imageCell.imageView.image);
 
   [item configureCell:cell withStyler:[[ChromeTableViewStyler alloc] init]];
-  EXPECT_NSEQ(text, imageCell.titleLabel.text);
+  EXPECT_NSEQ(text, imageCell.textLabel.text);
   EXPECT_NSEQ(detailText, imageCell.detailTextLabel.text);
   EXPECT_FALSE(imageCell.imageView.isHidden);
 }
diff --git a/ios/chrome/test/BUILD.gn b/ios/chrome/test/BUILD.gn
index 883988a8..393f666 100644
--- a/ios/chrome/test/BUILD.gn
+++ b/ios/chrome/test/BUILD.gn
@@ -97,19 +97,6 @@
   ]
 }
 
-source_set("unit_tests") {
-  configs += [ "//build/config/compiler:enable_arc" ]
-  testonly = true
-  sources = [
-    "google_toolbox_unittest.mm",
-  ]
-  deps = [
-    "//base",
-    "//testing/gtest",
-    "//third_party/google_toolbox_for_mac",
-  ]
-}
-
 test("ios_chrome_perftests") {
   deps = [
     # Ensure that all perf tests are run, use fake hooks and pack resources.
@@ -134,7 +121,6 @@
     ios_packed_resources_target,
 
     # Add unit_tests target here.
-    ":unit_tests",
     "//ios/chrome/app:unit_tests",
     "//ios/chrome/app/application_delegate:unit_tests",
     "//ios/chrome/app/spotlight:unit_tests",
diff --git a/ios/chrome/test/earl_grey/BUILD.gn b/ios/chrome/test/earl_grey/BUILD.gn
index 97b6437a..cb80093 100644
--- a/ios/chrome/test/earl_grey/BUILD.gn
+++ b/ios/chrome/test/earl_grey/BUILD.gn
@@ -10,6 +10,7 @@
     ":ios_chrome_autofill_automation_egtests",
     ":ios_chrome_bookmarks_egtests",
     ":ios_chrome_device_check_egtests",
+    ":ios_chrome_external_url_egtests",
     ":ios_chrome_flaky_egtests",
     ":ios_chrome_integration_egtests",
     ":ios_chrome_manual_fill_egtests",
@@ -201,6 +202,12 @@
   ]
 }
 
+chrome_ios_eg_test("ios_chrome_external_url_egtests") {
+  deps = [
+    "//ios/chrome/browser/translate:external_url_eg_tests",
+  ]
+}
+
 chrome_ios_eg_test("ios_chrome_unified_consent_egtests") {
   deps = [
     "//ios/chrome/browser/ui/settings/google_services:unified_consent_eg_tests",
diff --git a/ios/chrome/test/google_toolbox_unittest.mm b/ios/chrome/test/google_toolbox_unittest.mm
deleted file mode 100644
index c67dd1f..0000000
--- a/ios/chrome/test/google_toolbox_unittest.mm
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import <Foundation/Foundation.h>
-
-#include "base/logging.h"
-#import "testing/gtest_mac.h"
-#include "testing/platform_test.h"
-#import "third_party/google_toolbox_for_mac/src/Foundation/GTMNSDictionary+URLArguments.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-using GoogleToolboxForMacTest = PlatformTest;
-
-// TODO(crbug.com/933827): Remove this pragma.
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-
-// [NSDictionary gtm_dictionaryWithHttpArgumentsString] is used downstream.
-// This test ensures that we keep compiling the file.
-TEST_F(GoogleToolboxForMacTest, dictionaryWithHttpArgumentsString) {
-  NSDictionary* dict = [NSDictionary gtm_dictionaryWithHttpArgumentsString:@""];
-  EXPECT_EQ(0u, [dict count]);
-}
-
-#pragma clang diagnostic pop
diff --git a/ios/web/navigation/wk_navigation_util_unittest.mm b/ios/web/navigation/wk_navigation_util_unittest.mm
index 237f23b..e3ac50e 100644
--- a/ios/web/navigation/wk_navigation_util_unittest.mm
+++ b/ios/web/navigation/wk_navigation_util_unittest.mm
@@ -43,8 +43,7 @@
 }
 
 // Extracts session dictionary from |restore_session_url|.
-int ExtractSessionDict(GURL restore_session_url,
-                       std::unique_ptr<base::Value>* session_value) {
+base::JSONReader::ValueWithError ExtractSessionDict(GURL restore_session_url) {
   NSString* fragment = net::NSURLWithGURL(restore_session_url).fragment;
   NSString* encoded_session =
       [fragment substringFromIndex:strlen(kRestoreSessionSessionHashPrefix)];
@@ -52,11 +51,8 @@
       base::SysNSStringToUTF8(encoded_session),
       net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS);
 
-  int error_code = 0;
-  *session_value = base::JSONReader::ReadAndReturnErrorDeprecated(
-      session_json, base::JSON_PARSE_RFC, &error_code,
-      /*error_msg_out=*/nullptr);
-  return error_code;
+  return base::JSONReader::ReadAndReturnValueWithError(session_json,
+                                                       base::JSON_PARSE_RFC);
 }
 }
 
@@ -108,17 +104,18 @@
   ASSERT_TRUE(IsRestoreSessionUrl(restore_session_url));
 
   // Extract session JSON from restoration URL.
-  std::unique_ptr<base::Value> session_value;
-  ASSERT_EQ(0, ExtractSessionDict(restore_session_url, &session_value));
-  ASSERT_TRUE(session_value.get());
+  base::JSONReader::ValueWithError value_with_error =
+      ExtractSessionDict(restore_session_url);
+  ASSERT_EQ(base::JSONReader::JSON_NO_ERROR, value_with_error.error_code);
+  ASSERT_TRUE(value_with_error.value.has_value());
 
   // Verify that all titles and URLs are present.
-  base::Value* titles_value = session_value->FindKey("titles");
+  base::Value* titles_value = value_with_error.value->FindKey("titles");
   ASSERT_TRUE(titles_value);
   ASSERT_TRUE(titles_value->is_list());
   ASSERT_EQ(kItemCount, titles_value->GetList().size());
 
-  base::Value* urls_value = session_value->FindKey("urls");
+  base::Value* urls_value = value_with_error.value->FindKey("urls");
   ASSERT_TRUE(urls_value);
   ASSERT_TRUE(urls_value->is_list());
   ASSERT_EQ(kItemCount, urls_value->GetList().size());
@@ -137,12 +134,13 @@
   ASSERT_TRUE(IsRestoreSessionUrl(restore_session_url));
 
   // Extract session JSON from restoration URL.
-  std::unique_ptr<base::Value> session_value;
-  ASSERT_EQ(0, ExtractSessionDict(restore_session_url, &session_value));
-  ASSERT_TRUE(session_value.get());
+  base::JSONReader::ValueWithError value_with_error =
+      ExtractSessionDict(restore_session_url);
+  ASSERT_EQ(base::JSONReader::JSON_NO_ERROR, value_with_error.error_code);
+  ASSERT_TRUE(value_with_error.value.has_value());
 
   // Verify that first kMaxSessionSize titles and URLs are present.
-  base::Value* titles_value = session_value->FindKey("titles");
+  base::Value* titles_value = value_with_error.value->FindKey("titles");
   ASSERT_TRUE(titles_value);
   ASSERT_TRUE(titles_value->is_list());
   ASSERT_EQ(static_cast<size_t>(kMaxSessionSize),
@@ -150,7 +148,7 @@
   ASSERT_EQ("Test0", titles_value->GetList()[0].GetString());
   ASSERT_EQ("Test74", titles_value->GetList()[kMaxSessionSize - 1].GetString());
 
-  base::Value* urls_value = session_value->FindKey("urls");
+  base::Value* urls_value = value_with_error.value->FindKey("urls");
   ASSERT_TRUE(urls_value);
   ASSERT_TRUE(urls_value->is_list());
   ASSERT_EQ(static_cast<size_t>(kMaxSessionSize), urls_value->GetList().size());
@@ -159,7 +157,8 @@
             urls_value->GetList()[kMaxSessionSize - 1].GetString());
 
   // Verify the offset is correct.
-  ASSERT_EQ(1 - kMaxSessionSize, session_value->FindKey("offset")->GetInt());
+  ASSERT_EQ(1 - kMaxSessionSize,
+            value_with_error.value->FindKey("offset")->GetInt());
 }
 
 // Verifies that large session can be stored in NSURL and that extra items
@@ -175,12 +174,13 @@
   ASSERT_TRUE(IsRestoreSessionUrl(restore_session_url));
 
   // Extract session JSON from restoration URL.
-  std::unique_ptr<base::Value> session_value;
-  ASSERT_EQ(0, ExtractSessionDict(restore_session_url, &session_value));
-  ASSERT_TRUE(session_value.get());
+  base::JSONReader::ValueWithError value_with_error =
+      ExtractSessionDict(restore_session_url);
+  ASSERT_EQ(base::JSONReader::JSON_NO_ERROR, value_with_error.error_code);
+  ASSERT_TRUE(value_with_error.value.has_value());
 
   // Verify that last kMaxSessionSize titles and URLs are present.
-  base::Value* titles_value = session_value->FindKey("titles");
+  base::Value* titles_value = value_with_error.value->FindKey("titles");
   ASSERT_TRUE(titles_value);
   ASSERT_TRUE(titles_value->is_list());
   ASSERT_EQ(static_cast<size_t>(kMaxSessionSize),
@@ -189,7 +189,7 @@
   ASSERT_EQ("Test149",
             titles_value->GetList()[kMaxSessionSize - 1].GetString());
 
-  base::Value* urls_value = session_value->FindKey("urls");
+  base::Value* urls_value = value_with_error.value->FindKey("urls");
   ASSERT_TRUE(urls_value);
   ASSERT_TRUE(urls_value->is_list());
   ASSERT_EQ(static_cast<size_t>(kMaxSessionSize), urls_value->GetList().size());
@@ -198,7 +198,7 @@
             urls_value->GetList()[kMaxSessionSize - 1].GetString());
 
   // Verify the offset is correct.
-  ASSERT_EQ(0, session_value->FindKey("offset")->GetInt());
+  ASSERT_EQ(0, value_with_error.value->FindKey("offset")->GetInt());
 }
 
 // Verifies that large session can be stored in NSURL and that extra items
@@ -215,12 +215,13 @@
   ASSERT_TRUE(IsRestoreSessionUrl(restore_session_url));
 
   // Extract session JSON from restoration URL.
-  std::unique_ptr<base::Value> session_value;
-  ASSERT_EQ(0, ExtractSessionDict(restore_session_url, &session_value));
-  ASSERT_TRUE(session_value.get());
+  base::JSONReader::ValueWithError value_with_error =
+      ExtractSessionDict(restore_session_url);
+  ASSERT_EQ(base::JSONReader::JSON_NO_ERROR, value_with_error.error_code);
+  ASSERT_TRUE(value_with_error.value.has_value());
 
   // Verify that last kMaxSessionSize titles and URLs are present.
-  base::Value* titles_value = session_value->FindKey("titles");
+  base::Value* titles_value = value_with_error.value->FindKey("titles");
   ASSERT_TRUE(titles_value);
   ASSERT_TRUE(titles_value->is_list());
   ASSERT_EQ(static_cast<size_t>(kMaxSessionSize),
@@ -229,7 +230,7 @@
   ASSERT_EQ("Test112",
             titles_value->GetList()[kMaxSessionSize - 1].GetString());
 
-  base::Value* urls_value = session_value->FindKey("urls");
+  base::Value* urls_value = value_with_error.value->FindKey("urls");
   ASSERT_TRUE(urls_value);
   ASSERT_TRUE(urls_value->is_list());
   ASSERT_EQ(static_cast<size_t>(kMaxSessionSize), urls_value->GetList().size());
@@ -239,7 +240,7 @@
 
   // Verify the offset is correct.
   ASSERT_EQ((1 - kMaxSessionSize) / 2,
-            session_value->FindKey("offset")->GetInt());
+            value_with_error.value->FindKey("offset")->GetInt());
 }
 
 TEST_F(WKNavigationUtilTest, IsNotRestoreSessionUrl) {
diff --git a/ios/web/public/test/fakes/test_web_client.mm b/ios/web/public/test/fakes/test_web_client.mm
index e12c87bc..e5c0fda 100644
--- a/ios/web/public/test/fakes/test_web_client.mm
+++ b/ios/web/public/test/fakes/test_web_client.mm
@@ -32,7 +32,7 @@
   return url.SchemeIs(kTestWebUIScheme) ||
          url.SchemeIs(kTestNativeContentScheme) ||
          url.SchemeIs(kTestAppSpecificScheme);
-};
+}
 
 base::string16 TestWebClient::GetPluginNotSupportedText() const {
   return plugin_not_supported_text_;
diff --git a/ios/web_view/internal/cwv_download_task.mm b/ios/web_view/internal/cwv_download_task.mm
index c029681a..e6fad725 100644
--- a/ios/web_view/internal/cwv_download_task.mm
+++ b/ios/web_view/internal/cwv_download_task.mm
@@ -25,7 +25,8 @@
 NSErrorDomain const CWVDownloadErrorDomain =
     @"org.chromium.chromewebview.DownloadErrorDomain";
 
-NSInteger const CWVDownloadErrorUnknown = -100;
+NSInteger const CWVDownloadErrorFailed = -100;
+NSInteger const CWVDownloadErrorAborted = -101;
 
 @interface CWVDownloadTask ()
 
@@ -132,18 +133,30 @@
 }
 
 - (void)downloadWasUpdated {
-  if (_internalTask->IsDone()) {
-    int errorCode = _internalTask->GetErrorCode();
-    if (errorCode == net::OK) {
-      // The writer deletes the file on its destructor by default. This prevents
-      // the deletion.
-      _internalTask->GetResponseWriter()->AsFileWriter()->DisownFile();
+  switch (_internalTask->GetState()) {
+    case web::DownloadTask::State::kInProgress: {
+      if ([_delegate
+              respondsToSelector:@selector(downloadTaskProgressDidChange:)]) {
+        [_delegate downloadTaskProgressDidChange:self];
+      }
+      break;
     }
-    [self notifyFinishWithErrorCode:errorCode];
-  } else {
-    if ([_delegate
-            respondsToSelector:@selector(downloadTaskProgressDidChange:)]) {
-      [_delegate downloadTaskProgressDidChange:self];
+    case web::DownloadTask::State::kComplete: {
+      int errorCode = _internalTask->GetErrorCode();
+      if (errorCode == net::OK) {
+        // The writer deletes the file on its destructor by default. This
+        // prevents the deletion.
+        _internalTask->GetResponseWriter()->AsFileWriter()->DisownFile();
+      }
+      [self notifyFinishWithErrorCode:errorCode];
+      break;
+    }
+    case web::DownloadTask::State::kNotStarted:
+    case web::DownloadTask::State::kCancelled: {
+      // Nothing to be done in these states.
+      // Note that state kCancelled is immediately followed by state kComplete
+      // with error code net::ERR_ABORTED, which is handled above.
+      break;
     }
   }
 }
@@ -151,14 +164,18 @@
 - (void)notifyFinishWithErrorCode:(int)errorCode {
   NSError* error = nil;
   if (errorCode != net::OK) {
+    // Use CWVDownloadErrorFailed for any errors other than net::ERR_ABORTED
+    // because a detailed error code is likely not very useful. Text
+    // representation of the error is still available via
+    // error.localizedDescription.
+    NSInteger cwvErrorCode = errorCode == net::ERR_ABORTED
+                                 ? CWVDownloadErrorAborted
+                                 : CWVDownloadErrorFailed;
     NSString* errorDescription =
         base::SysUTF8ToNSString(net::ErrorToShortString(errorCode));
-    // Always use CWVDownloadErrorUnknown so far because a detailed error code
-    // is likely not very useful. Text representation of the error is still
-    // available via error.localizedDescription.
     error = [NSError
         errorWithDomain:CWVDownloadErrorDomain
-                   code:CWVDownloadErrorUnknown
+                   code:cwvErrorCode
                userInfo:@{NSLocalizedDescriptionKey : errorDescription}];
   }
   if ([_delegate
diff --git a/ios/web_view/internal/cwv_download_task_unittest.mm b/ios/web_view/internal/cwv_download_task_unittest.mm
index a83456c..aa5572fa 100644
--- a/ios/web_view/internal/cwv_download_task_unittest.mm
+++ b/ios/web_view/internal/cwv_download_task_unittest.mm
@@ -101,8 +101,11 @@
                                                 valid_local_file_path_)];
   ASSERT_TRUE(WaitUntilTaskStarts());
 
-  OCMExpect([mock_delegate_ downloadTask:cwv_task_
-                      didFinishWithError:[OCMArg isNotNil]]);
+  OCMExpect([mock_delegate_
+            downloadTask:cwv_task_
+      didFinishWithError:[OCMArg checkWithBlock:^(NSError* error) {
+        return error.code == CWVDownloadErrorFailed;
+      }]]);
   ASSERT_TRUE(FinishResponseWriter());
   fake_internal_task_->SetErrorCode(net::ERR_FAILED);
   fake_internal_task_->SetDone(true);
@@ -115,10 +118,23 @@
                                                 valid_local_file_path_)];
   ASSERT_TRUE(WaitUntilTaskStarts());
 
+  OCMExpect([mock_delegate_
+            downloadTask:cwv_task_
+      didFinishWithError:[OCMArg checkWithBlock:^(NSError* error) {
+        return error.code == CWVDownloadErrorAborted;
+      }]]);
+
   ASSERT_TRUE(FinishResponseWriter());
   [cwv_task_ cancel];
   EXPECT_EQ(web::DownloadTask::State::kCancelled,
             fake_internal_task_->GetState());
+
+  // Simulate behavior of a real web::DownloadTask which transition to state
+  // kComplete with error code net::ERR_ABORTED when cancelled.
+  fake_internal_task_->SetErrorCode(net::ERR_ABORTED);
+  fake_internal_task_->SetDone(true);
+
+  EXPECT_OCMOCK_VERIFY((id)mock_delegate_);
 }
 
 // Tests a case when it fails to write to the specified local file path.
diff --git a/ios/web_view/public/cwv_download_task.h b/ios/web_view/public/cwv_download_task.h
index 017c0ad5..5ca204f8 100644
--- a/ios/web_view/public/cwv_download_task.h
+++ b/ios/web_view/public/cwv_download_task.h
@@ -19,8 +19,12 @@
 // The error domain for download errors.
 FOUNDATION_EXPORT CWV_EXPORT NSErrorDomain const CWVDownloadErrorDomain;
 
-// An error code for CWVDownloadErrorDomain which doesn't indicate the cause.
-FOUNDATION_EXPORT CWV_EXPORT NSInteger const CWVDownloadErrorUnknown;
+// An error code for CWVDownloadErrorDomain for generic failure.
+FOUNDATION_EXPORT CWV_EXPORT NSInteger const CWVDownloadErrorFailed;
+
+// An error code for CWVDownloadErrorDomain when the task is aborted
+// (cancelled).
+FOUNDATION_EXPORT CWV_EXPORT NSInteger const CWVDownloadErrorAborted;
 
 // Represents a single browser download task.
 CWV_EXPORT
@@ -58,8 +62,11 @@
 // called if the task is not in progress.
 - (void)startDownloadToLocalFileAtPath:(NSString*)path;
 
-// Cancels the download. Cancelled download can be restarted by calling
-// -startDownloadToLocalFileAtPath:
+// Cancels the download.
+//
+// It triggers a delegate method -downloadTask:didFinishWithError: with
+// |error.code| CWVDownloadErrorAborted. Cancelled download can be restarted by
+// calling -startDownloadToLocalFileAtPath:
 - (void)cancel;
 
 @end
@@ -70,8 +77,9 @@
 
 // Called when the download has finished. |error| is nil when it has completed
 // successfully. |error| represents the error when the download has failed
-// e.g., due to network errors. |error| contains a description which describes
-// the type of an error.
+// (e.g., due to network errors) or has been cancelled. |error.code| is either
+// CWVDownloadErrorFailed or CWVDownloadErrorAborted. |error| also contains a
+// description which describes the type of an error.
 - (void)downloadTask:(CWVDownloadTask*)downloadTask
     didFinishWithError:(nullable NSError*)error;
 
diff --git a/ios/web_view/shell/shell_view_controller.m b/ios/web_view/shell/shell_view_controller.m
index 1ed8f80..ce05bd1 100644
--- a/ios/web_view/shell/shell_view_controller.m
+++ b/ios/web_view/shell/shell_view_controller.m
@@ -376,6 +376,13 @@
                                              .usesSyncAndWalletSandbox ^= YES;
                                        }]];
 
+  [alertController
+      addAction:[UIAlertAction actionWithTitle:@"Cancel download"
+                                         style:UIAlertActionStyleDefault
+                                       handler:^(UIAlertAction* action) {
+                                         [weakSelf.downloadTask cancel];
+                                       }]];
+
   [self presentViewController:alertController animated:YES completion:nil];
 }
 
diff --git a/ipc/ipc_channel_mojo.cc b/ipc/ipc_channel_mojo.cc
index 8669322..fc33968 100644
--- a/ipc/ipc_channel_mojo.cc
+++ b/ipc/ipc_channel_mojo.cc
@@ -287,7 +287,7 @@
     auto serialized_handle = mojo::native::SerializedHandle::New();
     serialized_handle->the_handle = attachment->TakeMojoHandle();
     serialized_handle->type =
-        mojo::ConvertTo<mojo::native::SerializedHandle::Type>(
+        mojo::ConvertTo<mojo::native::SerializedHandleType>(
             attachment->GetType());
     output_handles.emplace_back(std::move(serialized_handle));
   }
diff --git a/ipc/native_handle_type_converters.cc b/ipc/native_handle_type_converters.cc
index a0391b6..f09e084 100644
--- a/ipc/native_handle_type_converters.cc
+++ b/ipc/native_handle_type_converters.cc
@@ -7,19 +7,19 @@
 namespace mojo {
 
 // static
-IPC::MessageAttachment::Type
-TypeConverter<IPC::MessageAttachment::Type, native::SerializedHandle_Type>::
-    Convert(native::SerializedHandle_Type type) {
+IPC::MessageAttachment::Type TypeConverter<
+    IPC::MessageAttachment::Type,
+    native::SerializedHandleType>::Convert(native::SerializedHandleType type) {
   switch (type) {
-    case native::SerializedHandle_Type::MOJO_HANDLE:
+    case native::SerializedHandleType::MOJO_HANDLE:
       return IPC::MessageAttachment::Type::MOJO_HANDLE;
-    case native::SerializedHandle_Type::PLATFORM_FILE:
+    case native::SerializedHandleType::PLATFORM_FILE:
       return IPC::MessageAttachment::Type::PLATFORM_FILE;
-    case native::SerializedHandle_Type::WIN_HANDLE:
+    case native::SerializedHandleType::WIN_HANDLE:
       return IPC::MessageAttachment::Type::WIN_HANDLE;
-    case native::SerializedHandle_Type::MACH_PORT:
+    case native::SerializedHandleType::MACH_PORT:
       return IPC::MessageAttachment::Type::MACH_PORT;
-    case native::SerializedHandle_Type::FUCHSIA_HANDLE:
+    case native::SerializedHandleType::FUCHSIA_HANDLE:
       return IPC::MessageAttachment::Type::FUCHSIA_HANDLE;
   }
   NOTREACHED();
@@ -27,23 +27,23 @@
 }
 
 // static
-native::SerializedHandle_Type TypeConverter<
-    native::SerializedHandle_Type,
+native::SerializedHandleType TypeConverter<
+    native::SerializedHandleType,
     IPC::MessageAttachment::Type>::Convert(IPC::MessageAttachment::Type type) {
   switch (type) {
     case IPC::MessageAttachment::Type::MOJO_HANDLE:
-      return native::SerializedHandle_Type::MOJO_HANDLE;
+      return native::SerializedHandleType::MOJO_HANDLE;
     case IPC::MessageAttachment::Type::PLATFORM_FILE:
-      return native::SerializedHandle_Type::PLATFORM_FILE;
+      return native::SerializedHandleType::PLATFORM_FILE;
     case IPC::MessageAttachment::Type::WIN_HANDLE:
-      return native::SerializedHandle_Type::WIN_HANDLE;
+      return native::SerializedHandleType::WIN_HANDLE;
     case IPC::MessageAttachment::Type::MACH_PORT:
-      return native::SerializedHandle_Type::MACH_PORT;
+      return native::SerializedHandleType::MACH_PORT;
     case IPC::MessageAttachment::Type::FUCHSIA_HANDLE:
-      return native::SerializedHandle_Type::FUCHSIA_HANDLE;
+      return native::SerializedHandleType::FUCHSIA_HANDLE;
   }
   NOTREACHED();
-  return native::SerializedHandle_Type::MOJO_HANDLE;
+  return native::SerializedHandleType::MOJO_HANDLE;
 }
 
 }  // namespace mojo
diff --git a/ipc/native_handle_type_converters.h b/ipc/native_handle_type_converters.h
index 81d26c5..def5de8 100644
--- a/ipc/native_handle_type_converters.h
+++ b/ipc/native_handle_type_converters.h
@@ -13,15 +13,15 @@
 
 template <>
 struct TypeConverter<IPC::MessageAttachment::Type,
-                     native::SerializedHandle_Type> {
+                     native::SerializedHandleType> {
   static IPC::MessageAttachment::Type Convert(
-      native::SerializedHandle_Type type);
+      native::SerializedHandleType type);
 };
 
 template <>
-struct TypeConverter<native::SerializedHandle_Type,
+struct TypeConverter<native::SerializedHandleType,
                      IPC::MessageAttachment::Type> {
-  static native::SerializedHandle_Type Convert(
+  static native::SerializedHandleType Convert(
       IPC::MessageAttachment::Type type);
 };
 
diff --git a/media/audio/fake_audio_input_stream.cc b/media/audio/fake_audio_input_stream.cc
index d097fb1..0c03c65 100644
--- a/media/audio/fake_audio_input_stream.cc
+++ b/media/audio/fake_audio_input_stream.cc
@@ -54,7 +54,7 @@
 void FakeAudioInputStream::Start(AudioInputCallback* callback)  {
   DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
   callback_ = callback;
-  fake_audio_worker_.Start(base::Bind(
+  fake_audio_worker_.Start(base::BindRepeating(
       &FakeAudioInputStream::ReadAudioFromSource, base::Unretained(this)));
 }
 
@@ -102,16 +102,27 @@
   // Not supported. Do nothing.
 }
 
-void FakeAudioInputStream::ReadAudioFromSource() {
+void FakeAudioInputStream::ReadAudioFromSource(base::TimeTicks ideal_time,
+                                               base::TimeTicks now) {
   DCHECK(audio_manager_->GetWorkerTaskRunner()->BelongsToCurrentThread());
   DCHECK(callback_);
 
   if (!audio_source_)
     audio_source_ = ChooseSource();
 
-  audio_source_->OnMoreData(base::TimeDelta(), base::TimeTicks::Now(), 0,
-                            audio_bus_.get());
-  callback_->OnData(audio_bus_.get(), base::TimeTicks::Now(), 1.0);
+  // This OnMoreData()/OnData() timing would never happen in a real system:
+  //
+  //   1. Real AudioSources would never be asked to generate audio that should
+  //      already be playing-out exactly at this very moment; they are asked to
+  //      do so for audio to be played-out in the future.
+  //   2. Real AudioInputStreams could never provide audio that is striking a
+  //      microphone element exactly at this very moment; they provide audio
+  //      that happened in the recent past.
+  //
+  // However, it would be pointless to add a FIFO queue here to delay the signal
+  // in this "fake" implementation. So, just hack the timing and carry-on.
+  audio_source_->OnMoreData(base::TimeDelta(), ideal_time, 0, audio_bus_.get());
+  callback_->OnData(audio_bus_.get(), ideal_time, 1.0);
 }
 
 using AudioSourceCallback = AudioOutputStream::AudioSourceCallback;
diff --git a/media/audio/fake_audio_input_stream.h b/media/audio/fake_audio_input_stream.h
index 61c4142..29f74df 100644
--- a/media/audio/fake_audio_input_stream.h
+++ b/media/audio/fake_audio_input_stream.h
@@ -65,7 +65,7 @@
   ~FakeAudioInputStream() override;
 
   std::unique_ptr<AudioOutputStream::AudioSourceCallback> ChooseSource();
-  void ReadAudioFromSource();
+  void ReadAudioFromSource(base::TimeTicks ideal_time, base::TimeTicks now);
 
   AudioManagerBase* audio_manager_;
   AudioInputCallback* callback_;
diff --git a/media/audio/fake_audio_output_stream.cc b/media/audio/fake_audio_output_stream.cc
index e285050a..b0ef0481 100644
--- a/media/audio/fake_audio_output_stream.cc
+++ b/media/audio/fake_audio_output_stream.cc
@@ -22,10 +22,10 @@
 FakeAudioOutputStream::FakeAudioOutputStream(AudioManagerBase* manager,
                                              const AudioParameters& params)
     : audio_manager_(manager),
+      fixed_data_delay_(FakeAudioWorker::ComputeFakeOutputDelay(params)),
       callback_(NULL),
       fake_worker_(manager->GetWorkerTaskRunner(), params),
-      audio_bus_(AudioBus::Create(params)) {
-}
+      audio_bus_(AudioBus::Create(params)) {}
 
 FakeAudioOutputStream::~FakeAudioOutputStream() {
   DCHECK(!callback_);
@@ -40,8 +40,8 @@
 void FakeAudioOutputStream::Start(AudioSourceCallback* callback)  {
   DCHECK(audio_manager_->GetTaskRunner()->BelongsToCurrentThread());
   callback_ = callback;
-  fake_worker_.Start(base::Bind(
-      &FakeAudioOutputStream::CallOnMoreData, base::Unretained(this)));
+  fake_worker_.Start(base::BindRepeating(&FakeAudioOutputStream::CallOnMoreData,
+                                         base::Unretained(this)));
 }
 
 void FakeAudioOutputStream::Stop() {
@@ -62,9 +62,12 @@
   *volume = 0;
 }
 
-void FakeAudioOutputStream::CallOnMoreData() {
+void FakeAudioOutputStream::CallOnMoreData(base::TimeTicks ideal_time,
+                                           base::TimeTicks now) {
   DCHECK(audio_manager_->GetWorkerTaskRunner()->BelongsToCurrentThread());
-  callback_->OnMoreData(base::TimeDelta(), base::TimeTicks::Now(), 0,
+  // Real streams provide small tweaks to their delay values, alongside the
+  // current system time; and so the same is done here.
+  callback_->OnMoreData(fixed_data_delay_ + (ideal_time - now), now, 0,
                         audio_bus_.get());
 }
 
diff --git a/media/audio/fake_audio_output_stream.h b/media/audio/fake_audio_output_stream.h
index c8f2cf2c..1233b2d 100644
--- a/media/audio/fake_audio_output_stream.h
+++ b/media/audio/fake_audio_output_stream.h
@@ -40,12 +40,13 @@
   ~FakeAudioOutputStream() override;
 
   // Task that periodically calls OnMoreData() to consume audio data.
-  void CallOnMoreData();
+  void CallOnMoreData(base::TimeTicks ideal_time, base::TimeTicks now);
 
-  AudioManagerBase* audio_manager_;
+  AudioManagerBase* const audio_manager_;
+  const base::TimeDelta fixed_data_delay_;
   AudioSourceCallback* callback_;
   FakeAudioWorker fake_worker_;
-  std::unique_ptr<AudioBus> audio_bus_;
+  const std::unique_ptr<AudioBus> audio_bus_;
 
   DISALLOW_COPY_AND_ASSIGN(FakeAudioOutputStream);
 };
diff --git a/media/audio/null_audio_sink.cc b/media/audio/null_audio_sink.cc
index f436cf2..971197af6 100644
--- a/media/audio/null_audio_sink.cc
+++ b/media/audio/null_audio_sink.cc
@@ -27,6 +27,7 @@
                                RenderCallback* callback) {
   DCHECK(!started_);
   fake_worker_.reset(new FakeAudioWorker(task_runner_, params));
+  fixed_data_delay_ = FakeAudioWorker::ComputeFakeOutputDelay(params);
   audio_bus_ = AudioBus::Create(params);
   callback_ = callback;
   initialized_ = true;
@@ -54,8 +55,8 @@
   if (playing_)
     return;
 
-  fake_worker_->Start(base::Bind(
-      &NullAudioSink::CallRender, base::Unretained(this)));
+  fake_worker_->Start(
+      base::BindRepeating(&NullAudioSink::CallRender, base::Unretained(this)));
 
   playing_ = true;
 }
@@ -98,11 +99,16 @@
   std::move(callback).Run(OUTPUT_DEVICE_STATUS_ERROR_INTERNAL);
 }
 
-void NullAudioSink::CallRender() {
+void NullAudioSink::CallRender(base::TimeTicks ideal_time,
+                               base::TimeTicks now) {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
-  int frames_received = callback_->Render(
-      base::TimeDelta(), base::TimeTicks::Now(), 0, audio_bus_.get());
+  // Since NullAudioSink is only used for cases where a real audio sink was not
+  // available, provide "idealized" delay-timing arguments. This will drive the
+  // smoothest playback (since video is sync'ed to audio). See
+  // content::AudioRendererImpl and media::AudioClock for further details.
+  int frames_received =
+      callback_->Render(fixed_data_delay_, ideal_time, 0, audio_bus_.get());
   if (!audio_hash_ || frames_received <= 0)
     return;
 
diff --git a/media/audio/null_audio_sink.h b/media/audio/null_audio_sink.h
index 69e1a48..3ca75aa 100644
--- a/media/audio/null_audio_sink.h
+++ b/media/audio/null_audio_sink.h
@@ -51,7 +51,7 @@
 
  private:
   // Task that periodically calls Render() to consume audio data.
-  void CallRender();
+  void CallRender(base::TimeTicks ideal_time, base::TimeTicks now);
 
   bool initialized_;
   bool started_;
@@ -63,6 +63,7 @@
 
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
   std::unique_ptr<FakeAudioWorker> fake_worker_;
+  base::TimeDelta fixed_data_delay_;
   std::unique_ptr<AudioBus> audio_bus_;
 
   DISALLOW_COPY_AND_ASSIGN(NullAudioSink);
diff --git a/media/audio/virtual_audio_input_stream.cc b/media/audio/virtual_audio_input_stream.cc
index 7c8ca14b..373b3c0 100644
--- a/media/audio/virtual_audio_input_stream.cc
+++ b/media/audio/virtual_audio_input_stream.cc
@@ -54,8 +54,8 @@
 void VirtualAudioInputStream::Start(AudioInputCallback* callback) {
   DCHECK(thread_checker_.CalledOnValidThread());
   callback_ = callback;
-  fake_worker_.Start(base::Bind(
-      &VirtualAudioInputStream::PumpAudio, base::Unretained(this)));
+  fake_worker_.Start(base::BindRepeating(&VirtualAudioInputStream::PumpAudio,
+                                         base::Unretained(this)));
 }
 
 void VirtualAudioInputStream::Stop() {
@@ -99,7 +99,8 @@
   DCHECK_LE(0, num_attached_output_streams_);
 }
 
-void VirtualAudioInputStream::PumpAudio() {
+void VirtualAudioInputStream::PumpAudio(base::TimeTicks ideal_time,
+                                        base::TimeTicks now) {
   DCHECK(worker_task_runner_->BelongsToCurrentThread());
 
   {
@@ -110,7 +111,7 @@
   }
   // Because the audio is being looped-back, the delay since since it was
   // recorded is zero.
-  callback_->OnData(audio_bus_.get(), base::TimeTicks::Now(), 1.0);
+  callback_->OnData(audio_bus_.get(), ideal_time, 1.0);
 }
 
 void VirtualAudioInputStream::Close() {
diff --git a/media/audio/virtual_audio_input_stream.h b/media/audio/virtual_audio_input_stream.h
index 174a5f7c..aaceaa34 100644
--- a/media/audio/virtual_audio_input_stream.h
+++ b/media/audio/virtual_audio_input_stream.h
@@ -80,7 +80,7 @@
   // Pulls audio data from all attached VirtualAudioOutputStreams, mixes and
   // converts the streams into one, and pushes the result to |callback_|.
   // Invoked on the worker thread.
-  void PumpAudio();
+  void PumpAudio(base::TimeTicks ideal_time, base::TimeTicks now);
 
   const scoped_refptr<base::SingleThreadTaskRunner> worker_task_runner_;
 
diff --git a/media/base/fake_audio_worker.cc b/media/base/fake_audio_worker.cc
index 09b4ea4..7e9ba6ef 100644
--- a/media/base/fake_audio_worker.cc
+++ b/media/base/fake_audio_worker.cc
@@ -4,6 +4,8 @@
 
 #include "media/base/fake_audio_worker.h"
 
+#include <utility>
+
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/cancelable_callback.h"
@@ -16,6 +18,7 @@
 #include "base/threading/thread_checker.h"
 #include "base/time/time.h"
 #include "media/base/audio_parameters.h"
+#include "media/base/audio_timestamp_helper.h"
 
 namespace media {
 
@@ -26,7 +29,7 @@
          const AudioParameters& params);
 
   bool IsStopped();
-  void Start(const base::Closure& worker_cb);
+  void Start(FakeAudioWorker::Callback worker_cb);
   void Stop();
 
  private:
@@ -45,11 +48,13 @@
   void DoRead();
 
   const scoped_refptr<base::SingleThreadTaskRunner> worker_task_runner_;
-  const base::TimeDelta buffer_duration_;
+  const int sample_rate_;
+  const int frames_per_read_;
 
   base::Lock worker_cb_lock_;  // Held while mutating or running |worker_cb_|.
-  base::Closure worker_cb_ GUARDED_BY(worker_cb_lock_);
-  base::TimeTicks next_read_time_;
+  FakeAudioWorker::Callback worker_cb_ GUARDED_BY(worker_cb_lock_);
+  base::TimeTicks first_read_time_;
+  int64_t frames_elapsed_;
 
   // Used to cancel any delayed tasks still inside the worker loop's queue.
   base::CancelableClosure worker_task_cb_;
@@ -68,22 +73,32 @@
   DCHECK(worker_->IsStopped());
 }
 
-void FakeAudioWorker::Start(const base::Closure& worker_cb) {
+void FakeAudioWorker::Start(FakeAudioWorker::Callback worker_cb) {
   DCHECK(worker_->IsStopped());
-  worker_->Start(worker_cb);
+  worker_->Start(std::move(worker_cb));
 }
 
 void FakeAudioWorker::Stop() {
   worker_->Stop();
 }
 
+// static
+base::TimeDelta FakeAudioWorker::ComputeFakeOutputDelay(
+    const AudioParameters& params) {
+  // Typical delay values used by real AudioOutputStreams on Win, Mac, and Linux
+  // tend to be around 1.5X to 3X of the buffer duration. So, 2X is chosen as a
+  // general-purpose value.
+  constexpr int kDelayFactor = 2;
+  return AudioTimestampHelper::FramesToTime(
+      params.frames_per_buffer() * kDelayFactor, params.sample_rate());
+}
+
 FakeAudioWorker::Worker::Worker(
     const scoped_refptr<base::SingleThreadTaskRunner>& worker_task_runner,
     const AudioParameters& params)
     : worker_task_runner_(worker_task_runner),
-      buffer_duration_(base::TimeDelta::FromMicroseconds(
-          params.frames_per_buffer() * base::Time::kMicrosecondsPerSecond /
-          static_cast<float>(params.sample_rate()))) {
+      sample_rate_(params.sample_rate()),
+      frames_per_read_(params.frames_per_buffer()) {
   // Worker can be constructed on any thread, but will DCHECK that its
   // Start/Stop methods are called from the same thread.
   DETACH_FROM_THREAD(thread_checker_);
@@ -98,13 +113,13 @@
   return !worker_cb_;
 }
 
-void FakeAudioWorker::Worker::Start(const base::Closure& worker_cb) {
+void FakeAudioWorker::Worker::Start(FakeAudioWorker::Callback worker_cb) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(worker_cb);
   {
     base::AutoLock scoped_lock(worker_cb_lock_);
     DCHECK(!worker_cb_);
-    worker_cb_ = worker_cb;
+    worker_cb_ = std::move(worker_cb);
   }
   worker_task_runner_->PostTask(FROM_HERE,
                                 base::BindOnce(&Worker::DoStart, this));
@@ -112,7 +127,8 @@
 
 void FakeAudioWorker::Worker::DoStart() {
   DCHECK(worker_task_runner_->BelongsToCurrentThread());
-  next_read_time_ = base::TimeTicks::Now();
+  first_read_time_ = base::TimeTicks::Now();
+  frames_elapsed_ = 0;
   worker_task_cb_.Reset(base::Bind(&Worker::DoRead, this));
   worker_task_cb_.callback().Run();
 }
@@ -137,24 +153,37 @@
 void FakeAudioWorker::Worker::DoRead() {
   DCHECK(worker_task_runner_->BelongsToCurrentThread());
 
+  const base::TimeTicks read_time =
+      first_read_time_ +
+      AudioTimestampHelper::FramesToTime(frames_elapsed_, sample_rate_);
+  frames_elapsed_ += frames_per_read_;
+  base::TimeTicks next_read_time =
+      first_read_time_ +
+      AudioTimestampHelper::FramesToTime(frames_elapsed_, sample_rate_);
+
+  base::TimeTicks now;
   {
     base::AutoLock scoped_lock(worker_cb_lock_);
-    if (worker_cb_)
-      worker_cb_.Run();
+    // Important to sample the clock after waiting to acquire the lock.
+    now = base::TimeTicks::Now();
+    if (worker_cb_ && next_read_time > now) {
+      worker_cb_.Run(read_time, now);
+    }
   }
 
-  // Need to account for time spent here due to the cost of |worker_cb| as well
-  // as the imprecision of PostDelayedTask().
-  const base::TimeTicks now = base::TimeTicks::Now();
-  base::TimeDelta delay = next_read_time_ + buffer_duration_ - now;
-
-  // If we're behind, find the next nearest ontime interval.
-  if (delay < base::TimeDelta())
-    delay += buffer_duration_ * (-delay / buffer_duration_ + 1);
-  next_read_time_ = now + delay;
+  // If we're behind, find the next nearest ontime interval. Note, we could be
+  // behind many intervals (e.g., if the system is resuming from sleep).
+  if (next_read_time <= now) {
+    frames_elapsed_ = AudioTimestampHelper::TimeToFrames(now - first_read_time_,
+                                                         sample_rate_);
+    frames_elapsed_ =
+        ((frames_elapsed_ / frames_per_read_) + 1) * frames_per_read_;
+    next_read_time = first_read_time_ + AudioTimestampHelper::FramesToTime(
+                                            frames_elapsed_, sample_rate_);
+  }
 
   worker_task_runner_->PostDelayedTask(FROM_HERE, worker_task_cb_.callback(),
-                                       delay);
+                                       next_read_time - now);
 }
 
 }  // namespace media
diff --git a/media/base/fake_audio_worker.h b/media/base/fake_audio_worker.h
index b24c85b..72debb9 100644
--- a/media/base/fake_audio_worker.h
+++ b/media/base/fake_audio_worker.h
@@ -21,6 +21,12 @@
 // call back the provided callback like a real audio consumer or producer would.
 class MEDIA_EXPORT FakeAudioWorker {
  public:
+  // The worker callback, which is run at regular intervals. |ideal_time| is
+  // when the callback was scheduled to run, while |now| is when the callback is
+  // actually being run.
+  using Callback = base::RepeatingCallback<void(base::TimeTicks ideal_time,
+                                                base::TimeTicks now)>;
+
   // |worker_task_runner| is the task runner on which the closure provided to
   // Start() will be executed on.  This may or may not be the be for the same
   // thread that invokes the Start/Stop methods.
@@ -32,13 +38,17 @@
 
   // Start executing |worker_cb| at a regular intervals.  Stop() must be called
   // by the same thread before destroying FakeAudioWorker.
-  void Start(const base::Closure& worker_cb);
+  void Start(Callback worker_cb);
 
   // Stop executing the closure provided to Start(). Blocks until the worker
   // loop is not inside a closure invocation. Safe to call multiple times.
   // Must be called on the same thread that called Start().
   void Stop();
 
+  // Returns a reasonable fixed output delay value for a "sink" using a
+  // FakeAudioWorker.
+  static base::TimeDelta ComputeFakeOutputDelay(const AudioParameters& params);
+
  private:
   // All state and implementation is kept within this ref-counted class because
   // cancellation of posted tasks must happen on the worker thread some time
diff --git a/media/base/fake_audio_worker_unittest.cc b/media/base/fake_audio_worker_unittest.cc
index 94d2ee9..f165583d 100644
--- a/media/base/fake_audio_worker_unittest.cc
+++ b/media/base/fake_audio_worker_unittest.cc
@@ -32,12 +32,14 @@
 
   ~FakeAudioWorkerTest() override = default;
 
-  void CalledByFakeWorker() { seen_callbacks_++; }
+  void CalledByFakeWorker(base::TimeTicks ideal_time, base::TimeTicks now) {
+    seen_callbacks_++;
+  }
 
   void RunOnAudioThread() {
     ASSERT_TRUE(message_loop_.task_runner()->BelongsToCurrentThread());
-    fake_worker_.Start(base::Bind(&FakeAudioWorkerTest::CalledByFakeWorker,
-                                  base::Unretained(this)));
+    fake_worker_.Start(base::BindRepeating(
+        &FakeAudioWorkerTest::CalledByFakeWorker, base::Unretained(this)));
   }
 
   void RunOnceOnAudioThread() {
diff --git a/media/base/silent_sink_suspender.cc b/media/base/silent_sink_suspender.cc
index 64c0f46..42112710 100644
--- a/media/base/silent_sink_suspender.cc
+++ b/media/base/silent_sink_suspender.cc
@@ -143,10 +143,17 @@
       is_transition_pending_ = false;
       is_using_fake_sink_ = true;
     }
-    fake_sink_.Start(
-        base::Bind(base::IgnoreResult(&SilentSinkSuspender::Render),
-                   base::Unretained(this), latest_output_delay_,
-                   latest_output_delay_timestamp_, 0, nullptr));
+    fake_sink_.Start(base::BindRepeating(
+        [](SilentSinkSuspender* suspender, base::TimeDelta frozen_delay,
+           base::TimeTicks frozen_delay_timestamp, base::TimeTicks ideal_time,
+           base::TimeTicks now) {
+          // TODO: Seems that the code in Render() might benefit from the two
+          // new timestamps being provided by FakeAudioWorker, in that it's call
+          // to base::TimeTicks::Now() can be eliminated (use |now| instead),
+          // along with its custom delay timestamp calculations.
+          suspender->Render(frozen_delay, frozen_delay_timestamp, 0, nullptr);
+        },
+        this, latest_output_delay_, latest_output_delay_timestamp_));
   } else {
     fake_sink_.Stop();
 
diff --git a/media/blink/resource_multibuffer_data_provider.cc b/media/blink/resource_multibuffer_data_provider.cc
index 5b166702..b79632c 100644
--- a/media/blink/resource_multibuffer_data_provider.cc
+++ b/media/blink/resource_multibuffer_data_provider.cc
@@ -197,8 +197,8 @@
 }
 
 void ResourceMultiBufferDataProvider::DidSendData(
-    unsigned long long bytes_sent,
-    unsigned long long total_bytes_to_be_sent) {
+    uint64_t bytes_sent,
+    uint64_t total_bytes_to_be_sent) {
   NOTIMPLEMENTED();
 }
 
@@ -421,8 +421,7 @@
   // Beware, this object might be deleted here.
 }
 
-void ResourceMultiBufferDataProvider::DidDownloadData(
-    unsigned long long dataLength) {
+void ResourceMultiBufferDataProvider::DidDownloadData(uint64_t dataLength) {
   NOTIMPLEMENTED();
 }
 
diff --git a/media/blink/resource_multibuffer_data_provider.h b/media/blink/resource_multibuffer_data_provider.h
index 8da76d2..3a848f5 100644
--- a/media/blink/resource_multibuffer_data_provider.h
+++ b/media/blink/resource_multibuffer_data_provider.h
@@ -52,10 +52,9 @@
   bool WillFollowRedirect(
       const blink::WebURL& new_url,
       const blink::WebURLResponse& redirect_response) override;
-  void DidSendData(unsigned long long bytesSent,
-                   unsigned long long totalBytesToBeSent) override;
+  void DidSendData(uint64_t bytesSent, uint64_t totalBytesToBeSent) override;
   void DidReceiveResponse(const blink::WebURLResponse& response) override;
-  void DidDownloadData(unsigned long long data_length) override;
+  void DidDownloadData(uint64_t data_length) override;
   void DidReceiveData(const char* data, int data_length) override;
   void DidReceiveCachedMetadata(const char* data, int dataLength) override;
   void DidFinishLoading() override;
diff --git a/mojo/public/cpp/bindings/README.md b/mojo/public/cpp/bindings/README.md
index 1935034..75ef5a7 100644
--- a/mojo/public/cpp/bindings/README.md
+++ b/mojo/public/cpp/bindings/README.md
@@ -1793,7 +1793,7 @@
 
 ### Using Mojo Bindings in Chrome
 
-See [Converting Legacy Chrome IPC To Mojo](/ipc/README.md).
+See [Converting Legacy Chrome IPC To Mojo](/docs/mojo_ipc_conversion.md).
 
 ### Additional Documentation
 
diff --git a/mojo/public/cpp/bindings/lib/native_struct_serialization.cc b/mojo/public/cpp/bindings/lib/native_struct_serialization.cc
index 2830800..5eedd5b 100644
--- a/mojo/public/cpp/bindings/lib/native_struct_serialization.cc
+++ b/mojo/public/cpp/bindings/lib/native_struct_serialization.cc
@@ -90,7 +90,7 @@
     internal::Serializer<ScopedHandle, ScopedHandle>::Serialize(
         handle, &handle_writer->the_handle, context);
     handle_writer->type = static_cast<int32_t>(
-        mojo::ConvertTo<native::SerializedHandle::Type>(attachment->GetType()));
+        mojo::ConvertTo<native::SerializedHandleType>(attachment->GetType()));
     handles_writer.data()->at(i).Set(handle_writer.data());
   }
   writer->data()->handles.Set(handles_writer.data());
@@ -115,7 +115,7 @@
     auto attachment = IPC::MessageAttachment::CreateFromMojoHandle(
         std::move(handle),
         mojo::ConvertTo<IPC::MessageAttachment::Type>(
-            static_cast<native::SerializedHandle::Type>(handle_data->type)));
+            static_cast<native::SerializedHandleType>(handle_data->type)));
     message->attachment_set()->AddAttachment(std::move(attachment));
   }
   return true;
diff --git a/mojo/public/interfaces/bindings/native_struct.mojom b/mojo/public/interfaces/bindings/native_struct.mojom
index f2e869cf..c6a2852 100644
--- a/mojo/public/interfaces/bindings/native_struct.mojom
+++ b/mojo/public/interfaces/bindings/native_struct.mojom
@@ -5,18 +5,17 @@
 [JavaPackage="org.chromium.mojo.native_types"]
 module mojo.native;
 
+enum SerializedHandleType {
+  MOJO_HANDLE,
+  PLATFORM_FILE,
+  WIN_HANDLE,
+  MACH_PORT,
+  FUCHSIA_HANDLE,
+};
+
 struct SerializedHandle {
   handle the_handle;
-
-  enum Type {
-    MOJO_HANDLE,
-    PLATFORM_FILE,
-    WIN_HANDLE,
-    MACH_PORT,
-    FUCHSIA_HANDLE,
-  };
-
-  Type type;
+  SerializedHandleType type;
 };
 
 [CustomSerializer]
diff --git a/net/http/transport_security_state_static.json b/net/http/transport_security_state_static.json
index 6e68b3aa..51ac1ce 100644
--- a/net/http/transport_security_state_static.json
+++ b/net/http/transport_security_state_static.json
@@ -227,21 +227,6 @@
       ]
     },
     {
-      "name": "ncsccs",
-      "static_spki_hashes": [
-        "LetsEncryptAuthorityPrimary_X1_X3",
-        "LetsEncryptAuthorityBackup_X2_X4",
-        "DigiCertGlobalRoot",
-        "DigiCertEVRoot",
-        "DigiCertAssuredIDRoot",
-        "AddTrustExternalCARoot",
-        "BaltimoreCyberTrustRoot",
-        "COMODORSACertificationAuthority",
-        "COMODOECCCertificationAuthority"
-      ],
-      "report_uri": "https://log.ncsccs.com/report/hpkp"
-    },
-    {
       "name": "tumblr",
       "static_spki_hashes": [
         "DigiCertEVRoot",
@@ -60935,7 +60920,6 @@
     { "name": "cnnet.in", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "cobaltis.co.uk", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "code-vikings.de", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "codebreaking.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "codehz.one", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "codersatlas.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "coldcardwallet.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -67465,7 +67449,6 @@
     { "name": "bytenoc.nl", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "cadmanlaw.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "cafejulian.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
-    { "name": "calculates.org", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "calypsohost.net", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "cameroonlounge.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
     { "name": "camisetasmalwee.com.br", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true },
@@ -68328,13 +68311,16 @@
     { "name": "www.techrepublic.com", "policy": "custom", "mode": "force-https", "include_subdomains": true },
     { "name": "aka.ms", "policy": "custom", "mode": "force-https", "include_subdomains": true },
     { "name": "go.microsoft.com", "policy": "custom", "mode": "force-https", "include_subdomains": true },
-    { "name": "typewritten.net", "policy": "custom", "mode": "force-https", "include_subdomains": true },
     { "name": "airbnb.com", "policy": "custom", "mode": "force-https", "include_subdomains": true },
     { "name": "airbnb.tools", "policy": "custom", "mode": "force-https", "include_subdomains": true },
     { "name": "account.bbc.com", "policy": "custom", "mode": "force-https", "include_subdomains": true },
     { "name": "session.bbc.com", "policy": "custom", "mode": "force-https", "include_subdomains": true },
     { "name": "session.bbc.co.uk", "policy": "custom", "mode": "force-https", "include_subdomains": true },
     { "name": "bank.barclays.co.uk", "policy": "custom", "mode": "force-https", "include_subdomains": true },
+    // Burton domains (contact: burton at typewritten.net)
+    { "name": "typewritten.net", "policy": "custom", "mode": "force-https", "include_subdomains": true },
+    { "name": "codebreaking.org", "policy": "custom", "mode": "force-https", "include_subdomains": true },
+    { "name": "calculates.org", "policy": "custom", "mode": "force-https", "include_subdomains": true },
     // IP Address
     { "name": "1.0.0.1", "policy": "custom", "mode": "force-https", "include_subdomains": false },
     // No subdomains
@@ -68347,8 +68333,6 @@
     { "name": "gov.uk", "policy": "custom", "mode": "force-https", "include_subdomains": false },
     // HPKP
     { "name": "swehack.org", "policy": "custom", "mode": "force-https", "include_subdomains": true, "pins": "swehackCom" },
-    { "name": "ncsccs.com", "policy": "custom", "mode": "force-https", "include_subdomains": true, "pins": "ncsccs" },
-    { "name": "themathematician.uk", "policy": "custom", "mode": "force-https", "include_subdomains": true, "pins": "ncsccs" },
     // TODO(elawrence): hstspreload.org can't scan IPv6-only sites due to Google
     // Cloud limitations. Move these entries to the bulk entries once they can
     // be handled automatically: github.com/chromium/hstspreload.org/issues/43
@@ -68435,13 +68419,6 @@
       "mode": "force-https", "include_subdomains": true
     },
     {
-      "name": "0.me.uk",
-      "policy": "custom",
-      "mode": "force-https", "include_subdomains": true, "pins": "ncsccs",
-      "expect_ct": true,
-      "expect_ct_report_uri": "https://log.ncsccs.com/report/expectct"
-    },
-    {
       "name": "photistic.org",
       "policy": "custom",
       "mode": "force-https", "include_subdomains": true
@@ -68460,13 +68437,6 @@
       "expect_ct_report_uri": "https://history.report-uri.com/r/d/ct/reportOnly"
     },
     {
-      "name": "sirburton.com",
-      "policy": "custom",
-      "mode": "force-https", "include_subdomains": true, "pins": "ncsccs",
-      "expect_ct": true,
-      "expect_ct_report_uri": "https://log.ncsccs.com/report/expectct"
-    },
-    {
       "name": "cortis-consulting.ch",
       "policy": "custom",
       "mode": "force-https", "include_subdomains": true
diff --git a/net/http/transport_security_state_static.pins b/net/http/transport_security_state_static.pins
index 9f41460..f199694 100644
--- a/net/http/transport_security_state_static.pins
+++ b/net/http/transport_security_state_static.pins
@@ -1724,62 +1724,6 @@
 +AZxAeKCINT+b72x
 -----END CERTIFICATE-----
 
-# https://support.comodo.com/index.php?/Knowledgebase/Article/View/969/108/root-comodo-rsa-certification-authority-sha-2
-COMODORSACertificationAuthority
------BEGIN CERTIFICATE-----
-MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB
-hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
-A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
-BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5
-MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
-EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
-Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh
-dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR
-6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X
-pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC
-9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV
-/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf
-Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z
-+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w
-qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah
-SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC
-u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf
-Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq
-crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E
-FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB
-/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl
-wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM
-4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV
-2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna
-FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ
-CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK
-boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke
-jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL
-S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb
-QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl
-0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB
-NVOFBkpdn627G190
------END CERTIFICATE-----
-
-# https://bugzilla.mozilla.org/show_bug.cgi?id=421946
-COMODOECCCertificationAuthority
------BEGIN CERTIFICATE-----
-MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL
-MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
-BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT
-IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw
-MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy
-ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N
-T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv
-biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR
-FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J
-cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW
-BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
-BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm
-fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv
-GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
------END CERTIFICATE-----
-
 # From https://crbug.com/745781#c7
 TumblrBackup
 -----BEGIN PUBLIC KEY-----
diff --git a/net/log/net_log_event_type_list.h b/net/log/net_log_event_type_list.h
index 7c62128..c2531a3 100644
--- a/net/log/net_log_event_type_list.h
+++ b/net/log/net_log_event_type_list.h
@@ -1989,7 +1989,9 @@
 // Session was closed, either remotely or by the peer.
 //   {
 //     "quic_error": <quic::QuicErrorCode which caused the connection to be
-//     closed>, "from_peer":  <True if the peer closed the connection>
+//                    closed>,
+//     "details": <The error details string in the connection close.>,
+//     "from_peer":  <True if the peer closed the connection>
 //   }
 EVENT_TYPE(QUIC_SESSION_CLOSED)
 
diff --git a/net/quic/quic_connection_logger.cc b/net/quic/quic_connection_logger.cc
index a28a7ba..38d7578 100644
--- a/net/quic/quic_connection_logger.cc
+++ b/net/quic/quic_connection_logger.cc
@@ -244,10 +244,12 @@
 
 std::unique_ptr<base::Value> NetLogQuicOnConnectionClosedCallback(
     quic::QuicErrorCode error,
+    string error_details,
     quic::ConnectionCloseSource source,
     NetLogCaptureMode /* capture_mode */) {
   std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
   dict->SetInteger("quic_error", error);
+  dict->SetString("details", error_details);
   dict->SetBoolean("from_peer", source == quic::ConnectionCloseSource::FROM_PEER
                                     ? true
                                     : false);
@@ -797,9 +799,9 @@
     quic::ConnectionCloseSource source) {
   if (!net_log_is_capturing_)
     return;
-  net_log_.AddEvent(
-      NetLogEventType::QUIC_SESSION_CLOSED,
-      base::Bind(&NetLogQuicOnConnectionClosedCallback, error, source));
+  net_log_.AddEvent(NetLogEventType::QUIC_SESSION_CLOSED,
+                    base::Bind(&NetLogQuicOnConnectionClosedCallback, error,
+                               error_details, source));
 }
 
 void QuicConnectionLogger::OnSuccessfulVersionNegotiation(
diff --git a/net/socket/udp_socket_posix.cc b/net/socket/udp_socket_posix.cc
index bbbe297..4f7b9d1 100644
--- a/net/socket/udp_socket_posix.cc
+++ b/net/socket/udp_socket_posix.cc
@@ -1196,17 +1196,12 @@
     DatagramBuffers buffers) const {
   base::StackVector<struct iovec, kWriteAsyncMaxBuffersThreshold + 1> msg_iov;
   base::StackVector<struct mmsghdr, kWriteAsyncMaxBuffersThreshold + 1> msgvec;
-  int i = 0;
-  for (auto& buffer : buffers) {
-    msg_iov[i].iov_base = const_cast<char*>(buffer->data());
-    msg_iov[i].iov_len = buffer->length();
-    i++;
-  }
-  for (size_t j = 0; j < buffers.size(); j++) {
-    std::memset(&msgvec[j], 0, sizeof(msgvec[j]));
-    msgvec[j].msg_hdr.msg_iov = &msg_iov[j];
-    msgvec[j].msg_hdr.msg_iovlen = 1;
-  }
+  msg_iov->reserve(buffers.size());
+  for (auto& buffer : buffers)
+    msg_iov->push_back({const_cast<char*>(buffer->data()), buffer->length()});
+  msgvec->reserve(buffers.size());
+  for (size_t j = 0; j < buffers.size(); j++)
+    msgvec->push_back({{nullptr, 0, &msg_iov[j], 1, nullptr, 0, 0}, 0});
   int result = HANDLE_EINTR(Sendmmsg(fd, &msgvec[0], buffers.size(), 0));
   SendResult send_result(0, 0, std::move(buffers));
   if (result < 0) {
diff --git a/services/audio/loopback_stream.cc b/services/audio/loopback_stream.cc
index d16d354..ca51b49 100644
--- a/services/audio/loopback_stream.cc
+++ b/services/audio/loopback_stream.cc
@@ -23,11 +23,17 @@
 
 namespace audio {
 
-// static
-constexpr double LoopbackStream::kMaxVolume;
+namespace {
+
+// Start with a conservative, but reasonable capture delay that should work for
+// most platforms (i.e., not needing an increase during a loopback session).
+constexpr base::TimeDelta kInitialCaptureDelay =
+    base::TimeDelta::FromMilliseconds(20);
+
+}  // namespace
 
 // static
-constexpr base::TimeDelta LoopbackStream::kCaptureDelay;
+constexpr double LoopbackStream::kMaxVolume;
 
 LoopbackStream::LoopbackStream(
     CreatedCallback created_callback,
@@ -294,6 +300,7 @@
   first_generate_time_ = clock_->NowTicks();
   frames_elapsed_ = 0;
   next_generate_time_ = first_generate_time_;
+  capture_delay_ = kInitialCaptureDelay;
 
   flow_task_runner_->PostTask(
       FROM_HERE,
@@ -319,21 +326,38 @@
   TRACE_EVENT_WITH_FLOW0("audio", "GenerateMoreAudio", this,
                          TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
 
-  // Always generate audio from the recent past, to prevent buffer underruns
-  // in the inputs.
-  const base::TimeTicks delayed_capture_time =
-      next_generate_time_ - kCaptureDelay;
-
   // Drive the audio flows from the SnooperNodes and produce the single result
   // stream. Hold the lock during this part of the process to prevent any of the
   // control methods from altering the configuration of the network.
   double output_volume;
+  base::TimeTicks delayed_capture_time;
   {
     base::AutoLock scoped_lock(lock_);
     output_volume = volume_;
 
     HelpDiagnoseCauseOfLoopbackCrash("generating");
 
+    // Compute the reference time to use for audio rendering. Query each input
+    // node and update |capture_delay_|, if necessary. This is used to always
+    // generate audio from a "safe point" in the recent past, to prevent buffer
+    // underruns in the inputs. http://crbug.com/934770
+    delayed_capture_time = next_generate_time_ - capture_delay_;
+    for (SnooperNode* node : inputs_) {
+      const base::Optional<base::TimeTicks> suggestion =
+          node->SuggestLatestRenderTime(mix_bus_->frames());
+      if (suggestion.value_or(delayed_capture_time) < delayed_capture_time) {
+        const base::TimeDelta increase = delayed_capture_time - (*suggestion);
+        TRACE_EVENT_INSTANT2("audio", "GenerateMoreAudio Capture Delay Change",
+                             TRACE_EVENT_SCOPE_THREAD, "old capture delay (µs)",
+                             capture_delay_.InMicroseconds(), "change (µs)",
+                             increase.InMicroseconds());
+        delayed_capture_time = *suggestion;
+        capture_delay_ += increase;
+      }
+    }
+    TRACE_COUNTER_ID1("audio", "Loopback Capture Delay (µs)", this,
+                      capture_delay_.InMicroseconds());
+
     // Render the audio from each input, apply this stream's volume setting by
     // scaling the data, then mix it all together to form a single audio
     // signal. If there are no snoopers, just render silence.
diff --git a/services/audio/loopback_stream.h b/services/audio/loopback_stream.h
index 074d34b8..c6e408a3 100644
--- a/services/audio/loopback_stream.h
+++ b/services/audio/loopback_stream.h
@@ -99,13 +99,6 @@
   // than 1.0.
   static constexpr double kMaxVolume = 2.0;
 
-  // The amount of time in the past from which to capture the audio. The audio
-  // recorded from each LoopbackGroupMember is being generated with a target
-  // playout time in the near future (usually 1 to 20 ms). To avoid underflow,
-  // LoopbackStream fetches the audio from a position in the recent past.
-  static constexpr base::TimeDelta kCaptureDelay =
-      base::TimeDelta::FromMilliseconds(20);
-
  private:
   // Drives all audio flows, re-mixing the audio from multiple SnooperNodes into
   // a single audio stream. This class mainly operates on a separate task runner
@@ -201,6 +194,14 @@
     int64_t frames_elapsed_ = 0;
     base::TimeTicks next_generate_time_;
 
+    // The amount of time in the past from which to capture the audio. The audio
+    // recorded from each SnooperNode input is being generated with a target
+    // playout time in the near future (usually 1 to 20 ms). To avoid underflow,
+    // audio is always fetched from a safe position in the recent past.
+    //
+    // This is updated to match the SnooperNode whose recording is most delayed.
+    base::TimeDelta capture_delay_;
+
     // Used to transfer the audio from each SnooperNode and mix them into a
     // single audio signal. |transfer_bus_| is only allocated when first needed,
     // but |mix_bus_| is allocated in the constructor because it is always
diff --git a/services/audio/snooper_node.cc b/services/audio/snooper_node.cc
index 1e4fb6d..61eb8e3f 100644
--- a/services/audio/snooper_node.cc
+++ b/services/audio/snooper_node.cc
@@ -39,6 +39,12 @@
 // data extraction.
 constexpr int kResamplerRequestSize = 3 * media::SincResampler::kKernelSize;
 
+// Returns the deviation, around an estimated reference time, beyond which a
+// SnooperNode considers a skip in input/output to have occurred.
+base::TimeDelta GetReferenceTimeSkipThreshold(base::TimeDelta bus_duration) {
+  return bus_duration / 2;
+}
+
 }  // namespace
 
 // static
@@ -62,6 +68,7 @@
       buffer_(
           Helper::TimeToFrames(kDelayBufferSize, input_params_.sample_rate())),
       write_position_(kNullPosition),
+      checkpoint_time_(base::TimeTicks::Min()),
       read_position_(kNullPosition),
       correction_fps_(0),
       resampler_(
@@ -118,13 +125,36 @@
   if (write_position_ == kNullPosition) {
     write_position_ = kWriteStartPosition;
   } else {
+    const base::TimeDelta threshold =
+        GetReferenceTimeSkipThreshold(input_bus_duration_);
     const base::TimeDelta delta = reference_time - write_reference_time_;
-    if (delta >= input_bus_duration_) {
+    if (delta < -threshold) {
+      TRACE_EVENT_INSTANT1("audio", "SnooperNode Discards Input",
+                           TRACE_EVENT_SCOPE_THREAD, "wait_time_remaining (μs)",
+                           (-delta).InMicroseconds());
+      // It's illegal to back-track the |write_position_| and/or attempt to
+      // "rewrite history" in the delay buffer. Thus, simply drop input until it
+      // catches up. Events such as this are generally only caused by device-
+      // switching in audio::OutputController, where the delay timestamps may
+      // shift. http://crbug.com/934770
+      return;
+    } else if (delta > threshold) {
       TRACE_EVENT_INSTANT1("audio", "SnooperNode Input Gap",
                            TRACE_EVENT_SCOPE_THREAD, "gap (μs)",
                            delta.InMicroseconds());
+      // Skip the |write_position_| forward, which will create a zero-fill gap
+      // in the delay buffer.
       write_position_ +=
           Helper::TimeToFrames(delta, input_params_.sample_rate());
+    } else {
+      // Normal case: Continue writing into the delay buffer at the current
+      // |write_position_|.
+      //
+      // Note that, if input was being discarded (in the prior OnData() call),
+      // there will be no "recovery adjustment" to the |write_position_|.
+      // Instead, any significant jump in |write_reference_time_| will cause
+      // Render() to gradually re-synchronize the audio. There will be no
+      // zero-fill gap inserted into the delay buffer.
     }
   }
 
@@ -134,6 +164,34 @@
   write_reference_time_ = reference_time + input_bus_duration_;
 }
 
+base::Optional<base::TimeTicks> SnooperNode::SuggestLatestRenderTime(
+    FrameTicks duration) {
+  DCHECK_GE(duration, 0);
+
+  const base::TimeTicks last_checkpoint_time = checkpoint_time_;
+  {
+    base::AutoLock scoped_lock(lock_);
+    if (write_position_ == kNullPosition) {
+      return base::nullopt;  // OnData() never called yet.
+    }
+    checkpoint_time_ = write_reference_time_;
+  }
+
+  // Do not suggest any changes if OnData() has not been called since the last
+  // call to this method. This may indicate an input discontinuity is occurring.
+  if (checkpoint_time_ == last_checkpoint_time) {
+    return base::nullopt;
+  }
+
+  // Suggest a render time no later than a "safety margin" away from the end of
+  // the data currently recorded in the delay buffer. This extra margin helps to
+  // avoid underruns when the machine is under high stress.
+  const base::TimeDelta buffer_duration =
+      Helper::FramesToTime(duration, output_params_.sample_rate());
+  return checkpoint_time_ - buffer_duration -
+         GetReferenceTimeSkipThreshold(buffer_duration);
+}
+
 void SnooperNode::Render(base::TimeTicks reference_time,
                          media::AudioBus* output_bus) {
   DCHECK_EQ(output_bus->channels(), output_params_.channels());
@@ -172,8 +230,10 @@
         estimated_output_position + std::lround(resampler_.BufferedFrames());
     DCHECK_EQ(correction_fps_, 0);
   } else {
+    const base::TimeDelta threshold =
+        GetReferenceTimeSkipThreshold(output_bus_duration_);
     const base::TimeDelta delta = reference_time - render_reference_time_;
-    if (delta < output_bus_duration_) {  // Normal case: No gap.
+    if (delta.magnitude() < threshold) {  // Normal case: No gap.
       // Compute the drift, which is the number of frames the resampler is
       // behind in reading from the delay buffer. This calculation also accounts
       // for the frames buffered within the resampler.
@@ -207,12 +267,12 @@
       } else {
         // No correction necessary.
       }
-    } else /* if (delta >= threshold) */ {  // Gap detected.
-      TRACE_EVENT_INSTANT1("audio", "SnooperNode Render Gap",
-                           TRACE_EVENT_SCOPE_THREAD, "gap (μs)",
+    } else {  // Some type of rewind, fast-forward, or a rendering gap.
+      TRACE_EVENT_INSTANT1("audio", "SnooperNode Render Skip",
+                           TRACE_EVENT_SCOPE_THREAD, "delta (μs)",
                            delta.InMicroseconds());
 
-      // Rather than flush and re-prime the resampler, just skip-ahead its next
+      // Rather than flush and re-prime the resampler, just seek to its next
       // read-from position.
       read_position_ +=
           Helper::TimeToFrames(delta, input_params_.sample_rate());
diff --git a/services/audio/snooper_node.h b/services/audio/snooper_node.h
index 7ead2e79..8b88a18 100644
--- a/services/audio/snooper_node.h
+++ b/services/audio/snooper_node.h
@@ -9,6 +9,7 @@
 #include <memory>
 
 #include "base/macros.h"
+#include "base/optional.h"
 #include "base/synchronization/lock.h"
 #include "base/time/time.h"
 #include "media/base/audio_parameters.h"
@@ -74,6 +75,13 @@
               base::TimeTicks reference_time,
               double volume) final;
 
+  // Given the timing of recent OnData() calls and the |duration| of output that
+  // would be requested in a call to Render(), determine the latest possible
+  // |reference_time| for a Render() call that won't result in an underrun.
+  // Returns base::nullopt while current conditions prohibit making a reliable
+  // suggestion.
+  base::Optional<base::TimeTicks> SuggestLatestRenderTime(FrameTicks duration);
+
   // Renders more audio that was recorded from the GroupMember until
   // |output_bus| is filled, resampling and remixing the channels if necessary.
   // |reference_time| is used for detecting skip-ahead (i.e., a significant
@@ -119,6 +127,11 @@
   FrameTicks write_position_;             // Guarded by |lock_|.
   base::TimeTicks write_reference_time_;  // Guarded by |lock_|.
 
+  // Used by SuggestLatestRenderTime() to track whether OnData() has been called
+  // recently, and as a basis for its suggestion. Other methods should not
+  // depend on this value for anything.
+  base::TimeTicks checkpoint_time_;
+
   // The next frame position from which to read from the delay buffer. This is
   // the position of the frames about to be pushed into the resampler, not the
   // position of frames about to be Render()'ed.
diff --git a/services/audio/stream_factory.cc b/services/audio/stream_factory.cc
index 3a5ea36..5d02fcb 100644
--- a/services/audio/stream_factory.cc
+++ b/services/audio/stream_factory.cc
@@ -54,9 +54,9 @@
   CHECK_EQ(magic_bytes_, 0x600DC0DEu);
   DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
   SetStateForCrashing("creating input stream");
-  TRACE_EVENT_NESTABLE_ASYNC_INSTANT1(
+  TRACE_EVENT_NESTABLE_ASYNC_INSTANT2(
       "audio", "CreateInputStream", bindings_.dispatch_context().id_for_trace(),
-      "device id", device_id);
+      "device id", device_id, "params", params.AsHumanReadableString());
 
   if (processing_config && processing_config->settings.requires_apm() &&
       params.GetBufferDuration() != base::TimeDelta::FromMilliseconds(10)) {
@@ -112,10 +112,10 @@
   CHECK_EQ(magic_bytes_, 0x600DC0DEu);
   DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
   SetStateForCrashing("creating output stream");
-  TRACE_EVENT_NESTABLE_ASYNC_INSTANT1(
+  TRACE_EVENT_NESTABLE_ASYNC_INSTANT2(
       "audio", "CreateOutputStream",
       bindings_.dispatch_context().id_for_trace(), "device id",
-      output_device_id);
+      output_device_id, "params", params.AsHumanReadableString());
 
   media::mojom::AudioOutputStreamObserverAssociatedPtr observer;
   observer.Bind(std::move(observer_info));
@@ -183,10 +183,11 @@
   CHECK_EQ(magic_bytes_, 0x600DC0DEu);
   DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
   SetStateForCrashing("creating loopback stream");
-  TRACE_EVENT_NESTABLE_ASYNC_INSTANT1(
+  TRACE_EVENT_NESTABLE_ASYNC_INSTANT2(
       "audio", "CreateLoopbackStream",
       bindings_.dispatch_context().id_for_trace(), "group id",
-      group_id.GetLowForSerialization());
+      group_id.GetLowForSerialization(), "params",
+      params.AsHumanReadableString());
 
   auto stream = std::make_unique<LoopbackStream>(
       std::move(created_callback),
diff --git a/services/image_annotation/BUILD.gn b/services/image_annotation/BUILD.gn
index 8b28b73..67c1c3b 100644
--- a/services/image_annotation/BUILD.gn
+++ b/services/image_annotation/BUILD.gn
@@ -6,9 +6,13 @@
   sources = [
     "annotator.cc",
     "annotator.h",
+    "image_annotation_metrics.cc",
+    "image_annotation_metrics.h",
+    "image_annotation_utils.cc",
+    "image_annotation_utils.h",
   ]
 
-  visibility = [ ":*" ]
+  visibility = [ "/*" ]
 
   deps = [
     "//base",
diff --git a/services/image_annotation/annotator.cc b/services/image_annotation/annotator.cc
index 40b623bb..00db319 100644
--- a/services/image_annotation/annotator.cc
+++ b/services/image_annotation/annotator.cc
@@ -13,11 +13,15 @@
 #include "base/feature_list.h"
 #include "base/json/json_writer.h"
 #include "base/location.h"
+#include "base/logging.h"
+#include "base/no_destructor.h"
 #include "base/stl_util.h"
 #include "components/google/core/common/google_util.h"
 #include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "services/data_decoder/public/mojom/constants.mojom.h"
+#include "services/image_annotation/image_annotation_metrics.h"
 #include "services/service_manager/public/cpp/connector.h"
 #include "url/gurl.h"
 
@@ -34,11 +38,16 @@
 // annotations message.
 mojom::AnnotationPtr ParseJsonOcrAnnotation(const base::Value& ocr_engine,
                                             const double min_ocr_confidence) {
+  if (!ocr_engine.is_dict())
+    return mojom::AnnotationPtr(nullptr);
+
   // No OCR regions is valid - it just means there is no text.
   const base::Value* const ocr_regions = ocr_engine.FindKey("ocrRegions");
-  if (!ocr_regions)
+  if (!ocr_regions) {
+    ReportOcrAnnotation(1.0 /* confidence */, true /* empty */);
     return mojom::Annotation::New(mojom::AnnotationType::kOcr, 1.0 /* score */,
                                   std::string() /* text */);
+  }
 
   if (!ocr_regions->is_list())
     return mojom::AnnotationPtr(nullptr);
@@ -90,9 +99,11 @@
     all_ocr_text += region_ocr_text;
   }
 
-  return mojom::Annotation::New(
-      mojom::AnnotationType::kOcr,
-      word_count == 0 ? 1.0 : word_confidence_sum / word_count, all_ocr_text);
+  const double all_ocr_confidence =
+      word_count == 0 ? 1.0 : word_confidence_sum / word_count;
+  ReportOcrAnnotation(all_ocr_confidence, all_ocr_text.empty());
+  return mojom::Annotation::New(mojom::AnnotationType::kOcr, all_ocr_confidence,
+                                all_ocr_text);
 }
 
 // Extracts annotations from the given description engine result into the second
@@ -102,19 +113,28 @@
 // classified as containing adult content.
 std::tuple<bool, std::vector<mojom::AnnotationPtr>> ParseJsonDescAnnotations(
     const base::Value& desc_engine) {
-  constexpr char kAdultFailureReason[] = "ADULT";
-  const std::map<std::string, mojom::AnnotationType> annotation_types = {
-      {"OCR", mojom::AnnotationType::kOcr},
-      {"CAPTION", mojom::AnnotationType::kCaption},
-      {"LABEL", mojom::AnnotationType::kLabel}};
+  static const base::NoDestructor<std::map<std::string, mojom::AnnotationType>>
+      kAnnotationTypes({{"OCR", mojom::AnnotationType::kOcr},
+                        {"CAPTION", mojom::AnnotationType::kCaption},
+                        {"LABEL", mojom::AnnotationType::kLabel}});
 
-  const base::Value* const failure_reason =
-      desc_engine.FindKey("failureReason");
-
-  const bool adult = failure_reason && failure_reason->is_string() &&
-                     failure_reason->GetString() == kAdultFailureReason;
+  bool adult = false;
   std::vector<mojom::AnnotationPtr> results;
 
+  if (!desc_engine.is_dict())
+    return {adult, std::move(results)};
+
+  // If there is a failure reason, log it and track whether it is due to adult
+  // content.
+  const base::Value* const failure_reason_value =
+      desc_engine.FindKey("failureReason");
+  if (failure_reason_value && failure_reason_value->is_string()) {
+    const DescFailureReason failure_reason =
+        ParseDescFailureReason(failure_reason_value->GetString());
+    ReportDescFailure(failure_reason);
+    adult = failure_reason == DescFailureReason::kAdult;
+  }
+
   const base::Value* const desc_list_dict =
       desc_engine.FindKey("descriptionList");
   if (!desc_list_dict || !desc_list_dict->is_dict())
@@ -132,8 +152,8 @@
     if (!type || !type->is_string())
       continue;
 
-    const auto type_lookup = annotation_types.find(type->GetString());
-    if (type_lookup == annotation_types.end())
+    const auto type_lookup = kAnnotationTypes->find(type->GetString());
+    if (type_lookup == kAnnotationTypes->end())
       continue;
 
     const base::Value* const score = desc.FindKey("score");
@@ -145,6 +165,8 @@
     if (!text || !text->is_string() || text->GetString().empty())
       continue;
 
+    ReportDescAnnotation(type_lookup->second, score->GetDouble(),
+                         text->GetString().empty());
     results.push_back(mojom::Annotation::New(
         type_lookup->second, score->GetDouble(), text->GetString()));
   }
@@ -152,6 +174,33 @@
   return {adult, std::move(results)};
 }
 
+// Returns the integer status code for this engine, or -1 if no status can be
+// extracted.
+int ExtractStatusCode(const base::Value* const status_dict) {
+  if (!status_dict || !status_dict->is_dict())
+    return -1;
+
+  const base::Value* const code_value = status_dict->FindKey("code");
+
+  // A missing code is the same as a default (i.e. OK) code.
+  if (!code_value)
+    return 0;
+
+  if (!code_value->is_int())
+    return -1;
+  const int code = code_value->GetInt();
+
+#ifndef NDEBUG
+  // Also log error status messages (which are helpful for debugging).
+  const base::Value* const message = status_dict->FindKey("message");
+  if (code != 0 && message && message->is_string())
+    DVLOG(1) << "Engine failed with status " << code << " and message '"
+             << message->GetString() << "'";
+#endif
+
+  return code;
+}
+
 // Attempts to extract annotation results from the server response, returning a
 // map from each source ID to its annotations (if successfully extracted).
 std::map<std::string, mojom::AnnotateImageResultPtr> UnpackJsonResponse(
@@ -188,18 +237,38 @@
       if (!engine_result.is_dict())
         continue;
 
+      // A non-zero status code means the following:
+      //  -1:                       The status dict could not be parsed. We
+      //                            still try to parse an engine result in this
+      //                            case to be robust.
+      //  any other non-zero value: The status dict was parsed and contains a
+      //                            known failure. We always report an error
+      //                            in this case.
+      const int status_code =
+          ExtractStatusCode(engine_result.FindKey("status"));
+
       const base::Value* const desc_engine =
           engine_result.FindKey("descriptionEngine");
       const base::Value* const ocr_engine = engine_result.FindKey("ocrEngine");
 
-      if (desc_engine && desc_engine->is_dict()) {
+      if (desc_engine) {
         // Add description annotations and update the adult image flag.
-        std::tie(adult, annotations) = ParseJsonDescAnnotations(*desc_engine);
-      } else if (ocr_engine && ocr_engine->is_dict()) {
+        ReportDescStatus(status_code);
+
+        if (status_code <= 0) {
+          std::tie(adult, annotations) = ParseJsonDescAnnotations(*desc_engine);
+        }
+      } else if (ocr_engine) {
         // Update the specialized OCR annotations.
-        ocr_annotation =
-            ParseJsonOcrAnnotation(*ocr_engine, min_ocr_confidence);
+        ReportOcrStatus(status_code);
+
+        if (status_code <= 0) {
+          ocr_annotation =
+              ParseJsonOcrAnnotation(*ocr_engine, min_ocr_confidence);
+        }
       }
+
+      ReportEngineKnown(ocr_engine || desc_engine);
     }
 
     // Remove any description OCR data (which is lower quality) if we have
@@ -214,10 +283,7 @@
     if (adult) {
       out[image_id->GetString()] = mojom::AnnotateImageResult::NewErrorCode(
           mojom::AnnotateImageError::kAdult);
-    } else if (annotations.empty()) {
-      out[image_id->GetString()] = mojom::AnnotateImageResult::NewErrorCode(
-          mojom::AnnotateImageError::kFailure);
-    } else {
+    } else if (!annotations.empty()) {
       out[image_id->GetString()] =
           mojom::AnnotateImageResult::NewAnnotations(std::move(annotations));
     }
@@ -252,7 +318,15 @@
   DCHECK(connector_);
 }
 
-Annotator::~Annotator() {}
+Annotator::~Annotator() {
+  // Report any clients still connected at service shutdown.
+  for (const auto& request_info_kv : request_infos_) {
+    for (const auto& unused : request_info_kv.second) {
+      ReportClientResult(ClientResult::kShutdown);
+      ANALYZER_ALLOW_UNUSED(unused);
+    }
+  }
+}
 
 void Annotator::BindRequest(mojom::AnnotatorRequest request) {
   bindings_.AddBinding(this, std::move(request));
@@ -263,6 +337,7 @@
                               AnnotateImageCallback callback) {
   // Return cached results if they exist.
   const auto cache_lookup = cached_results_.find(source_id);
+  ReportCacheHit(cache_lookup != cached_results_.end());
   if (cache_lookup != cached_results_.end()) {
     std::move(callback).Run(cache_lookup->second.Clone());
     return;
@@ -278,7 +353,7 @@
   // processor was responsible for some ongoing work.
   request_info_list.back().first.set_connection_error_handler(base::BindOnce(
       &Annotator::RemoveRequestInfo, base::Unretained(this), source_id,
-      --request_info_list.end(), mojom::AnnotateImageError::kCanceled));
+      --request_info_list.end(), true /* canceled */));
 
   // Don't start local work if it would duplicate some ongoing or already-
   // completed work.
@@ -337,6 +412,8 @@
   std::string json_request;
   base::JSONWriter::Write(request, &json_request);
 
+  ReportServerRequestSizeKB(json_request.size() / 1024);
+
   return json_request;
 }
 
@@ -408,11 +485,12 @@
     const std::string& source_id,
     const RequestInfoList::iterator request_info_it,
     const std::vector<uint8_t>& image_bytes) {
+  ReportPixelFetchSuccess(!image_bytes.empty());
+
   // Failed to retrieve bytes from local processor; remove dead processor and
   // reschedule processing.
   if (image_bytes.empty()) {
-    RemoveRequestInfo(source_id, request_info_it,
-                      mojom::AnnotateImageError::kFailure);
+    RemoveRequestInfo(source_id, request_info_it, false /* canceled */);
     return;
   }
 
@@ -463,6 +541,15 @@
     const std::set<std::string>& source_ids,
     const UrlLoaderList::iterator http_request_it,
     const std::unique_ptr<std::string> json_response) {
+  ReportServerNetError(http_request_it->get()->NetError());
+
+  if (const network::ResourceResponseInfo* const response_info =
+          http_request_it->get()->ResponseInfo()) {
+    ReportServerResponseCode(response_info->headers->response_code());
+    ReportServerLatency(response_info->response_time -
+                        response_info->request_time);
+  }
+
   http_requests_.erase(http_request_it);
 
   if (!json_response) {
@@ -471,6 +558,8 @@
     return;
   }
 
+  ReportServerResponseSizeBytes(json_response->size());
+
   // Send JSON string to a dedicated service for safe parsing.
   GetJsonParser().Parse(*json_response,
                         base::BindOnce(&Annotator::OnResponseJsonParsed,
@@ -481,15 +570,17 @@
     const std::set<std::string>& source_ids,
     const base::Optional<base::Value> json_data,
     const base::Optional<std::string>& error) {
+  const bool success = json_data.has_value() && !error.has_value();
+  ReportJsonParseSuccess(success);
+
   // Extract annotation results for each source ID with valid results.
-  std::map<std::string, mojom::AnnotateImageResultPtr> results;
-  if (!json_data.has_value() || error.has_value()) {
+  if (success) {
+    ProcessResults(source_ids,
+                   UnpackJsonResponse(*json_data, min_ocr_confidence_));
+  } else {
     DVLOG(1) << "Parsing server response JSON failed with error: "
              << error.value_or("No reason reported.");
     ProcessResults(source_ids, {});
-  } else {
-    ProcessResults(source_ids,
-                   UnpackJsonResponse(*json_data, min_ocr_confidence_));
   }
 }
 
@@ -519,11 +610,15 @@
                                   ? result_lookup->second.Clone()
                                   : mojom::AnnotateImageResult::NewErrorCode(
                                         mojom::AnnotateImageError::kFailure);
+    const auto client_result = result_lookup != results.end()
+                                   ? ClientResult::kSucceeded
+                                   : ClientResult::kFailed;
 
     // Notify clients of success or failure.
     // TODO(crbug.com/916420): explore server retry strategies.
     for (auto& info : request_info_it->second) {
       std::move(info.second).Run(image_result.Clone());
+      ReportClientResult(client_result);
     }
     request_infos_.erase(request_info_it);
   }
@@ -544,7 +639,7 @@
 void Annotator::RemoveRequestInfo(
     const std::string& source_id,
     const RequestInfoList::iterator request_info_it,
-    const mojom::AnnotateImageError error) {
+    const bool canceled) {
   // Check whether we are deleting the ImageProcessor responsible for current
   // local processing.
   auto lookup = local_processors_.find(source_id);
@@ -552,8 +647,12 @@
                                lookup->second == &request_info_it->first;
 
   // Notify client of cancellation / failure.
+  ReportClientResult(canceled ? ClientResult::kCanceled
+                              : ClientResult::kFailed);
   std::move(request_info_it->second)
-      .Run(mojom::AnnotateImageResult::NewErrorCode(error));
+      .Run(mojom::AnnotateImageResult::NewErrorCode(
+          canceled ? mojom::AnnotateImageError::kCanceled
+                   : mojom::AnnotateImageError::kFailure));
 
   // Delete the specified ImageProcessor.
   RequestInfoList& request_info_list = request_infos_[source_id];
diff --git a/services/image_annotation/annotator.h b/services/image_annotation/annotator.h
index bf4df32..aec2be8 100644
--- a/services/image_annotation/annotator.h
+++ b/services/image_annotation/annotator.h
@@ -115,7 +115,7 @@
   // image processor had some ongoing.
   void RemoveRequestInfo(const std::string& source_id,
                          RequestInfoList::iterator request_info_it,
-                         mojom::AnnotateImageError error);
+                         bool canceled);
 
   // Called when a local handler returns compressed image data for the given
   // source ID.
diff --git a/services/image_annotation/annotator_unittest.cc b/services/image_annotation/annotator_unittest.cc
index 7a6686e..8724076 100644
--- a/services/image_annotation/annotator_unittest.cc
+++ b/services/image_annotation/annotator_unittest.cc
@@ -4,19 +4,24 @@
 
 #include "services/image_annotation/annotator.h"
 
+#include <cstring>
+
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/json/json_reader.h"
 #include "base/json/json_writer.h"
 #include "base/optional.h"
 #include "base/strings/stringprintf.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/time/time.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
+#include "net/base/net_errors.h"
 #include "net/http/http_status_code.h"
 #include "services/data_decoder/public/cpp/test_data_decoder_service.h"
 #include "services/data_decoder/public/mojom/constants.mojom.h"
 #include "services/data_decoder/public/mojom/json_parser.mojom.h"
+#include "services/image_annotation/image_annotation_metrics.h"
 #include "services/image_annotation/public/mojom/image_annotation.mojom.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 #include "services/network/public/mojom/url_loader.mojom-shared.h"
@@ -30,10 +35,12 @@
 
 namespace {
 
+using base::Bucket;
 using testing::ElementsAre;
 using testing::Eq;
 using testing::IsEmpty;
 using testing::SizeIs;
+using testing::UnorderedElementsAre;
 
 constexpr char kTestServerUrl[] = "https://ia-pa.googleapis.com/v1/annotation";
 
@@ -97,6 +104,7 @@
     {
       "imageId": "https://www.example.com/image1.jpg",
       "engineResults": [{
+        "status": {},
         "ocrEngine": {
           "ocrRegions": [
             {
@@ -140,7 +148,8 @@
       "status": {
         "code": 8,
         "message": "Resource exhaused"
-      }
+      },
+      "ocrEngine": {}
     }]
   }]
 }
@@ -156,6 +165,7 @@
     {
       "imageId": "https://www.example.com/image2.jpg",
       "engineResults": [{
+        "status": {},
         "ocrEngine": {
           "ocrRegions": [{
             "words": [{
@@ -169,6 +179,7 @@
     {
       "imageId": "https://www.example.com/image1.jpg",
       "engineResults": [{
+        "status": {},
         "ocrEngine": {
           "ocrRegions": [{
             "words": [{
@@ -185,7 +196,8 @@
         "status": {
           "code": 8,
           "message": "Resource exhaused"
-        }
+        },
+        "ocrEngine": {}
       }]
     }
   ]
@@ -347,6 +359,7 @@
   TestServerURLLoaderFactory test_url_factory(
       "https://ia-pa.googleapis.com/v1/");
   data_decoder::TestDataDecoderService test_dd_service;
+  base::HistogramTester histogram_tester;
 
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
@@ -378,15 +391,40 @@
     test_task_env.RunUntilIdle();
 
     // HTTP request should have been made.
+    const std::string request =
+        ReformatJson(base::StringPrintf(kTemplateRequest, kImage1Url, "AQID"));
     test_url_factory.ExpectRequestAndSimulateResponse(
-        "annotation", {} /* expected_headers */,
-        ReformatJson(base::StringPrintf(kTemplateRequest, kImage1Url, "AQID")),
-        kSuccessResponse, net::HTTP_OK);
+        "annotation", {} /* expected_headers */, request, kSuccessResponse,
+        net::HTTP_OK);
     test_task_env.RunUntilIdle();
 
     // HTTP response should have completed and callback should have been called.
     ASSERT_THAT(error, Eq(base::nullopt));
     EXPECT_THAT(ocr_text, Eq("Region 1\nRegion 2"));
+
+    // Metrics should have been logged for the major actions of the service.
+    histogram_tester.ExpectUniqueSample(metrics_internal::kCacheHit, false, 1);
+    histogram_tester.ExpectUniqueSample(metrics_internal::kPixelFetchSuccess,
+                                        true, 1);
+    histogram_tester.ExpectUniqueSample(metrics_internal::kPixelFetchSuccess,
+                                        true, 1);
+    histogram_tester.ExpectUniqueSample(metrics_internal::kServerRequestSize,
+                                        request.size() / 1024, 1);
+    histogram_tester.ExpectUniqueSample(metrics_internal::kServerNetError,
+                                        net::Error::OK, 1);
+    histogram_tester.ExpectUniqueSample(
+        metrics_internal::kServerHttpResponseCode, net::HTTP_OK, 1);
+    histogram_tester.ExpectUniqueSample(metrics_internal::kServerResponseSize,
+                                        std::strlen(kSuccessResponse), 1);
+    histogram_tester.ExpectUniqueSample(
+        base::StringPrintf(metrics_internal::kAnnotationStatus, "Ocr"),
+        0 /* OK RPC status */, 1);
+    histogram_tester.ExpectUniqueSample(
+        base::StringPrintf(metrics_internal::kAnnotationConfidence, "Ocr"), 100,
+        1);
+    histogram_tester.ExpectUniqueSample(
+        base::StringPrintf(metrics_internal::kAnnotationEmpty, "Ocr"), false,
+        1);
   }
 
   // Second call uses cached results.
@@ -404,6 +442,10 @@
     // Results should have been directly returned without any server call.
     ASSERT_THAT(error, Eq(base::nullopt));
     EXPECT_THAT(ocr_text, Eq("Region 1\nRegion 2"));
+
+    // Metrics should have been logged for a cache hit.
+    EXPECT_THAT(histogram_tester.GetAllSamples(metrics_internal::kCacheHit),
+                UnorderedElementsAre(Bucket(false, 1), Bucket(true, 1)));
   }
 }
 
@@ -414,6 +456,7 @@
   TestServerURLLoaderFactory test_url_factory(
       "https://ia-pa.googleapis.com/v1/");
   data_decoder::TestDataDecoderService test_dd_service;
+  base::HistogramTester histogram_tester;
 
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
@@ -451,6 +494,14 @@
   // HTTP response should have completed and callback should have been called.
   EXPECT_THAT(error, Eq(mojom::AnnotateImageError::kFailure));
   EXPECT_THAT(ocr_text, Eq(base::nullopt));
+
+  // Metrics about the HTTP request failure should have been logged.
+  histogram_tester.ExpectUniqueSample(metrics_internal::kServerNetError,
+                                      net::Error::ERR_FAILED, 1);
+  histogram_tester.ExpectUniqueSample(metrics_internal::kServerHttpResponseCode,
+                                      net::HTTP_INTERNAL_SERVER_ERROR, 1);
+  histogram_tester.ExpectUniqueSample(metrics_internal::kClientResult,
+                                      ClientResult::kFailed, 1);
 }
 
 // Test that backend failure is gracefully handled.
@@ -460,6 +511,7 @@
   TestServerURLLoaderFactory test_url_factory(
       "https://ia-pa.googleapis.com/v1/");
   data_decoder::TestDataDecoderService test_dd_service;
+  base::HistogramTester histogram_tester;
 
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
@@ -498,6 +550,17 @@
   // with an error status.
   EXPECT_THAT(error, Eq(mojom::AnnotateImageError::kFailure));
   EXPECT_THAT(ocr_text, Eq(base::nullopt));
+
+  // Metrics about the backend failure should have been logged.
+  histogram_tester.ExpectUniqueSample(metrics_internal::kServerNetError,
+                                      net::Error::OK, 1);
+  histogram_tester.ExpectUniqueSample(metrics_internal::kServerHttpResponseCode,
+                                      net::HTTP_OK, 1);
+  histogram_tester.ExpectUniqueSample(
+      base::StringPrintf(metrics_internal::kAnnotationStatus, "Ocr"),
+      8 /* Failed RPC status */, 1);
+  histogram_tester.ExpectUniqueSample(metrics_internal::kClientResult,
+                                      ClientResult::kFailed, 1);
 }
 
 // Test that server failure (i.e. nonsense response) is gracefully handled.
@@ -507,6 +570,7 @@
   TestServerURLLoaderFactory test_url_factory(
       "https://ia-pa.googleapis.com/v1/");
   data_decoder::TestDataDecoderService test_dd_service;
+  base::HistogramTester histogram_tester;
 
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
@@ -545,6 +609,16 @@
   // with an error status.
   EXPECT_THAT(error, Eq(mojom::AnnotateImageError::kFailure));
   EXPECT_THAT(ocr_text, Eq(base::nullopt));
+
+  // Metrics about the invalid response format should have been logged.
+  histogram_tester.ExpectUniqueSample(metrics_internal::kServerNetError,
+                                      net::Error::OK, 1);
+  histogram_tester.ExpectUniqueSample(metrics_internal::kServerHttpResponseCode,
+                                      net::HTTP_OK, 1);
+  histogram_tester.ExpectUniqueSample(metrics_internal::kJsonParseSuccess,
+                                      false, 1);
+  histogram_tester.ExpectUniqueSample(metrics_internal::kClientResult,
+                                      ClientResult::kFailed, 1);
 }
 
 // Test that work is reassigned if a processor fails.
@@ -554,6 +628,7 @@
   TestServerURLLoaderFactory test_url_factory(
       "https://ia-pa.googleapis.com/v1/");
   data_decoder::TestDataDecoderService test_dd_service;
+  base::HistogramTester histogram_tester;
 
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
@@ -609,6 +684,15 @@
                                  base::nullopt, base::nullopt));
   EXPECT_THAT(ocr_text, ElementsAre(base::nullopt, "Region 1\nRegion 2",
                                     "Region 1\nRegion 2"));
+
+  // Metrics about the pixel fetch failure should have been logged.
+  EXPECT_THAT(
+      histogram_tester.GetAllSamples(metrics_internal::kPixelFetchSuccess),
+      UnorderedElementsAre(Bucket(false, 1), Bucket(true, 1)));
+  EXPECT_THAT(histogram_tester.GetAllSamples(metrics_internal::kClientResult),
+              UnorderedElementsAre(
+                  Bucket(static_cast<int32_t>(ClientResult::kFailed), 1),
+                  Bucket(static_cast<int32_t>(ClientResult::kSucceeded), 2)));
 }
 
 // Test that work is reassigned if processor dies.
@@ -618,6 +702,7 @@
   TestServerURLLoaderFactory test_url_factory(
       "https://ia-pa.googleapis.com/v1/");
   data_decoder::TestDataDecoderService test_dd_service;
+  base::HistogramTester histogram_tester;
 
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
@@ -672,6 +757,12 @@
                                  base::nullopt, base::nullopt));
   EXPECT_THAT(ocr_text, ElementsAre(base::nullopt, "Region 1\nRegion 2",
                                     "Region 1\nRegion 2"));
+
+  // Metrics about the client cancelation should have been logged.
+  EXPECT_THAT(histogram_tester.GetAllSamples(metrics_internal::kClientResult),
+              UnorderedElementsAre(
+                  Bucket(static_cast<int32_t>(ClientResult::kCanceled), 1),
+                  Bucket(static_cast<int32_t>(ClientResult::kSucceeded), 2)));
 }
 
 // Test that multiple concurrent requests are handled in the same batch.
@@ -681,6 +772,7 @@
   TestServerURLLoaderFactory test_url_factory(
       "https://ia-pa.googleapis.com/v1/");
   data_decoder::TestDataDecoderService test_dd_service;
+  base::HistogramTester histogram_tester;
 
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
@@ -734,6 +826,26 @@
   ASSERT_THAT(error, ElementsAre(base::nullopt, base::nullopt,
                                  mojom::AnnotateImageError::kFailure));
   EXPECT_THAT(ocr_text, ElementsAre("1", "2", base::nullopt));
+
+  // Metrics should have been logged for a single server response with multiple
+  // results included.
+  histogram_tester.ExpectUniqueSample(metrics_internal::kServerNetError,
+                                      net::Error::OK, 1);
+  histogram_tester.ExpectUniqueSample(metrics_internal::kServerHttpResponseCode,
+                                      net::HTTP_OK, 1);
+  EXPECT_THAT(histogram_tester.GetAllSamples(base::StringPrintf(
+                  metrics_internal::kAnnotationStatus, "Ocr")),
+              UnorderedElementsAre(Bucket(8 /* Failed RPC status */, 1),
+                                   Bucket(0 /* OK RPC status */, 2)));
+  histogram_tester.ExpectUniqueSample(
+      base::StringPrintf(metrics_internal::kAnnotationConfidence, "Ocr"), 100,
+      2);
+  histogram_tester.ExpectUniqueSample(
+      base::StringPrintf(metrics_internal::kAnnotationEmpty, "Ocr"), false, 2);
+  EXPECT_THAT(histogram_tester.GetAllSamples(metrics_internal::kClientResult),
+              UnorderedElementsAre(
+                  Bucket(static_cast<int32_t>(ClientResult::kFailed), 1),
+                  Bucket(static_cast<int32_t>(ClientResult::kSucceeded), 2)));
 }
 
 // Test that multiple concurrent requests are handled in separate batches.
@@ -743,6 +855,7 @@
   TestServerURLLoaderFactory test_url_factory(
       "https://ia-pa.googleapis.com/v1/");
   data_decoder::TestDataDecoderService test_dd_service;
+  base::HistogramTester histogram_tester;
 
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
@@ -798,6 +911,7 @@
            "results": [{
              "imageId": "https://www.example.com/image1.jpg",
              "engineResults": [{
+               "status": {},
                "ocrEngine": {
                  "ocrRegions": [{
                    "words": [{
@@ -822,6 +936,7 @@
            "results": [{
              "imageId": "https://www.example.com/image2.jpg",
              "engineResults": [{
+               "status": {},
                "ocrEngine": {
                  "ocrRegions": [{
                    "words": [{
@@ -840,6 +955,22 @@
   // Annotator should have called each callback with its corresponding text.
   ASSERT_THAT(error, ElementsAre(base::nullopt, base::nullopt));
   EXPECT_THAT(ocr_text, ElementsAre("1", "2"));
+
+  // Metrics should have been logged for two server responses.
+  histogram_tester.ExpectUniqueSample(metrics_internal::kServerNetError,
+                                      net::Error::OK, 2);
+  histogram_tester.ExpectUniqueSample(metrics_internal::kServerHttpResponseCode,
+                                      net::HTTP_OK, 2);
+  histogram_tester.ExpectUniqueSample(
+      base::StringPrintf(metrics_internal::kAnnotationStatus, "Ocr"),
+      0 /* OK RPC status */, 2);
+  histogram_tester.ExpectUniqueSample(
+      base::StringPrintf(metrics_internal::kAnnotationConfidence, "Ocr"), 100,
+      2);
+  histogram_tester.ExpectUniqueSample(
+      base::StringPrintf(metrics_internal::kAnnotationEmpty, "Ocr"), false, 2);
+  histogram_tester.ExpectUniqueSample(metrics_internal::kClientResult,
+                                      ClientResult::kSucceeded, 2);
 }
 
 // Test that work is not duplicated if it is already ongoing.
@@ -849,6 +980,7 @@
   TestServerURLLoaderFactory test_url_factory(
       "https://ia-pa.googleapis.com/v1/");
   data_decoder::TestDataDecoderService test_dd_service;
+  base::HistogramTester histogram_tester;
 
   Annotator annotator(
       GURL(kTestServerUrl), std::string() /* api_key */, kThrottle,
@@ -933,6 +1065,10 @@
   EXPECT_THAT(ocr_text,
               ElementsAre("Region 1\nRegion 2", "Region 1\nRegion 2",
                           "Region 1\nRegion 2", "Region 1\nRegion 2"));
+
+  // Metrics should have been logged for a single pixel fetch.
+  histogram_tester.ExpectUniqueSample(metrics_internal::kPixelFetchSuccess,
+                                      true, 1);
 }
 
 // Test that the specified API key is sent, but only to Google-associated server
diff --git a/services/image_annotation/image_annotation_metrics.cc b/services/image_annotation/image_annotation_metrics.cc
new file mode 100644
index 0000000..03be0b94c
--- /dev/null
+++ b/services/image_annotation/image_annotation_metrics.cc
@@ -0,0 +1,127 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/image_annotation/image_annotation_metrics.h"
+
+#include <map>
+#include <string>
+
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/no_destructor.h"
+#include "base/strings/strcat.h"
+#include "base/strings/stringprintf.h"
+
+namespace image_annotation {
+
+using namespace metrics_internal;
+
+void ReportCacheHit(const bool cache_hit) {
+  UMA_HISTOGRAM_BOOLEAN(kCacheHit, cache_hit);
+}
+
+void ReportJsonParseSuccess(const bool success) {
+  UMA_HISTOGRAM_BOOLEAN(kJsonParseSuccess, success);
+}
+
+void ReportPixelFetchSuccess(const bool success) {
+  UMA_HISTOGRAM_BOOLEAN(kPixelFetchSuccess, success);
+}
+
+void ReportOcrAnnotation(const double confidence, const bool empty) {
+  const int confidence_percent = static_cast<int>(std::round(confidence * 100));
+  UMA_HISTOGRAM_PERCENTAGE(base::StringPrintf(kAnnotationConfidence, "Ocr"),
+                           confidence_percent);
+  UMA_HISTOGRAM_BOOLEAN(base::StringPrintf(kAnnotationEmpty, "Ocr"), empty);
+}
+
+void ReportDescAnnotation(const mojom::AnnotationType type,
+                          const double confidence,
+                          const bool empty) {
+  static const base::NoDestructor<std::map<mojom::AnnotationType, std::string>>
+      kTypeNames({{mojom::AnnotationType::kOcr, "Ocr"},
+                  {mojom::AnnotationType::kLabel, "Label"},
+                  {mojom::AnnotationType::kCaption, "Caption"}});
+
+  const auto lookup = kTypeNames->find(type);
+  const std::string type_name = base::StrCat(
+      {"Desc", lookup == kTypeNames->end() ? "Unknown" : lookup->second});
+
+  const int confidence_percent = static_cast<int>(std::round(confidence * 100));
+
+  // We use function variants here since our histogram name is not a "runtime
+  // constant".
+  base::UmaHistogramPercentage(
+      base::StringPrintf(kAnnotationConfidence, type_name.c_str()),
+      confidence_percent);
+  base::UmaHistogramBoolean(
+      base::StringPrintf(kAnnotationEmpty, type_name.c_str()), empty);
+
+  UMA_HISTOGRAM_ENUMERATION(kDescType, type);
+}
+
+void ReportDescFailure(const DescFailureReason reason) {
+  UMA_HISTOGRAM_ENUMERATION(kDescFailure, reason);
+}
+
+void ReportServerNetError(const int code) {
+  base::UmaHistogramSparse(kServerNetError, code);
+}
+
+void ReportServerResponseCode(const int code) {
+  base::UmaHistogramSparse(kServerHttpResponseCode, code);
+}
+
+void ReportServerLatency(const base::TimeDelta latency) {
+  // Use a custom time histogram with ~10 buckets per order of magnitude between
+  // 1ms and 30sec.
+  UMA_HISTOGRAM_CUSTOM_TIMES(kServerLatency, latency,
+                             base::TimeDelta::FromMilliseconds(1),
+                             base::TimeDelta::FromSeconds(30), 50);
+}
+
+void ReportServerRequestSizeKB(const size_t size_kb) {
+  // Use a custom memory histogram with ~10 buckets per order of magnitude
+  // between 1KB and 30MB.
+  UMA_HISTOGRAM_CUSTOM_COUNTS(kServerRequestSize, size_kb, 1, 30000, 50);
+}
+
+void ReportServerResponseSizeBytes(const size_t size_bytes) {
+  // Use a custom memory histogram with ~10 buckets per order of magnitude
+  // between 1byte and 1MB (at which point we stop downloading).
+  UMA_HISTOGRAM_CUSTOM_COUNTS(kServerResponseSize, size_bytes, 1, 1000000, 70);
+}
+
+void ReportOcrStatus(const int status) {
+  base::UmaHistogramSparse(base::StringPrintf(kAnnotationStatus, "Ocr"),
+                           status);
+}
+
+void ReportDescStatus(const int status) {
+  base::UmaHistogramSparse(base::StringPrintf(kAnnotationStatus, "Desc"),
+                           status);
+}
+
+void ReportEngineKnown(const bool known) {
+  UMA_HISTOGRAM_BOOLEAN(kEngineKnown, known);
+}
+
+void ReportSourcePixelCount(const size_t pixel_count) {
+  // Use a custom memory histogram with ~10 buckets per order of magnitude
+  // 1x1 and 8K image resolution.
+  UMA_HISTOGRAM_CUSTOM_COUNTS(kSourcePixelCount, pixel_count, 1, 7680 * 4320,
+                              80);
+}
+
+void ReportEncodedJpegSize(const size_t size_bytes) {
+  // Use a custom memory histogram with ~10 buckets per order of magnitude
+  // between 1byte and 1MB.
+  UMA_HISTOGRAM_CUSTOM_COUNTS(kEncodedJpegSize, size_bytes, 1, 1000000, 70);
+}
+
+void ReportClientResult(const ClientResult result) {
+  UMA_HISTOGRAM_ENUMERATION(kClientResult, result);
+}
+
+}  // namespace image_annotation
diff --git a/services/image_annotation/image_annotation_metrics.h b/services/image_annotation/image_annotation_metrics.h
new file mode 100644
index 0000000..6893c7a
--- /dev/null
+++ b/services/image_annotation/image_annotation_metrics.h
@@ -0,0 +1,124 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_IMAGE_ANNOTATION_IMAGE_ANNOTATION_METRICS_H_
+#define SERVICES_IMAGE_ANNOTATION_IMAGE_ANNOTATION_METRICS_H_
+
+#include "services/image_annotation/image_annotation_utils.h"
+#include "services/image_annotation/public/mojom/image_annotation.mojom.h"
+
+namespace image_annotation {
+
+// Implementation details exposed only for testing. May change without warning.
+namespace metrics_internal {
+
+// TODO(crbug.com/916420): separate out client / annotation types when we have
+//                         more use cases for the service.
+constexpr char kCacheHit[] = "ImageAnnotationService.AccessibilityV1.CacheHit";
+constexpr char kJsonParseSuccess[] =
+    "ImageAnnotationService.AccessibilityV1.JsonParseSuccess";
+constexpr char kPixelFetchSuccess[] =
+    "ImageAnnotationService.AccessibilityV1.PixelFetchSuccess";
+constexpr char kAnnotationConfidence[] =
+    "ImageAnnotationService.AccessibilityV1.%s.Confidence";
+constexpr char kAnnotationEmpty[] =
+    "ImageAnnotationService.AccessibilityV1.%s.Empty";
+constexpr char kAnnotationStatus[] =
+    "ImageAnnotationService.AccessibilityV1.%s.Status";
+constexpr char kDescType[] = "ImageAnnotationService.AccessibilityV1.DescType";
+constexpr char kDescFailure[] =
+    "ImageAnnotationService.AccessibilityV1.DescFailure";
+constexpr char kEngineKnown[] =
+    "ImageAnnotationService.AccessibilityV1.EngineKnown";
+constexpr char kServerNetError[] =
+    "ImageAnnotationService.AccessibilityV1.ServerNetError";
+constexpr char kServerHttpResponseCode[] =
+    "ImageAnnotationService.AccessibilityV1.ServerHttpResponseCode";
+constexpr char kServerLatency[] =
+    "ImageAnnotationService.AccessibilityV1.ServerLatencyMs";
+constexpr char kServerRequestSize[] =
+    "ImageAnnotationService.AccessibilityV1.ServerRequestSizeKB";
+constexpr char kServerResponseSize[] =
+    "ImageAnnotationService.AccessibilityV1.ServerResponseSizeBytes";
+constexpr char kSourcePixelCount[] =
+    "ImageAnnotationService.AccessibilityV1.SourcePixelCount";
+constexpr char kEncodedJpegSize[] =
+    "ImageAnnotationService.AccessibilityV1.EncodedJpegSizeKB";
+constexpr char kClientResult[] =
+    "ImageAnnotationService.AccessibilityV1.ClientResult";
+
+}  // namespace metrics_internal
+
+// An enum for reporting the end result of an image annotation request.
+//
+// Logged in metrics - do not reuse or reassign values.
+enum class ClientResult {
+  kUnknown = 0,
+  kSucceeded = 1,
+  kCanceled = 2,
+  kFailed = 3,
+  kShutdown = 4,
+  kMaxValue = kShutdown,
+};
+
+// Report whether or not annotations for an image were already stored in the
+// service cache.
+void ReportCacheHit(bool cache_hit);
+
+// Report whether or not JSON returned by the image annotation service was
+// successfully parsed or not.
+void ReportJsonParseSuccess(bool success);
+
+// Report whether or not pixel data is successfully fetched from a client.
+void ReportPixelFetchSuccess(bool success);
+
+// Report metadata for a successful OCR annotation.
+void ReportOcrAnnotation(double confidence, bool empty);
+
+// Report metadata for a successful description annotation.
+void ReportDescAnnotation(mojom::AnnotationType type, double score, bool empty);
+
+// Report an unsuccessful description response.
+void ReportDescFailure(DescFailureReason reason);
+
+// Report the net error from the image annotation server request. This will be
+// populated even if e.g. the server URL is incorrect.
+void ReportServerNetError(int code);
+
+// Report a HTTP response code from the image annotation server.
+void ReportServerResponseCode(int code);
+
+// Report the length of time taken for a response to be returned from the
+// server.
+void ReportServerLatency(base::TimeDelta latency);
+
+// Report the size of the request sent to the image annotation server.
+void ReportServerRequestSizeKB(size_t size_kb);
+
+// Report the size of the response returned by the image annotation server.
+void ReportServerResponseSizeBytes(size_t size_bytes);
+
+// Report the status code attached to the OCR engine response.
+void ReportOcrStatus(int status);
+
+// Report the status code attached to the description engine response.
+void ReportDescStatus(int status);
+
+// Report whether or not each engine is recognised as either OCR or description.
+void ReportEngineKnown(bool known);
+
+// Report the number of source (i.e. pre-scaling) pixels in an image sent to the
+// image annotation service.
+void ReportSourcePixelCount(size_t pixel_count);
+
+// Report the size of a single encoded image to be sent to the image annotation
+// service.
+void ReportEncodedJpegSize(size_t size_kb);
+
+// Report the result of the image annotation request for a client.
+void ReportClientResult(ClientResult result);
+
+}  // namespace image_annotation
+
+#endif  // SERVICES_IMAGE_ANNOTATION_IMAGE_ANNOTATION_METRICS_H_
diff --git a/services/image_annotation/image_annotation_utils.cc b/services/image_annotation/image_annotation_utils.cc
new file mode 100644
index 0000000..0ab09b0
--- /dev/null
+++ b/services/image_annotation/image_annotation_utils.cc
@@ -0,0 +1,26 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/image_annotation/image_annotation_utils.h"
+
+#include <map>
+
+#include "base/no_destructor.h"
+
+namespace image_annotation {
+
+DescFailureReason ParseDescFailureReason(const std::string& reason_string) {
+  const static base::NoDestructor<std::map<std::string, DescFailureReason>>
+      kFailureReasonStrings(
+          {{"UNKNOWN", DescFailureReason::kUnknown},
+           {"OTHER", DescFailureReason::kOther},
+           {"POLICY_VIOLATION", DescFailureReason::kPolicyViolation},
+           {"ADULT", DescFailureReason::kAdult}});
+
+  const auto lookup = kFailureReasonStrings->find(reason_string);
+  return lookup == kFailureReasonStrings->end() ? DescFailureReason::kUnknown
+                                                : lookup->second;
+}
+
+}  // namespace image_annotation
diff --git a/services/image_annotation/image_annotation_utils.h b/services/image_annotation/image_annotation_utils.h
new file mode 100644
index 0000000..d1ba3bac
--- /dev/null
+++ b/services/image_annotation/image_annotation_utils.h
@@ -0,0 +1,29 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_IMAGE_ANNOTATION_IMAGE_ANNOTATION_UTILS_H_
+#define SERVICES_IMAGE_ANNOTATION_IMAGE_ANNOTATION_UTILS_H_
+
+#include <string>
+
+namespace image_annotation {
+
+// An enum to describe the modes of description engine failure.
+//
+// Logged in metrics - do not reuse or reassign values.
+enum class DescFailureReason {
+  kUnknown = 0,
+  kOther = 1,
+  kPolicyViolation = 2,
+  kAdult = 3,
+  kMaxValue = kAdult,
+};
+
+// Returns the DescFailureReason enum value that the given string represents, or
+// |kUnknown| if none apply.
+DescFailureReason ParseDescFailureReason(const std::string& reason_string);
+
+}  // namespace image_annotation
+
+#endif  // SERVICES_IMAGE_ANNOTATION_IMAGE_ANNOTATION_UTILS_H_
diff --git a/services/image_annotation/public/cpp/BUILD.gn b/services/image_annotation/public/cpp/BUILD.gn
index 9756393a..a60d2773 100644
--- a/services/image_annotation/public/cpp/BUILD.gn
+++ b/services/image_annotation/public/cpp/BUILD.gn
@@ -10,6 +10,7 @@
 
   public_deps = [
     "//base",
+    "//services/image_annotation:lib",
     "//services/image_annotation/public/mojom",
     "//skia",
     "//ui/gfx/codec",
@@ -41,6 +42,7 @@
     ":cpp",
     "//base",
     "//base/test:test_support",
+    "//services/image_annotation:lib",
     "//testing/gmock",
     "//testing/gtest",
     "//ui/gfx/codec",
diff --git a/services/image_annotation/public/cpp/image_processor.cc b/services/image_annotation/public/cpp/image_processor.cc
index cdcd7d8..4b88098 100644
--- a/services/image_annotation/public/cpp/image_processor.cc
+++ b/services/image_annotation/public/cpp/image_processor.cc
@@ -8,6 +8,7 @@
 #include "base/task/post_task.h"
 #include "base/task_runner_util.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
+#include "services/image_annotation/image_annotation_metrics.h"
 #include "third_party/skia/include/core/SkCanvas.h"
 #include "ui/gfx/codec/jpeg_codec.h"
 
@@ -37,6 +38,11 @@
                                          const int max_pixels,
                                          const int jpg_quality) {
   const int num_pixels = image.width() * image.height();
+  ReportSourcePixelCount(num_pixels);
+
+  if (num_pixels == 0)
+    return {};
+
   const SkBitmap scaled_image =
       num_pixels <= max_pixels
           ? image
@@ -45,6 +51,7 @@
   std::vector<uint8_t> encoded;
   if (!gfx::JPEGCodec::Encode(scaled_image, jpg_quality, &encoded))
     encoded.clear();
+  ReportEncodedJpegSize(encoded.size());
 
   return encoded;
 }
diff --git a/services/image_annotation/public/cpp/image_processor_unittest.cc b/services/image_annotation/public/cpp/image_processor_unittest.cc
index a06bb16c..d6b6af2 100644
--- a/services/image_annotation/public/cpp/image_processor_unittest.cc
+++ b/services/image_annotation/public/cpp/image_processor_unittest.cc
@@ -8,7 +8,9 @@
 #include <limits>
 
 #include "base/bind.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_task_environment.h"
+#include "services/image_annotation/image_annotation_metrics.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/skia/src/core/SkEndian.h"
@@ -87,6 +89,7 @@
 
 TEST(ImageProcessorTest, NullImage) {
   base::test::ScopedTaskEnvironment test_task_env;
+  base::HistogramTester histogram_tester;
 
   bool empty_bytes = false;
 
@@ -101,10 +104,14 @@
   test_task_env.RunUntilIdle();
 
   EXPECT_THAT(empty_bytes, Eq(true));
+
+  histogram_tester.ExpectUniqueSample(metrics_internal::kSourcePixelCount,
+                                      0 /* sample */, 1 /* count */);
 }
 
 TEST(ImageProcessorTest, ImageContent) {
   base::test::ScopedTaskEnvironment test_task_env;
+  base::HistogramTester histogram_tester;
 
   // Create one image that doesn't need scaling and one image that does.
   const int max_dim = static_cast<int>(std::sqrt(ImageProcessor::kMaxPixels));
@@ -128,6 +135,14 @@
           base::BindOnce(&OutputImageError, &scale_error, small_orig));
   test_task_env.RunUntilIdle();
   EXPECT_THAT(scale_error, Lt(kMaxError));
+
+  histogram_tester.ExpectBucketCount(metrics_internal::kSourcePixelCount,
+                                     max_dim * max_dim /* sample */,
+                                     1 /* count */);
+  histogram_tester.ExpectBucketCount(metrics_internal::kSourcePixelCount,
+                                     4 * max_dim * max_dim /* sample */,
+                                     1 /* count */);
+  histogram_tester.ExpectTotalCount(metrics_internal::kSourcePixelCount, 2);
 }
 
 }  // namespace image_annotation
diff --git a/services/image_annotation/public/mojom/image_annotation.mojom b/services/image_annotation/public/mojom/image_annotation.mojom
index 4fa23b1..5f676384 100644
--- a/services/image_annotation/public/mojom/image_annotation.mojom
+++ b/services/image_annotation/public/mojom/image_annotation.mojom
@@ -20,10 +20,12 @@
 };
 
 // The types of annotations that can be returned.
+//
+// Logged in metrics - do not reuse or reassign values.
 enum AnnotationType {
-  kOcr,
-  kLabel,
-  kCaption,
+  kOcr = 1,
+  kLabel = 2,
+  kCaption = 3,
 };
 
 // One annotation for an image.
diff --git a/services/network/network_service.cc b/services/network/network_service.cc
index 635eba6f1..5a0f0c2 100644
--- a/services/network/network_service.cc
+++ b/services/network/network_service.cc
@@ -335,7 +335,9 @@
     file_net_log_observer_->StopObserving(nullptr /*polled_data*/,
                                           base::OnceClosure());
   }
-  trace_net_log_observer_.StopWatchForTraceStart();
+
+  if (initialized_)
+    trace_net_log_observer_.StopWatchForTraceStart();
 }
 
 void NetworkService::set_os_crypt_is_configured() {
@@ -707,8 +709,10 @@
   // The SetDnsConfigOverrides() call will will fail any in-progress DNS
   // lookups, but only if there are current config overrides (which there will
   // be if DNS over HTTPS is currently enabled).
-  host_resolver_->SetDnsConfigOverrides(net::DnsConfigOverrides());
-  host_resolver_->SetRequestContext(nullptr);
+  if (host_resolver_) {
+    host_resolver_->SetDnsConfigOverrides(net::DnsConfigOverrides());
+    host_resolver_->SetRequestContext(nullptr);
+  }
 
   DCHECK_LE(owned_network_contexts_.size(), 1u);
   owned_network_contexts_.clear();
diff --git a/services/tracing/perfetto/chrome_event_bundle_json_exporter.cc b/services/tracing/perfetto/chrome_event_bundle_json_exporter.cc
index a4b30e4..46de552 100644
--- a/services/tracing/perfetto/chrome_event_bundle_json_exporter.cc
+++ b/services/tracing/perfetto/chrome_event_bundle_json_exporter.cc
@@ -151,10 +151,9 @@
 }  // namespace
 
 ChromeEventBundleJsonExporter::ChromeEventBundleJsonExporter(
-    JSONTraceExporter::ArgumentFilterPredicate argument_filter_predicate,
+    bool filter_args,
     JSONTraceExporter::OnTraceEventJSONCallback callback)
-    : JSONTraceExporter(std::move(argument_filter_predicate),
-                        std::move(callback)) {}
+    : JSONTraceExporter(filter_args, std::move(callback)) {}
 
 void ChromeEventBundleJsonExporter::ProcessPackets(
     const std::vector<perfetto::TracePacket>& packets) {
diff --git a/services/tracing/perfetto/chrome_event_bundle_json_exporter.h b/services/tracing/perfetto/chrome_event_bundle_json_exporter.h
index db1cc3c..08e331d0 100644
--- a/services/tracing/perfetto/chrome_event_bundle_json_exporter.h
+++ b/services/tracing/perfetto/chrome_event_bundle_json_exporter.h
@@ -25,9 +25,8 @@
 // Conversion happens on-the-fly as new trace packets are received.
 class ChromeEventBundleJsonExporter : public JSONTraceExporter {
  public:
-  ChromeEventBundleJsonExporter(
-      ArgumentFilterPredicate argument_filter_predicate,
-      OnTraceEventJSONCallback callback);
+  ChromeEventBundleJsonExporter(bool filter_args,
+                                OnTraceEventJSONCallback callback);
   ~ChromeEventBundleJsonExporter() override = default;
 
  protected:
diff --git a/services/tracing/perfetto/chrome_event_bundle_json_exporter_unittest.cc b/services/tracing/perfetto/chrome_event_bundle_json_exporter_unittest.cc
index 395ee5f..cf30a719 100644
--- a/services/tracing/perfetto/chrome_event_bundle_json_exporter_unittest.cc
+++ b/services/tracing/perfetto/chrome_event_bundle_json_exporter_unittest.cc
@@ -31,36 +31,11 @@
 
 namespace tracing {
 
-namespace {
-
-bool IsArgNameWhitelisted(const char* arg_name) {
-  return base::MatchPattern(arg_name, "granular_arg_whitelisted");
-}
-
-bool IsTraceEventArgsWhitelisted(
-    const char* category_group_name,
-    const char* event_name,
-    base::trace_event::ArgumentNameFilterPredicate* arg_filter) {
-  if (base::MatchPattern(category_group_name, "toplevel") &&
-      base::MatchPattern(event_name, "*")) {
-    return true;
-  }
-  if (base::MatchPattern(category_group_name, "benchmark") &&
-      base::MatchPattern(event_name, "granularly_whitelisted")) {
-    *arg_filter = base::BindRepeating(&IsArgNameWhitelisted);
-    return true;
-  }
-
-  return false;
-}
-
-}  // namespace
-
 class ChromeEventBundleJsonExporterTest : public testing::Test {
  public:
   void SetUp() override {
     json_trace_exporter_.reset(new ChromeEventBundleJsonExporter(
-        JSONTraceExporter::ArgumentFilterPredicate(),
+        /*filter_args=*/false,
         base::BindRepeating(
             &ChromeEventBundleJsonExporterTest::OnTraceEventJSON,
             base::Unretained(this))));
@@ -69,8 +44,7 @@
   void TearDown() override { json_trace_exporter_.reset(); }
 
   void EnableArgumentFilter() {
-    json_trace_exporter_->SetArgumentFilterForTesting(
-        base::BindRepeating(&IsTraceEventArgsWhitelisted));
+    json_trace_exporter_->set_filter_args_for_testing(true);
   }
 
   void OnTraceEventJSON(const std::string& json,
@@ -696,13 +670,13 @@
     auto* new_trace_event =
         trace_packet_proto.mutable_chrome_events()->add_trace_events();
     SetTestPacketBasicData(new_trace_event);
-    new_trace_event->set_name("granularly_whitelisted");
-    new_trace_event->set_category_group_name("benchmark");
+    new_trace_event->set_name("ScopedBlockingCallTest");
+    new_trace_event->set_category_group_name("base");
     auto* new_arg1 = new_trace_event->add_args();
-    new_arg1->set_name("granular_arg_whitelisted");
+    new_arg1->set_name("file_name");
     new_arg1->set_string_value("whitelisted_value");
     auto* new_arg2 = new_trace_event->add_args();
-    new_arg2->set_name("granular_arg_blacklisted");
+    new_arg2->set_name("file_number");
     new_arg2->set_string_value("blacklisted_value");
   }
 
@@ -727,12 +701,12 @@
   {
     const auto* trace_event = trace_analyzer()->FindFirstOf(
         trace_analyzer::Query(trace_analyzer::Query::EVENT_NAME) ==
-        trace_analyzer::Query::String("granularly_whitelisted"));
+        trace_analyzer::Query::String("ScopedBlockingCallTest"));
     EXPECT_TRUE(trace_event);
     EXPECT_EQ("whitelisted_value",
-              trace_event->GetKnownArgAsString(("granular_arg_whitelisted")));
+              trace_event->GetKnownArgAsString(("file_name")));
     EXPECT_EQ("__stripped__",
-              trace_event->GetKnownArgAsString(("granular_arg_blacklisted")));
+              trace_event->GetKnownArgAsString(("file_number")));
   }
 }
 
diff --git a/services/tracing/perfetto/json_trace_exporter.cc b/services/tracing/perfetto/json_trace_exporter.cc
index 6e6b4af7..6cda65b 100644
--- a/services/tracing/perfetto/json_trace_exporter.cc
+++ b/services/tracing/perfetto/json_trace_exporter.cc
@@ -11,6 +11,7 @@
 #include "base/json/json_writer.h"
 #include "base/json/string_escape.h"
 #include "base/trace_event/trace_event.h"
+#include "services/tracing/public/cpp/trace_event_args_whitelist.h"
 #include "third_party/perfetto/include/perfetto/tracing/core/trace_packet.h"
 #include "third_party/perfetto/protos/perfetto/trace/chrome/chrome_trace_event.pbzero.h"
 #include "third_party/perfetto/protos/perfetto/trace/chrome/chrome_trace_packet.pb.h"
@@ -23,12 +24,11 @@
 
 }  // namespace
 
-JSONTraceExporter::JSONTraceExporter(
-    ArgumentFilterPredicate argument_filter_predicate,
-    OnTraceEventJSONCallback callback)
+JSONTraceExporter::JSONTraceExporter(bool filter_args,
+                                     OnTraceEventJSONCallback callback)
     : out_(callback),
       metadata_(std::make_unique<base::DictionaryValue>()),
-      argument_filter_predicate_(std::move(argument_filter_predicate)) {}
+      filter_args_(filter_args) {}
 
 JSONTraceExporter::~JSONTraceExporter() = default;
 
@@ -194,8 +194,8 @@
                                  int32_t tid) {
   DCHECK(ShouldOutputTraceEvents());
   return JSONTraceExporter::ScopedJSONTraceEventAppender(
-      AddJSONTraceEvent(), argument_filter_predicate_, name, categories, phase,
-      timestamp, pid, tid);
+      AddJSONTraceEvent(), filter_args_, name, categories, phase, timestamp,
+      pid, tid);
 }
 
 JSONTraceExporter::StringBuffer* JSONTraceExporter::AddJSONTraceEvent() {
@@ -279,16 +279,14 @@
 }
 
 JSONTraceExporter::ArgumentBuilder::ArgumentBuilder(
-    const ArgumentFilterPredicate& argument_filter_predicate,
+    bool filter_args,
     const char* name,
     const char* category_group_name,
     StringBuffer* out)
     : out_(out) {
-  JSONTraceExporter::ArgumentNameFilterPredicate argument_name_filter_predicate;
-  strip_args_ =
-      !argument_filter_predicate.is_null() &&
-      !argument_filter_predicate.Run(category_group_name, name,
-                                     &argument_name_filter_predicate_);
+  strip_args_ = filter_args &&
+                !IsTraceEventArgsWhitelisted(category_group_name, name,
+                                             &argument_name_filter_predicate_);
   *out_ += ",\"args\":";
 }
 
@@ -343,7 +341,7 @@
 
 JSONTraceExporter::ScopedJSONTraceEventAppender::ScopedJSONTraceEventAppender(
     JSONTraceExporter::StringBuffer* out,
-    JSONTraceExporter::ArgumentFilterPredicate argument_filter_predicate,
+    bool filter_args,
     const char* name,
     const char* categories,
     int32_t phase,
@@ -355,7 +353,7 @@
       out_(out),
       event_name_(name),
       category_group_name_(categories),
-      argument_filter_predicate_(std::move(argument_filter_predicate)) {
+      filter_args_(filter_args) {
   out_->AppendF("{\"pid\":%i,\"tid\":%i,\"ts\":%" PRId64
                 ",\"ph\":\"%c\",\"cat\":\"%s\",\"name\":",
                 pid, tid, timestamp, phase_, categories);
@@ -366,7 +364,7 @@
     JSONTraceExporter::ScopedJSONTraceEventAppender&& move) {
   out_ = move.out_;
   phase_ = move.phase_;
-  argument_filter_predicate_ = std::move(move.argument_filter_predicate_);
+  filter_args_ = move.filter_args_;
   // We null out the string so that the destructor knows not to append the
   // closing brace for the json.
   move.out_ = nullptr;
@@ -477,7 +475,7 @@
 JSONTraceExporter::ScopedJSONTraceEventAppender::BuildArgs() {
   DCHECK(!added_args_);
   added_args_ = true;
-  return std::make_unique<ArgumentBuilder>(
-      argument_filter_predicate_, event_name_, category_group_name_, out_);
+  return std::make_unique<ArgumentBuilder>(filter_args_, event_name_,
+                                           category_group_name_, out_);
 }
 }  // namespace tracing
diff --git a/services/tracing/perfetto/json_trace_exporter.h b/services/tracing/perfetto/json_trace_exporter.h
index 2b1878b..4aae8e0 100644
--- a/services/tracing/perfetto/json_trace_exporter.h
+++ b/services/tracing/perfetto/json_trace_exporter.h
@@ -36,29 +36,19 @@
   using ArgumentNameFilterPredicate =
       base::RepeatingCallback<bool(const char* arg_name)>;
 
-  // Given trace event name and category group name, returns a argument name
-  // filter predicate callback that can filter arguments for the given event.
-  using ArgumentFilterPredicate =
-      base::RepeatingCallback<bool(const char* category_group_name,
-                                   const char* event_name,
-                                   ArgumentNameFilterPredicate*)>;
-
   using OnTraceEventJSONCallback =
       base::RepeatingCallback<void(const std::string& json,
                                    base::DictionaryValue* metadata,
                                    bool has_more)>;
 
-  JSONTraceExporter(ArgumentFilterPredicate argument_filter_predicate,
-                    OnTraceEventJSONCallback callback);
+  JSONTraceExporter(bool filter_args, OnTraceEventJSONCallback callback);
   virtual ~JSONTraceExporter();
 
   // Called to notify the exporter of new trace packets. Will call the
   // |json_callback| passed in the constructor with the converted trace data.
   void OnTraceData(std::vector<perfetto::TracePacket> packets, bool has_more);
 
-  void SetArgumentFilterForTesting(const ArgumentFilterPredicate& predicate) {
-    argument_filter_predicate_ = predicate;
-  }
+  void set_filter_args_for_testing(bool value) { filter_args_ = value; }
 
   void set_label_filter(const std::string& label_filter) {
     label_filter_ = label_filter;
@@ -102,7 +92,7 @@
 
   class ArgumentBuilder {
    public:
-    ArgumentBuilder(const ArgumentFilterPredicate& argument_filter_predicate,
+    ArgumentBuilder(bool filter_args,
                     const char* name,
                     const char* category_group_name,
                     StringBuffer* out);
@@ -182,15 +172,14 @@
    private:
     // Subclasses of JSONTraceExporter can create a new instance by calling
     // AddTraceEvent().
-    ScopedJSONTraceEventAppender(
-        StringBuffer* out,
-        ArgumentFilterPredicate argument_filter_predicate,
-        const char* name,
-        const char* categories,
-        int32_t phase,
-        int64_t timestamp,
-        int32_t pid,
-        int32_t tid);
+    ScopedJSONTraceEventAppender(StringBuffer* out,
+                                 bool filter_args,
+                                 const char* name,
+                                 const char* categories,
+                                 int32_t phase,
+                                 int64_t timestamp,
+                                 int32_t pid,
+                                 int32_t tid);
     friend class JSONTraceExporter;
 
     char phase_;
@@ -198,7 +187,7 @@
     StringBuffer* out_;
     const char* event_name_;
     const char* category_group_name_;
-    ArgumentFilterPredicate argument_filter_predicate_;
+    bool filter_args_;
   };
 
   // Subclasses implement this to add data from |packets| to the JSON output.
@@ -248,7 +237,7 @@
   std::string label_filter_;
   std::string legacy_system_ftrace_output_;
   std::unique_ptr<base::DictionaryValue> metadata_;
-  ArgumentFilterPredicate argument_filter_predicate_;
+  bool filter_args_;
 
   DISALLOW_COPY_AND_ASSIGN(JSONTraceExporter);
 };
diff --git a/services/tracing/perfetto/json_trace_exporter_unittest.cc b/services/tracing/perfetto/json_trace_exporter_unittest.cc
index da512a135..983a251 100644
--- a/services/tracing/perfetto/json_trace_exporter_unittest.cc
+++ b/services/tracing/perfetto/json_trace_exporter_unittest.cc
@@ -84,10 +84,8 @@
 
 class TestJSONTraceExporter : public JSONTraceExporter {
  public:
-  TestJSONTraceExporter(ArgumentFilterPredicate argument_filter_predicate,
-                        OnTraceEventJSONCallback callback)
-      : JSONTraceExporter(std::move(argument_filter_predicate),
-                          std::move(callback)) {}
+  TestJSONTraceExporter(bool filter_args, OnTraceEventJSONCallback callback)
+      : JSONTraceExporter(filter_args, std::move(callback)) {}
   ~TestJSONTraceExporter() override = default;
 
   int process_packets_calls() const { return process_packets_calls_; }
@@ -188,7 +186,7 @@
  public:
   JsonTraceExporterTest()
       : json_trace_exporter_(new TestJSONTraceExporter(
-            JSONTraceExporter::ArgumentFilterPredicate(),
+            /* filter_args =*/false,
             base::BindRepeating(&JsonTraceExporterTest::OnTraceEventJSON,
                                 base::Unretained(this)))) {}
 
@@ -442,70 +440,53 @@
 
 TEST_F(JsonTraceExporterTest, TestAddArgsArgumentStripping) {
   std::vector<FakeTraceInfo> infos = {
-      FakeTraceInfo("event1", "toplevel", 'B', /* timestamp = */ 1,
+      FakeTraceInfo("event1", "base", 'B', /* timestamp = */ 1,
                     /* pid = */ 2,
                     /* tid = */ 3),
-      FakeTraceInfo("event2", "whitewashed", 'B', /* timestamp = */ 1,
+      FakeTraceInfo("whitewashed", "base", 'B', /* timestamp = */ 1,
                     /* pid = */ 2, /* tid = */ 3),
-      FakeTraceInfo("event3", "granular_whitelisted", 'B',
+      FakeTraceInfo("ScopedBlockingCallTest", "base", 'B',
                     /* timestamp = */ 1, /* pid = */ 2, /* tid = */ 3)};
   infos[0].args.emplace_back("int_one", int64_t(1));
 
   infos[1].args.emplace_back("int_two", uint64_t(2));
 
   // Third arg only has index into the string table.
-  infos[2].args.emplace_back("granular_arg_whitelisted",
-                             std::string("\"whitelisted_value\""));
-  infos[2].args.emplace_back("granular_arg_blacklisted",
+  infos[2].args.emplace_back("file_name", std::string("\"whitelisted_value\""));
+  infos[2].args.emplace_back("file_number",
                              std::string("\"blacklisted_value\""));
 
-  json_trace_exporter_->SetArgumentFilterForTesting(base::BindRepeating(
-      [](const char* category_group_name, const char* event_name,
-         base::trace_event::ArgumentNameFilterPredicate* arg_filter) {
-        if (base::MatchPattern(category_group_name, "toplevel") &&
-            base::MatchPattern(event_name, "*")) {
-          return true;
-        }
-        if (base::MatchPattern(category_group_name, "granular_whitelisted") &&
-            base::MatchPattern(event_name, "event3")) {
-          *arg_filter = base::BindRepeating([](const char* arg_name) {
-            return base::MatchPattern(arg_name, "granular_arg_whitelisted");
-          });
-          return true;
-        }
-        return false;
-      }));
+  json_trace_exporter_->set_filter_args_for_testing(true);
 
   json_trace_exporter_->SetFakeTraceEvents(infos);
   json_trace_exporter_->OnTraceData(
       std::vector<perfetto::TracePacket>(infos.size()), false);
   EXPECT_EQ(
       "{\"traceEvents\":[{\"pid\":2,\"tid\":3,\"ts\":1,\"ph\":\"B\","
-      "\"cat\":\"toplevel\",\"name\":\"event1\",\"args\":{\"int_one\":1}},\n"
-      "{\"pid\":2,\"tid\":3,\"ts\":1,\"ph\":\"B\",\"cat\":\"whitewashed\","
-      "\"name\":\"event2\",\"args\":\"__stripped__\"},\n"
+      "\"cat\":\"base\",\"name\":\"event1\",\"args\":\"__stripped__\"},\n"
+      "{\"pid\":2,\"tid\":3,\"ts\":1,\"ph\":\"B\",\"cat\":\"base\","
+      "\"name\":\"whitewashed\",\"args\":\"__stripped__\"},\n"
       "{\"pid\":2,\"tid\":3,\"ts\":1,\"ph\":\"B\","
-      "\"cat\":\"granular_whitelisted\",\"name\":\"event3\","
-      "\"args\":{\"granular_arg_whitelisted\":\"whitelisted_value\","
-      "\"granular_arg_blacklisted\":\"__stripped__\"}}]}",
+      "\"cat\":\"base\",\"name\":\"ScopedBlockingCallTest\","
+      "\"args\":{\"file_name\":\"whitelisted_value\","
+      "\"file_number\":\"__stripped__\"}}]}",
       unparsed_trace_data_);
 
   const trace_analyzer::TraceEvent* trace_event = trace_analyzer_->FindFirstOf(
       trace_analyzer::Query(trace_analyzer::Query::EVENT_NAME) ==
       trace_analyzer::Query::String("event1"));
+  EXPECT_FALSE(trace_event->HasArg("int_one"));
   EXPECT_TRUE(trace_event);
-  EXPECT_EQ(1, trace_event->GetKnownArgAsDouble("int_one"));
   trace_event = trace_analyzer_->FindFirstOf(
       trace_analyzer::Query(trace_analyzer::Query::EVENT_NAME) ==
-      trace_analyzer::Query::String("event2"));
+      trace_analyzer::Query::String("whitewashed"));
   EXPECT_FALSE(trace_event->HasArg("int_two"));
   trace_event = trace_analyzer_->FindFirstOf(
       trace_analyzer::Query(trace_analyzer::Query::EVENT_NAME) ==
-      trace_analyzer::Query::String("event3"));
+      trace_analyzer::Query::String("ScopedBlockingCallTest"));
   EXPECT_EQ("whitelisted_value",
-            trace_event->GetKnownArgAsString(("granular_arg_whitelisted")));
-  EXPECT_EQ("__stripped__",
-            trace_event->GetKnownArgAsString(("granular_arg_blacklisted")));
+            trace_event->GetKnownArgAsString(("file_name")));
+  EXPECT_EQ("__stripped__", trace_event->GetKnownArgAsString(("file_number")));
 }
 
 TEST_F(JsonTraceExporterTest, TestFtraceLegacyOutput) {
diff --git a/services/tracing/perfetto/perfetto_tracing_coordinator.cc b/services/tracing/perfetto/perfetto_tracing_coordinator.cc
index 16e4210..e4f43527 100644
--- a/services/tracing/perfetto/perfetto_tracing_coordinator.cc
+++ b/services/tracing/perfetto/perfetto_tracing_coordinator.cc
@@ -11,7 +11,6 @@
 #include "base/bind.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
-#include "base/trace_event/trace_log.h"
 #include "build/build_config.h"
 #include "mojo/public/cpp/system/data_pipe_utils.h"
 #include "services/tracing/perfetto/chrome_event_bundle_json_exporter.h"
@@ -38,10 +37,7 @@
       : tracing_over_callback_(std::move(tracing_over_callback)) {
     base::trace_event::TraceConfig chrome_trace_config_obj(config);
     json_trace_exporter_ = std::make_unique<ChromeEventBundleJsonExporter>(
-        chrome_trace_config_obj.IsArgumentFilterEnabled()
-            ? base::trace_event::TraceLog::GetInstance()
-                  ->GetArgumentFilterPredicate()
-            : JSONTraceExporter::ArgumentFilterPredicate(),
+        chrome_trace_config_obj.IsArgumentFilterEnabled(),
         base::BindRepeating(&TracingSession::OnJSONTraceEventCallback,
                             base::Unretained(this)));
     perfetto::TracingService* service =
diff --git a/services/tracing/public/cpp/BUILD.gn b/services/tracing/public/cpp/BUILD.gn
index 204812f..ac9afd76 100644
--- a/services/tracing/public/cpp/BUILD.gn
+++ b/services/tracing/public/cpp/BUILD.gn
@@ -43,6 +43,8 @@
       "perfetto/track_event_thread_local_event_sink.h",
       "trace_event_agent.cc",
       "trace_event_agent.h",
+      "trace_event_args_whitelist.cc",
+      "trace_event_args_whitelist.h",
       "trace_startup.cc",
       "trace_startup.h",
       "traced_process_impl.cc",
diff --git a/services/tracing/public/cpp/trace_event_agent.cc b/services/tracing/public/cpp/trace_event_agent.cc
index faca13d1..3da7991 100644
--- a/services/tracing/public/cpp/trace_event_agent.cc
+++ b/services/tracing/public/cpp/trace_event_agent.cc
@@ -20,6 +20,7 @@
 #include "build/build_config.h"
 #include "services/tracing/public/cpp/perfetto/producer_client.h"
 #include "services/tracing/public/cpp/perfetto/trace_event_data_source.h"
+#include "services/tracing/public/cpp/trace_event_args_whitelist.h"
 #include "services/tracing/public/cpp/tracing_features.h"
 
 namespace {
@@ -44,6 +45,17 @@
       weak_ptr_factory_(this) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
+  // These filters are used by TraceLog in the legacy tracing system and JSON
+  // exporter (only in tracing service) in perfetto bcakend.
+  if (base::trace_event::TraceLog::GetInstance()
+          ->GetArgumentFilterPredicate()
+          .is_null()) {
+    base::trace_event::TraceLog::GetInstance()->SetArgumentFilterPredicate(
+        base::BindRepeating(&IsTraceEventArgsWhitelisted));
+    base::trace_event::TraceLog::GetInstance()->SetMetadataFilterPredicate(
+        base::BindRepeating(&IsMetadataWhitelisted));
+  }
+
   base::trace_event::TraceLog::GetInstance()->AddAsyncEnabledStateObserver(
       weak_ptr_factory_.GetWeakPtr());
 
diff --git a/chrome/common/trace_event_args_whitelist.cc b/services/tracing/public/cpp/trace_event_args_whitelist.cc
similarity index 85%
rename from chrome/common/trace_event_args_whitelist.cc
rename to services/tracing/public/cpp/trace_event_args_whitelist.cc
index c2771b1..c370c774 100644
--- a/chrome/common/trace_event_args_whitelist.cc
+++ b/services/tracing/public/cpp/trace_event_args_whitelist.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/common/trace_event_args_whitelist.h"
+#include "services/tracing/public/cpp/trace_event_args_whitelist.h"
 
 #include "base/bind.h"
 #include "base/strings/pattern.h"
@@ -10,11 +10,17 @@
 #include "base/strings/string_util.h"
 #include "base/trace_event/trace_event.h"
 
+namespace tracing {
 namespace {
 
+// Each whitelist entry is used to whitelist an array arguments for a
+// single or group of trace events.
 struct WhitelistEntry {
+  // Category name of the intertested trace event.
   const char* category_name;
+  // Pattern to match the intertested trace event name.
   const char* event_name;
+  // List of patterns that match the whitelisted arguements.
   const char* const* arg_name_filter;
 };
 
@@ -37,6 +43,7 @@
     {"__metadata", "stackFrames", nullptr},
     {"__metadata", "typeNames", nullptr},
     {"base", "ScopedBlockingCall*", kScopedBlockingCallAllowedArgs},
+    {"benchmark", "TestWhitelist*", nullptr},
     {"browser", "KeyedServiceFactory::GetServiceForContext", nullptr},
     {"GPU", "*", kGPUAllowedArgs},
     {"ipc", "GpuChannelHost::Send", nullptr},
@@ -74,7 +81,8 @@
                                     "product-version",
                                     "scenario_name",
                                     "trace-config",
-                                    "user-agent"};
+                                    "user-agent",
+                                    nullptr};
 
 }  // namespace
 
@@ -106,8 +114,8 @@
                              whitelist_entry.category_name) &&
           base::MatchPattern(event_name, whitelist_entry.event_name)) {
         if (whitelist_entry.arg_name_filter) {
-          *arg_name_filter = base::Bind(&IsTraceArgumentNameWhitelisted,
-                                        whitelist_entry.arg_name_filter);
+          *arg_name_filter = base::BindRepeating(
+              &IsTraceArgumentNameWhitelisted, whitelist_entry.arg_name_filter);
         }
         return true;
       }
@@ -118,10 +126,12 @@
 }
 
 bool IsMetadataWhitelisted(const std::string& metadata_name) {
-  for (auto* key : kMetadataWhitelist) {
-    if (base::MatchPattern(metadata_name, key)) {
+  for (size_t i = 0; kMetadataWhitelist[i] != nullptr; ++i) {
+    if (base::MatchPattern(metadata_name, kMetadataWhitelist[i])) {
       return true;
     }
   }
   return false;
 }
+
+}  // namespace tracing
diff --git a/services/tracing/public/cpp/trace_event_args_whitelist.h b/services/tracing/public/cpp/trace_event_args_whitelist.h
new file mode 100644
index 0000000..e3a1945
--- /dev/null
+++ b/services/tracing/public/cpp/trace_event_args_whitelist.h
@@ -0,0 +1,32 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_TRACING_PUBLIC_CPP_TRACE_EVENT_ARGS_WHITELIST_H_
+#define SERVICES_TRACING_PUBLIC_CPP_TRACE_EVENT_ARGS_WHITELIST_H_
+
+#include <string>
+
+#include "base/component_export.h"
+#include "base/trace_event/trace_event_impl.h"
+
+namespace tracing {
+
+// TODO(ssid): This is a temporary argument filter that will be removed once
+// slow reports moves to using proto completely.
+
+// Used to filter trace event arguments against a whitelist of events that
+// have been manually vetted to not include any PII.
+bool COMPONENT_EXPORT(TRACING_CPP) IsTraceEventArgsWhitelisted(
+    const char* category_group_name,
+    const char* event_name,
+    base::trace_event::ArgumentNameFilterPredicate* arg_name_filter);
+
+// Used to filter metadata against a whitelist of metadata names that have been
+// manually vetted to not include any PII.
+bool COMPONENT_EXPORT(TRACING_CPP)
+    IsMetadataWhitelisted(const std::string& metadata_name);
+
+}  // namespace tracing
+
+#endif  // SERVICES_TRACING_PUBLIC_CPP_TRACE_EVENT_ARGS_WHITELIST_H_
diff --git a/third_party/blink/common/feature_policy/feature_policy.cc b/third_party/blink/common/feature_policy/feature_policy.cc
index eec0d5e..2b7d6f1 100644
--- a/third_party/blink/common/feature_policy/feature_policy.cc
+++ b/third_party/blink/common/feature_policy/feature_policy.cc
@@ -371,6 +371,9 @@
        {mojom::FeaturePolicyFeature::kGyroscope,
         FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf,
                             mojom::PolicyValueType::kBool)},
+       {mojom::FeaturePolicyFeature::kHid,
+        FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForSelf,
+                            mojom::PolicyValueType::kBool)},
        {mojom::FeaturePolicyFeature::kUnoptimizedImages,
         FeatureDefaultValue(FeaturePolicy::FeatureDefault::EnableForAll,
                             mojom::PolicyValueType::kBool)},
diff --git a/third_party/blink/public/mojom/dom_storage/storage_partition_service.mojom b/third_party/blink/public/mojom/dom_storage/storage_partition_service.mojom
index 6042fd0..2873860 100644
--- a/third_party/blink/public/mojom/dom_storage/storage_partition_service.mojom
+++ b/third_party/blink/public/mojom/dom_storage/storage_partition_service.mojom
@@ -13,15 +13,8 @@
 // TODO(mek): If more than just DOMStorage related interfaces get added this
 // should probably move to some other directory.
 interface StoragePartitionService {
-  // TODO(dmurph): Change this back to non-sync after the cause of the renderer
-  // hang is discovered. http://crbug.com/927534
-  [Sync]
   OpenLocalStorage(url.mojom.Origin origin,
-                   StorageArea& area) => ();
-
-  // TODO(dmurph): Change this back to non-sync after the cause of the renderer
-  // hang is discovered. http://crbug.com/927534
-  [Sync]
+                   StorageArea& area);
   OpenSessionStorage(string namespace_id,
-                     SessionStorageNamespace& session_namespace) => ();
+                     SessionStorageNamespace& session_namespace);
 };
diff --git a/third_party/blink/public/mojom/feature_policy/feature_policy.mojom b/third_party/blink/public/mojom/feature_policy/feature_policy.mojom
index f61f126..648335d0 100644
--- a/third_party/blink/public/mojom/feature_policy/feature_policy.mojom
+++ b/third_party/blink/public/mojom/feature_policy/feature_policy.mojom
@@ -120,6 +120,8 @@
   kFrobulate = 41,
   // Controls access to Serial
   kSerial = 42,
+  // Controls access to WebHID.
+  kHid = 43,
 
   // Don't change assigned numbers of any item, and don't reuse removed slots.
   // Also, run update_feature_policy_enum.py in
diff --git a/third_party/blink/public/platform/web_url_loader_client.h b/third_party/blink/public/platform/web_url_loader_client.h
index 8e66379..4344efe 100644
--- a/third_party/blink/public/platform/web_url_loader_client.h
+++ b/third_party/blink/public/platform/web_url_loader_client.h
@@ -70,8 +70,8 @@
 
   // Called to report upload progress. The bytes reported correspond to
   // the HTTP message body.
-  virtual void DidSendData(unsigned long long bytes_sent,
-                           unsigned long long total_bytes_to_be_sent) {}
+  virtual void DidSendData(uint64_t bytes_sent,
+                           uint64_t total_bytes_to_be_sent) {}
 
   // Called when response headers are received.
   virtual void DidReceiveResponse(const WebURLResponse&) {}
diff --git a/third_party/blink/public/web/web_associated_url_loader_client.h b/third_party/blink/public/web/web_associated_url_loader_client.h
index 669fca66..b9135af 100644
--- a/third_party/blink/public/web/web_associated_url_loader_client.h
+++ b/third_party/blink/public/web/web_associated_url_loader_client.h
@@ -17,10 +17,10 @@
                                   const WebURLResponse& redirect_response) {
     return true;
   }
-  virtual void DidSendData(unsigned long long bytes_sent,
-                           unsigned long long total_bytes_to_be_sent) {}
+  virtual void DidSendData(uint64_t bytes_sent,
+                           uint64_t total_bytes_to_be_sent) {}
   virtual void DidReceiveResponse(const WebURLResponse&) {}
-  virtual void DidDownloadData(unsigned long long data_length) {}
+  virtual void DidDownloadData(uint64_t data_length) {}
   virtual void DidReceiveData(const char* data, int data_length) {}
   virtual void DidReceiveCachedMetadata(const char* data, int data_length) {}
   virtual void DidFinishLoading() {}
diff --git a/third_party/blink/public/web/web_plugin_container.h b/third_party/blink/public/web/web_plugin_container.h
index 2060c5a..17f39dc6 100644
--- a/third_party/blink/public/web/web_plugin_container.h
+++ b/third_party/blink/public/web/web_plugin_container.h
@@ -69,8 +69,8 @@
   // Synchronously dispatches the progress event.
   virtual void DispatchProgressEvent(const WebString& type,
                                      bool length_computable,
-                                     unsigned long long loaded,
-                                     unsigned long long total,
+                                     uint64_t loaded,
+                                     uint64_t total,
                                      const WebString& url) = 0;
 
   // Enqueue's a task to dispatch the event.
diff --git a/third_party/blink/public/web/web_view.h b/third_party/blink/public/web/web_view.h
index 9a7645b..61ea5ce4 100644
--- a/third_party/blink/public/web/web_view.h
+++ b/third_party/blink/public/web/web_view.h
@@ -98,8 +98,8 @@
   // After this call MainFrameImpl() will return a valid frame until it is
   // detached. It receives the WebWidgetClient* that provides input/compositing
   // services for the attached main frame.
-  // This is only called for composited WebViews. Non-composited WebViews do not
-  // have a WebWidgetClient.
+  // This must be called for composited WebViews. Non-composited WebViews do not
+  // require a WebWidgetClient, but must call this in order to establish one.
   virtual void DidAttachLocalMainFrame(WebWidgetClient*) = 0;
 
   // Called to inform WebViewImpl that it has an initial remote main frame. This
@@ -111,8 +111,8 @@
   // TODO(danakj): Remove this method when WebViewImpl does not need a
   // WebWidgetClient without a local main frame. At that point it should
   // also drop the WebWidgetClient when a local main frame is detached.
-  // This is only called for composited WebViews. Non-composited WebViews do not
-  // have a WebWidgetClient.
+  // This must only be called for composited WebViews. Non-composited WebViews
+  // do not require a WebWidgetClient.
   virtual void DidAttachRemoteMainFrame(WebWidgetClient*) = 0;
 
   // Initializes the various client interfaces.
diff --git a/third_party/blink/renderer/build/scripts/templates/instrumenting_probes_impl.cc.tmpl b/third_party/blink/renderer/build/scripts/templates/instrumenting_probes_impl.cc.tmpl
index 7a8fef6..ec29570 100644
--- a/third_party/blink/renderer/build/scripts/templates/instrumenting_probes_impl.cc.tmpl
+++ b/third_party/blink/renderer/build/scripts/templates/instrumenting_probes_impl.cc.tmpl
@@ -35,7 +35,7 @@
 {{sink_class}}::~{{sink_class}}() {
   MutexLocker lock(AgentCountMutex());
 {% for agent in agents %}
-  if (has{{agent}}s() && --s_numSinksWith{{agent}} == 0)
+  if (Has{{agent}}s() && --s_numSinksWith{{agent}} == 0)
     s_existingAgents &= ~k{{agent}};
 {% endfor %}
 }
@@ -47,8 +47,8 @@
 // static
 unsigned {{sink_class}}::s_numSinksWith{{agent}} = 0;
 
-void {{sink_class}}::add{{agent}}({{class_name}}* agent) {
-  bool already_had_agent = has{{agent}}s();
+void {{sink_class}}::Add{{agent}}({{class_name}}* agent) {
+  bool already_had_agent = Has{{agent}}s();
   {{getter_name}}s_.insert(agent);
 
   if (!already_had_agent) {
@@ -60,19 +60,19 @@
   DCHECK(HasAgentsGlobal(k{{agent}}));
 }
 
-void {{sink_class}}::remove{{agent}}({{class_name}}* agent) {
-  if (!has{{agent}}s())
+void {{sink_class}}::Remove{{agent}}({{class_name}}* agent) {
+  if (!Has{{agent}}s())
     return;
 
   {{getter_name}}s_.erase(agent);
 
-  if (!has{{agent}}s()) {
+  if (!Has{{agent}}s()) {
     MutexLocker lock(AgentCountMutex());
     if (--s_numSinksWith{{agent}} == 0)
       s_existingAgents &= ~k{{agent}};
   }
 
-  if (has{{agent}}s())
+  if (Has{{agent}}s())
     DCHECK(HasAgentsGlobal(k{{agent}}));
 }
 
@@ -100,7 +100,7 @@
     return;
 {% for agent in probe.agents %}
 {% set class_name = agent | agent_name_to_class %}
-  if (probe_sink->has{{agent}}s()) {
+  if (probe_sink->Has{{agent}}s()) {
     for ({{class_name}}* agent : probe_sink->{{ agent | to_lower_case }}s())
       agent->{{agent_probe_name}}({{caller()}});
   }
diff --git a/third_party/blink/renderer/build/scripts/templates/probe_sink.h.tmpl b/third_party/blink/renderer/build/scripts/templates/probe_sink.h.tmpl
index facc4929..fc11dce 100644
--- a/third_party/blink/renderer/build/scripts/templates/probe_sink.h.tmpl
+++ b/third_party/blink/renderer/build/scripts/templates/probe_sink.h.tmpl
@@ -42,10 +42,10 @@
 {% for agent in agents %}
 {% set class_name = agent | agent_name_to_class %}
 {% set getter_name = agent | to_lower_case %}
-  bool has{{agent}}s() const { return !{{getter_name}}s_.IsEmpty(); }
+  bool Has{{agent}}s() const { return !{{getter_name}}s_.IsEmpty(); }
   const HeapListHashSet<Member<{{class_name}}>>& {{getter_name}}s() const { return {{getter_name}}s_; }
-  void add{{agent}}({{class_name}}* agent);
-  void remove{{agent}}({{class_name}}* agent);
+  void Add{{agent}}({{class_name}}* agent);
+  void Remove{{agent}}({{class_name}}* agent);
 
 {% endfor %}
   // Queries process-wide. Intended for fast-return cases only.
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index e0c5bf1..0fbe847b 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -1813,6 +1813,7 @@
     "css/threaded/text_renderer_threaded_test.cc",
     "display_lock/display_lock_budget_test.cc",
     "display_lock/display_lock_context_test.cc",
+    "display_lock/display_lock_utilities_test.cc",
     "dom/attr_test.cc",
     "dom/document_statistics_collector_test.cc",
     "dom/document_test.cc",
diff --git a/third_party/blink/renderer/core/display_lock/BUILD.gn b/third_party/blink/renderer/core/display_lock/BUILD.gn
index 467defabe..db3d6ed0 100644
--- a/third_party/blink/renderer/core/display_lock/BUILD.gn
+++ b/third_party/blink/renderer/core/display_lock/BUILD.gn
@@ -12,6 +12,8 @@
     "display_lock_budget.h",
     "display_lock_context.cc",
     "display_lock_context.h",
+    "display_lock_utilities.cc",
+    "display_lock_utilities.h",
     "strict_yielding_display_lock_budget.cc",
     "strict_yielding_display_lock_budget.h",
     "unyielding_display_lock_budget.cc",
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_context_test.cc b/third_party/blink/renderer/core/display_lock/display_lock_context_test.cc
index 4939387..b89523c 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_context_test.cc
+++ b/third_party/blink/renderer/core/display_lock/display_lock_context_test.cc
@@ -5,7 +5,9 @@
 #include "third_party/blink/renderer/core/display_lock/display_lock_context.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
 #include "third_party/blink/renderer/core/display_lock/display_lock_options.h"
+#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
 #include "third_party/blink/renderer/core/editing/finder/text_finder.h"
+#include "third_party/blink/renderer/core/editing/visible_units.h"
 #include "third_party/blink/renderer/core/frame/find_in_page.h"
 #include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
@@ -18,7 +20,10 @@
 class DisplayLockTestFindInPageClient : public mojom::blink::FindInPageClient {
  public:
   DisplayLockTestFindInPageClient()
-      : find_results_are_ready_(false), count_(-1), binding_(this) {}
+      : find_results_are_ready_(false),
+        active_index_(-1),
+        count_(-1),
+        binding_(this) {}
 
   ~DisplayLockTestFindInPageClient() override = default;
 
@@ -41,19 +46,29 @@
                       const WebRect& active_match_rect,
                       int active_match_ordinal,
                       mojom::blink::FindMatchUpdateType final_update) final {
+    active_match_rect_ = active_match_rect;
+    active_index_ = active_match_ordinal;
     find_results_are_ready_ =
         (final_update == mojom::blink::FindMatchUpdateType::kFinalUpdate);
   }
 
   bool FindResultsAreReady() const { return find_results_are_ready_; }
   int Count() const { return count_; }
+  int ActiveIndex() const { return active_index_; }
+  IntRect ActiveMatchRect() const { return active_match_rect_; }
+
   void Reset() {
     find_results_are_ready_ = false;
     count_ = -1;
+    active_index_ = -1;
+    active_match_rect_ = IntRect();
   }
 
  private:
+  IntRect active_match_rect_;
   bool find_results_are_ready_;
+  int active_index_;
+
   int count_;
   mojo::Binding<mojom::blink::FindInPageClient> binding_;
 };
@@ -342,6 +357,7 @@
   test::RunPendingTasks();
   EXPECT_TRUE(client.FindResultsAreReady());
   EXPECT_EQ(1, client.Count());
+  EXPECT_EQ(1, client.ActiveIndex());
   client.Reset();
 
   // Check if the result is correct if we update the contents.
@@ -353,7 +369,10 @@
   test::RunPendingTasks();
   EXPECT_TRUE(client.FindResultsAreReady());
   EXPECT_EQ(0, client.Count());
+  EXPECT_EQ(-1, client.ActiveIndex());
   client.Reset();
+  // Assert the container is still locked.
+  EXPECT_TRUE(element->GetDisplayLockContext()->IsLocked());
 
   // Check if the result is correct if we have non-activatable lock.
   element->SetInnerHTMLFromString(
@@ -383,16 +402,34 @@
   EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 4);
   EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 2);
 
+  EXPECT_TRUE(element->GetDisplayLockContext()->IsLocked());
+  EXPECT_TRUE(activatable->GetDisplayLockContext()->IsLocked());
+  EXPECT_TRUE(non_activatable->GetDisplayLockContext()->IsLocked());
+  EXPECT_TRUE(nested_non_activatable->GetDisplayLockContext()->IsLocked());
+
   find_in_page->Find(current_id++, "testing", find_options->Clone());
   EXPECT_FALSE(client.FindResultsAreReady());
   test::RunPendingTasks();
   EXPECT_TRUE(client.FindResultsAreReady());
   EXPECT_EQ(2, client.Count());
+  EXPECT_EQ(1, client.ActiveIndex());
   client.Reset();
 
+  UpdateAllLifecyclePhasesForTest();
+  // The locked container should be unlocked, since the match is inside that
+  // container ("testing1" inside the div).
+  EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 3);
+  EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 2);
+  EXPECT_FALSE(element->GetDisplayLockContext()->IsLocked());
+  // Since the active match isn't in any locked container, they need to be
+  // locked.
+  EXPECT_TRUE(activatable->GetDisplayLockContext()->IsLocked());
+  EXPECT_TRUE(non_activatable->GetDisplayLockContext()->IsLocked());
+  EXPECT_TRUE(nested_non_activatable->GetDisplayLockContext()->IsLocked());
+
   // Check if the result is correct if we update style.
   activatable->setAttribute("style", "contain: style layout; display: none;");
-  EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 4);
+  EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 3);
   EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 2);
 
   find_in_page->Find(current_id++, "testing", find_options->Clone());
@@ -400,6 +437,7 @@
   test::RunPendingTasks();
   EXPECT_TRUE(client.FindResultsAreReady());
   EXPECT_EQ(1, client.Count());
+  EXPECT_EQ(1, client.ActiveIndex());
   client.Reset();
 
   // Now commit all the locks and ensure we can find.
@@ -421,7 +459,162 @@
   test::RunPendingTasks();
   EXPECT_TRUE(client.FindResultsAreReady());
   EXPECT_EQ(2, client.Count());
+}
+
+// Tests find-in-page active match navigation (find next/previous).
+TEST_F(DisplayLockContextTest, FindInPageNavigateLockedMatches) {
+  ResizeAndFocus();
+  SetHtmlInnerHTML(R"HTML(
+    <style>
+    div {
+      width: 100px;
+      height: 100px;
+      contain: content;
+    }
+    </style>
+    <body>
+      <div id="container">
+        <div id="one">result</div>
+        <div id="two"><b>r</b>esult</div>
+        <div id="three">r<i>esul</i>t</div>
+      </div>
+    </body>
+  )HTML");
+
+  WebString search_text(String("result"));
+  auto* find_in_page = GetFindInPage();
+  ASSERT_TRUE(find_in_page);
+
+  DisplayLockTestFindInPageClient client;
+  client.SetFrame(LocalMainFrame());
+
+  DisplayLockOptions options;
+  options.setActivatable(true);
+
+  // Lock the children and container.
+  auto* container = GetDocument().getElementById("container");
+  auto* div_one = GetDocument().getElementById("one");
+  auto* div_two = GetDocument().getElementById("two");
+  auto* div_three = GetDocument().getElementById("three");
+  auto* script_state = ToScriptStateForMainWorld(GetDocument().GetFrame());
+  {
+    ScriptState::Scope scope(script_state);
+    container->getDisplayLockForBindings()->acquire(script_state, &options);
+    div_one->getDisplayLockForBindings()->acquire(script_state, &options);
+    div_two->getDisplayLockForBindings()->acquire(script_state, &options);
+    div_three->getDisplayLockForBindings()->acquire(script_state, &options);
+  }
+  UpdateAllLifecyclePhasesForTest();
+
+  EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 4);
+  EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 0);
+
+  auto find_options = mojom::blink::FindOptions::New();
+  find_options->run_synchronously_for_testing = true;
+  find_options->find_next = false;
+  find_options->forward = true;
+
+  int current_id = 123;
+
+  // Find should activate "result" number 1 in "#one".
+  find_in_page->Find(current_id++, search_text, find_options->Clone());
+  test::RunPendingTasks();
+  EXPECT_EQ(3, client.Count());
+  EXPECT_EQ(1, client.ActiveIndex());
+
+  EphemeralRange range_one = EphemeralRange::RangeOfContents(*div_one);
+  ASSERT_FALSE(range_one.IsNull());
+  EXPECT_EQ(ComputeTextRect(range_one), client.ActiveMatchRect());
+
+  UpdateAllLifecyclePhasesForTest();
+  // |div_one| and the container should be unlocked.
+  EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 2);
+  EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 0);
+  EXPECT_FALSE(container->GetDisplayLockContext()->IsLocked());
+  EXPECT_FALSE(div_one->GetDisplayLockContext()->IsLocked());
+  EXPECT_TRUE(div_two->GetDisplayLockContext()->IsLocked());
+  EXPECT_TRUE(div_three->GetDisplayLockContext()->IsLocked());
+
+  // Find next should activate "result" number 2 in "#two".
   client.Reset();
+  find_options->find_next = true;
+  find_in_page->Find(current_id++, search_text, find_options->Clone());
+  test::RunPendingTasks();
+  EXPECT_EQ(3, client.Count());
+  EXPECT_EQ(2, client.ActiveIndex());
+
+  EphemeralRange range_two = EphemeralRange::RangeOfContents(*div_two);
+  ASSERT_FALSE(range_one.IsNull());
+  EXPECT_EQ(ComputeTextRect(range_two), client.ActiveMatchRect());
+
+  UpdateAllLifecyclePhasesForTest();
+  // |div_two| should be unlocked.
+  EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 1);
+  EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 0);
+  EXPECT_FALSE(container->GetDisplayLockContext()->IsLocked());
+  EXPECT_FALSE(div_one->GetDisplayLockContext()->IsLocked());
+  EXPECT_FALSE(div_two->GetDisplayLockContext()->IsLocked());
+  EXPECT_TRUE(div_three->GetDisplayLockContext()->IsLocked());
+
+  // Find next should activate "result" number 3 in "#three".
+  client.Reset();
+  find_options->find_next = true;
+  find_in_page->Find(current_id++, search_text, find_options->Clone());
+  test::RunPendingTasks();
+  EXPECT_EQ(3, client.Count());
+  EXPECT_EQ(3, client.ActiveIndex());
+
+  EphemeralRange range_three = EphemeralRange::RangeOfContents(*div_three);
+  ASSERT_FALSE(range_three.IsNull());
+  EXPECT_EQ(ComputeTextRect(range_three), client.ActiveMatchRect());
+
+  UpdateAllLifecyclePhasesForTest();
+  // |div_three| should be unlocked.
+  EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 0);
+  EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 0);
+  EXPECT_FALSE(container->GetDisplayLockContext()->IsLocked());
+  EXPECT_FALSE(div_one->GetDisplayLockContext()->IsLocked());
+  EXPECT_FALSE(div_two->GetDisplayLockContext()->IsLocked());
+  EXPECT_FALSE(div_three->GetDisplayLockContext()->IsLocked());
+
+  // Lock them again, now making |div_two| non-activatable.
+  {
+    ScriptState::Scope scope(script_state);
+    div_one->getDisplayLockForBindings()->acquire(script_state, &options);
+    div_two->getDisplayLockForBindings()->acquire(script_state, nullptr);
+    div_three->getDisplayLockForBindings()->acquire(script_state, &options);
+  }
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 3);
+  EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 1);
+
+  // Find result in #one.
+  find_in_page->ClearActiveFindMatch();
+  client.Reset();
+  find_options->find_next = false;
+  find_in_page->Find(current_id++, search_text, find_options->Clone());
+  test::RunPendingTasks();
+  EXPECT_EQ(2, client.Count());
+  EXPECT_EQ(1, client.ActiveIndex());
+  EXPECT_EQ(ComputeTextRect(range_one), client.ActiveMatchRect());
+
+  // Going forward from #one would go to #three.
+  client.Reset();
+  find_options->find_next = true;
+  find_in_page->Find(current_id++, search_text, find_options->Clone());
+  test::RunPendingTasks();
+  EXPECT_EQ(2, client.Count());
+  EXPECT_EQ(2, client.ActiveIndex());
+  EXPECT_EQ(ComputeTextRect(range_three), client.ActiveMatchRect());
+
+  // Going backwards from #three would go to #one.
+  client.Reset();
+  find_options->forward = false;
+  find_in_page->Find(current_id++, search_text, find_options->Clone());
+  test::RunPendingTasks();
+  EXPECT_EQ(2, client.Count());
+  EXPECT_EQ(1, client.ActiveIndex());
+  EXPECT_EQ(ComputeTextRect(range_one), client.ActiveMatchRect());
 }
 
 TEST_F(DisplayLockContextTest, CallUpdateStyleAndLayoutAfterChange) {
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_utilities.cc b/third_party/blink/renderer/core/display_lock/display_lock_utilities.cc
new file mode 100644
index 0000000..b64b7e8
--- /dev/null
+++ b/third_party/blink/renderer/core/display_lock/display_lock_utilities.cc
@@ -0,0 +1,63 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
+
+#include "third_party/blink/renderer/core/display_lock/display_lock_context.h"
+#include "third_party/blink/renderer/core/dom/element.h"
+#include "third_party/blink/renderer/core/dom/flat_tree_traversal.h"
+#include "third_party/blink/renderer/core/dom/node.h"
+#include "third_party/blink/renderer/core/dom/text.h"
+#include "third_party/blink/renderer/core/editing/editing_boundary.h"
+#include "third_party/blink/renderer/core/editing/editing_utilities.h"
+
+namespace blink {
+
+bool DisplayLockUtilities::ActivateFindInPageMatchRangeIfNeeded(
+    const EphemeralRangeInFlatTree& range) {
+  if (!RuntimeEnabledFeatures::DisplayLockingEnabled())
+    return false;
+  DCHECK(!range.IsNull());
+  DCHECK(!range.IsCollapsed());
+  if (range.GetDocument().LockedDisplayLockCount() ==
+      range.GetDocument().ActivationBlockingDisplayLockCount())
+    return false;
+  // Find-in-page matches can't span multiple block-level elements (because the
+  // text will be broken by newlines between blocks), so first we find the
+  // block-level element which contains the match.
+  // This means we only need to traverse up from one node in the range, in this
+  // case we are traversing from the start position of the range.
+  Element* enclosing_block =
+      EnclosingBlock(range.StartPosition(), kCannotCrossEditingBoundary);
+  DCHECK(enclosing_block);
+  DCHECK_EQ(enclosing_block,
+            EnclosingBlock(range.EndPosition(), kCannotCrossEditingBoundary));
+  const HeapVector<Member<Element>>& elements_to_activate =
+      ActivatableLockedInclusiveAncestors(*enclosing_block);
+  for (Element* element : elements_to_activate) {
+    // We save the elements to a vector and go through & activate them one by
+    // one like this because the DOM structure might change due to running event
+    // handlers of the beforeactivate event.
+    element->ActivateDisplayLockIfNeeded();
+  }
+  return !elements_to_activate.IsEmpty();
+}
+
+const HeapVector<Member<Element>>
+DisplayLockUtilities::ActivatableLockedInclusiveAncestors(Element& element) {
+  HeapVector<Member<Element>> elements_to_activate;
+  for (Node& ancestor : FlatTreeTraversal::InclusiveAncestorsOf(element)) {
+    if (!ancestor.IsElementNode())
+      continue;
+    if (auto* context = ToElement(ancestor).GetDisplayLockContext()) {
+      DCHECK(context->IsActivatable());
+      if (!context->IsLocked())
+        continue;
+      elements_to_activate.push_back(&ToElement(ancestor));
+    }
+  }
+  return elements_to_activate;
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_utilities.h b/third_party/blink/renderer/core/display_lock/display_lock_utilities.h
new file mode 100644
index 0000000..0b202d6
--- /dev/null
+++ b/third_party/blink/renderer/core/display_lock/display_lock_utilities.h
@@ -0,0 +1,35 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_DISPLAY_LOCK_DISPLAY_LOCK_UTILITIES_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_DISPLAY_LOCK_DISPLAY_LOCK_UTILITIES_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+
+namespace blink {
+
+// Static utility class for display-locking related helpers.
+class CORE_EXPORT DisplayLockUtilities {
+  STATIC_ONLY(DisplayLockUtilities);
+
+ public:
+  // Activates all the nodes within a find-in-page match |range|.
+  // Returns true if at least one node gets activated.
+  // See: http://bit.ly/2RXULVi, "beforeactivate Event" part.
+  static bool ActivateFindInPageMatchRangeIfNeeded(
+      const EphemeralRangeInFlatTree& range);
+
+  // Returns activatable-locked inclusive ancestors of |element|.
+  // Note that this function will have failing DCHECKs if |element| is inside a
+  // non-activatable locked subtree (e.g. at least one ancestor is not
+  // activatable-locked).
+  static const HeapVector<Member<Element>> ActivatableLockedInclusiveAncestors(
+      Element& element);
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_DISPLAY_LOCK_DISPLAY_LOCK_UTILITIES_H_
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_utilities_test.cc b/third_party/blink/renderer/core/display_lock/display_lock_utilities_test.cc
new file mode 100644
index 0000000..98444a74
--- /dev/null
+++ b/third_party/blink/renderer/core/display_lock/display_lock_utilities_test.cc
@@ -0,0 +1,154 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
+#include "third_party/blink/renderer/core/display_lock/display_lock_context.h"
+#include "third_party/blink/renderer/core/display_lock/display_lock_options.h"
+#include "third_party/blink/renderer/core/dom/shadow_root.h"
+#include "third_party/blink/renderer/core/testing/page_test_base.h"
+
+namespace blink {
+
+class DisplayLockUtilitiesTest : public PageTestBase {
+ public:
+  void SetUp() override {
+    PageTestBase::SetUp(IntSize());
+    RuntimeEnabledFeatures::SetDisplayLockingEnabled(true);
+  }
+
+  void TearDown() override {
+    PageTestBase::TearDown();
+    RuntimeEnabledFeatures::SetDisplayLockingEnabled(false);
+  }
+};
+
+TEST_F(DisplayLockUtilitiesTest, ActivatableLockedInclusiveAncestors) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      div {
+        contain: style layout;
+      }
+    </style>
+    <div id='outer'>
+      <div id='innerA'>
+        <div id='innermost'>text_node</div>
+      </div>
+      <div id='innerB'></div>
+    </div>
+  )HTML");
+  Element& outer = *GetDocument().getElementById("outer");
+  Element& inner_a = *GetDocument().getElementById("innerA");
+  Element& inner_b = *GetDocument().getElementById("innerB");
+  Element& innermost = *GetDocument().getElementById("innermost");
+  ShadowRoot& shadow_root =
+      inner_b.AttachShadowRootInternal(ShadowRootType::kOpen);
+  shadow_root.SetInnerHTMLFromString("<div id='shadowDiv'>shadow!</div>");
+  Element& shadow_div = *shadow_root.getElementById("shadowDiv");
+
+  auto* script_state = ToScriptStateForMainWorld(GetDocument().GetFrame());
+
+  DisplayLockOptions options;
+  options.setActivatable(true);
+  // Lock outer with activatable flag.
+  {
+    ScriptState::Scope scope(script_state);
+    outer.getDisplayLockForBindings()->acquire(script_state, &options);
+  }
+
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 1);
+  EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 0);
+  // Querying from every element gives |outer|.
+  HeapVector<Member<Element>> result_for_outer =
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(outer);
+  EXPECT_EQ(result_for_outer.size(), 1u);
+  EXPECT_EQ(result_for_outer.at(0), outer);
+
+  HeapVector<Member<Element>> result_for_inner_a =
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(inner_a);
+  EXPECT_EQ(result_for_inner_a.size(), 1u);
+  EXPECT_EQ(result_for_inner_a.at(0), outer);
+
+  HeapVector<Member<Element>> result_for_innermost =
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(innermost);
+  EXPECT_EQ(result_for_innermost.size(), 1u);
+  EXPECT_EQ(result_for_innermost.at(0), outer);
+
+  HeapVector<Member<Element>> result_for_inner_b =
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(inner_b);
+  EXPECT_EQ(result_for_inner_b.size(), 1u);
+  EXPECT_EQ(result_for_inner_b.at(0), outer);
+
+  HeapVector<Member<Element>> result_for_shadow_div =
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(shadow_div);
+  EXPECT_EQ(result_for_shadow_div.size(), 1u);
+  EXPECT_EQ(result_for_shadow_div.at(0), outer);
+
+  // Lock innermost with activatable flag.
+  {
+    ScriptState::Scope scope(script_state);
+    innermost.getDisplayLockForBindings()->acquire(script_state, &options);
+  }
+
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 2);
+  EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 0);
+
+  result_for_outer =
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(outer);
+  EXPECT_EQ(result_for_outer.size(), 1u);
+  EXPECT_EQ(result_for_outer.at(0), outer);
+
+  result_for_inner_a =
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(inner_a);
+  EXPECT_EQ(result_for_inner_a.size(), 1u);
+  EXPECT_EQ(result_for_inner_a.at(0), outer);
+
+  result_for_innermost =
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(innermost);
+  EXPECT_EQ(result_for_innermost.size(), 2u);
+  EXPECT_EQ(result_for_innermost.at(0), innermost);
+  EXPECT_EQ(result_for_innermost.at(1), outer);
+
+  result_for_inner_b =
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(inner_b);
+  EXPECT_EQ(result_for_inner_b.size(), 1u);
+  EXPECT_EQ(result_for_inner_b.at(0), outer);
+
+  result_for_shadow_div =
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(shadow_div);
+  EXPECT_EQ(result_for_shadow_div.size(), 1u);
+  EXPECT_EQ(result_for_shadow_div.at(0), outer);
+
+  // Unlock everything.
+  {
+    ScriptState::Scope scope(script_state);
+    innermost.getDisplayLockForBindings()->commit(script_state);
+    outer.getDisplayLockForBindings()->commit(script_state);
+  }
+
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 0);
+  EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 0);
+
+  EXPECT_EQ(
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(outer).size(),
+      0u);
+  EXPECT_EQ(
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(inner_a).size(),
+      0u);
+  EXPECT_EQ(DisplayLockUtilities::ActivatableLockedInclusiveAncestors(innermost)
+                .size(),
+            0u);
+  EXPECT_EQ(
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(inner_b).size(),
+      0u);
+  EXPECT_EQ(
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(shadow_div)
+          .size(),
+      0u);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/events/event_path.cc b/third_party/blink/renderer/core/dom/events/event_path.cc
index 302ceab..b465753 100644
--- a/third_party/blink/renderer/core/dom/events/event_path.cc
+++ b/third_party/blink/renderer/core/dom/events/event_path.cc
@@ -180,7 +180,8 @@
   TreeScopeEventContext* tree_scope_event_context =
       GetTreeScopeEventContext(*tree_scope);
   if (!tree_scope_event_context) {
-    tree_scope_event_context = TreeScopeEventContext::Create(*tree_scope);
+    tree_scope_event_context =
+        MakeGarbageCollected<TreeScopeEventContext>(*tree_scope);
     tree_scope_event_contexts_.push_back(tree_scope_event_context);
 
     TreeScopeEventContext* parent_tree_scope_event_context =
diff --git a/third_party/blink/renderer/core/dom/events/tree_scope_event_context.cc b/third_party/blink/renderer/core/dom/events/tree_scope_event_context.cc
index 7af87ee..28fbb1ae 100644
--- a/third_party/blink/renderer/core/dom/events/tree_scope_event_context.cc
+++ b/third_party/blink/renderer/core/dom/events/tree_scope_event_context.cc
@@ -86,10 +86,6 @@
   return *touch_event_context_;
 }
 
-TreeScopeEventContext* TreeScopeEventContext::Create(TreeScope& tree_scope) {
-  return MakeGarbageCollected<TreeScopeEventContext>(tree_scope);
-}
-
 TreeScopeEventContext::TreeScopeEventContext(TreeScope& tree_scope)
     : tree_scope_(tree_scope),
       containing_closed_shadow_tree_(nullptr),
diff --git a/third_party/blink/renderer/core/dom/events/tree_scope_event_context.h b/third_party/blink/renderer/core/dom/events/tree_scope_event_context.h
index b3aaa54..18b7c455 100644
--- a/third_party/blink/renderer/core/dom/events/tree_scope_event_context.h
+++ b/third_party/blink/renderer/core/dom/events/tree_scope_event_context.h
@@ -47,9 +47,7 @@
 class CORE_EXPORT TreeScopeEventContext final
     : public GarbageCollected<TreeScopeEventContext> {
  public:
-  static TreeScopeEventContext* Create(TreeScope&);
-
-  TreeScopeEventContext(TreeScope&);
+  explicit TreeScopeEventContext(TreeScope&);
   void Trace(Visitor*);
 
   TreeScope& GetTreeScope() const { return *tree_scope_; }
diff --git a/third_party/blink/renderer/core/editing/finder/text_finder.cc b/third_party/blink/renderer/core/editing/finder/text_finder.cc
index a5a26c5..fc71904 100644
--- a/third_party/blink/renderer/core/editing/finder/text_finder.cc
+++ b/third_party/blink/renderer/core/editing/finder/text_finder.cc
@@ -37,6 +37,7 @@
 #include "third_party/blink/public/web/web_local_frame_client.h"
 #include "third_party/blink/public/web/web_view_client.h"
 #include "third_party/blink/renderer/core/accessibility/ax_object_cache_base.h"
+#include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
 #include "third_party/blink/renderer/core/dom/range.h"
 #include "third_party/blink/renderer/core/dom/shadow_root.h"
 #include "third_party/blink/renderer/core/editing/editor.h"
@@ -74,9 +75,13 @@
 
 static void ScrollToVisible(Range* match) {
   const Node& first_node = *match->FirstNode();
-  if (RuntimeEnabledFeatures::InvisibleDOMEnabled() &&
-      InvisibleDOM::ActivateRangeIfNeeded(EphemeralRangeInFlatTree(match)))
-    first_node.GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets();
+  if (RuntimeEnabledFeatures::InvisibleDOMEnabled() ||
+      RuntimeEnabledFeatures::DisplayLockingEnabled()) {
+    const EphemeralRangeInFlatTree range(match);
+    if (InvisibleDOM::ActivateRangeIfNeeded(range) ||
+        DisplayLockUtilities::ActivateFindInPageMatchRangeIfNeeded(range))
+      first_node.GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets();
+  }
   Settings* settings = first_node.GetDocument().GetSettings();
   bool smooth_find_enabled =
       settings ? settings->GetSmoothScrollForFindEnabled() : false;
diff --git a/third_party/blink/renderer/core/editing/selection_controller.cc b/third_party/blink/renderer/core/editing/selection_controller.cc
index 45d4c27b..d3a2489a 100644
--- a/third_party/blink/renderer/core/editing/selection_controller.cc
+++ b/third_party/blink/renderer/core/editing/selection_controller.cc
@@ -494,8 +494,10 @@
                 Selection().ComputeVisibleSelectionInDOMTree().Start(),
                 hit_test_result.LocalPoint(), target)
           : PositionWithAffinity();
-  VisiblePositionInFlatTree target_position = CreateVisiblePosition(
-      FromPositionInDOMTree<EditingInFlatTreeStrategy>(raw_target_position));
+  const PositionInFlatTreeWithAffinity target_position =
+      CreateVisiblePosition(
+          FromPositionInDOMTree<EditingInFlatTreeStrategy>(raw_target_position))
+          .ToPositionWithAffinity();
   // Don't modify the selection if we're not on a node.
   if (target_position.IsNull())
     return;
@@ -534,9 +536,9 @@
   }
 
   const PositionInFlatTreeWithAffinity adjusted_position =
-      AdjustPositionRespectUserSelectAll(
-          target, visible_selection.Start(), visible_selection.End(),
-          target_position.ToPositionWithAffinity());
+      AdjustPositionRespectUserSelectAll(target, visible_selection.Start(),
+                                         visible_selection.End(),
+                                         target_position);
   const SelectionInFlatTree& adjusted_selection =
       should_extend_selection
           ? ExtendSelectionAsDirectional(adjusted_position,
diff --git a/third_party/blink/renderer/core/events/touch_event.cc b/third_party/blink/renderer/core/events/touch_event.cc
index 9a38f24..a006923d 100644
--- a/third_party/blink/renderer/core/events/touch_event.cc
+++ b/third_party/blink/renderer/core/events/touch_event.cc
@@ -312,7 +312,7 @@
           break;
         case PassiveMode::kPassiveForcedDocumentLevel:
           UseCounter::Count(
-              local_frame,
+              local_dom_window->document(),
               WebFeature::
                   kTouchEventPreventedForcedDocumentPassiveNoTouchAction);
           break;
diff --git a/third_party/blink/renderer/core/exported/web_associated_url_loader_impl.cc b/third_party/blink/renderer/core/exported/web_associated_url_loader_impl.cc
index e8c4139d..c378ef7 100644
--- a/third_party/blink/renderer/core/exported/web_associated_url_loader_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_associated_url_loader_impl.cc
@@ -107,10 +107,10 @@
                 scoped_refptr<base::SingleThreadTaskRunner>);
 
   // ThreadableLoaderClient
-  void DidSendData(unsigned long long /*bytesSent*/,
-                   unsigned long long /*totalBytesToBeSent*/) override;
+  void DidSendData(uint64_t /*bytesSent*/,
+                   uint64_t /*totalBytesToBeSent*/) override;
   void DidReceiveResponse(unsigned long, const ResourceResponse&) override;
-  void DidDownloadData(unsigned long long /*dataLength*/) override;
+  void DidDownloadData(uint64_t /*dataLength*/) override;
   void DidReceiveData(const char*, unsigned /*dataLength*/) override;
   void DidReceiveCachedMetadata(const char*, int /*dataLength*/) override;
   void DidFinishLoading(unsigned long /*identifier*/) override;
@@ -188,8 +188,8 @@
 }
 
 void WebAssociatedURLLoaderImpl::ClientAdapter::DidSendData(
-    unsigned long long bytes_sent,
-    unsigned long long total_bytes_to_be_sent) {
+    uint64_t bytes_sent,
+    uint64_t total_bytes_to_be_sent) {
   if (!client_)
     return;
 
@@ -236,7 +236,7 @@
 }
 
 void WebAssociatedURLLoaderImpl::ClientAdapter::DidDownloadData(
-    unsigned long long data_length) {
+    uint64_t data_length) {
   if (!client_)
     return;
 
diff --git a/third_party/blink/renderer/core/exported/web_associated_url_loader_impl_test.cc b/third_party/blink/renderer/core/exported/web_associated_url_loader_impl_test.cc
index d13d9c5..c7fa93a4 100644
--- a/third_party/blink/renderer/core/exported/web_associated_url_loader_impl_test.cc
+++ b/third_party/blink/renderer/core/exported/web_associated_url_loader_impl_test.cc
@@ -132,8 +132,8 @@
     return true;
   }
 
-  void DidSendData(unsigned long long bytes_sent,
-                   unsigned long long total_bytes_to_be_sent) override {
+  void DidSendData(uint64_t bytes_sent,
+                   uint64_t total_bytes_to_be_sent) override {
     did_send_data_ = true;
   }
 
@@ -145,7 +145,7 @@
     EXPECT_EQ(expected_response_.HttpStatusCode(), response.HttpStatusCode());
   }
 
-  void DidDownloadData(unsigned long long data_length) override {
+  void DidDownloadData(uint64_t data_length) override {
     did_download_data_ = true;
   }
 
diff --git a/third_party/blink/renderer/core/exported/web_frame_test.cc b/third_party/blink/renderer/core/exported/web_frame_test.cc
index 85d653bd..6444501 100644
--- a/third_party/blink/renderer/core/exported/web_frame_test.cc
+++ b/third_party/blink/renderer/core/exported/web_frame_test.cc
@@ -12843,7 +12843,7 @@
                                                    ->Loader()
                                                    .GetDocumentLoader()
                                                    ->GetNavigationTimingInfo();
-  EXPECT_EQ(navigation_timing_info->TransferSize(), 34);
+  EXPECT_EQ(navigation_timing_info->TransferSize(), static_cast<uint64_t>(34));
 }
 
 TEST_F(WebFrameSimTest, EnterFullscreenResetScrollAndScaleState) {
diff --git a/third_party/blink/renderer/core/exported/web_performance.cc b/third_party/blink/renderer/core/exported/web_performance.cc
index e84272159..618c4ca 100644
--- a/third_party/blink/renderer/core/exported/web_performance.cc
+++ b/third_party/blink/renderer/core/exported/web_performance.cc
@@ -34,7 +34,7 @@
 
 namespace blink {
 
-static double MillisecondsToSeconds(unsigned long long milliseconds) {
+static double MillisecondsToSeconds(uint64_t milliseconds) {
   return static_cast<double>(milliseconds / 1000.0);
 }
 
diff --git a/third_party/blink/renderer/core/exported/web_plugin_container_impl.cc b/third_party/blink/renderer/core/exported/web_plugin_container_impl.cc
index 2360367..96c455fc 100644
--- a/third_party/blink/renderer/core/exported/web_plugin_container_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_plugin_container_impl.cc
@@ -466,8 +466,8 @@
 
 void WebPluginContainerImpl::DispatchProgressEvent(const WebString& type,
                                                    bool length_computable,
-                                                   unsigned long long loaded,
-                                                   unsigned long long total,
+                                                   uint64_t loaded,
+                                                   uint64_t total,
                                                    const WebString& url) {
   ProgressEvent* event;
   if (url.IsEmpty()) {
diff --git a/third_party/blink/renderer/core/exported/web_plugin_container_impl.h b/third_party/blink/renderer/core/exported/web_plugin_container_impl.h
index e0a9970..c69946e 100644
--- a/third_party/blink/renderer/core/exported/web_plugin_container_impl.h
+++ b/third_party/blink/renderer/core/exported/web_plugin_container_impl.h
@@ -125,8 +125,8 @@
   WebDocument GetDocument() override;
   void DispatchProgressEvent(const WebString& type,
                              bool length_computable,
-                             unsigned long long loaded,
-                             unsigned long long total,
+                             uint64_t loaded,
+                             uint64_t total,
                              const WebString& url) override;
   void EnqueueMessageEvent(const WebDOMMessageEvent&) override;
   void Invalidate() override;
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 bcfc925..8dd8d5e 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -1979,12 +1979,13 @@
 }
 
 void WebViewImpl::DidAttachLocalMainFrame(WebWidgetClient* client) {
-  DCHECK(does_composite_);
   DCHECK(MainFrameImpl());
 
   AsWidget().client = client;
-  // When attaching a local main frame, set up any state on the compositor.
-  AsWidget().client->SetBackgroundColor(BackgroundColor());
+  if (does_composite_) {
+    // When attaching a local main frame, set up any state on the compositor.
+    AsWidget().client->SetBackgroundColor(BackgroundColor());
+  }
 }
 
 void WebViewImpl::DidAttachRemoteMainFrame(WebWidgetClient* client) {
@@ -3322,10 +3323,14 @@
   if (!MainFrameImpl())
     return;
 
-  if (has_scrolled_by_wheel)
-    UseCounter::Count(MainFrameImpl()->GetFrame(), WebFeature::kScrollByWheel);
-  if (has_scrolled_by_touch)
-    UseCounter::Count(MainFrameImpl()->GetFrame(), WebFeature::kScrollByTouch);
+  if (has_scrolled_by_wheel) {
+    UseCounter::Count(MainFrameImpl()->GetDocument(),
+                      WebFeature::kScrollByWheel);
+  }
+  if (has_scrolled_by_touch) {
+    UseCounter::Count(MainFrameImpl()->GetDocument(),
+                      WebFeature::kScrollByTouch);
+  }
 }
 
 Node* WebViewImpl::FindNodeFromScrollableCompositorElementId(
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy.cc b/third_party/blink/renderer/core/feature_policy/feature_policy.cc
index 4b596e2..e7ea0550 100644
--- a/third_party/blink/renderer/core/feature_policy/feature_policy.cc
+++ b/third_party/blink/renderer/core/feature_policy/feature_policy.cc
@@ -354,6 +354,9 @@
       default_feature_name_map.Set("top-navigation",
                                    mojom::FeaturePolicyFeature::kTopNavigation);
     }
+    if (RuntimeEnabledFeatures::WebHIDEnabled()) {
+      default_feature_name_map.Set("hid", mojom::FeaturePolicyFeature::kHid);
+    }
     if (RuntimeEnabledFeatures::PaymentRequestEnabled()) {
       default_feature_name_map.Set("payment",
                                    mojom::FeaturePolicyFeature::kPayment);
diff --git a/third_party/blink/renderer/core/frame/ad_tracker.cc b/third_party/blink/renderer/core/frame/ad_tracker.cc
index 58f7644..3bfe8ef 100644
--- a/third_party/blink/renderer/core/frame/ad_tracker.cc
+++ b/third_party/blink/renderer/core/frame/ad_tracker.cc
@@ -33,7 +33,7 @@
 }  // namespace
 
 AdTracker::AdTracker(LocalFrame* local_root) : local_root_(local_root) {
-  local_root_->GetProbeSink()->addAdTracker(this);
+  local_root_->GetProbeSink()->AddAdTracker(this);
 }
 
 AdTracker::~AdTracker() {
@@ -43,7 +43,7 @@
 void AdTracker::Shutdown() {
   if (!local_root_)
     return;
-  local_root_->GetProbeSink()->removeAdTracker(this);
+  local_root_->GetProbeSink()->RemoveAdTracker(this);
   local_root_ = nullptr;
 }
 
diff --git a/third_party/blink/renderer/core/frame/dom_window.cc b/third_party/blink/renderer/core/frame/dom_window.cc
index a911157..3b7d287 100644
--- a/third_party/blink/renderer/core/frame/dom_window.cc
+++ b/third_party/blink/renderer/core/frame/dom_window.cc
@@ -132,7 +132,7 @@
                             const WindowPostMessageOptions* options,
                             ExceptionState& exception_state) {
   LocalDOMWindow* incumbent_window = IncumbentDOMWindow(isolate);
-  UseCounter::Count(incumbent_window->GetFrame(),
+  UseCounter::Count(incumbent_window->document(),
                     WebFeature::kWindowPostMessage);
 
   Transferables transferables;
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.cc b/third_party/blink/renderer/core/frame/local_dom_window.cc
index ede46e4..de8d95f 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window.cc
+++ b/third_party/blink/renderer/core/frame/local_dom_window.cc
@@ -600,7 +600,7 @@
           sender, RedirectStatus::kNoRedirect,
           SecurityViolationReportingPolicy::kSuppressReporting)) {
     UseCounter::Count(
-        GetFrame(), WebFeature::kPostMessageIncomingWouldBeBlockedByConnectSrc);
+        document(), WebFeature::kPostMessageIncomingWouldBeBlockedByConnectSrc);
   }
 
   DispatchEvent(*event);
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
index 487d19c..04c8ce4 100644
--- a/third_party/blink/renderer/core/frame/local_frame.cc
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -364,7 +364,7 @@
   }
   idleness_detector_->Shutdown();
   if (inspector_trace_events_)
-    probe_sink_->removeInspectorTraceEvents(inspector_trace_events_);
+    probe_sink_->RemoveInspectorTraceEvents(inspector_trace_events_);
   inspector_task_runner_->Dispose();
 
   PluginScriptForbiddenScope forbid_plugin_destructor_scripting;
@@ -983,7 +983,7 @@
     probe_sink_ = MakeGarbageCollected<CoreProbeSink>();
     performance_monitor_ = MakeGarbageCollected<PerformanceMonitor>(this);
     inspector_trace_events_ = MakeGarbageCollected<InspectorTraceEvents>();
-    probe_sink_->addInspectorTraceEvents(inspector_trace_events_);
+    probe_sink_->AddInspectorTraceEvents(inspector_trace_events_);
     if (RuntimeEnabledFeatures::AdTaggingEnabled()) {
       ad_tracker_ = MakeGarbageCollected<AdTracker>(this);
     }
diff --git a/third_party/blink/renderer/core/frame/performance_monitor.cc b/third_party/blink/renderer/core/frame/performance_monitor.cc
index a03c81e..83dda120 100644
--- a/third_party/blink/renderer/core/frame/performance_monitor.cc
+++ b/third_party/blink/renderer/core/frame/performance_monitor.cc
@@ -76,7 +76,7 @@
     : local_root_(local_root) {
   std::fill(std::begin(thresholds_), std::end(thresholds_), base::TimeDelta());
   Thread::Current()->AddTaskTimeObserver(this);
-  local_root_->GetProbeSink()->addPerformanceMonitor(this);
+  local_root_->GetProbeSink()->AddPerformanceMonitor(this);
 }
 
 PerformanceMonitor::~PerformanceMonitor() {
@@ -108,7 +108,7 @@
   subscriptions_.clear();
   UpdateInstrumentation();
   Thread::Current()->RemoveTaskTimeObserver(this);
-  local_root_->GetProbeSink()->removePerformanceMonitor(this);
+  local_root_->GetProbeSink()->RemovePerformanceMonitor(this);
   local_root_ = nullptr;
 }
 
diff --git a/third_party/blink/renderer/core/frame/use_counter.cc b/third_party/blink/renderer/core/frame/use_counter.cc
index 35044a5..043255e 100644
--- a/third_party/blink/renderer/core/frame/use_counter.cc
+++ b/third_party/blink/renderer/core/frame/use_counter.cc
@@ -1375,16 +1375,6 @@
   }
 }
 
-// TODO(loonybear): Replace Count(LocalFrame*) by Count(DocumentLoader*).
-void UseCounter::Count(const LocalFrame* frame, WebFeature feature) {
-  if (!frame)
-    return;
-  DocumentLoader* loader = frame->GetDocument()
-                               ? frame->GetDocument()->Loader()
-                               : frame->Loader().GetProvisionalDocumentLoader();
-  UseCounter::Count(loader, feature);
-}
-
 void UseCounter::Count(DocumentLoader* loader, WebFeature feature) {
   if (!loader)
     return;
@@ -1441,7 +1431,7 @@
                                         WebFeature feature) {
   LocalFrame* frame = document.GetFrame();
   if (frame && frame->IsCrossOriginSubframe())
-    Count(frame, feature);
+    Count(document, feature);
 }
 
 void UseCounter::ReportAndTraceMeasurementByCSSSampleId(int sample_id,
diff --git a/third_party/blink/renderer/core/frame/use_counter.h b/third_party/blink/renderer/core/frame/use_counter.h
index b42fee3..4d92c9c 100644
--- a/third_party/blink/renderer/core/frame/use_counter.h
+++ b/third_party/blink/renderer/core/frame/use_counter.h
@@ -100,10 +100,6 @@
   };
 
   // "count" sets the bit for this feature to 1. Repeated calls are ignored.
-  // Count(const LocalFrame*) is being deprecated since during a navigation it
-  // may pick the wrong DocumentLoader (will guess and avoid using the
-  // provisional document loader when both loaders are present).
-  static void Count(const LocalFrame*, WebFeature);
   static void Count(DocumentLoader*, WebFeature);
   static void Count(const Document&, WebFeature);
   static void Count(ExecutionContext*, WebFeature);
diff --git a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
index 8b1136a..6d5991f 100644
--- a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
+++ b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
@@ -333,8 +333,7 @@
     // but we need to subscribe to it or else dispatching frames will not work.
     frame_dispatcher_->SetNeedsBeginFrame(GetPage()->IsPageVisible());
 
-    UseCounter::Count(GetDocument().GetFrame(),
-                      WebFeature::kHTMLCanvasElementLowLatency);
+    UseCounter::Count(GetDocument(), WebFeature::kHTMLCanvasElementLowLatency);
   }
 
   SetNeedsCompositingUpdate();
diff --git a/third_party/blink/renderer/core/html/forms/html_form_element.cc b/third_party/blink/renderer/core/html/forms/html_form_element.cc
index 913a642..87acaea 100644
--- a/third_party/blink/renderer/core/html/forms/html_form_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_form_element.cc
@@ -498,8 +498,7 @@
   UseCounter::Count(GetDocument(), WebFeature::kFormsSubmitted);
   if (MixedContentChecker::IsMixedFormAction(GetDocument().GetFrame(),
                                              submission->Action())) {
-    UseCounter::Count(GetDocument().GetFrame(),
-                      WebFeature::kMixedContentFormsSubmitted);
+    UseCounter::Count(GetDocument(), WebFeature::kMixedContentFormsSubmitted);
   }
   if (FastHasAttribute(kDisabledAttr)) {
     UseCounter::Count(GetDocument(),
@@ -567,8 +566,7 @@
                                        : attributes_.Action());
     if (MixedContentChecker::IsMixedFormAction(GetDocument().GetFrame(),
                                                action_url)) {
-      UseCounter::Count(GetDocument().GetFrame(),
-                        WebFeature::kMixedContentFormPresent);
+      UseCounter::Count(GetDocument(), WebFeature::kMixedContentFormPresent);
     }
   } else if (name == kTargetAttr) {
     attributes_.SetTarget(params.new_value);
diff --git a/third_party/blink/renderer/core/input/pointer_event_manager.cc b/third_party/blink/renderer/core/input/pointer_event_manager.cc
index 4e4ebce..3b9b732 100644
--- a/third_party/blink/renderer/core/input/pointer_event_manager.cc
+++ b/third_party/blink/renderer/core/input/pointer_event_manager.cc
@@ -894,10 +894,10 @@
 
 void PointerEventManager::SetPointerCapture(PointerId pointer_id,
                                             Element* target) {
-  UseCounter::Count(frame_, WebFeature::kPointerEventSetCapture);
+  UseCounter::Count(frame_->GetDocument(), WebFeature::kPointerEventSetCapture);
   if (pointer_event_factory_.IsActiveButtonsState(pointer_id)) {
     if (pointer_id != dispatching_pointer_id_) {
-      UseCounter::Count(frame_,
+      UseCounter::Count(frame_->GetDocument(),
                         WebFeature::kPointerEventSetCaptureOutsideDispatch);
     }
     pending_pointer_capture_target_.Set(pointer_id, target);
diff --git a/third_party/blink/renderer/core/input/touch_event_manager.cc b/third_party/blink/renderer/core/input/touch_event_manager.cc
index e3f6c92a..e9536645 100644
--- a/third_party/blink/renderer/core/input/touch_event_manager.cc
+++ b/third_party/blink/renderer/core/input/touch_event_manager.cc
@@ -79,12 +79,6 @@
   }
 }
 
-enum TouchEventDispatchResultType {
-  kUnhandledTouches,  // Unhandled touch events.
-  kHandledTouches,    // Handled touch events.
-  kTouchEventDispatchResultTypeMax,
-};
-
 WebTouchPoint::State TouchPointStateFromPointerEventType(
     WebInputEvent::Type type,
     bool stale) {
diff --git a/third_party/blink/renderer/core/inspector/devtools_session.cc b/third_party/blink/renderer/core/inspector/devtools_session.cc
index b631c20..13d2868 100644
--- a/third_party/blink/renderer/core/inspector/devtools_session.cc
+++ b/third_party/blink/renderer/core/inspector/devtools_session.cc
@@ -147,7 +147,7 @@
   bool restore = !!session_state_.ReattachState();
   v8_session_state_.InitFrom(&session_state_);
   agent_->client_->AttachSession(this, restore);
-  agent_->probe_sink_->addDevToolsSession(this);
+  agent_->probe_sink_->AddDevToolsSession(this);
   if (restore) {
     for (wtf_size_t i = 0; i < agents_.size(); i++)
       agents_[i]->Restore();
@@ -183,7 +183,7 @@
   host_ptr_.reset();
   io_session_->DeleteSoon();
   io_session_ = nullptr;
-  agent_->probe_sink_->removeDevToolsSession(this);
+  agent_->probe_sink_->RemoveDevToolsSession(this);
   inspector_backend_dispatcher_.reset();
   for (wtf_size_t i = agents_.size(); i > 0; i--)
     agents_[i - 1]->Dispose();
diff --git a/third_party/blink/renderer/core/inspector/inspector_animation_agent.cc b/third_party/blink/renderer/core/inspector/inspector_animation_agent.cc
index 389a998..2c651e2 100644
--- a/third_party/blink/renderer/core/inspector/inspector_animation_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_animation_agent.cc
@@ -55,7 +55,7 @@
 
 Response InspectorAnimationAgent::enable() {
   enabled_.Set(true);
-  instrumenting_agents_->addInspectorAnimationAgent(this);
+  instrumenting_agents_->AddInspectorAnimationAgent(this);
   return Response::OK();
 }
 
@@ -64,7 +64,7 @@
   for (const auto& clone : id_to_animation_clone_.Values())
     clone->cancel();
   enabled_.Clear();
-  instrumenting_agents_->removeInspectorAnimationAgent(this);
+  instrumenting_agents_->RemoveInspectorAnimationAgent(this);
   id_to_animation_.clear();
   id_to_animation_type_.clear();
   id_to_animation_clone_.clear();
diff --git a/third_party/blink/renderer/core/inspector/inspector_application_cache_agent.cc b/third_party/blink/renderer/core/inspector/inspector_application_cache_agent.cc
index 91d89a1..e32b7d8 100644
--- a/third_party/blink/renderer/core/inspector/inspector_application_cache_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_application_cache_agent.cc
@@ -45,7 +45,7 @@
 
 void InspectorApplicationCacheAgent::InnerEnable() {
   enabled_.Set(true);
-  instrumenting_agents_->addInspectorApplicationCacheAgent(this);
+  instrumenting_agents_->AddInspectorApplicationCacheAgent(this);
   GetFrontend()->networkStateUpdated(GetNetworkStateNotifier().OnLine());
 }
 
@@ -62,7 +62,7 @@
 
 Response InspectorApplicationCacheAgent::disable() {
   enabled_.Clear();
-  instrumenting_agents_->removeInspectorApplicationCacheAgent(this);
+  instrumenting_agents_->RemoveInspectorApplicationCacheAgent(this);
   return Response::OK();
 }
 
diff --git a/third_party/blink/renderer/core/inspector/inspector_css_agent.cc b/third_party/blink/renderer/core/inspector/inspector_css_agent.cc
index d560255f..8c86a55 100644
--- a/third_party/blink/renderer/core/inspector/inspector_css_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_css_agent.cc
@@ -728,7 +728,7 @@
 }
 
 void InspectorCSSAgent::CompleteEnabled() {
-  instrumenting_agents_->addInspectorCSSAgent(this);
+  instrumenting_agents_->AddInspectorCSSAgent(this);
   dom_agent_->SetDOMListener(this);
   HeapVector<Member<Document>> documents = dom_agent_->Documents();
   for (Document* document : documents)
@@ -739,7 +739,7 @@
 Response InspectorCSSAgent::disable() {
   Reset();
   dom_agent_->SetDOMListener(nullptr);
-  instrumenting_agents_->removeInspectorCSSAgent(this);
+  instrumenting_agents_->RemoveInspectorCSSAgent(this);
   enable_completed_ = false;
   enable_requested_.Set(false);
   resource_content_loader_->Cancel(resource_content_loader_client_id_);
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc b/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
index 3a7cbd6..4553a958 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
@@ -453,7 +453,7 @@
   history_ = MakeGarbageCollected<InspectorHistory>();
   dom_editor_ = MakeGarbageCollected<DOMEditor>(history_.Get());
   document_ = inspected_frames_->Root()->GetDocument();
-  instrumenting_agents_->addInspectorDOMAgent(this);
+  instrumenting_agents_->AddInspectorDOMAgent(this);
 }
 
 Response InspectorDOMAgent::enable() {
@@ -466,7 +466,7 @@
   if (!enabled_.Get())
     return Response::Error("DOM agent hasn't been enabled");
   enabled_.Clear();
-  instrumenting_agents_->removeInspectorDOMAgent(this);
+  instrumenting_agents_->RemoveInspectorDOMAgent(this);
   history_.Clear();
   dom_editor_.Clear();
   SetDocument(nullptr);
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_debugger_agent.cc b/third_party/blink/renderer/core/inspector/inspector_dom_debugger_agent.cc
index 224365e..046f179 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_debugger_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_debugger_agent.cc
@@ -224,7 +224,7 @@
 
 void InspectorDOMDebuggerAgent::Restore() {
   if (enabled_.Get())
-    instrumenting_agents_->addInspectorDOMDebuggerAgent(this);
+    instrumenting_agents_->AddInspectorDOMDebuggerAgent(this);
 }
 
 Response InspectorDOMDebuggerAgent::setEventListenerBreakpoint(
@@ -756,9 +756,9 @@
 void InspectorDOMDebuggerAgent::SetEnabled(bool enabled) {
   enabled_.Set(enabled);
   if (enabled)
-    instrumenting_agents_->addInspectorDOMDebuggerAgent(this);
+    instrumenting_agents_->AddInspectorDOMDebuggerAgent(this);
   else
-    instrumenting_agents_->removeInspectorDOMDebuggerAgent(this);
+    instrumenting_agents_->RemoveInspectorDOMDebuggerAgent(this);
 }
 
 void InspectorDOMDebuggerAgent::DidCommitLoadForLocalFrame(LocalFrame*) {
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.cc b/third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.cc
index fdb6884..e44c61b 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_snapshot_agent.cc
@@ -212,7 +212,7 @@
 void InspectorDOMSnapshotAgent::EnableAndReset() {
   enabled_.Set(true);
   origin_url_map_ = std::make_unique<OriginUrlMap>();
-  instrumenting_agents_->addInspectorDOMSnapshotAgent(this);
+  instrumenting_agents_->AddInspectorDOMSnapshotAgent(this);
 }
 
 void InspectorDOMSnapshotAgent::Restore() {
@@ -231,7 +231,7 @@
     return Response::Error("DOM snapshot agent hasn't been enabled.");
   enabled_.Clear();
   origin_url_map_.reset();
-  instrumenting_agents_->removeInspectorDOMSnapshotAgent(this);
+  instrumenting_agents_->RemoveInspectorDOMSnapshotAgent(this);
   return Response::OK();
 }
 
diff --git a/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc b/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc
index a3a6006..22622ecf 100644
--- a/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc
@@ -128,7 +128,7 @@
 
 Response InspectorEmulationAgent::disable() {
   if (enabled_)
-    instrumenting_agents_->removeInspectorEmulationAgent(this);
+    instrumenting_agents_->RemoveInspectorEmulationAgent(this);
   setUserAgentOverride(String(), protocol::Maybe<String>(),
                        protocol::Maybe<String>());
   if (!web_local_frame_)
@@ -464,7 +464,7 @@
   if (enabled_)
     return;
   enabled_ = true;
-  instrumenting_agents_->addInspectorEmulationAgent(this);
+  instrumenting_agents_->AddInspectorEmulationAgent(this);
 }
 
 Response InspectorEmulationAgent::AssertPage() {
diff --git a/third_party/blink/renderer/core/inspector/inspector_layer_tree_agent.cc b/third_party/blink/renderer/core/inspector/inspector_layer_tree_agent.cc
index 7e20a15..82eab79 100644
--- a/third_party/blink/renderer/core/inspector/inspector_layer_tree_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_layer_tree_agent.cc
@@ -266,7 +266,7 @@
 }
 
 Response InspectorLayerTreeAgent::enable() {
-  instrumenting_agents_->addInspectorLayerTreeAgent(this);
+  instrumenting_agents_->AddInspectorLayerTreeAgent(this);
   Document* document = inspected_frames_->Root()->GetDocument();
   if (!document)
     return Response::Error("The root frame doesn't have document");
@@ -283,7 +283,7 @@
 }
 
 Response InspectorLayerTreeAgent::disable() {
-  instrumenting_agents_->removeInspectorLayerTreeAgent(this);
+  instrumenting_agents_->RemoveInspectorLayerTreeAgent(this);
   snapshot_by_id_.clear();
   return Response::OK();
 }
diff --git a/third_party/blink/renderer/core/inspector/inspector_log_agent.cc b/third_party/blink/renderer/core/inspector/inspector_log_agent.cc
index dd50a9c82d..59b5969 100644
--- a/third_party/blink/renderer/core/inspector/inspector_log_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_log_agent.cc
@@ -165,7 +165,7 @@
 }
 
 void InspectorLogAgent::InnerEnable() {
-  instrumenting_agents_->addInspectorLogAgent(this);
+  instrumenting_agents_->AddInspectorLogAgent(this);
   if (storage_->ExpiredCount()) {
     std::unique_ptr<protocol::Log::LogEntry> expired =
         protocol::Log::LogEntry::create()
@@ -195,7 +195,7 @@
     return Response::OK();
   enabled_.Clear();
   stopViolationsReport();
-  instrumenting_agents_->removeInspectorLogAgent(this);
+  instrumenting_agents_->RemoveInspectorLogAgent(this);
   return Response::OK();
 }
 
diff --git a/third_party/blink/renderer/core/inspector/inspector_network_agent.cc b/third_party/blink/renderer/core/inspector/inspector_network_agent.cc
index fbb9011..a385019 100644
--- a/third_party/blink/renderer/core/inspector/inspector_network_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_network_agent.cc
@@ -1328,12 +1328,12 @@
   enabled_.Set(true);
   resources_data_->SetResourcesDataSizeLimits(total_buffer_size_.Get(),
                                               resource_buffer_size_.Get());
-  instrumenting_agents_->addInspectorNetworkAgent(this);
+  instrumenting_agents_->AddInspectorNetworkAgent(this);
 }
 
 Response InspectorNetworkAgent::disable() {
   DCHECK(!pending_request_type_);
-  instrumenting_agents_->removeInspectorNetworkAgent(this);
+  instrumenting_agents_->RemoveInspectorNetworkAgent(this);
   agent_state_.ClearAllFields();
   resources_data_->Clear();
   return Response::OK();
diff --git a/third_party/blink/renderer/core/inspector/inspector_page_agent.cc b/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
index a9b55eb..58f53f4 100644
--- a/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
@@ -526,7 +526,7 @@
 
 Response InspectorPageAgent::enable() {
   enabled_.Set(true);
-  instrumenting_agents_->addInspectorPageAgent(this);
+  instrumenting_agents_->AddInspectorPageAgent(this);
   return Response::OK();
 }
 
@@ -534,7 +534,7 @@
   agent_state_.ClearAllFields();
   script_to_evaluate_on_load_once_ = String();
   pending_script_to_evaluate_on_load_once_ = String();
-  instrumenting_agents_->removeInspectorPageAgent(this);
+  instrumenting_agents_->RemoveInspectorPageAgent(this);
   inspector_resource_content_loader_->Cancel(
       resource_content_loader_client_id_);
 
diff --git a/third_party/blink/renderer/core/inspector/inspector_performance_agent.cc b/third_party/blink/renderer/core/inspector/inspector_performance_agent.cc
index 2db4f6ef..898e5c9 100644
--- a/third_party/blink/renderer/core/inspector/inspector_performance_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_performance_agent.cc
@@ -49,7 +49,7 @@
 }
 
 void InspectorPerformanceAgent::InnerEnable() {
-  instrumenting_agents_->addInspectorPerformanceAgent(this);
+  instrumenting_agents_->AddInspectorPerformanceAgent(this);
   Thread::Current()->AddTaskTimeObserver(this);
   layout_start_ticks_ = TimeTicks();
   recalc_style_start_ticks_ = TimeTicks();
@@ -71,7 +71,7 @@
   if (!enabled_.Get())
     return Response::OK();
   enabled_.Clear();
-  instrumenting_agents_->removeInspectorPerformanceAgent(this);
+  instrumenting_agents_->RemoveInspectorPerformanceAgent(this);
   Thread::Current()->RemoveTaskTimeObserver(this);
   return Response::OK();
 }
diff --git a/third_party/blink/renderer/core/inspector/worker_inspector_controller.cc b/third_party/blink/renderer/core/inspector/worker_inspector_controller.cc
index 179d0296..57a3170a 100644
--- a/third_party/blink/renderer/core/inspector/worker_inspector_controller.cc
+++ b/third_party/blink/renderer/core/inspector/worker_inspector_controller.cc
@@ -74,7 +74,7 @@
       thread_(thread),
       inspected_frames_(nullptr),
       probe_sink_(MakeGarbageCollected<CoreProbeSink>()) {
-  probe_sink_->addInspectorTraceEvents(
+  probe_sink_->AddInspectorTraceEvents(
       MakeGarbageCollected<InspectorTraceEvents>());
   if (auto* scope = DynamicTo<WorkerGlobalScope>(thread->GlobalScope())) {
     worker_devtools_token_ = devtools_params->devtools_worker_token;
diff --git a/third_party/blink/renderer/core/layout/layout_embedded_content.cc b/third_party/blink/renderer/core/layout/layout_embedded_content.cc
index 2c76c27..35d6c4a 100644
--- a/third_party/blink/renderer/core/layout/layout_embedded_content.cc
+++ b/third_party/blink/renderer/core/layout/layout_embedded_content.cc
@@ -172,7 +172,13 @@
 
   // A hit test can never hit an off-screen element; only off-screen iframes are
   // throttled; therefore, hit tests can skip descending into throttled iframes.
-  if (local_frame_view->ShouldThrottleRendering()) {
+  // We also check the document lifecycle state because the frame may have been
+  // throttled at the time lifecycle updates happened, in which case it will not
+  // be up-to-date and we can't hit test it.
+  if (local_frame_view->ShouldThrottleRendering() ||
+      !local_frame_view->GetFrame().GetDocument() ||
+      local_frame_view->GetFrame().GetDocument()->Lifecycle().GetState() <
+          DocumentLifecycle::kCompositingClean) {
     return NodeAtPointOverEmbeddedContentView(result, location_in_container,
                                               accumulated_offset, action);
   }
diff --git a/third_party/blink/renderer/core/layout/layout_image.cc b/third_party/blink/renderer/core/layout/layout_image.cc
index 90f8ef2b..223d194 100644
--- a/third_party/blink/renderer/core/layout/layout_image.cc
+++ b/third_party/blink/renderer/core/layout/layout_image.cc
@@ -161,8 +161,7 @@
   // https://github.com/igrigorik/http-client-hints/blob/master/draft-grigorik-http-client-hints-01.txt#L255
   if (image_resource_->CachedImage() &&
       image_resource_->CachedImage()->HasDevicePixelRatioHeaderValue()) {
-    UseCounter::Count(&(View()->GetFrameView()->GetFrame()),
-                      WebFeature::kClientHintsContentDPR);
+    UseCounter::Count(GetDocument(), WebFeature::kClientHintsContentDPR);
     image_device_pixel_ratio_ =
         1 / image_resource_->CachedImage()->DevicePixelRatioHeaderValue();
   }
diff --git a/third_party/blink/renderer/core/loader/threadable_loader.cc b/third_party/blink/renderer/core/loader/threadable_loader.cc
index 36fde33..0c7fa5d 100644
--- a/third_party/blink/renderer/core/loader/threadable_loader.cc
+++ b/third_party/blink/renderer/core/loader/threadable_loader.cc
@@ -730,8 +730,8 @@
 }
 
 void ThreadableLoader::DataSent(Resource* resource,
-                                unsigned long long bytes_sent,
-                                unsigned long long total_bytes_to_be_sent) {
+                                uint64_t bytes_sent,
+                                uint64_t total_bytes_to_be_sent) {
   DCHECK(client_);
   DCHECK_EQ(resource, GetResource());
   DCHECK(async_);
@@ -741,7 +741,7 @@
 }
 
 void ThreadableLoader::DataDownloaded(Resource* resource,
-                                      unsigned long long data_length) {
+                                      uint64_t data_length) {
   DCHECK(client_);
   DCHECK_EQ(resource, GetResource());
   DCHECK(actual_request_.IsNull());
diff --git a/third_party/blink/renderer/core/loader/threadable_loader.h b/third_party/blink/renderer/core/loader/threadable_loader.h
index fce09dd..d52f555 100644
--- a/third_party/blink/renderer/core/loader/threadable_loader.h
+++ b/third_party/blink/renderer/core/loader/threadable_loader.h
@@ -151,8 +151,8 @@
 
   // RawResourceClient
   void DataSent(Resource*,
-                unsigned long long bytes_sent,
-                unsigned long long total_bytes_to_be_sent) override;
+                uint64_t bytes_sent,
+                uint64_t total_bytes_to_be_sent) override;
   void ResponseReceived(Resource*, const ResourceResponse&) override;
   void ResponseBodyReceived(Resource*, BytesConsumer& body) override;
   void SetSerializedCachedMetadata(Resource*, const uint8_t*, size_t) override;
@@ -161,7 +161,7 @@
                         const ResourceRequest&,
                         const ResourceResponse&) override;
   void RedirectBlocked() override;
-  void DataDownloaded(Resource*, unsigned long long) override;
+  void DataDownloaded(Resource*, uint64_t) override;
   void DidReceiveResourceTiming(Resource*, const ResourceTimingInfo&) override;
   void DidDownloadToBlob(Resource*, scoped_refptr<BlobDataHandle>) override;
 
diff --git a/third_party/blink/renderer/core/loader/threadable_loader_client.h b/third_party/blink/renderer/core/loader/threadable_loader_client.h
index 302c4125..9bbaaeb 100644
--- a/third_party/blink/renderer/core/loader/threadable_loader_client.h
+++ b/third_party/blink/renderer/core/loader/threadable_loader_client.h
@@ -48,8 +48,8 @@
 
 class CORE_EXPORT ThreadableLoaderClient : public GarbageCollectedMixin {
  public:
-  virtual void DidSendData(unsigned long long /*bytesSent*/,
-                           unsigned long long /*totalBytesToBeSent*/) {}
+  virtual void DidSendData(uint64_t /*bytesSent*/,
+                           uint64_t /*totalBytesToBeSent*/) {}
   // Note that redirects for redirect modes kError and kManual are still
   // notified here. A client must return false in such cases.
   virtual bool WillFollowRedirect(const KURL& new_url,
@@ -66,7 +66,7 @@
   virtual void DidFailRedirectCheck() {}
   virtual void DidReceiveResourceTiming(const ResourceTimingInfo&) {}
 
-  virtual void DidDownloadData(unsigned long long /*dataLength*/) {}
+  virtual void DidDownloadData(uint64_t /*dataLength*/) {}
   // Called for requests that had DownloadToBlob set to true. Can be called with
   // null if creating the blob failed for some reason (but the download itself
   // otherwise succeeded). Could also not be called at all if the downloaded
diff --git a/third_party/blink/renderer/core/loader/threadable_loader_test.cc b/third_party/blink/renderer/core/loader/threadable_loader_test.cc
index a082c596..d44c210 100644
--- a/third_party/blink/renderer/core/loader/threadable_loader_test.cc
+++ b/third_party/blink/renderer/core/loader/threadable_loader_test.cc
@@ -59,7 +59,7 @@
 
  public:
   MockThreadableLoaderClient() = default;
-  MOCK_METHOD2(DidSendData, void(unsigned long long, unsigned long long));
+  MOCK_METHOD2(DidSendData, void(uint64_t, uint64_t));
   MOCK_METHOD2(DidReceiveResponse,
                void(unsigned long, const ResourceResponse&));
   MOCK_METHOD2(DidReceiveData, void(const char*, unsigned));
@@ -68,7 +68,7 @@
   MOCK_METHOD1(DidFail, void(const ResourceError&));
   MOCK_METHOD0(DidFailRedirectCheck, void());
   MOCK_METHOD1(DidReceiveResourceTiming, void(const ResourceTimingInfo&));
-  MOCK_METHOD1(DidDownloadData, void(unsigned long long));
+  MOCK_METHOD1(DidDownloadData, void(uint64_t));
 };
 
 bool IsCancellation(const ResourceError& error) {
diff --git a/third_party/blink/renderer/core/page/autoscroll_controller.cc b/third_party/blink/renderer/core/page/autoscroll_controller.cc
index 4889cff..1991582e37 100644
--- a/third_party/blink/renderer/core/page/autoscroll_controller.cc
+++ b/third_party/blink/renderer/core/page/autoscroll_controller.cc
@@ -201,7 +201,7 @@
     autoscroll_type_ = kAutoscrollForDragAndDrop;
     autoscroll_layout_object_ = scrollable;
     drag_and_drop_autoscroll_start_time_ = event_time;
-    UseCounter::Count(autoscroll_layout_object_->GetFrame(),
+    UseCounter::Count(drop_target_node->GetDocument(),
                       WebFeature::kDragAndDropScrollStart);
     ScheduleMainThreadAnimation();
   } else if (autoscroll_layout_object_ != scrollable) {
diff --git a/third_party/blink/renderer/core/page/create_window.cc b/third_party/blink/renderer/core/page/create_window.cc
index 6ee6ab7..5ad345e1 100644
--- a/third_party/blink/renderer/core/page/create_window.cc
+++ b/third_party/blink/renderer/core/page/create_window.cc
@@ -373,7 +373,8 @@
           ? KURL(g_empty_string)
           : entered_window_frame.GetDocument()->CompleteURL(url_string);
   if (!completed_url.IsEmpty() && !completed_url.IsValid()) {
-    UseCounter::Count(active_frame, WebFeature::kWindowOpenWithInvalidURL);
+    UseCounter::Count(incumbent_window.document(),
+                      WebFeature::kWindowOpenWithInvalidURL);
     exception_state.ThrowDOMException(
         DOMExceptionCode::kSyntaxError,
         "Unable to open a window with invalid URL '" +
diff --git a/third_party/blink/renderer/core/page/frame_tree.cc b/third_party/blink/renderer/core/page/frame_tree.cc
index c3a32ac..b69a663 100644
--- a/third_party/blink/renderer/core/page/frame_tree.cc
+++ b/third_party/blink/renderer/core/page/frame_tree.cc
@@ -59,7 +59,8 @@
                         WebFeature::kCrossOriginMainFrameNulledNameAccessed);
       if (!name_.IsEmpty()) {
         UseCounter::Count(
-            frame, WebFeature::kCrossOriginMainFrameNulledNonEmptyNameAccessed);
+            frame->GetDocument(),
+            WebFeature::kCrossOriginMainFrameNulledNonEmptyNameAccessed);
       }
     }
   }
diff --git a/third_party/blink/renderer/core/page/scrolling/root_scroller_controller.cc b/third_party/blink/renderer/core/page/scrolling/root_scroller_controller.cc
index fb04079..bc8b072 100644
--- a/third_party/blink/renderer/core/page/scrolling/root_scroller_controller.cc
+++ b/third_party/blink/renderer/core/page/scrolling/root_scroller_controller.cc
@@ -165,8 +165,7 @@
       new_effective_root_scroller = root_scroller_;
     } else if (implicit_root_scroller_) {
       new_effective_root_scroller = implicit_root_scroller_;
-      UseCounter::Count(document_->GetFrame(),
-                        WebFeature::kActivatedImplicitRootScroller);
+      UseCounter::Count(document_, WebFeature::kActivatedImplicitRootScroller);
     }
   }
 
diff --git a/third_party/blink/renderer/core/script/pending_script.cc b/third_party/blink/renderer/core/script/pending_script.cc
index c0738b1..b346fae9 100644
--- a/third_party/blink/renderer/core/script/pending_script.cc
+++ b/third_party/blink/renderer/core/script/pending_script.cc
@@ -149,7 +149,8 @@
 
     // TODO(hiroshige): Also do not execute classic scripts.
     // https://crbug.com/721914
-    UseCounter::Count(frame, WebFeature::kEvaluateScriptMovedBetweenDocuments);
+    UseCounter::Count(context_document,
+                      WebFeature::kEvaluateScriptMovedBetweenDocuments);
   }
 
   Script* script = GetSource(document_url);
diff --git a/third_party/blink/renderer/core/timing/performance_navigation_timing.cc b/third_party/blink/renderer/core/timing/performance_navigation_timing.cc
index 1227356f..909fd410 100644
--- a/third_party/blink/renderer/core/timing/performance_navigation_timing.cc
+++ b/third_party/blink/renderer/core/timing/performance_navigation_timing.cc
@@ -84,15 +84,15 @@
   return resource_timing_info_->FinalResponse().ConnectionReused();
 }
 
-unsigned long long PerformanceNavigationTiming::GetTransferSize() const {
+uint64_t PerformanceNavigationTiming::GetTransferSize() const {
   return resource_timing_info_->TransferSize();
 }
 
-unsigned long long PerformanceNavigationTiming::GetEncodedBodySize() const {
+uint64_t PerformanceNavigationTiming::GetEncodedBodySize() const {
   return resource_timing_info_->FinalResponse().EncodedBodyLength();
 }
 
-unsigned long long PerformanceNavigationTiming::GetDecodedBodySize() const {
+uint64_t PerformanceNavigationTiming::GetDecodedBodySize() const {
   return resource_timing_info_->FinalResponse().DecodedBodyLength();
 }
 
diff --git a/third_party/blink/renderer/core/timing/performance_navigation_timing.h b/third_party/blink/renderer/core/timing/performance_navigation_timing.h
index 4b2516f0..5889f9e 100644
--- a/third_party/blink/renderer/core/timing/performance_navigation_timing.h
+++ b/third_party/blink/renderer/core/timing/performance_navigation_timing.h
@@ -78,9 +78,9 @@
   ResourceLoadTiming* GetResourceLoadTiming() const override;
   bool AllowTimingDetails() const override;
   bool DidReuseConnection() const override;
-  unsigned long long GetTransferSize() const override;
-  unsigned long long GetEncodedBodySize() const override;
-  unsigned long long GetDecodedBodySize() const override;
+  uint64_t GetTransferSize() const override;
+  uint64_t GetEncodedBodySize() const override;
+  uint64_t GetDecodedBodySize() const override;
 
   bool GetAllowRedirectDetails() const;
 
diff --git a/third_party/blink/renderer/core/timing/performance_resource_timing.cc b/third_party/blink/renderer/core/timing/performance_resource_timing.cc
index ee5bb1bd..6ab95ff 100644
--- a/third_party/blink/renderer/core/timing/performance_resource_timing.cc
+++ b/third_party/blink/renderer/core/timing/performance_resource_timing.cc
@@ -109,15 +109,15 @@
   return did_reuse_connection_;
 }
 
-unsigned long long PerformanceResourceTiming::GetTransferSize() const {
+uint64_t PerformanceResourceTiming::GetTransferSize() const {
   return transfer_size_;
 }
 
-unsigned long long PerformanceResourceTiming::GetEncodedBodySize() const {
+uint64_t PerformanceResourceTiming::GetEncodedBodySize() const {
   return encoded_body_size_;
 }
 
-unsigned long long PerformanceResourceTiming::GetDecodedBodySize() const {
+uint64_t PerformanceResourceTiming::GetDecodedBodySize() const {
   return decoded_body_size_;
 }
 
@@ -310,21 +310,21 @@
       time_origin_, response_end_, allow_negative_value_);
 }
 
-unsigned long long PerformanceResourceTiming::transferSize() const {
+uint64_t PerformanceResourceTiming::transferSize() const {
   if (!AllowTimingDetails())
     return 0;
 
   return GetTransferSize();
 }
 
-unsigned long long PerformanceResourceTiming::encodedBodySize() const {
+uint64_t PerformanceResourceTiming::encodedBodySize() const {
   if (!AllowTimingDetails())
     return 0;
 
   return GetEncodedBodySize();
 }
 
-unsigned long long PerformanceResourceTiming::decodedBodySize() const {
+uint64_t PerformanceResourceTiming::decodedBodySize() const {
   if (!AllowTimingDetails())
     return 0;
 
diff --git a/third_party/blink/renderer/core/timing/performance_resource_timing.h b/third_party/blink/renderer/core/timing/performance_resource_timing.h
index 25da939..c9738ab 100644
--- a/third_party/blink/renderer/core/timing/performance_resource_timing.h
+++ b/third_party/blink/renderer/core/timing/performance_resource_timing.h
@@ -83,9 +83,9 @@
   DOMHighResTimeStamp requestStart() const;
   DOMHighResTimeStamp responseStart() const;
   virtual DOMHighResTimeStamp responseEnd() const;
-  unsigned long long transferSize() const;
-  unsigned long long encodedBodySize() const;
-  unsigned long long decodedBodySize() const;
+  uint64_t transferSize() const;
+  uint64_t encodedBodySize() const;
+  uint64_t decodedBodySize() const;
   const HeapVector<Member<PerformanceServerTiming>>& serverTiming() const;
 
   void Trace(blink::Visitor*) override;
@@ -108,9 +108,9 @@
   virtual ResourceLoadTiming* GetResourceLoadTiming() const;
   virtual bool AllowTimingDetails() const;
   virtual bool DidReuseConnection() const;
-  virtual unsigned long long GetTransferSize() const;
-  virtual unsigned long long GetEncodedBodySize() const;
-  virtual unsigned long long GetDecodedBodySize() const;
+  virtual uint64_t GetTransferSize() const;
+  virtual uint64_t GetEncodedBodySize() const;
+  virtual uint64_t GetDecodedBodySize() const;
 
   AtomicString initiator_type_;
   AtomicString alpn_negotiated_protocol_;
@@ -119,9 +119,9 @@
   scoped_refptr<ResourceLoadTiming> timing_;
   TimeTicks last_redirect_end_time_;
   TimeTicks response_end_;
-  unsigned long long transfer_size_;
-  unsigned long long encoded_body_size_;
-  unsigned long long decoded_body_size_;
+  uint64_t transfer_size_;
+  uint64_t encoded_body_size_;
+  uint64_t decoded_body_size_;
   bool did_reuse_connection_;
   bool allow_timing_details_;
   bool allow_redirect_details_;
diff --git a/third_party/blink/renderer/core/timing/performance_timing.cc b/third_party/blink/renderer/core/timing/performance_timing.cc
index d334819..89fdd690 100644
--- a/third_party/blink/renderer/core/timing/performance_timing.cc
+++ b/third_party/blink/renderer/core/timing/performance_timing.cc
@@ -52,18 +52,18 @@
 // Legacy support for NT1(https://www.w3.org/TR/navigation-timing/).
 namespace blink {
 
-static unsigned long long ToIntegerMilliseconds(TimeDelta duration) {
+static uint64_t ToIntegerMilliseconds(TimeDelta duration) {
   // TODO(npm): add histograms to understand when/why |duration| is sometimes
   // negative.
   double clamped_seconds =
       Performance::ClampTimeResolution(duration.InSecondsF());
-  return static_cast<unsigned long long>(clamped_seconds * 1000.0);
+  return static_cast<uint64_t>(clamped_seconds * 1000.0);
 }
 
 PerformanceTiming::PerformanceTiming(LocalFrame* frame)
     : DOMWindowClient(frame) {}
 
-unsigned long long PerformanceTiming::navigationStart() const {
+uint64_t PerformanceTiming::navigationStart() const {
   DocumentLoadTiming* timing = GetDocumentLoadTiming();
   if (!timing)
     return 0;
@@ -71,7 +71,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->NavigationStart());
 }
 
-unsigned long long PerformanceTiming::inputStart() const {
+uint64_t PerformanceTiming::inputStart() const {
   DocumentLoadTiming* timing = GetDocumentLoadTiming();
   if (!timing)
     return 0;
@@ -79,7 +79,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->InputStart());
 }
 
-unsigned long long PerformanceTiming::unloadEventStart() const {
+uint64_t PerformanceTiming::unloadEventStart() const {
   DocumentLoadTiming* timing = GetDocumentLoadTiming();
   if (!timing)
     return 0;
@@ -91,7 +91,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->UnloadEventStart());
 }
 
-unsigned long long PerformanceTiming::unloadEventEnd() const {
+uint64_t PerformanceTiming::unloadEventEnd() const {
   DocumentLoadTiming* timing = GetDocumentLoadTiming();
   if (!timing)
     return 0;
@@ -103,7 +103,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->UnloadEventEnd());
 }
 
-unsigned long long PerformanceTiming::redirectStart() const {
+uint64_t PerformanceTiming::redirectStart() const {
   DocumentLoadTiming* timing = GetDocumentLoadTiming();
   if (!timing)
     return 0;
@@ -114,7 +114,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->RedirectStart());
 }
 
-unsigned long long PerformanceTiming::redirectEnd() const {
+uint64_t PerformanceTiming::redirectEnd() const {
   DocumentLoadTiming* timing = GetDocumentLoadTiming();
   if (!timing)
     return 0;
@@ -125,7 +125,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->RedirectEnd());
 }
 
-unsigned long long PerformanceTiming::fetchStart() const {
+uint64_t PerformanceTiming::fetchStart() const {
   DocumentLoadTiming* timing = GetDocumentLoadTiming();
   if (!timing)
     return 0;
@@ -133,7 +133,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->FetchStart());
 }
 
-unsigned long long PerformanceTiming::domainLookupStart() const {
+uint64_t PerformanceTiming::domainLookupStart() const {
   ResourceLoadTiming* timing = GetResourceLoadTiming();
   if (!timing)
     return fetchStart();
@@ -148,7 +148,7 @@
   return MonotonicTimeToIntegerMilliseconds(dns_start);
 }
 
-unsigned long long PerformanceTiming::domainLookupEnd() const {
+uint64_t PerformanceTiming::domainLookupEnd() const {
   ResourceLoadTiming* timing = GetResourceLoadTiming();
   if (!timing)
     return domainLookupStart();
@@ -163,7 +163,7 @@
   return MonotonicTimeToIntegerMilliseconds(dns_end);
 }
 
-unsigned long long PerformanceTiming::connectStart() const {
+uint64_t PerformanceTiming::connectStart() const {
   DocumentLoader* loader = GetDocumentLoader();
   if (!loader)
     return domainLookupEnd();
@@ -188,7 +188,7 @@
   return MonotonicTimeToIntegerMilliseconds(connect_start);
 }
 
-unsigned long long PerformanceTiming::connectEnd() const {
+uint64_t PerformanceTiming::connectEnd() const {
   DocumentLoader* loader = GetDocumentLoader();
   if (!loader)
     return connectStart();
@@ -207,7 +207,7 @@
   return MonotonicTimeToIntegerMilliseconds(connect_end);
 }
 
-unsigned long long PerformanceTiming::secureConnectionStart() const {
+uint64_t PerformanceTiming::secureConnectionStart() const {
   DocumentLoader* loader = GetDocumentLoader();
   if (!loader)
     return 0;
@@ -223,7 +223,7 @@
   return MonotonicTimeToIntegerMilliseconds(ssl_start);
 }
 
-unsigned long long PerformanceTiming::requestStart() const {
+uint64_t PerformanceTiming::requestStart() const {
   ResourceLoadTiming* timing = GetResourceLoadTiming();
 
   if (!timing || timing->SendStart().is_null())
@@ -232,7 +232,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->SendStart());
 }
 
-unsigned long long PerformanceTiming::responseStart() const {
+uint64_t PerformanceTiming::responseStart() const {
   ResourceLoadTiming* timing = GetResourceLoadTiming();
   if (!timing)
     return requestStart();
@@ -246,7 +246,7 @@
   return MonotonicTimeToIntegerMilliseconds(response_start);
 }
 
-unsigned long long PerformanceTiming::responseEnd() const {
+uint64_t PerformanceTiming::responseEnd() const {
   DocumentLoadTiming* timing = GetDocumentLoadTiming();
   if (!timing)
     return 0;
@@ -254,7 +254,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->ResponseEnd());
 }
 
-unsigned long long PerformanceTiming::domLoading() const {
+uint64_t PerformanceTiming::domLoading() const {
   const DocumentTiming* timing = GetDocumentTiming();
   if (!timing)
     return fetchStart();
@@ -262,7 +262,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->DomLoading());
 }
 
-unsigned long long PerformanceTiming::domInteractive() const {
+uint64_t PerformanceTiming::domInteractive() const {
   const DocumentTiming* timing = GetDocumentTiming();
   if (!timing)
     return 0;
@@ -270,7 +270,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->DomInteractive());
 }
 
-unsigned long long PerformanceTiming::domContentLoadedEventStart() const {
+uint64_t PerformanceTiming::domContentLoadedEventStart() const {
   const DocumentTiming* timing = GetDocumentTiming();
   if (!timing)
     return 0;
@@ -279,7 +279,7 @@
       timing->DomContentLoadedEventStart());
 }
 
-unsigned long long PerformanceTiming::domContentLoadedEventEnd() const {
+uint64_t PerformanceTiming::domContentLoadedEventEnd() const {
   const DocumentTiming* timing = GetDocumentTiming();
   if (!timing)
     return 0;
@@ -287,7 +287,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->DomContentLoadedEventEnd());
 }
 
-unsigned long long PerformanceTiming::domComplete() const {
+uint64_t PerformanceTiming::domComplete() const {
   const DocumentTiming* timing = GetDocumentTiming();
   if (!timing)
     return 0;
@@ -295,7 +295,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->DomComplete());
 }
 
-unsigned long long PerformanceTiming::loadEventStart() const {
+uint64_t PerformanceTiming::loadEventStart() const {
   DocumentLoadTiming* timing = GetDocumentLoadTiming();
   if (!timing)
     return 0;
@@ -303,7 +303,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->LoadEventStart());
 }
 
-unsigned long long PerformanceTiming::loadEventEnd() const {
+uint64_t PerformanceTiming::loadEventEnd() const {
   DocumentLoadTiming* timing = GetDocumentLoadTiming();
   if (!timing)
     return 0;
@@ -311,7 +311,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->LoadEventEnd());
 }
 
-unsigned long long PerformanceTiming::FirstLayout() const {
+uint64_t PerformanceTiming::FirstLayout() const {
   const DocumentTiming* timing = GetDocumentTiming();
   if (!timing)
     return 0;
@@ -319,7 +319,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->FirstLayout());
 }
 
-unsigned long long PerformanceTiming::FirstPaint() const {
+uint64_t PerformanceTiming::FirstPaint() const {
   const PaintTiming* timing = GetPaintTiming();
   if (!timing)
     return 0;
@@ -327,7 +327,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->FirstPaint());
 }
 
-unsigned long long PerformanceTiming::FirstImagePaint() const {
+uint64_t PerformanceTiming::FirstImagePaint() const {
   const PaintTiming* timing = GetPaintTiming();
   if (!timing)
     return 0;
@@ -335,7 +335,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->FirstImagePaint());
 }
 
-unsigned long long PerformanceTiming::FirstContentfulPaint() const {
+uint64_t PerformanceTiming::FirstContentfulPaint() const {
   const PaintTiming* timing = GetPaintTiming();
   if (!timing)
     return 0;
@@ -343,7 +343,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->FirstContentfulPaint());
 }
 
-unsigned long long PerformanceTiming::FirstMeaningfulPaint() const {
+uint64_t PerformanceTiming::FirstMeaningfulPaint() const {
   const PaintTiming* timing = GetPaintTiming();
   if (!timing)
     return 0;
@@ -351,7 +351,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->FirstMeaningfulPaint());
 }
 
-unsigned long long PerformanceTiming::FirstMeaningfulPaintCandidate() const {
+uint64_t PerformanceTiming::FirstMeaningfulPaintCandidate() const {
   const PaintTiming* timing = GetPaintTiming();
   if (!timing)
     return 0;
@@ -360,7 +360,7 @@
       timing->FirstMeaningfulPaintCandidate());
 }
 
-unsigned long long PerformanceTiming::LargestImagePaint() const {
+uint64_t PerformanceTiming::LargestImagePaint() const {
   PaintTimingDetector* paint_timing_detector = GetPaintTimingDetector();
   if (!paint_timing_detector)
     return 0;
@@ -378,7 +378,7 @@
       .LargestImagePaintSize();
 }
 
-unsigned long long PerformanceTiming::LastImagePaint() const {
+uint64_t PerformanceTiming::LastImagePaint() const {
   PaintTimingDetector* paint_timing_detector = GetPaintTimingDetector();
   if (!paint_timing_detector)
     return 0;
@@ -396,7 +396,7 @@
       .LastImagePaintSize();
 }
 
-unsigned long long PerformanceTiming::LargestTextPaint() const {
+uint64_t PerformanceTiming::LargestTextPaint() const {
   PaintTimingDetector* paint_timing_detector = GetPaintTimingDetector();
   if (!paint_timing_detector)
     return 0;
@@ -414,7 +414,7 @@
       .LargestTextPaintSize();
 }
 
-unsigned long long PerformanceTiming::LastTextPaint() const {
+uint64_t PerformanceTiming::LastTextPaint() const {
   PaintTimingDetector* paint_timing_detector = GetPaintTimingDetector();
   if (!paint_timing_detector)
     return 0;
@@ -432,7 +432,7 @@
       .LastTextPaintSize();
 }
 
-unsigned long long PerformanceTiming::PageInteractive() const {
+uint64_t PerformanceTiming::PageInteractive() const {
   InteractiveDetector* interactive_detector = GetInteractiveDetector();
   if (!interactive_detector)
     return 0;
@@ -441,7 +441,7 @@
       interactive_detector->GetInteractiveTime());
 }
 
-unsigned long long PerformanceTiming::PageInteractiveDetection() const {
+uint64_t PerformanceTiming::PageInteractiveDetection() const {
   InteractiveDetector* interactive_detector = GetInteractiveDetector();
   if (!interactive_detector)
     return 0;
@@ -450,8 +450,7 @@
       interactive_detector->GetInteractiveDetectionTime());
 }
 
-unsigned long long PerformanceTiming::FirstInputInvalidatingInteractive()
-    const {
+uint64_t PerformanceTiming::FirstInputInvalidatingInteractive() const {
   InteractiveDetector* interactive_detector = GetInteractiveDetector();
   if (!interactive_detector)
     return 0;
@@ -460,7 +459,7 @@
       interactive_detector->GetFirstInvalidatingInputTime());
 }
 
-unsigned long long PerformanceTiming::FirstInputDelay() const {
+uint64_t PerformanceTiming::FirstInputDelay() const {
   const InteractiveDetector* interactive_detector = GetInteractiveDetector();
   if (!interactive_detector)
     return 0;
@@ -468,7 +467,7 @@
   return ToIntegerMilliseconds(interactive_detector->GetFirstInputDelay());
 }
 
-unsigned long long PerformanceTiming::FirstInputTimestamp() const {
+uint64_t PerformanceTiming::FirstInputTimestamp() const {
   const InteractiveDetector* interactive_detector = GetInteractiveDetector();
   if (!interactive_detector)
     return 0;
@@ -477,7 +476,7 @@
       interactive_detector->GetFirstInputTimestamp());
 }
 
-unsigned long long PerformanceTiming::LongestInputDelay() const {
+uint64_t PerformanceTiming::LongestInputDelay() const {
   const InteractiveDetector* interactive_detector = GetInteractiveDetector();
   if (!interactive_detector)
     return 0;
@@ -485,7 +484,7 @@
   return ToIntegerMilliseconds(interactive_detector->GetLongestInputDelay());
 }
 
-unsigned long long PerformanceTiming::LongestInputTimestamp() const {
+uint64_t PerformanceTiming::LongestInputTimestamp() const {
   const InteractiveDetector* interactive_detector = GetInteractiveDetector();
   if (!interactive_detector)
     return 0;
@@ -494,7 +493,7 @@
       interactive_detector->GetLongestInputTimestamp());
 }
 
-unsigned long long PerformanceTiming::ParseStart() const {
+uint64_t PerformanceTiming::ParseStart() const {
   const DocumentParserTiming* timing = GetDocumentParserTiming();
   if (!timing)
     return 0;
@@ -502,7 +501,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->ParserStart());
 }
 
-unsigned long long PerformanceTiming::ParseStop() const {
+uint64_t PerformanceTiming::ParseStop() const {
   const DocumentParserTiming* timing = GetDocumentParserTiming();
   if (!timing)
     return 0;
@@ -510,7 +509,7 @@
   return MonotonicTimeToIntegerMilliseconds(timing->ParserStop());
 }
 
-unsigned long long PerformanceTiming::ParseBlockedOnScriptLoadDuration() const {
+uint64_t PerformanceTiming::ParseBlockedOnScriptLoadDuration() const {
   const DocumentParserTiming* timing = GetDocumentParserTiming();
   if (!timing)
     return 0;
@@ -518,8 +517,8 @@
   return ToIntegerMilliseconds(timing->ParserBlockedOnScriptLoadDuration());
 }
 
-unsigned long long
-PerformanceTiming::ParseBlockedOnScriptLoadFromDocumentWriteDuration() const {
+uint64_t PerformanceTiming::ParseBlockedOnScriptLoadFromDocumentWriteDuration()
+    const {
   const DocumentParserTiming* timing = GetDocumentParserTiming();
   if (!timing)
     return 0;
@@ -528,8 +527,7 @@
       timing->ParserBlockedOnScriptLoadFromDocumentWriteDuration());
 }
 
-unsigned long long PerformanceTiming::ParseBlockedOnScriptExecutionDuration()
-    const {
+uint64_t PerformanceTiming::ParseBlockedOnScriptExecutionDuration() const {
   const DocumentParserTiming* timing = GetDocumentParserTiming();
   if (!timing)
     return 0;
@@ -538,7 +536,7 @@
       timing->ParserBlockedOnScriptExecutionDuration());
 }
 
-unsigned long long
+uint64_t
 PerformanceTiming::ParseBlockedOnScriptExecutionFromDocumentWriteDuration()
     const {
   const DocumentParserTiming* timing = GetDocumentParserTiming();
@@ -661,7 +659,7 @@
   return result.GetScriptValue();
 }
 
-unsigned long long PerformanceTiming::MonotonicTimeToIntegerMilliseconds(
+uint64_t PerformanceTiming::MonotonicTimeToIntegerMilliseconds(
     TimeTicks time) const {
   const DocumentLoadTiming* timing = GetDocumentLoadTiming();
   if (!timing)
diff --git a/third_party/blink/renderer/core/timing/performance_timing.h b/third_party/blink/renderer/core/timing/performance_timing.h
index 04cd99a..8134043 100644
--- a/third_party/blink/renderer/core/timing/performance_timing.h
+++ b/third_party/blink/renderer/core/timing/performance_timing.h
@@ -66,106 +66,105 @@
 
   explicit PerformanceTiming(LocalFrame*);
 
-  unsigned long long navigationStart() const;
-  unsigned long long inputStart() const;
-  unsigned long long unloadEventStart() const;
-  unsigned long long unloadEventEnd() const;
-  unsigned long long redirectStart() const;
-  unsigned long long redirectEnd() const;
-  unsigned long long fetchStart() const;
-  unsigned long long domainLookupStart() const;
-  unsigned long long domainLookupEnd() const;
-  unsigned long long connectStart() const;
-  unsigned long long connectEnd() const;
-  unsigned long long secureConnectionStart() const;
-  unsigned long long requestStart() const;
-  unsigned long long responseStart() const;
-  unsigned long long responseEnd() const;
-  unsigned long long domLoading() const;
-  unsigned long long domInteractive() const;
-  unsigned long long domContentLoadedEventStart() const;
-  unsigned long long domContentLoadedEventEnd() const;
-  unsigned long long domComplete() const;
-  unsigned long long loadEventStart() const;
-  unsigned long long loadEventEnd() const;
+  uint64_t navigationStart() const;
+  uint64_t inputStart() const;
+  uint64_t unloadEventStart() const;
+  uint64_t unloadEventEnd() const;
+  uint64_t redirectStart() const;
+  uint64_t redirectEnd() const;
+  uint64_t fetchStart() const;
+  uint64_t domainLookupStart() const;
+  uint64_t domainLookupEnd() const;
+  uint64_t connectStart() const;
+  uint64_t connectEnd() const;
+  uint64_t secureConnectionStart() const;
+  uint64_t requestStart() const;
+  uint64_t responseStart() const;
+  uint64_t responseEnd() const;
+  uint64_t domLoading() const;
+  uint64_t domInteractive() const;
+  uint64_t domContentLoadedEventStart() const;
+  uint64_t domContentLoadedEventEnd() const;
+  uint64_t domComplete() const;
+  uint64_t loadEventStart() const;
+  uint64_t loadEventEnd() const;
 
   // The below are non-spec timings, for Page Load UMA metrics.
 
   // The time the first document layout is performed.
-  unsigned long long FirstLayout() const;
+  uint64_t FirstLayout() const;
   // The time the first paint operation was performed.
-  unsigned long long FirstPaint() const;
+  uint64_t FirstPaint() const;
   // The time the first paint operation for image was performed.
-  unsigned long long FirstImagePaint() const;
+  uint64_t FirstImagePaint() const;
   // The time of the first 'contentful' paint. A contentful paint is a paint
   // that includes content of some kind (for example, text or image content).
-  unsigned long long FirstContentfulPaint() const;
+  uint64_t FirstContentfulPaint() const;
   // The time of the first 'meaningful' paint, A meaningful paint is a paint
   // where the page's primary content is visible.
-  unsigned long long FirstMeaningfulPaint() const;
+  uint64_t FirstMeaningfulPaint() const;
   // The time of the candidate of first 'meaningful' paint, A meaningful paint
   // candidate indicates the first time we considered a paint to qualify as the
   // potential first meaningful paint. But, be careful that it may be an
   // optimistic (i.e., too early) estimate.
   // TODO(crbug.com/848639): This function is exposed as an experiment, and if
   // not useful, this function can be removed.
-  unsigned long long FirstMeaningfulPaintCandidate() const;
+  uint64_t FirstMeaningfulPaintCandidate() const;
   // Largest Image Paint is the first paint after the largest image within
   // viewport being fully loaded. LargestImagePaint and LargestImagePaintSize
   // are the time and size of it.
-  unsigned long long LargestImagePaint() const;
+  uint64_t LargestImagePaint() const;
   uint64_t LargestImagePaintSize() const;
   // Last Image Paint is the first paint after the last image within viewport
   // being fully loaded. LastImagePaint and LastImagePaintSize are the time and
   // size of it.
-  unsigned long long LastImagePaint() const;
+  uint64_t LastImagePaint() const;
   uint64_t LastImagePaintSize() const;
   // The time of the first paint of the largest text within viewport.
   // Largest Text Paint is the first paint after the largest text within
   // viewport being painted. LargestTextPaint and LargestTextPaintSize
   // are the time and size of it.
-  unsigned long long LargestTextPaint() const;
+  uint64_t LargestTextPaint() const;
   uint64_t LargestTextPaintSize() const;
   // Last Text Paint is the first paint after the last text within viewport
   // being painted. LastTextPaint and LastTextPaintSize are the time and
   // size of it.
-  unsigned long long LastTextPaint() const;
+  uint64_t LastTextPaint() const;
   uint64_t LastTextPaintSize() const;
   // The first time the page is considered 'interactive'. This is determined
   // using heuristics based on main thread and network activity.
-  unsigned long long PageInteractive() const;
+  uint64_t PageInteractive() const;
   // The time of when we detect the page is interactive. There is a delay
   // between when the page was interactive and when we were able to detect it.
-  unsigned long long PageInteractiveDetection() const;
+  uint64_t PageInteractiveDetection() const;
   // The time of when a significant input event happened that may cause
   // observers to discard the value of Time to Interactive.
-  unsigned long long FirstInputInvalidatingInteractive() const;
+  uint64_t FirstInputInvalidatingInteractive() const;
   // The duration between the hardware timestamp and being queued on the main
   // thread for the first click, tap, key press, cancellable touchstart, or
   // pointer down followed by a pointer up.
-  unsigned long long FirstInputDelay() const;
+  uint64_t FirstInputDelay() const;
   // The timestamp of the event whose delay is reported by FirstInputDelay().
-  unsigned long long FirstInputTimestamp() const;
+  uint64_t FirstInputTimestamp() const;
   // The longest duration between the hardware timestamp and being queued on the
   // main thread for the click, tap, key press, cancellable touchstart, or
   // pointer down followed by a pointer up.
-  unsigned long long LongestInputDelay() const;
+  uint64_t LongestInputDelay() const;
   // The timestamp of the event whose delay is reported by LongestInputDelay().
-  unsigned long long LongestInputTimestamp() const;
+  uint64_t LongestInputTimestamp() const;
 
-  unsigned long long ParseStart() const;
-  unsigned long long ParseStop() const;
-  unsigned long long ParseBlockedOnScriptLoadDuration() const;
-  unsigned long long ParseBlockedOnScriptLoadFromDocumentWriteDuration() const;
-  unsigned long long ParseBlockedOnScriptExecutionDuration() const;
-  unsigned long long ParseBlockedOnScriptExecutionFromDocumentWriteDuration()
-      const;
+  uint64_t ParseStart() const;
+  uint64_t ParseStop() const;
+  uint64_t ParseBlockedOnScriptLoadDuration() const;
+  uint64_t ParseBlockedOnScriptLoadFromDocumentWriteDuration() const;
+  uint64_t ParseBlockedOnScriptExecutionDuration() const;
+  uint64_t ParseBlockedOnScriptExecutionFromDocumentWriteDuration() const;
 
   ScriptValue toJSONForBinding(ScriptState*) const;
 
   void Trace(blink::Visitor*) override;
 
-  unsigned long long MonotonicTimeToIntegerMilliseconds(TimeTicks) const;
+  uint64_t MonotonicTimeToIntegerMilliseconds(TimeTicks) const;
 
   std::unique_ptr<TracedValue> GetNavigationTracingData();
 
diff --git a/third_party/blink/renderer/core/timing/performance_user_timing.h b/third_party/blink/renderer/core/timing/performance_user_timing.h
index c78ed09..a6c2d0e 100644
--- a/third_party/blink/renderer/core/timing/performance_user_timing.h
+++ b/third_party/blink/renderer/core/timing/performance_user_timing.h
@@ -37,8 +37,7 @@
 class ExceptionState;
 class Performance;
 
-typedef unsigned long long (
-    PerformanceTiming::*NavigationTimingFunction)() const;
+typedef uint64_t (PerformanceTiming::*NavigationTimingFunction)() const;
 using PerformanceEntryMap = HeapHashMap<AtomicString, PerformanceEntryVector>;
 
 class UserTiming final : public GarbageCollected<UserTiming> {
diff --git a/third_party/blink/renderer/core/timing/window_performance.cc b/third_party/blink/renderer/core/timing/window_performance.cc
index b8d9093..943e6180 100644
--- a/third_party/blink/renderer/core/timing/window_performance.cc
+++ b/third_party/blink/renderer/core/timing/window_performance.cc
@@ -196,8 +196,7 @@
     return;
 
   if (HasObserverFor(PerformanceEntry::kLongTask)) {
-    UseCounter::Count(&GetFrame()->LocalFrameRoot(),
-                      WebFeature::kLongTaskObserver);
+    UseCounter::Count(GetFrame()->GetDocument(), WebFeature::kLongTaskObserver);
     GetFrame()->GetPerformanceMonitor()->Subscribe(
         PerformanceMonitor::kLongTask, kLongTaskObserverThreshold, this);
   } else {
@@ -423,7 +422,8 @@
     return;
   DCHECK_EQ("firstInput", entry->entryType());
   if (HasObserverFor(PerformanceEntry::kFirstInput)) {
-    UseCounter::Count(GetFrame(), WebFeature::kEventTimingExplicitlyRequested);
+    UseCounter::Count(GetFrame()->GetDocument(),
+                      WebFeature::kEventTimingExplicitlyRequested);
     NotifyObserversOfEntry(*entry);
   }
 
diff --git a/third_party/blink/renderer/core/workers/main_thread_worklet_reporting_proxy.cc b/third_party/blink/renderer/core/workers/main_thread_worklet_reporting_proxy.cc
index 0a19f57..be3b4f9 100644
--- a/third_party/blink/renderer/core/workers/main_thread_worklet_reporting_proxy.cc
+++ b/third_party/blink/renderer/core/workers/main_thread_worklet_reporting_proxy.cc
@@ -18,14 +18,14 @@
   DCHECK(IsMainThread());
   // A parent document is on the same thread, so just record API use in the
   // document's UseCounter.
-  UseCounter::Count(document_->GetFrame(), feature);
+  UseCounter::Count(document_, feature);
 }
 
 void MainThreadWorkletReportingProxy::CountDeprecation(WebFeature feature) {
   DCHECK(IsMainThread());
   // A parent document is on the same thread, so just record API use in the
   // document's UseCounter.
-  Deprecation::CountDeprecation(document_->GetFrame(), feature);
+  Deprecation::CountDeprecation(document_, feature);
 }
 
 void MainThreadWorkletReportingProxy::DidTerminateWorkerThread() {
diff --git a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc
index 5740ad6..6e229da 100644
--- a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc
+++ b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc
@@ -1302,11 +1302,10 @@
                                            long long expected_length) {
   bool length_computable =
       expected_length > 0 && received_length <= expected_length;
-  unsigned long long loaded =
-      received_length >= 0 ? static_cast<unsigned long long>(received_length)
-                           : 0;
-  unsigned long long total =
-      length_computable ? static_cast<unsigned long long>(expected_length) : 0;
+  uint64_t loaded =
+      received_length >= 0 ? static_cast<uint64_t>(received_length) : 0;
+  uint64_t total =
+      length_computable ? static_cast<uint64_t>(expected_length) : 0;
 
   ExecutionContext* context = GetExecutionContext();
   probe::AsyncTask async_task(
@@ -1776,8 +1775,8 @@
         GetDocument()->GetFrame());
 }
 
-void XMLHttpRequest::DidSendData(unsigned long long bytes_sent,
-                                 unsigned long long total_bytes_to_be_sent) {
+void XMLHttpRequest::DidSendData(uint64_t bytes_sent,
+                                 uint64_t total_bytes_to_be_sent) {
   NETWORK_DVLOG(1) << this << " didSendData(" << bytes_sent << ", "
                    << total_bytes_to_be_sent << ")";
   ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_);
@@ -1920,7 +1919,7 @@
   TrackProgress(len);
 }
 
-void XMLHttpRequest::DidDownloadData(unsigned long long data_length) {
+void XMLHttpRequest::DidDownloadData(uint64_t data_length) {
   ScopedEventDispatchProtect protect(&event_dispatch_recursion_level_);
   if (error_)
     return;
diff --git a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.h b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.h
index d682ec4..e8e37bd 100644
--- a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.h
+++ b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.h
@@ -180,14 +180,14 @@
   const SecurityOrigin* GetSecurityOrigin() const;
   SecurityOrigin* GetMutableSecurityOrigin();
 
-  void DidSendData(unsigned long long bytes_sent,
-                   unsigned long long total_bytes_to_be_sent) override;
+  void DidSendData(uint64_t bytes_sent,
+                   uint64_t total_bytes_to_be_sent) override;
   void DidReceiveResponse(unsigned long identifier,
                           const ResourceResponse&) override;
   void DidReceiveData(const char* data, unsigned data_length) override;
   // When responseType is set to "blob", didDownloadData() is called instead
   // of didReceiveData().
-  void DidDownloadData(unsigned long long data_length) override;
+  void DidDownloadData(uint64_t data_length) override;
   void DidDownloadToBlob(scoped_refptr<BlobDataHandle>) override;
   void DidFinishLoading(unsigned long identifier) override;
   void DidFail(const ResourceError&) override;
diff --git a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request_progress_event_throttle.cc b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request_progress_event_throttle.cc
index 6655c12..1dbea17 100644
--- a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request_progress_event_throttle.cc
+++ b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request_progress_event_throttle.cc
@@ -49,8 +49,8 @@
 
 void XMLHttpRequestProgressEventThrottle::DeferredEvent::Set(
     bool length_computable,
-    unsigned long long loaded,
-    unsigned long long total) {
+    uint64_t loaded,
+    uint64_t total) {
   is_set_ = true;
 
   length_computable_ = length_computable;
@@ -90,8 +90,8 @@
 void XMLHttpRequestProgressEventThrottle::DispatchProgressEvent(
     const AtomicString& type,
     bool length_computable,
-    unsigned long long loaded,
-    unsigned long long total) {
+    uint64_t loaded,
+    uint64_t total) {
   // Given that ResourceDispatcher doesn't deliver an event when suspended,
   // we don't have to worry about event dispatching while suspended.
   if (type != event_type_names::kProgress) {
diff --git a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request_progress_event_throttle.h b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request_progress_event_throttle.h
index d426d691..fa0087f 100644
--- a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request_progress_event_throttle.h
+++ b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request_progress_event_throttle.h
@@ -80,8 +80,8 @@
   // as well.
   void DispatchProgressEvent(const AtomicString&,
                              bool length_computable,
-                             unsigned long long loaded,
-                             unsigned long long total);
+                             uint64_t loaded,
+                             uint64_t total);
   // Dispatches the given event after operation about the "progress" event
   // depending on the value of the ProgressEventAction argument.
   void DispatchReadyStateChangeEvent(Event*, DeferredEventAction);
@@ -103,16 +103,14 @@
 
    public:
     DeferredEvent();
-    void Set(bool length_computable,
-             unsigned long long loaded,
-             unsigned long long total);
+    void Set(bool length_computable, uint64_t loaded, uint64_t total);
     void Clear();
     bool IsSet() const { return is_set_; }
     Event* Take();
 
    private:
-    unsigned long long loaded_;
-    unsigned long long total_;
+    uint64_t loaded_;
+    uint64_t total_;
     bool length_computable_;
 
     bool is_set_;
diff --git a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request_upload.cc b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request_upload.cc
index ec381e5..099554a 100644
--- a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request_upload.cc
+++ b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request_upload.cc
@@ -47,8 +47,8 @@
 }
 
 void XMLHttpRequestUpload::DispatchProgressEvent(
-    unsigned long long bytes_sent,
-    unsigned long long total_bytes_to_be_sent) {
+    uint64_t bytes_sent,
+    uint64_t total_bytes_to_be_sent) {
   last_bytes_sent_ = bytes_sent;
   last_total_bytes_to_be_sent_ = total_bytes_to_be_sent;
   probe::AsyncTask async_task(GetExecutionContext(), xml_http_request_,
@@ -57,11 +57,10 @@
                                        bytes_sent, total_bytes_to_be_sent));
 }
 
-void XMLHttpRequestUpload::DispatchEventAndLoadEnd(
-    const AtomicString& type,
-    bool length_computable,
-    unsigned long long bytes_sent,
-    unsigned long long total) {
+void XMLHttpRequestUpload::DispatchEventAndLoadEnd(const AtomicString& type,
+                                                   bool length_computable,
+                                                   uint64_t bytes_sent,
+                                                   uint64_t total) {
   DCHECK(type == event_type_names::kLoad || type == event_type_names::kAbort ||
          type == event_type_names::kError ||
          type == event_type_names::kTimeout);
diff --git a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request_upload.h b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request_upload.h
index c47e24d..5119a2d 100644
--- a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request_upload.h
+++ b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request_upload.h
@@ -52,11 +52,8 @@
   const AtomicString& InterfaceName() const override;
   ExecutionContext* GetExecutionContext() const override;
 
-  void DispatchEventAndLoadEnd(const AtomicString&,
-                               bool,
-                               unsigned long long,
-                               unsigned long long);
-  void DispatchProgressEvent(unsigned long long, unsigned long long);
+  void DispatchEventAndLoadEnd(const AtomicString&, bool, uint64_t, uint64_t);
+  void DispatchProgressEvent(uint64_t, uint64_t);
 
   void HandleRequestError(const AtomicString&);
 
@@ -67,8 +64,8 @@
 
   // Last progress event values; used when issuing the
   // required 'progress' event on a request error or abort.
-  unsigned long long last_bytes_sent_;
-  unsigned long long last_total_bytes_to_be_sent_;
+  uint64_t last_bytes_sent_;
+  uint64_t last_total_bytes_to_be_sent_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/devtools/front_end/Images/whatsnew.png b/third_party/blink/renderer/devtools/front_end/Images/whatsnew.png
index bb96abe..678a831 100644
--- a/third_party/blink/renderer/devtools/front_end/Images/whatsnew.png
+++ b/third_party/blink/renderer/devtools/front_end/Images/whatsnew.png
Binary files differ
diff --git a/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/category-renderer.js b/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/category-renderer.js
index c20523bc..377cbe3 100644
--- a/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/category-renderer.js
+++ b/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/category-renderer.js
@@ -85,10 +85,12 @@
         .appendChild(this.dom.convertMarkdownLinkSnippets(audit.result.description));
 
     const header = /** @type {HTMLDetailsElement} */ (this.dom.find('details', auditEl));
-    if (audit.result.details && audit.result.details.type) {
+    if (audit.result.details) {
       const elem = this.detailsRenderer.render(audit.result.details);
-      elem.classList.add('lh-details');
-      header.appendChild(elem);
+      if (elem) {
+        elem.classList.add('lh-details');
+        header.appendChild(elem);
+      }
     }
     this.dom.find('.lh-audit__index', auditEl).textContent = `${index + 1}`;
 
diff --git a/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/crc-details-renderer.js b/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/crc-details-renderer.js
index 9ffde22..150064d 100644
--- a/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/crc-details-renderer.js
+++ b/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/crc-details-renderer.js
@@ -145,7 +145,7 @@
    * @param {DocumentFragment} tmpl
    * @param {CRCSegment} segment
    * @param {Element} elem Parent element.
-   * @param {CRCDetailsJSON} details
+   * @param {LH.Audit.Details.CriticalRequestChain} details
    */
   static buildTree(dom, tmpl, segment, elem, details) {
     elem.appendChild(CriticalRequestChainRenderer.createChainNode(dom, tmpl, segment));
@@ -161,7 +161,7 @@
   /**
    * @param {DOM} dom
    * @param {ParentNode} templateContext
-   * @param {CRCDetailsJSON} details
+   * @param {LH.Audit.Details.CriticalRequestChain} details
    * @return {Element}
    */
   static render(dom, templateContext, details) {
@@ -195,14 +195,6 @@
 }
 
 /** @typedef {{
-      type: string,
-      header: {text: string},
-      longestChain: {duration: number, length: number, transferSize: number},
-      chains: LH.Audit.SimpleCriticalRequestNode
-  }} CRCDetailsJSON
- */
-
-/** @typedef {{
       node: LH.Audit.SimpleCriticalRequestNode[string],
       isLastChild: boolean,
       hasChildren: boolean,
diff --git a/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/details-renderer.js b/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/details-renderer.js
index b440662f..dff93dc 100644
--- a/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/details-renderer.js
+++ b/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/details-renderer.js
@@ -16,13 +16,10 @@
  */
 'use strict';
 
-/* globals self CriticalRequestChainRenderer Util URL */
+/* globals self CriticalRequestChainRenderer SnippetRenderer Util URL */
 
 /** @typedef {import('./dom.js')} DOM */
-/** @typedef {import('./crc-details-renderer.js')} CRCDetailsJSON */
-/** @typedef {LH.Result.Audit.OpportunityDetails} OpportunityDetails */
 
-/** @type {Array<string>} */
 const URL_PREFIXES = ['http://', 'https://', 'data:'];
 
 class DetailsRenderer {
@@ -44,46 +41,34 @@
   }
 
   /**
-   * @param {DetailsJSON|OpportunityDetails} details
-   * @return {Element}
+   * @param {LH.Audit.Details} details
+   * @return {Element|null}
    */
   render(details) {
     switch (details.type) {
-      case 'text':
-        return this._renderText(/** @type {StringDetailsJSON} */ (details));
-      case 'url':
-        return this._renderTextURL(/** @type {StringDetailsJSON} */ (details));
-      case 'bytes':
-        return this._renderBytes(/** @type {NumericUnitDetailsJSON} */ (details));
-      case 'ms':
-        // eslint-disable-next-line max-len
-        return this._renderMilliseconds(/** @type {NumericUnitDetailsJSON} */ (details));
-      case 'link':
-        // @ts-ignore - TODO(bckenny): Fix type hierarchy
-        return this._renderLink(/** @type {LinkDetailsJSON} */ (details));
-      case 'thumbnail':
-        return this._renderThumbnail(/** @type {ThumbnailDetails} */ (details));
       case 'filmstrip':
-        // @ts-ignore - TODO(bckenny): Fix type hierarchy
-        return this._renderFilmstrip(/** @type {FilmstripDetails} */ (details));
+        return this._renderFilmstrip(details);
+      case 'list':
+        return this._renderList(details);
       case 'table':
-        // @ts-ignore - TODO(bckenny): Fix type hierarchy
-        return this._renderTable(/** @type {TableDetailsJSON} */ (details));
-      case 'code':
-        return this._renderCode(/** @type {DetailsJSON} */ (details));
-      case 'node':
-        return this.renderNode(/** @type {NodeDetailsJSON} */(details));
+        return this._renderTable(details);
       case 'criticalrequestchain':
-        return CriticalRequestChainRenderer.render(this._dom, this._templateContext,
-          // @ts-ignore - TODO(bckenny): Fix type hierarchy
-          /** @type {CRCDetailsJSON} */ (details));
+        return CriticalRequestChainRenderer.render(this._dom, this._templateContext, details);
       case 'opportunity':
-        // @ts-ignore - TODO(bckenny): Fix type hierarchy
-        return this._renderOpportunityTable(details);
-      case 'numeric':
-        return this._renderNumeric(/** @type {StringDetailsJSON} */ (details));
+        return this._renderTable(details);
+
+      // Internal-only details, not for rendering.
+      case 'screenshot':
+      case 'diagnostic':
+        return null;
+      // Fallback for old LHRs, where no type meant don't render.
+      case undefined:
+        return null;
+
       default: {
-        throw new Error(`Unknown type: ${details.type}`);
+        // @ts-ignore tsc thinks this unreachable, but ts-ignore for error message just in case.
+        const detailsType = details.type;
+        throw new Error(`Unknown type: ${detailsType}`);
       }
     }
   }
@@ -95,7 +80,7 @@
   _renderBytes(details) {
     // TODO: handle displayUnit once we have something other than 'kb'
     const value = Util.formatBytesToKB(details.value, details.granularity);
-    return this._renderText({value});
+    return this._renderText(value);
   }
 
   /**
@@ -108,15 +93,15 @@
       value = Util.formatDuration(details.value);
     }
 
-    return this._renderText({value});
+    return this._renderText(value);
   }
 
   /**
-   * @param {{value: string}} text
+   * @param {string} text
    * @return {HTMLElement}
    */
   _renderTextURL(text) {
-    const url = text.value;
+    const url = text;
 
     let displayedPath;
     let displayedHost;
@@ -131,14 +116,10 @@
     }
 
     const element = this._dom.createElement('div', 'lh-text__url');
-    element.appendChild(this._renderText({
-      value: displayedPath,
-    }));
+    element.appendChild(this._renderText(displayedPath));
 
     if (displayedHost) {
-      const hostElem = this._renderText({
-        value: displayedHost,
-      });
+      const hostElem = this._renderText(displayedHost);
       hostElem.classList.add('lh-text__url-host');
       element.appendChild(hostElem);
     }
@@ -148,7 +129,7 @@
   }
 
   /**
-   * @param {LinkDetailsJSON} details
+   * @param {LH.Audit.Details.LinkValue} details
    * @return {Element}
    */
   _renderLink(details) {
@@ -156,9 +137,7 @@
     const url = new URL(details.url);
     if (!allowedProtocols.includes(url.protocol)) {
       // Fall back to just the link text if protocol not allowed.
-      return this._renderText({
-        value: details.text,
-      });
+      return this._renderText(details.text);
     }
 
     const a = this._dom.createElement('a');
@@ -171,34 +150,33 @@
   }
 
   /**
-   * @param {{value: string}} text
+   * @param {string} text
    * @return {Element}
    */
   _renderText(text) {
     const element = this._dom.createElement('div', 'lh-text');
-    element.textContent = text.value;
+    element.textContent = text;
     return element;
   }
 
   /**
-   * @param {{value: string}} text
+   * @param {string} text
    * @return {Element}
    */
   _renderNumeric(text) {
     const element = this._dom.createElement('div', 'lh-numeric');
-    element.textContent = text.value;
+    element.textContent = text;
     return element;
   }
 
   /**
    * Create small thumbnail with scaled down image asset.
-   * If the supplied details doesn't have an image/* mimeType, then an empty span is returned.
-   * @param {{value: string}} details
+   * @param {string} details
    * @return {Element}
    */
   _renderThumbnail(details) {
     const element = this._dom.createElement('img', 'lh-thumbnail');
-    const strValue = details.value;
+    const strValue = details;
     element.src = strValue;
     element.title = strValue;
     element.alt = '';
@@ -206,7 +184,114 @@
   }
 
   /**
-   * @param {TableDetailsJSON} details
+   * Render a details item value for embedding in a table. Renders the value
+   * based on the heading's valueType, unless the value itself has a `type`
+   * property to override it.
+   * @param {LH.Audit.Details.TableItem[string] | LH.Audit.Details.OpportunityItem[string]} value
+   * @param {LH.Audit.Details.OpportunityColumnHeading} heading
+   * @return {Element|null}
+   */
+  _renderTableValue(value, heading) {
+    if (typeof value === 'undefined' || value === null) {
+      return null;
+    }
+
+    // First deal with the possible object forms of value.
+    if (typeof value === 'object') {
+      // The value's type overrides the heading's for this column.
+      switch (value.type) {
+        case 'code': {
+          return this._renderCode(value.value);
+        }
+        case 'link': {
+          return this._renderLink(value);
+        }
+        case 'node': {
+          return this.renderNode(value);
+        }
+        case 'url': {
+          return this._renderTextURL(value.value);
+        }
+        default: {
+          throw new Error(`Unknown valueType: ${value.type}`);
+        }
+      }
+    }
+
+    // Next, deal with primitives.
+    switch (heading.valueType) {
+      case 'bytes': {
+        const numValue = Number(value);
+        return this._renderBytes({value: numValue, granularity: 1});
+      }
+      case 'code': {
+        const strValue = String(value);
+        return this._renderCode(strValue);
+      }
+      case 'ms': {
+        const msValue = {
+          value: Number(value),
+          granularity: heading.granularity,
+          displayUnit: heading.displayUnit,
+        };
+        return this._renderMilliseconds(msValue);
+      }
+      case 'numeric': {
+        const strValue = String(value);
+        return this._renderNumeric(strValue);
+      }
+      case 'text': {
+        const strValue = String(value);
+        return this._renderText(strValue);
+      }
+      case 'thumbnail': {
+        const strValue = String(value);
+        return this._renderThumbnail(strValue);
+      }
+      case 'timespanMs': {
+        const numValue = Number(value);
+        return this._renderMilliseconds({value: numValue});
+      }
+      case 'url': {
+        const strValue = String(value);
+        if (URL_PREFIXES.some(prefix => strValue.startsWith(prefix))) {
+          return this._renderTextURL(strValue);
+        } else {
+          // Fall back to <pre> rendering if not actually a URL.
+          return this._renderCode(strValue);
+        }
+      }
+      default: {
+        throw new Error(`Unknown valueType: ${heading.valueType}`);
+      }
+    }
+  }
+
+  /**
+   * Get the headings of a table-like details object, converted into the
+   * OpportunityColumnHeading type until we have all details use the same
+   * heading format.
+   * @param {LH.Audit.Details.Table|LH.Audit.Details.Opportunity} tableLike
+   * @return {Array<LH.Audit.Details.OpportunityColumnHeading>} header
+   */
+  _getCanonicalizedTableHeadings(tableLike) {
+    if (tableLike.type === 'opportunity') {
+      return tableLike.headings;
+    }
+
+    return tableLike.headings.map(heading => {
+      return {
+        key: heading.key,
+        label: heading.text,
+        valueType: heading.itemType,
+        displayUnit: heading.displayUnit,
+        granularity: heading.granularity,
+      };
+    });
+  }
+
+  /**
+   * @param {LH.Audit.Details.Table|LH.Audit.Details.Opportunity} details
    * @return {Element}
    */
   _renderTable(details) {
@@ -216,68 +301,9 @@
     const theadElem = this._dom.createChildOf(tableElem, 'thead');
     const theadTrElem = this._dom.createChildOf(theadElem, 'tr');
 
-    for (const heading of details.headings) {
-      const itemType = heading.itemType || 'text';
-      const classes = `lh-table-column--${itemType}`;
-      this._dom.createChildOf(theadTrElem, 'th', classes).appendChild(this.render({
-        type: 'text',
-        value: heading.text || '',
-      }));
-    }
+    const headings = this._getCanonicalizedTableHeadings(details);
 
-    const tbodyElem = this._dom.createChildOf(tableElem, 'tbody');
-    for (const row of details.items) {
-      const rowElem = this._dom.createChildOf(tbodyElem, 'tr');
-      for (const heading of details.headings) {
-        const key = /** @type {keyof DetailsJSON} */ (heading.key);
-        // TODO(bckenny): type should be naturally inferred here.
-        const value = /** @type {number|string|DetailsJSON|undefined} */ (row[key]);
-
-        if (typeof value === 'undefined' || value === null) {
-          this._dom.createChildOf(rowElem, 'td', 'lh-table-column--empty');
-          continue;
-        }
-        // handle nested types like code blocks in table rows.
-        // @ts-ignore - TODO(bckenny): narrow first
-        if (value.type) {
-          const valueAsDetails = /** @type {DetailsJSON} */ (value);
-          const classes = `lh-table-column--${valueAsDetails.type}`;
-          this._dom.createChildOf(rowElem, 'td', classes).appendChild(this.render(valueAsDetails));
-          continue;
-        }
-
-        // build new details item to render
-        const item = {
-          value: /** @type {number|string} */ (value),
-          type: heading.itemType,
-          displayUnit: heading.displayUnit,
-          granularity: heading.granularity,
-        };
-
-        /** @type {string|undefined} */
-        // @ts-ignore - TODO(bckenny): handle with refactoring above
-        const valueType = value.type;
-        const classes = `lh-table-column--${valueType || heading.itemType}`;
-        this._dom.createChildOf(rowElem, 'td', classes).appendChild(this.render(item));
-      }
-    }
-    return tableElem;
-  }
-
-  /**
-   * TODO(bckenny): migrate remaining table rendering to this function, then rename
-   * back to _renderTable and replace the original.
-   * @param {OpportunityDetails} details
-   * @return {Element}
-   */
-  _renderOpportunityTable(details) {
-    if (!details.items.length) return this._dom.createElement('span');
-
-    const tableElem = this._dom.createElement('table', 'lh-table');
-    const theadElem = this._dom.createChildOf(tableElem, 'thead');
-    const theadTrElem = this._dom.createChildOf(theadElem, 'tr');
-
-    for (const heading of details.headings) {
+    for (const heading of headings) {
       const valueType = heading.valueType || 'text';
       const classes = `lh-table-column--${valueType}`;
       const labelEl = this._dom.createElement('div', 'lh-text');
@@ -288,60 +314,38 @@
     const tbodyElem = this._dom.createChildOf(tableElem, 'tbody');
     for (const row of details.items) {
       const rowElem = this._dom.createChildOf(tbodyElem, 'tr');
-      for (const heading of details.headings) {
-        const key = /** @type {keyof LH.Result.Audit.OpportunityDetailsItem} */ (heading.key);
-        const value = row[key];
+      for (const heading of headings) {
+        const value = row[heading.key];
+        const valueElement = this._renderTableValue(value, heading);
 
-        if (typeof value === 'undefined' || value === null) {
+        if (valueElement) {
+          const classes = `lh-table-column--${heading.valueType}`;
+          this._dom.createChildOf(rowElem, 'td', classes).appendChild(valueElement);
+        } else {
           this._dom.createChildOf(rowElem, 'td', 'lh-table-column--empty');
-          continue;
         }
-
-        const valueType = heading.valueType;
-        let itemElement;
-
-        // TODO(bckenny): as we add more table types, split out into _renderTableItem fn.
-        switch (valueType) {
-          case 'url': {
-            // Fall back to <pre> rendering if not actually a URL.
-            const strValue = /** @type {string} */ (value);
-            if (URL_PREFIXES.some(prefix => strValue.startsWith(prefix))) {
-              itemElement = this._renderTextURL({value: strValue});
-            } else {
-              const codeValue = /** @type {(number|string|undefined)} */ (value);
-              itemElement = this._renderCode({value: codeValue});
-            }
-            break;
-          }
-          case 'timespanMs': {
-            const numValue = /** @type {number} */ (value);
-            itemElement = this._renderMilliseconds({value: numValue});
-            break;
-          }
-          case 'bytes': {
-            const numValue = /** @type {number} */ (value);
-            itemElement = this._renderBytes({value: numValue, granularity: 1});
-            break;
-          }
-          case 'thumbnail': {
-            const strValue = /** @type {string} */ (value);
-            itemElement = this._renderThumbnail({value: strValue});
-            break;
-          }
-          default: {
-            throw new Error(`Unknown valueType: ${valueType}`);
-          }
-        }
-
-        const classes = `lh-table-column--${valueType}`;
-        this._dom.createChildOf(rowElem, 'td', classes).appendChild(itemElement);
       }
     }
     return tableElem;
   }
 
   /**
-   * @param {NodeDetailsJSON} item
+   * @param {LH.Audit.Details.List} details
+   * @returns {Element}
+   */
+  _renderList(details) {
+    const listContainer = this._dom.createElement('div', 'lh-list');
+
+    details.items.forEach(item => {
+      const snippetEl = SnippetRenderer.render(this._dom, this._templateContext, item, this);
+      listContainer.appendChild(snippetEl);
+    });
+
+    return listContainer;
+  }
+
+  /**
+   * @param {LH.Audit.Details.NodeValue} item
    * @return {Element}
    * @protected
    */
@@ -360,7 +364,7 @@
   }
 
   /**
-   * @param {FilmstripDetails} details
+   * @param {LH.Audit.Details.Filmstrip} details
    * @return {Element}
    */
   _renderFilmstrip(details) {
@@ -377,12 +381,12 @@
   }
 
   /**
-   * @param {{value?: string|number}} details
+   * @param {string} text
    * @return {Element}
    */
-  _renderCode(details) {
+  _renderCode(text) {
     const pre = this._dom.createElement('pre', 'lh-code');
-    pre.textContent = /** @type {string} */ (details.value);
+    pre.textContent = text;
     return pre;
   }
 }
@@ -392,77 +396,3 @@
 } else {
   self.DetailsRenderer = DetailsRenderer;
 }
-
-// TODO, what's the diff between DetailsJSON and NumericUnitDetailsJSON?
-/**
- * @typedef {{
-      type: string,
-      value: (string|number|undefined),
-      granularity?: number,
-      displayUnit?: string
-  }} DetailsJSON
- */
-
-/**
- * @typedef {{
-      type: string,
-      value: string,
-      granularity?: number,
-      displayUnit?: string,
-  }} StringDetailsJSON
- */
-
-/**
- * @typedef {{
-      type: string,
-      value: number,
-      granularity?: number,
-      displayUnit?: string,
-  }} NumericUnitDetailsJSON
- */
-
-/**
- * @typedef {{
-      type: string,
-      path?: string,
-      selector?: string,
-      snippet?: string
-  }} NodeDetailsJSON
- */
-
-/**
- * @typedef {{
-      itemType: string,
-      key: string,
-      text?: string,
-      granularity?: number,
-      displayUnit?: string,
-  }} TableHeaderJSON
- */
-
-/** @typedef {{
-      type: string,
-      items: Array<DetailsJSON>,
-      headings: Array<TableHeaderJSON>
-  }} TableDetailsJSON
- */
-
-/** @typedef {{
-      type: string,
-      value: string,
-  }} ThumbnailDetails
- */
-
-/** @typedef {{
-      type: string,
-      text: string,
-      url: string
-  }} LinkDetailsJSON
- */
-
-/** @typedef {{
-      type: string,
-      scale: number,
-      items: Array<{timing: number, timestamp: number, data: string}>,
-  }} FilmstripDetails
- */
diff --git a/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/performance-category-renderer.js b/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/performance-category-renderer.js
index 870bdfc..9d7d9a0 100644
--- a/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/performance-category-renderer.js
+++ b/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/performance-category-renderer.js
@@ -19,8 +19,6 @@
 /* globals self, Util, CategoryRenderer */
 
 /** @typedef {import('./dom.js')} DOM */
-/** @typedef {import('./details-renderer.js').FilmstripDetails} FilmstripDetails */
-/** @typedef {LH.Result.Audit.OpportunityDetails} OpportunityDetails */
 
 class PerformanceCategoryRenderer extends CategoryRenderer {
   /**
@@ -67,8 +65,7 @@
     if (!audit.result.details || audit.result.scoreDisplayMode === 'error') {
       return element;
     }
-    // TODO(bckenny): remove cast when details is fully discriminated based on `type`.
-    const details = /** @type {OpportunityDetails} */ (audit.result.details);
+    const details = audit.result.details;
     if (details.type !== 'opportunity') {
       return element;
     }
@@ -98,8 +95,7 @@
    */
   _getWastedMs(audit) {
     if (audit.result.details && audit.result.details.type === 'opportunity') {
-      // TODO(bckenny): remove cast when details is fully discriminated based on `type`.
-      const details = /** @type {OpportunityDetails} */ (audit.result.details);
+      const details = audit.result.details;
       if (typeof details.overallSavingsMs !== 'number') {
         throw new Error('non-opportunity details passed to _getWastedMs');
       }
@@ -162,7 +158,7 @@
     if (thumbnailResult && thumbnailResult.details) {
       timelineEl.id = thumbnailResult.id;
       const filmstripEl = this.detailsRenderer.render(thumbnailResult.details);
-      timelineEl.appendChild(filmstripEl);
+      filmstripEl && timelineEl.appendChild(filmstripEl);
     }
 
     // Opportunities
diff --git a/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/psi.js b/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/psi.js
index a1b063c..35c4d104 100644
--- a/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/psi.js
+++ b/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/psi.js
@@ -74,7 +74,7 @@
 function _getFinalScreenshot(perfCategory) {
   const auditRef = perfCategory.auditRefs.find(audit => audit.id === 'final-screenshot');
   if (!auditRef || !auditRef.result || auditRef.result.scoreDisplayMode === 'error') return null;
-  return auditRef.result.details.data;
+  return /** @type {LH.Audit.Details.Screenshot} */ (auditRef.result.details).data;
 }
 
 if (typeof module !== 'undefined' && module.exports) {
diff --git a/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/report-renderer.js b/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/report-renderer.js
index cb013ef..325c27d 100644
--- a/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/report-renderer.js
+++ b/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/report-renderer.js
@@ -24,7 +24,6 @@
  */
 
 /** @typedef {import('./dom.js')} DOM */
-/** @typedef {import('./details-renderer.js').DetailsJSON} DetailsJSON */
 
 /* globals self, Util, DetailsRenderer, CategoryRenderer, PerformanceCategoryRenderer, PwaCategoryRenderer */
 
diff --git a/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/report-ui-features.js b/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/report-ui-features.js
index 4ed23e7f..2c28805 100644
--- a/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/report-ui-features.js
+++ b/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/report-ui-features.js
@@ -447,7 +447,6 @@
    */
   getReportHtml() {
     this._resetUIState();
-    // @ts-ignore - technically documentElement can be null, but that's dumb - https://dom.spec.whatwg.org/#document-element
     return this._document.documentElement.outerHTML;
   }
 
diff --git a/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/util.js b/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/util.js
index 6a752d5..5da98df 100644
--- a/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/util.js
+++ b/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/renderer/util.js
@@ -449,6 +449,48 @@
     // When testing, use a locale with more exciting numeric formatting
     if (Util.numberDateLocale === 'en-XA') Util.numberDateLocale = 'de';
   }
+
+  /**
+   * Returns only lines that are near a message, or the first few lines if there are
+   * no line messages.
+   * @param {LH.Audit.Details.SnippetValue['lines']} lines
+   * @param {LH.Audit.Details.SnippetValue['lineMessages']} lineMessages
+   * @param {number} surroundingLineCount Number of lines to include before and after
+   * the message. If this is e.g. 2 this function might return 5 lines.
+   */
+  static filterRelevantLines(lines, lineMessages, surroundingLineCount) {
+    if (lineMessages.length === 0) {
+      // no lines with messages, just return the first bunch of lines
+      return lines.slice(0, surroundingLineCount * 2 + 1);
+    }
+
+    const minGapSize = 3;
+    const lineNumbersToKeep = new Set();
+    // Sort messages so we can check lineNumbersToKeep to see how big the gap to
+    // the previous line is.
+    lineMessages = lineMessages.sort((a, b) => (a.lineNumber || 0) - (b.lineNumber || 0));
+    lineMessages.forEach(({lineNumber}) => {
+      let firstSurroundingLineNumber = lineNumber - surroundingLineCount;
+      let lastSurroundingLineNumber = lineNumber + surroundingLineCount;
+
+      while (firstSurroundingLineNumber < 1) {
+        // make sure we still show (surroundingLineCount * 2 + 1) lines in total
+        firstSurroundingLineNumber++;
+        lastSurroundingLineNumber++;
+      }
+      // If only a few lines would be omitted normally then we prefer to include
+      // extra lines to avoid the tiny gap
+      if (lineNumbersToKeep.has(firstSurroundingLineNumber - minGapSize - 1)) {
+        firstSurroundingLineNumber -= minGapSize;
+      }
+      for (let i = firstSurroundingLineNumber; i <= lastSurroundingLineNumber; i++) {
+        const surroundingLineNumber = i;
+        lineNumbersToKeep.add(surroundingLineNumber);
+      }
+    });
+
+    return lines.filter(line => lineNumbersToKeep.has(line.lineNumber));
+  }
 }
 
 /**
@@ -496,6 +538,11 @@
   /** Label of value shown in the summary of critical request chains. Refers to the total amount of time (milliseconds) of the longest critical path chain/sequence of network requests. Example value: 2310 ms */
   crcLongestDurationLabel: 'Maximum critical path latency:',
 
+  /** Label for button that shows all lines of the snippet when clicked */
+  snippetExpandButtonLabel: 'Expand snippet',
+  /** Label for button that only shows a few lines of the snippet when clicked */
+  snippetCollapseButtonLabel: 'Collapse snippet',
+
   /** Explanation shown to users below performance results to inform them that the test was done with a 4G network connection and to warn them that the numbers they see will likely change slightly the next time they run Lighthouse. 'Lighthouse' becomes link text to additional documentation. */
   lsPerformanceCategoryDescription: '[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.',
   /** Title of the lab data section of the Performance category. Within this section are various speed metrics which quantify the pageload performance into values presented in seconds and milliseconds. "Lab" is an abbreviated form of "laboratory", and refers to the fact that the data is from a controlled test of a website, not measurements from real users visiting that site.  */
diff --git a/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/report-styles.css b/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/report-styles.css
index 8ece38dc..7915e7b 100644
--- a/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/report-styles.css
+++ b/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/report-styles.css
@@ -725,6 +725,10 @@
   margin-top: var(--section-padding);
 }
 
+.lh-list > div:not(:last-child) {
+  padding-bottom: 20px;
+}
+
 .lh-header-container {
   display: block;
   margin: 0 auto;
diff --git a/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/templates.html b/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/templates.html
index 1f90384b..c63bc66 100644
--- a/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/templates.html
+++ b/third_party/blink/renderer/devtools/front_end/audits2/lighthouse/templates.html
@@ -206,6 +206,7 @@
     .lh-metadata__results {
       text-overflow: ellipsis;
       white-space: nowrap;
+      overflow: hidden;
     }
     .lh-metadata__url {
       color: currentColor;
@@ -891,3 +892,142 @@
     </div>
   </div>
 </template>
+
+
+<!-- Lighthouse snippet component -->
+<template id="tmpl-lh-snippet">
+    <div class="lh-snippet">
+      <style>
+          :root {
+            --snippet-highlight-light: #fbf1f2;
+            --snippet-highlight-dark: #ffd6d8;
+          }
+          
+         .lh-snippet__header {
+          position: relative;
+          overflow: hidden;
+          padding: 10px;
+          border-bottom: none;
+          color: var(--subheader-color);
+          background: var(--medium-50-gray);
+          border: 1px solid var(--report-secondary-border-color);
+        }
+        .lh-snippet__title {
+          font-weight: bold;
+          float: left;
+        }
+        .lh-snippet__node {
+          float: left;
+          margin-left: 4px;
+        }
+        .lh-snippet__toggle-expand {
+          padding: 1px 7px;
+          margin-top: -1px;
+          margin-right: -7px;
+          float: right;
+          background: transparent;
+          border: none;
+          cursor: pointer;
+          font-size: 14px;
+          color: #0c50c7;
+        }
+
+        .lh-snippet__snippet {
+          overflow: auto;
+          border: 1px solid var(--report-secondary-border-color);
+        }
+        /* Container needed so that all children grow to the width of the scroll container */
+        .lh-snippet__snippet-inner {
+          display: inline-block;
+          min-width: 100%;
+        }
+
+        .lh-snippet:not(.lh-snippet--expanded) .lh-snippet__show-if-expanded {
+          display: none;
+        }
+        .lh-snippet.lh-snippet--expanded .lh-snippet__show-if-collapsed {
+          display: none;
+        }
+
+        .lh-snippet__line {
+          background: white;
+          white-space: pre;
+          display: flex;
+        }        
+        .lh-snippet__line:not(.lh-snippet__line--message):first-child {
+          padding-top: 4px;
+        }
+        .lh-snippet__line:not(.lh-snippet__line--message):last-child {
+          padding-bottom: 4px;
+        }
+        .lh-snippet__line--content-highlighted {
+          background: var(--snippet-highlight-dark);
+        }
+        .lh-snippet__line--message {
+          background: var(--snippet-highlight-light);
+        }
+        .lh-snippet__line--message .lh-snippet__line-number {
+          padding-top: 10px;
+          padding-bottom: 10px;
+        }
+        .lh-snippet__line--message code {
+          padding: 10px;
+          padding-left: 5px;
+          color: var(--fail-color);
+          font-family: var(--text-font-family);
+        }
+        .lh-snippet__line--message code {
+          white-space: normal;
+        }
+        .lh-snippet__line-icon {
+          padding-top: 10px;
+          display: none;
+        }
+        .lh-snippet__line--message .lh-snippet__line-icon {
+          display: block;
+        }
+        .lh-snippet__line-icon:before {
+          content: "";
+          display: inline-block;
+          vertical-align: middle;
+          margin-right: 4px;
+          width: var(--lh-score-icon-width);
+          height: var(--lh-score-icon-width);
+          background-image: var(--fail-icon-url);
+        }
+        .lh-snippet__line-number {
+          flex-shrink: 0;
+          width: 40px;
+          text-align: right;
+          font-family: monospace;
+          padding-right: 5px;
+          margin-right: 5px;
+          color: var(--medium-75-gray);
+          user-select: none;
+        }
+      </style>
+      <template id="tmpl-lh-snippet__header">
+        <div class="lh-snippet__header">
+          <div class="lh-snippet__title"></div>
+          <div class="lh-snippet__node"></div>
+          <button class="lh-snippet__toggle-expand">
+            <span class="lh-snippet__btn-label-collapse lh-snippet__show-if-expanded"></span>
+            <span class="lh-snippet__btn-label-expand lh-snippet__show-if-collapsed"></span>
+          </button>
+        </div>
+      </template>
+      <template id="tmpl-lh-snippet__content">
+        <div class="lh-snippet__snippet">
+          <div class="lh-snippet__snippet-inner"></div>
+        </div>
+      </template>
+      <template id="tmpl-lh-snippet__line">
+          <div class="lh-snippet__line">
+            <div class="lh-snippet__line-number"></div>
+            <div class="lh-snippet__line-icon"></div>
+            <code></code>
+          </div>
+        </template>   
+    </div>
+  </template>
+  
diff --git a/third_party/blink/renderer/devtools/front_end/audits2_worker/lighthouse/lighthouse-dt-bundle.js b/third_party/blink/renderer/devtools/front_end/audits2_worker/lighthouse/lighthouse-dt-bundle.js
index 9e37afd2..6b938162b 100644
--- a/third_party/blink/renderer/devtools/front_end/audits2_worker/lighthouse/lighthouse-dt-bundle.js
+++ b/third_party/blink/renderer/devtools/front_end/audits2_worker/lighthouse/lighthouse-dt-bundle.js
@@ -1,5 +1,54 @@
-// lighthouse, browserified. 4.1.0 (aa68eaec9838afbdec549df12225fd7d71e0dfeb)
-require=function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a;}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r);},p,p.exports,r,e,n,t);}return n[i].exports;}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o;}return r;}()({"../audits/accessibility/aria-allowed-attr":[function(require,module,exports){
+// lighthouse, browserified. 4.2.0 (35b285b87d681905777ed2545e48292312910485)
+require=function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a;}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r);},p,p.exports,r,e,n,t);}return n[i].exports;}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o;}return r;}()({"../audits/accessibility/accesskeys":[function(require,module,exports){
+(function(__filename){
+
+
+
+
+
+'use strict';
+
+
+
+
+
+
+const AxeAudit=require('./axe-audit.js');
+const i18n=require('../../lib/i18n/i18n.js');
+
+const UIStrings={
+
+title:'`[accesskey]` values are unique',
+
+failureTitle:'`[accesskey]` values are not unique',
+
+description:'Access keys let users quickly focus a part of the page. For proper '+
+'navigation, each access key must be unique. '+
+'[Learn more](https://dequeuniversity.com/rules/axe/3.1/accesskeys?application=lighthouse).'};
+
+
+const str_=i18n.createMessageInstanceIdFn(__filename,UIStrings);
+
+class Accesskeys extends AxeAudit{
+
+
+
+static get meta(){
+return{
+id:'accesskeys',
+title:str_(UIStrings.title),
+failureTitle:str_(UIStrings.failureTitle),
+description:str_(UIStrings.description),
+requiredArtifacts:['Accessibility']};
+
+}}
+
+
+module.exports=Accesskeys;
+module.exports.UIStrings=UIStrings;
+
+}).call(this,"/lighthouse-core/audits/accessibility/accesskeys.js");
+},{"../../lib/i18n/i18n.js":63,"./axe-audit.js":2}],"../audits/accessibility/aria-allowed-attr":[function(require,module,exports){
 (function(__filename){
 
 
@@ -48,7 +97,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/aria-allowed-attr.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/aria-required-attr":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/aria-required-attr":[function(require,module,exports){
 (function(__filename){
 
 
@@ -96,7 +145,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/aria-required-attr.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/aria-required-children":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/aria-required-children":[function(require,module,exports){
 (function(__filename){
 
 
@@ -147,7 +196,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/aria-required-children.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/aria-required-parent":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/aria-required-parent":[function(require,module,exports){
 (function(__filename){
 
 
@@ -197,7 +246,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/aria-required-parent.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/aria-roles":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/aria-roles":[function(require,module,exports){
 (function(__filename){
 
 
@@ -246,7 +295,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/aria-roles.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/aria-valid-attr-value":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/aria-valid-attr-value":[function(require,module,exports){
 (function(__filename){
 
 
@@ -295,7 +344,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/aria-valid-attr-value.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/aria-valid-attr":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/aria-valid-attr":[function(require,module,exports){
 (function(__filename){
 
 
@@ -344,7 +393,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/aria-valid-attr.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/audio-caption":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/audio-caption":[function(require,module,exports){
 (function(__filename){
 
 
@@ -395,7 +444,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/audio-caption.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/button-name":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/button-name":[function(require,module,exports){
 (function(__filename){
 
 
@@ -444,7 +493,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/button-name.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/bypass":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/bypass":[function(require,module,exports){
 (function(__filename){
 
 
@@ -494,7 +543,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/bypass.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/color-contrast":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/color-contrast":[function(require,module,exports){
 (function(__filename){
 
 
@@ -544,7 +593,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/color-contrast.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/definition-list":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/definition-list":[function(require,module,exports){
 (function(__filename){
 
 
@@ -595,7 +644,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/definition-list.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/dlitem":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/dlitem":[function(require,module,exports){
 (function(__filename){
 
 
@@ -644,7 +693,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/dlitem.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/document-title":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/document-title":[function(require,module,exports){
 (function(__filename){
 
 
@@ -693,7 +742,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/document-title.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/duplicate-id":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/duplicate-id":[function(require,module,exports){
 (function(__filename){
 
 
@@ -742,7 +791,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/duplicate-id.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/frame-title":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/frame-title":[function(require,module,exports){
 (function(__filename){
 
 
@@ -790,7 +839,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/frame-title.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/html-has-lang":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/html-has-lang":[function(require,module,exports){
 (function(__filename){
 
 
@@ -841,7 +890,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/html-has-lang.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/html-lang-valid":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/html-lang-valid":[function(require,module,exports){
 (function(__filename){
 
 
@@ -891,7 +940,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/html-lang-valid.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/image-alt":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/image-alt":[function(require,module,exports){
 (function(__filename){
 
 
@@ -940,7 +989,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/image-alt.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/input-image-alt":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/input-image-alt":[function(require,module,exports){
 (function(__filename){
 
 
@@ -989,7 +1038,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/input-image-alt.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/label":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/label":[function(require,module,exports){
 (function(__filename){
 
 
@@ -1038,7 +1087,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/label.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/layout-table":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/layout-table":[function(require,module,exports){
 (function(__filename){
 
 
@@ -1091,7 +1140,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/layout-table.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/link-name":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/link-name":[function(require,module,exports){
 (function(__filename){
 
 
@@ -1141,7 +1190,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/link-name.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/listitem":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/listitem":[function(require,module,exports){
 (function(__filename){
 
 
@@ -1191,7 +1240,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/listitem.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/list":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/list":[function(require,module,exports){
 (function(__filename){
 
 
@@ -1242,60 +1291,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/list.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/manual/accesskeys":[function(require,module,exports){
-(function(__filename){
-
-
-
-
-
-'use strict';
-
-
-
-
-
-const Audit=require('../../audit.js');
-const i18n=require('../../../lib/i18n/i18n.js');
-
-const UIStrings={
-
-title:'`[accesskey]` values are unique',
-
-description:'Access keys let users quickly focus a part of the page. For proper '+
-'navigation, each access key must be unique. '+
-'[Learn more](https://dequeuniversity.com/rules/axe/3.1/accesskeys?application=lighthouse).'};
-
-
-const str_=i18n.createMessageInstanceIdFn(__filename,UIStrings);
-
-class Accesskeys extends Audit{
-
-
-
-static get meta(){
-return{
-id:'accesskeys',
-title:str_(UIStrings.title),
-description:str_(UIStrings.description),
-scoreDisplayMode:Audit.SCORING_MODES.MANUAL,
-requiredArtifacts:[]};
-
-}
-
-
-
-
-static audit(){
-return{rawValue:false};
-}}
-
-
-module.exports=Accesskeys;
-module.exports.UIStrings=UIStrings;
-
-}).call(this,"/lighthouse-core/audits/accessibility/manual/accesskeys.js");
-},{"../../../lib/i18n/i18n.js":60,"../../audit.js":3}],"../audits/accessibility/manual/custom-controls-labels":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/manual/custom-controls-labels":[function(require,module,exports){
 
 
 
@@ -1676,7 +1672,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/meta-refresh.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/meta-viewport":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/meta-viewport":[function(require,module,exports){
 (function(__filename){
 
 
@@ -1727,7 +1723,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/meta-viewport.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/object-alt":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/object-alt":[function(require,module,exports){
 (function(__filename){
 
 
@@ -1776,7 +1772,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/object-alt.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/tabindex":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/tabindex":[function(require,module,exports){
 (function(__filename){
 
 
@@ -1825,7 +1821,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/tabindex.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/td-headers-attr":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/td-headers-attr":[function(require,module,exports){
 (function(__filename){
 
 
@@ -1878,7 +1874,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/td-headers-attr.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/th-has-data-cells":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/th-has-data-cells":[function(require,module,exports){
 (function(__filename){
 
 
@@ -1930,7 +1926,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/th-has-data-cells.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/valid-lang":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/valid-lang":[function(require,module,exports){
 (function(__filename){
 
 
@@ -1979,7 +1975,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/valid-lang.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/video-caption":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/video-caption":[function(require,module,exports){
 (function(__filename){
 
 
@@ -2029,7 +2025,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/video-caption.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/accessibility/video-description":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/accessibility/video-description":[function(require,module,exports){
 (function(__filename){
 
 
@@ -2079,7 +2075,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/video-description.js");
-},{"../../lib/i18n/i18n.js":60,"./axe-audit":2}],"../audits/bootup-time":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./axe-audit":2}],"../audits/bootup-time":[function(require,module,exports){
 (function(__filename){
 
 
@@ -2105,7 +2101,7 @@
 'You may find delivering smaller JS payloads helps with this. [Learn '+
 'more](https://developers.google.com/web/tools/lighthouse/audits/bootup).',
 
-columnTotal:'Total',
+columnTotal:'Total CPU Time',
 
 columnScriptEval:'Script Evaluation',
 
@@ -2172,8 +2168,9 @@
 for(const task of tasks){
 const jsURL=task.attributableURLs.find(url=>jsURLs.has(url));
 const fallbackURL=task.attributableURLs[0];
-const attributableURL=jsURL||fallbackURL;
-if(!attributableURL||attributableURL==='about:blank')continue;
+let attributableURL=jsURL||fallbackURL;
+
+if(!attributableURL||attributableURL==='about:blank')attributableURL='Other';
 
 const timingByGroupId=result.get(attributableURL)||{};
 const originalTime=timingByGroupId[task.group.id]||0;
@@ -2206,26 +2203,26 @@
 const results=Array.from(executionTimings).
 map(([url,timingByGroupId])=>{
 
-let bootupTimeForURL=0;
+let totalExecutionTimeForURL=0;
 for(const[groupId,timespanMs]of Object.entries(timingByGroupId)){
 timingByGroupId[groupId]=timespanMs*multiplier;
-bootupTimeForURL+=timespanMs*multiplier;
-}
-
-
-if(bootupTimeForURL>=context.options.thresholdInMs){
-totalBootupTime+=bootupTimeForURL;
+totalExecutionTimeForURL+=timespanMs*multiplier;
 }
 
 const scriptingTotal=timingByGroupId[taskGroups.scriptEvaluation.id]||0;
 const parseCompileTotal=timingByGroupId[taskGroups.scriptParseCompile.id]||0;
 
+
+if(totalExecutionTimeForURL>=context.options.thresholdInMs){
+totalBootupTime+=scriptingTotal+parseCompileTotal;
+}
+
 hadExcessiveChromeExtension=hadExcessiveChromeExtension||
 url.startsWith('chrome-extension:')&&scriptingTotal>100;
 
 return{
 url:url,
-total:bootupTimeForURL,
+total:totalExecutionTimeForURL,
 
 scripting:scriptingTotal,
 scriptParseCompile:parseCompileTotal};
@@ -2242,6 +2239,7 @@
 
 const summary={wastedMs:totalBootupTime};
 
+
 const headings=[
 {key:'url',itemType:'url',text:str_(i18n.UIStrings.columnURL)},
 {key:'total',granularity:1,itemType:'ms',text:str_(UIStrings.columnTotal)},
@@ -2272,7 +2270,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/bootup-time.js");
-},{"../computed/main-thread-tasks.js":12,"../computed/network-records.js":29,"../lib/i18n/i18n.js":60,"../lib/network-request":68,"../lib/task-groups":72,"./audit":3}],"../audits/byte-efficiency/efficient-animated-content":[function(require,module,exports){
+},{"../computed/main-thread-tasks.js":12,"../computed/network-records.js":31,"../lib/i18n/i18n.js":63,"../lib/network-request":71,"../lib/task-groups":77,"./audit":3}],"../audits/byte-efficiency/efficient-animated-content":[function(require,module,exports){
 (function(__filename){
 
 
@@ -2368,7 +2366,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/byte-efficiency/efficient-animated-content.js");
-},{"../../lib/i18n/i18n.js":60,"../../lib/network-request":68,"./byte-efficiency-audit":4}],"../audits/byte-efficiency/offscreen-images":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"../../lib/network-request":71,"./byte-efficiency-audit":4}],"../audits/byte-efficiency/offscreen-images":[function(require,module,exports){
 (function(__filename){
 
 
@@ -2615,7 +2613,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/byte-efficiency/offscreen-images.js");
-},{"../../computed/metrics/interactive.js":18,"../../computed/trace-of-tab.js":33,"../../lib/i18n/i18n.js":60,"../../lib/sentry":70,"../../lib/url-shim":"url","./byte-efficiency-audit":4}],"../audits/byte-efficiency/render-blocking-resources":[function(require,module,exports){
+},{"../../computed/metrics/interactive.js":18,"../../computed/trace-of-tab.js":35,"../../lib/i18n/i18n.js":63,"../../lib/sentry":74,"../../lib/url-shim":"url","./byte-efficiency-audit":4}],"../audits/byte-efficiency/render-blocking-resources":[function(require,module,exports){
 (function(__filename){
 
 
@@ -2808,10 +2806,11 @@
 const wastedBytesByUrl=new Map();
 try{
 const results=await UnusedCSS.audit(artifacts,context);
-
+if(results.details&&results.details.type==='opportunity'){
 for(const item of results.details.items){
 wastedBytesByUrl.set(item.url,item.wastedBytes);
 }
+}
 }catch(_){}
 
 return wastedBytesByUrl;
@@ -2852,7 +2851,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/byte-efficiency/render-blocking-resources.js");
-},{"../../computed/load-simulator.js":10,"../../computed/metrics/first-contentful-paint.js":15,"../../computed/trace-of-tab.js":33,"../../lib/dependency-graph/base-node":49,"../../lib/i18n/i18n.js":60,"../../lib/network-request":68,"../audit":3,"./byte-efficiency-audit":4,"./unused-css-rules":"../audits/byte-efficiency/unused-css-rules"}],"../audits/byte-efficiency/total-byte-weight":[function(require,module,exports){
+},{"../../computed/load-simulator.js":10,"../../computed/metrics/first-contentful-paint.js":15,"../../computed/trace-of-tab.js":35,"../../lib/dependency-graph/base-node":52,"../../lib/i18n/i18n.js":63,"../../lib/network-request":71,"../audit":3,"./byte-efficiency-audit":4,"./unused-css-rules":"../audits/byte-efficiency/unused-css-rules"}],"../audits/byte-efficiency/total-byte-weight":[function(require,module,exports){
 (function(__filename){
 
 
@@ -2934,7 +2933,10 @@
 results.push(result);
 });
 const totalCompletedRequests=results.length;
-results=results.sort((itemA,itemB)=>itemB.totalBytes-itemA.totalBytes).slice(0,10);
+results=results.sort((itemA,itemB)=>{
+return itemB.totalBytes-itemA.totalBytes||
+itemA.url.localeCompare(itemB.url);
+}).slice(0,10);
 
 const score=ByteEfficiencyAudit.computeLogNormalScore(
 totalBytes,
@@ -2942,6 +2944,7 @@
 context.options.scoreMedian);
 
 
+
 const headings=[
 {key:'url',itemType:'url',text:str_(i18n.UIStrings.columnURL)},
 {key:'totalBytes',itemType:'bytes',text:str_(i18n.UIStrings.columnSize)}];
@@ -2968,7 +2971,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/byte-efficiency/total-byte-weight.js");
-},{"../../computed/network-records.js":29,"../../lib/i18n/i18n.js":60,"./byte-efficiency-audit":4}],"../audits/byte-efficiency/unminified-css":[function(require,module,exports){
+},{"../../computed/network-records.js":31,"../../lib/i18n/i18n.js":63,"./byte-efficiency-audit":4}],"../audits/byte-efficiency/unminified-css":[function(require,module,exports){
 (function(__filename){
 
 
@@ -3089,7 +3092,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/byte-efficiency/unminified-css.js");
-},{"../../lib/i18n/i18n.js":60,"../../lib/minification-estimator":66,"./byte-efficiency-audit":4,"./unused-css-rules":"../audits/byte-efficiency/unused-css-rules"}],"../audits/byte-efficiency/unminified-javascript":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"../../lib/minification-estimator":69,"./byte-efficiency-audit":4,"./unused-css-rules":"../audits/byte-efficiency/unused-css-rules"}],"../audits/byte-efficiency/unminified-javascript":[function(require,module,exports){
 (function(__filename){
 
 
@@ -3144,7 +3147,8 @@
 
 
 
-static computeWaste(scriptContent,networkRecord){
+
+static computeWaste(scriptContent,displayUrl,networkRecord){
 const contentLength=scriptContent.length;
 const totalTokenLength=computeTokenLength(scriptContent);
 
@@ -3154,7 +3158,7 @@
 const wastedBytes=Math.round(totalBytes*wastedRatio);
 
 return{
-url:networkRecord.url,
+url:displayUrl,
 totalBytes,
 wastedBytes,
 wastedPercent:100*wastedRatio};
@@ -3170,13 +3174,14 @@
 
 const items=[];
 const warnings=[];
-for(const requestId of Object.keys(artifacts.Scripts)){
-const scriptContent=artifacts.Scripts[requestId];
+for(const{requestId,inline,content}of artifacts.Scripts){
+if(!content)continue;
 const networkRecord=networkRecords.find(record=>record.requestId===requestId);
-if(!networkRecord||!scriptContent)continue;
-
+const displayUrl=inline||!networkRecord?
+`inline: ${content.substr(0,40)}...`:
+networkRecord.url;
 try{
-const result=UnminifiedJavaScript.computeWaste(scriptContent,networkRecord);
+const result=UnminifiedJavaScript.computeWaste(content,displayUrl,networkRecord);
 
 
 if(result.wastedPercent<IGNORE_THRESHOLD_IN_PERCENT||
@@ -3184,7 +3189,8 @@
 !Number.isFinite(result.wastedBytes))continue;
 items.push(result);
 }catch(err){
-warnings.push(`Unable to process ${networkRecord.url}: ${err.message}`);
+const url=networkRecord?networkRecord.url:'?';
+warnings.push(`Unable to process script ${url}: ${err.message}`);
 }
 }
 
@@ -3207,7 +3213,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/byte-efficiency/unminified-javascript.js");
-},{"../../lib/i18n/i18n.js":60,"../../lib/minification-estimator":66,"./byte-efficiency-audit":4}],"../audits/byte-efficiency/unused-css-rules":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"../../lib/minification-estimator":69,"./byte-efficiency-audit":4}],"../audits/byte-efficiency/unused-css-rules":[function(require,module,exports){
 (function(__filename){
 
 
@@ -3221,10 +3227,10 @@
 
 const UIStrings={
 
-title:'Defer unused CSS',
+title:'Remove unused CSS',
 
-description:'Remove unused rules from stylesheets to reduce unnecessary '+
-'bytes consumed by network activity. '+
+description:'Remove dead rules from stylesheets and defer the loading of CSS not used for '+
+'above-the-fold content to reduce unnecessary bytes consumed by network activity. '+
 '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/unused-css).'};
 
 
@@ -3362,8 +3368,7 @@
 }
 
 const usage=UnusedCSSRules.computeUsage(stylesheetInfo);
-
-return Object.assign({url},usage);
+return{url,...usage};
 }
 
 
@@ -3403,7 +3408,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/byte-efficiency/unused-css-rules.js");
-},{"../../lib/i18n/i18n.js":60,"./byte-efficiency-audit":4}],"../audits/byte-efficiency/unused-javascript":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./byte-efficiency-audit":4}],"../audits/byte-efficiency/unused-javascript":[function(require,module,exports){
 (function(__filename){
 
 
@@ -3541,7 +3546,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/byte-efficiency/unused-javascript.js");
-},{"../../lib/i18n/i18n.js":60,"./byte-efficiency-audit":4}],"../audits/byte-efficiency/uses-long-cache-ttl":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"./byte-efficiency-audit":4}],"../audits/byte-efficiency/uses-long-cache-ttl":[function(require,module,exports){
 (function(__filename){
 
 
@@ -3720,7 +3725,7 @@
 
 if(cacheControl&&(
 
-cacheControl['must-validate']||
+cacheControl['must-revalidate']||
 cacheControl['no-cache']||
 cacheControl['no-store']||
 cacheControl['private'])){
@@ -3782,12 +3787,19 @@
 totalWastedBytes+=wastedBytes;
 if(url.includes('?'))queryStringCount++;
 
+
+
+let diagnostic;
+if(cacheControl){
+diagnostic={
+type:'diagnostic',
+...cacheControl};
+
+}
+
 results.push({
 url,
-
-
-
-cacheControl:cacheControl,
+diagnostic,
 cacheLifetimeMs:cacheLifetimeInSeconds*1000,
 cacheHitProbability,
 totalBytes,
@@ -3795,9 +3807,11 @@
 
 }
 
-results.sort(
-(a,b)=>a.cacheLifetimeMs-b.cacheLifetimeMs||b.totalBytes-a.totalBytes);
-
+results.sort((a,b)=>{
+return a.cacheLifetimeMs-b.cacheLifetimeMs||
+b.totalBytes-a.totalBytes||
+a.url.localeCompare(b.url);
+});
 
 const score=Audit.computeLogNormalScore(
 totalWastedBytes,
@@ -3805,6 +3819,7 @@
 context.options.scoreMedian);
 
 
+
 const headings=[
 {key:'url',itemType:'url',text:str_(i18n.UIStrings.columnURL)},
 
@@ -3837,7 +3852,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/byte-efficiency/uses-long-cache-ttl.js");
-},{"../../computed/network-records.js":29,"../../lib/i18n/i18n.js":60,"../../lib/network-request":68,"../../lib/statistics":71,"../../lib/url-shim":"url","../audit":3,"assert":79,"parse-cache-control":128}],"../audits/byte-efficiency/uses-optimized-images":[function(require,module,exports){
+},{"../../computed/network-records.js":31,"../../lib/i18n/i18n.js":63,"../../lib/network-request":71,"../../lib/statistics":75,"../../lib/url-shim":"url","../audit":3,"assert":84,"parse-cache-control":134}],"../audits/byte-efficiency/uses-optimized-images":[function(require,module,exports){
 (function(__filename){
 
 
@@ -3941,7 +3956,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/byte-efficiency/uses-optimized-images.js");
-},{"../../lib/i18n/i18n.js":60,"../../lib/url-shim":"url","./byte-efficiency-audit":4}],"../audits/byte-efficiency/uses-responsive-images":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"../../lib/url-shim":"url","./byte-efficiency-audit":4}],"../audits/byte-efficiency/uses-responsive-images":[function(require,module,exports){
 (function(__filename){
 
 
@@ -4084,7 +4099,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js");
-},{"../../lib/i18n/i18n.js":60,"../../lib/sentry":70,"../../lib/url-shim":"url","./byte-efficiency-audit":4}],"../audits/byte-efficiency/uses-text-compression":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"../../lib/sentry":74,"../../lib/url-shim":"url","./byte-efficiency-audit":4}],"../audits/byte-efficiency/uses-text-compression":[function(require,module,exports){
 (function(__filename){
 
 
@@ -4188,7 +4203,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/byte-efficiency/uses-text-compression.js");
-},{"../../lib/i18n/i18n.js":60,"../../lib/url-shim":"url","./byte-efficiency-audit":4}],"../audits/byte-efficiency/uses-webp-images":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"../../lib/url-shim":"url","./byte-efficiency-audit":4}],"../audits/byte-efficiency/uses-webp-images":[function(require,module,exports){
 (function(__filename){
 
 
@@ -4291,7 +4306,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/byte-efficiency/uses-webp-images.js");
-},{"../../lib/i18n/i18n.js":60,"../../lib/url-shim":"url","./byte-efficiency-audit":4}],"../audits/content-width":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"../../lib/url-shim":"url","./byte-efficiency-audit":4}],"../audits/content-width":[function(require,module,exports){
 
 
 
@@ -4313,7 +4328,7 @@
 description:'If the width of your app\'s content doesn\'t match the width '+
 'of the viewport, your app might not be optimized for mobile screens. '+
 '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/content-sized-correctly-for-viewport).',
-requiredArtifacts:['ViewportDimensions','HostUserAgent']};
+requiredArtifacts:['ViewportDimensions','TestedAsMobileDevice']};
 
 }
 
@@ -4321,18 +4336,13 @@
 
 
 
-
-static audit(artifacts,context){
-const userAgent=artifacts.HostUserAgent;
+static audit(artifacts){
+const IsMobile=artifacts.TestedAsMobileDevice;
 const viewportWidth=artifacts.ViewportDimensions.innerWidth;
 const windowWidth=artifacts.ViewportDimensions.outerWidth;
 const widthsMatch=viewportWidth===windowWidth;
 
-const isMobileHost=userAgent.includes('Android')||userAgent.includes('Mobile');
-const isMobile=context.settings.emulatedFormFactor==='mobile'||
-context.settings.emulatedFormFactor!=='desktop'&&isMobileHost;
-
-if(isMobile){
+if(IsMobile){
 return{
 rawValue:widthsMatch,
 explanation:this.createExplanation(widthsMatch,artifacts.ViewportDimensions)};
@@ -4590,7 +4600,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/critical-request-chains.js");
-},{"../computed/critical-request-chains.js":9,"../lib/i18n/i18n.js":60,"./audit":3}],"../audits/deprecations":[function(require,module,exports){
+},{"../computed/critical-request-chains.js":9,"../lib/i18n/i18n.js":63,"./audit":3}],"../audits/deprecations":[function(require,module,exports){
 
 
 
@@ -4638,6 +4648,7 @@
 
 });
 
+
 const headings=[
 {key:'value',itemType:'code',text:'Deprecation / Warning'},
 {key:'url',itemType:'url',text:'URL'},
@@ -4665,7 +4676,88 @@
 
 module.exports=Deprecations;
 
-},{"../report/html/renderer/util":75,"./audit":3}],"../audits/dobetterweb/appcache-manifest":[function(require,module,exports){
+},{"../report/html/renderer/util":80,"./audit":3}],"../audits/diagnostics":[function(require,module,exports){
+
+
+
+
+
+'use strict';
+
+const Audit=require('./audit.js');
+const MainThreadTasksComputed=require('../computed/main-thread-tasks.js');
+const NetworkRecordsComputed=require('../computed/network-records.js');
+const NetworkAnalysisComputed=require('../computed/network-analysis.js');
+const NetworkAnalyzer=require('../lib/dependency-graph/simulator/network-analyzer.js');
+
+class Diagnostics extends Audit{
+
+
+
+static get meta(){
+return{
+id:'diagnostics',
+scoreDisplayMode:Audit.SCORING_MODES.INFORMATIVE,
+title:'Diagnostics',
+description:'Collection of useful page vitals.',
+requiredArtifacts:['traces','devtoolsLogs']};
+
+}
+
+
+
+
+
+
+static async audit(artifacts,context){
+const trace=artifacts.traces[Audit.DEFAULT_PASS];
+const devtoolsLog=artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
+const tasks=await MainThreadTasksComputed.request(trace,context);
+const records=await NetworkRecordsComputed.request(devtoolsLog,context);
+const analysis=await NetworkAnalysisComputed.request(devtoolsLog,context);
+
+const toplevelTasks=tasks.filter(t=>!t.parent);
+const mainDocumentTransferSize=NetworkAnalyzer.findMainDocument(records).transferSize;
+const totalByteWeight=records.reduce((sum,r)=>sum+(r.transferSize||0),0);
+const totalTaskTime=toplevelTasks.reduce((sum,t)=>sum+(t.duration||0),0);
+const maxRtt=Math.max(...analysis.additionalRttByOrigin.values())+analysis.rtt;
+const maxServerLatency=Math.max(...analysis.serverResponseTimeByOrigin.values());
+
+const diagnostics={
+numRequests:records.length,
+numScripts:records.filter(r=>r.resourceType==='Script').length,
+numStylesheets:records.filter(r=>r.resourceType==='Stylesheet').length,
+numFonts:records.filter(r=>r.resourceType==='Font').length,
+numTasks:toplevelTasks.length,
+numTasksOver10ms:toplevelTasks.filter(t=>t.duration>10).length,
+numTasksOver25ms:toplevelTasks.filter(t=>t.duration>25).length,
+numTasksOver50ms:toplevelTasks.filter(t=>t.duration>50).length,
+numTasksOver100ms:toplevelTasks.filter(t=>t.duration>100).length,
+numTasksOver500ms:toplevelTasks.filter(t=>t.duration>500).length,
+rtt:analysis.rtt,
+throughput:analysis.throughput,
+maxRtt,
+maxServerLatency,
+totalByteWeight,
+totalTaskTime,
+mainDocumentTransferSize};
+
+
+return{
+score:1,
+rawValue:1,
+details:{
+type:'diagnostic',
+
+items:[diagnostics]}};
+
+
+}}
+
+
+module.exports=Diagnostics;
+
+},{"../computed/main-thread-tasks.js":12,"../computed/network-analysis.js":30,"../computed/network-records.js":31,"../lib/dependency-graph/simulator/network-analyzer.js":57,"./audit.js":3}],"../audits/dobetterweb/appcache-manifest":[function(require,module,exports){
 
 
 
@@ -4892,6 +4984,7 @@
 context.options.scoreMedian);
 
 
+
 const headings=[
 {key:'statistic',itemType:'text',text:str_(UIStrings.columnStatistic)},
 {key:'element',itemType:'code',text:str_(UIStrings.columnElement)},
@@ -4939,7 +5032,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/dobetterweb/dom-size.js");
-},{"../../lib/i18n/i18n.js":60,"../../report/html/renderer/util.js":75,"../audit":3}],"../audits/dobetterweb/external-anchors-use-rel-noopener":[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"../../report/html/renderer/util.js":80,"../audit":3}],"../audits/dobetterweb/external-anchors-use-rel-noopener":[function(require,module,exports){
 
 
 
@@ -4962,7 +5055,7 @@
 description:'Add `rel="noopener"` or `rel="noreferrer"` to any external links to improve '+
 'performance and prevent security vulnerabilities. '+
 '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/noopener).',
-requiredArtifacts:['URL','AnchorsWithNoRelNoopener']};
+requiredArtifacts:['URL','AnchorElements']};
 
 }
 
@@ -4974,8 +5067,10 @@
 
 const warnings=[];
 const pageHost=new URL(artifacts.URL.finalUrl).host;
+const failingAnchors=artifacts.AnchorElements.
+filter(anchor=>anchor.target==='_blank'&&!anchor.rel.includes('noopener')&&
+!anchor.rel.includes('noreferrer')).
 
-const failingAnchors=artifacts.AnchorsWithNoRelNoopener.
 filter(anchor=>{
 try{
 return new URL(anchor.href).host!==pageHost;
@@ -4997,6 +5092,7 @@
 
 });
 
+
 const headings=[
 {key:'href',itemType:'url',text:'URL'},
 {key:'target',itemType:'text',text:'Target'},
@@ -5058,6 +5154,7 @@
 
 const results=ViolationAudit.getViolationResults(artifacts,/geolocation/);
 
+
 const headings=[
 {key:'url',itemType:'url',text:'URL'},
 {key:'label',itemType:'text',text:'Location'}];
@@ -5113,8 +5210,9 @@
 static audit(artifacts){
 const libDetails=artifacts.JSLibraries.map(lib=>({
 name:lib.name,
-version:lib.version,
-npm:lib.npmPkgName||null}));
+version:lib.version||undefined,
+npm:lib.npmPkgName||undefined}));
+
 
 
 const headings=[
@@ -5170,6 +5268,7 @@
 static audit(artifacts){
 const results=ViolationAudit.getViolationResults(artifacts,/document\.write/);
 
+
 const headings=[
 {key:'url',itemType:'url',text:'URL'},
 {key:'label',itemType:'text',text:'Location'}];
@@ -5367,6 +5466,7 @@
 displayValue=`${totalVulns} vulnerability detected`;
 }
 
+
 const headings=[
 {key:'detectedLib',itemType:'link',text:'Library Version'},
 {key:'vulnCount',itemType:'text',text:'Vulnerability Count'},
@@ -5388,7 +5488,7 @@
 
 module.exports=NoVulnerableLibrariesAudit;
 
-},{"../../../third-party/snyk/snapshot.json":162,"../../lib/sentry":70,"../audit":3,"semver":152}],"../audits/dobetterweb/notification-on-start":[function(require,module,exports){
+},{"../../../third-party/snyk/snapshot.json":168,"../../lib/sentry":74,"../audit":3,"semver":144}],"../audits/dobetterweb/notification-on-start":[function(require,module,exports){
 
 
 
@@ -5427,6 +5527,7 @@
 static audit(artifacts){
 const results=ViolationAudit.getViolationResults(artifacts,/notification permission/);
 
+
 const headings=[
 {key:'url',itemType:'url',text:'URL'},
 {key:'label',itemType:'text',text:'Location'}];
@@ -5486,6 +5587,7 @@
 
 });
 
+
 const headings=[
 {key:'node',itemType:'node',text:'Failing Elements'}];
 
@@ -5573,6 +5675,7 @@
 displayValue=`${resources.length} request not served via HTTP/2`;
 }
 
+
 const headings=[
 {key:'url',itemType:'url',text:'URL'},
 {key:'protocol',itemType:'text',text:'Protocol'}];
@@ -5595,7 +5698,7 @@
 
 module.exports=UsesHTTP2Audit;
 
-},{"../../computed/network-records.js":29,"../../lib/url-shim":"url","../../report/html/renderer/util.js":75,"../audit":3}],"../audits/dobetterweb/uses-passive-event-listeners":[function(require,module,exports){
+},{"../../computed/network-records.js":31,"../../lib/url-shim":"url","../../report/html/renderer/util.js":80,"../audit":3}],"../audits/dobetterweb/uses-passive-event-listeners":[function(require,module,exports){
 
 
 
@@ -5634,6 +5737,7 @@
 static audit(artifacts){
 const results=ViolationAudit.getViolationResults(artifacts,/passive event listener/);
 
+
 const headings=[
 {key:'url',itemType:'url',text:'URL'},
 {key:'label',itemType:'text',text:'Location'}];
@@ -5716,6 +5820,7 @@
 
 const tableRows=consoleRows.concat(runtimeExRows);
 
+
 const headings=[
 {key:'url',itemType:'url',text:'URL'},
 {key:'description',itemType:'code',text:'Description'}];
@@ -5787,7 +5892,7 @@
 
 module.exports=FinalScreenshot;
 
-},{"../computed/screenshots.js":31,"../lib/lh-error":64,"./audit":3}],"../audits/font-display":[function(require,module,exports){
+},{"../computed/screenshots.js":33,"../lib/lh-error":67,"./audit":3}],"../audits/font-display":[function(require,module,exports){
 (function(__filename){
 
 
@@ -5798,7 +5903,7 @@
 
 const Audit=require('./audit');
 const URL=require('../lib/url-shim').URL;
-const PASSING_FONT_DISPLAY_REGEX=/block|fallback|optional|swap/;
+const PASSING_FONT_DISPLAY_REGEX=/^(block|fallback|optional|swap)$/;
 const CSS_URL_REGEX=/url\((.*?)\)/;
 const CSS_URL_GLOBAL_REGEX=new RegExp(CSS_URL_REGEX,'g');
 const i18n=require('../lib/i18n/i18n.js');
@@ -5848,11 +5953,13 @@
 const fontFaceDeclarations=newlinesStripped.match(/@font-face\s*{(.*?)}/g)||[];
 
 for(const declaration of fontFaceDeclarations){
-const rawFontDisplay=declaration.match(/font-display:(.*?);/);
+
+
+const rawFontDisplay=declaration.match(/font-display\s*:\s*(\w+)\s*(;|\})/);
 
 if(!rawFontDisplay)continue;
 
-const hasPassingFontDisplay=PASSING_FONT_DISPLAY_REGEX.test(rawFontDisplay[0]);
+const hasPassingFontDisplay=PASSING_FONT_DISPLAY_REGEX.test(rawFontDisplay[1]);
 if(!hasPassingFontDisplay)continue;
 
 
@@ -5914,6 +6021,7 @@
 
 });
 
+
 const headings=[
 {key:'url',itemType:'url',text:str_(i18n.UIStrings.columnURL)},
 {key:'wastedMs',itemType:'ms',text:str_(i18n.UIStrings.columnWastedMs)}];
@@ -5933,7 +6041,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/font-display.js");
-},{"../computed/network-records.js":29,"../lib/i18n/i18n.js":60,"../lib/sentry.js":70,"../lib/url-shim":"url","./audit":3}],"../audits/image-aspect-ratio":[function(require,module,exports){
+},{"../computed/network-records.js":31,"../lib/i18n/i18n.js":63,"../lib/sentry.js":74,"../lib/url-shim":"url","./audit":3}],"../audits/image-aspect-ratio":[function(require,module,exports){
 
 
 
@@ -6029,6 +6137,7 @@
 if(!processed.doRatiosMatch)results.push(processed);
 });
 
+
 const headings=[
 {key:'url',itemType:'thumbnail',text:''},
 {key:'url',itemType:'url',text:'URL'},
@@ -6083,7 +6192,7 @@
 description:'Browsers can proactively prompt users to add your app to their homescreen, '+
 'which can lead to higher engagement. '+
 '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/install-prompt).',
-requiredArtifacts:['URL','Manifest']};
+requiredArtifacts:['URL','WebAppManifest']};
 
 }
 
@@ -6128,7 +6237,7 @@
 
 
 static async audit_(artifacts,context){
-const manifestValues=await ManifestValues.request(artifacts.Manifest,context);
+const manifestValues=await ManifestValues.request(artifacts.WebAppManifest,context);
 const manifestFailures=InstallableManifest.assessManifest(manifestValues);
 
 return{
@@ -6207,6 +6316,7 @@
 
 const items=Array.from(new Set(insecureURLs)).map(url=>({url}));
 
+
 const headings=[
 {key:'url',itemType:'url',text:'Insecure URL'}];
 
@@ -6225,7 +6335,7 @@
 
 module.exports=HTTPS;
 
-},{"../computed/network-records.js":29,"../lib/url-shim":"url","../report/html/renderer/util":75,"./audit":3}],"../audits/load-fast-enough-for-pwa":[function(require,module,exports){
+},{"../computed/network-records.js":31,"../lib/url-shim":"url","../report/html/renderer/util":80,"./audit":3}],"../audits/load-fast-enough-for-pwa":[function(require,module,exports){
 (function(__filename){
 
 
@@ -6333,7 +6443,70 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/load-fast-enough-for-pwa.js");
-},{"../computed/metrics/interactive.js":18,"../config/constants":37,"../lib/i18n/i18n.js":60,"./audit":3,"lodash.isequal":115}],"../audits/mainthread-work-breakdown":[function(require,module,exports){
+},{"../computed/metrics/interactive.js":18,"../config/constants":40,"../lib/i18n/i18n.js":63,"./audit":3,"lodash.isequal":121}],"../audits/main-thread-tasks":[function(require,module,exports){
+
+
+
+
+
+'use strict';
+
+const Audit=require('./audit.js');
+const MainThreadTasksComputed=require('../computed/main-thread-tasks.js');
+
+class MainThreadTasks extends Audit{
+
+
+
+static get meta(){
+return{
+id:'main-thread-tasks',
+scoreDisplayMode:Audit.SCORING_MODES.INFORMATIVE,
+title:'Tasks',
+description:'Lists the toplevel main thread tasks that executed during page load.',
+requiredArtifacts:['traces']};
+
+}
+
+
+
+
+
+
+static async audit(artifacts,context){
+const trace=artifacts.traces[Audit.DEFAULT_PASS];
+const tasks=await MainThreadTasksComputed.request(trace,context);
+
+const results=tasks.
+filter(task=>task.duration>5&&task.parent).
+map(task=>{
+return{
+type:task.group.id,
+duration:task.duration,
+startTime:task.startTime};
+
+});
+
+
+const headings=[
+{key:'type',itemType:'text',text:'Task Type'},
+{key:'startTime',itemType:'ms',granularity:1,text:'Start Time'},
+{key:'duration',itemType:'ms',granularity:1,text:'End Time'}];
+
+
+const tableDetails=Audit.makeTableDetails(headings,results);
+
+return{
+score:1,
+rawValue:results.length,
+details:tableDetails};
+
+}}
+
+
+module.exports=MainThreadTasks;
+
+},{"../computed/main-thread-tasks.js":12,"./audit.js":3}],"../audits/mainthread-work-breakdown":[function(require,module,exports){
 (function(__filename){
 
 
@@ -6442,6 +6615,7 @@
 
 });
 
+
 const headings=[
 {key:'groupLabel',itemType:'text',text:str_(UIStrings.columnCategory)},
 {key:'duration',itemType:'ms',granularity:1,text:str_(i18n.UIStrings.columnTimeSpent)}];
@@ -6469,7 +6643,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/mainthread-work-breakdown.js");
-},{"../computed/main-thread-tasks.js":12,"../lib/i18n/i18n.js":60,"../lib/task-groups":72,"./audit":3}],"../audits/manual/pwa-cross-browser":[function(require,module,exports){
+},{"../computed/main-thread-tasks.js":12,"../lib/i18n/i18n.js":63,"../lib/task-groups":77,"./audit":3}],"../audits/manual/pwa-cross-browser":[function(require,module,exports){
 
 
 
@@ -6640,7 +6814,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/metrics/estimated-input-latency.js");
-},{"../../computed/metrics/estimated-input-latency.js":14,"../../lib/i18n/i18n.js":60,"../audit":3}],"../audits/metrics/first-contentful-paint-3g":[function(require,module,exports){
+},{"../../computed/metrics/estimated-input-latency.js":14,"../../lib/i18n/i18n.js":63,"../audit":3}],"../audits/metrics/first-contentful-paint-3g":[function(require,module,exports){
 
 
 
@@ -6650,13 +6824,8 @@
 
 const Audit=require('../audit.js');
 const regular3G=require('../../config/constants.js').throttling.mobileRegluar3G;
-const i18n=require('../../lib/i18n/i18n.js');
-const FCP=require('./first-contentful-paint.js');
 const ComputedFcp=require('../../computed/metrics/first-contentful-paint.js');
 
-const i18nFilename=require.resolve('./first-contentful-paint.js');
-const str_=i18n.createMessageInstanceIdFn(i18nFilename,FCP.UIStrings);
-
 class FirstContentfulPaint3G extends Audit{
 
 
@@ -6664,8 +6833,9 @@
 static get meta(){
 return{
 id:'first-contentful-paint-3g',
-title:str_(FCP.UIStrings.title),
-description:str_(FCP.UIStrings.description),
+title:'First Contentful Paint (3G)',
+description:'First Contentful Paint 3G marks the time at which the first text or image is '+
+`painted while on a 3G network. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/first-contentful-paint).`,
 scoreDisplayMode:Audit.SCORING_MODES.NUMERIC,
 requiredArtifacts:['traces','devtoolsLogs']};
 
@@ -6703,14 +6873,14 @@
 context.options.scoreMedian),
 
 rawValue:metricResult.timing,
-displayValue:str_(i18n.UIStrings.seconds,{timeInMs:metricResult.timing})};
+displayValue:`${metricResult.timing}\xa0ms`};
 
 }}
 
 
 module.exports=FirstContentfulPaint3G;
 
-},{"../../computed/metrics/first-contentful-paint.js":15,"../../config/constants.js":37,"../../lib/i18n/i18n.js":60,"../audit.js":3,"./first-contentful-paint.js":"../audits/metrics/first-contentful-paint"}],"../audits/metrics/first-contentful-paint":[function(require,module,exports){
+},{"../../computed/metrics/first-contentful-paint.js":15,"../../config/constants.js":40,"../audit.js":3}],"../audits/metrics/first-contentful-paint":[function(require,module,exports){
 (function(__filename){
 
 
@@ -6787,7 +6957,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/metrics/first-contentful-paint.js");
-},{"../../computed/metrics/first-contentful-paint.js":15,"../../lib/i18n/i18n.js":60,"../audit":3}],"../audits/metrics/first-cpu-idle":[function(require,module,exports){
+},{"../../computed/metrics/first-contentful-paint.js":15,"../../lib/i18n/i18n.js":63,"../audit":3}],"../audits/metrics/first-cpu-idle":[function(require,module,exports){
 (function(__filename){
 
 
@@ -6868,7 +7038,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/metrics/first-cpu-idle.js");
-},{"../../computed/metrics/first-cpu-idle.js":16,"../../lib/i18n/i18n.js":60,"../audit":3}],"../audits/metrics/first-meaningful-paint":[function(require,module,exports){
+},{"../../computed/metrics/first-cpu-idle.js":16,"../../lib/i18n/i18n.js":63,"../audit":3}],"../audits/metrics/first-meaningful-paint":[function(require,module,exports){
 (function(__filename){
 
 
@@ -6948,7 +7118,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/metrics/first-meaningful-paint.js");
-},{"../../computed/metrics/first-meaningful-paint.js":17,"../../lib/i18n/i18n.js":60,"../audit":3}],"../audits/metrics/interactive":[function(require,module,exports){
+},{"../../computed/metrics/first-meaningful-paint.js":17,"../../lib/i18n/i18n.js":63,"../audit":3}],"../audits/metrics/interactive":[function(require,module,exports){
 (function(__filename){
 
 
@@ -7043,7 +7213,87 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/metrics/interactive.js");
-},{"../../computed/metrics/interactive.js":18,"../../lib/i18n/i18n.js":60,"../audit":3}],"../audits/metrics/speed-index":[function(require,module,exports){
+},{"../../computed/metrics/interactive.js":18,"../../lib/i18n/i18n.js":63,"../audit":3}],"../audits/metrics/max-potential-fid":[function(require,module,exports){
+(function(__filename){
+
+
+
+
+
+'use strict';
+
+const Audit=require('../audit');
+const ComputedFid=require('../../computed/metrics/max-potential-fid.js');
+const i18n=require('../../lib/i18n/i18n');
+
+const UIStrings={
+
+title:'Max Potential FID',
+
+description:'The potential First Input Delay that your users could experience is the '+
+'duration, in milliseconds, of the longest task.'};
+
+
+const str_=i18n.createMessageInstanceIdFn(__filename,UIStrings);
+
+
+
+
+
+
+class MaxPotentialFID extends Audit{
+
+
+
+static get meta(){
+return{
+id:'max-potential-fid',
+title:str_(UIStrings.title),
+description:str_(UIStrings.description),
+scoreDisplayMode:Audit.SCORING_MODES.NUMERIC,
+requiredArtifacts:['traces']};
+
+}
+
+
+
+
+static get defaultOptions(){
+return{
+
+scorePODR:100,
+scoreMedian:250};
+
+}
+
+
+
+
+
+
+static async audit(artifacts,context){
+const trace=artifacts.traces[Audit.DEFAULT_PASS];
+const devtoolsLog=artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
+const metricComputationData={trace,devtoolsLog,settings:context.settings};
+const metricResult=await ComputedFid.request(metricComputationData,context);
+
+return{
+score:Audit.computeLogNormalScore(
+metricResult.timing,
+context.options.scorePODR,
+context.options.scoreMedian),
+
+rawValue:metricResult.timing,
+displayValue:str_(i18n.UIStrings.ms,{timeInMs:metricResult.timing})};
+
+}}
+
+
+module.exports=MaxPotentialFID;
+module.exports.UIStrings=UIStrings;
+
+}).call(this,"/lighthouse-core/audits/metrics/max-potential-fid.js");
+},{"../../computed/metrics/max-potential-fid.js":27,"../../lib/i18n/i18n":63,"../audit":3}],"../audits/metrics/speed-index":[function(require,module,exports){
 (function(__filename){
 
 
@@ -7122,7 +7372,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/metrics/speed-index.js");
-},{"../../computed/metrics/speed-index.js":27,"../../lib/i18n/i18n.js":60,"../audit":3}],"../audits/metrics":[function(require,module,exports){
+},{"../../computed/metrics/speed-index.js":29,"../../lib/i18n/i18n.js":63,"../audit":3}],"../audits/metrics":[function(require,module,exports){
 
 
 
@@ -7164,13 +7414,25 @@
 const devtoolsLog=artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
 const metricComputationData={trace,devtoolsLog,settings:context.settings};
 
+
+
+
+
+
+
+
+
+const requestOrUndefined=(Artifact,artifact)=>{
+return Artifact.request(artifact,context).catch(_=>undefined);
+};
+
 const traceOfTab=await TraceOfTab.request(trace,context);
 const speedline=await Speedline.request(trace,context);
 const firstContentfulPaint=await FirstContentfulPaint.request(metricComputationData,context);
 const firstMeaningfulPaint=await FirstMeaningfulPaint.request(metricComputationData,context);
-const firstCPUIdle=await FirstCPUIdle.request(metricComputationData,context);
-const interactive=await Interactive.request(metricComputationData,context);
-const speedIndex=await SpeedIndex.request(metricComputationData,context);
+const firstCPUIdle=await requestOrUndefined(FirstCPUIdle,metricComputationData);
+const interactive=await requestOrUndefined(Interactive,metricComputationData);
+const speedIndex=await requestOrUndefined(SpeedIndex,metricComputationData);
 const estimatedInputLatency=await EstimatedInputLatency.request(metricComputationData,context);
 
 
@@ -7180,12 +7442,12 @@
 firstContentfulPaintTs:firstContentfulPaint.timestamp,
 firstMeaningfulPaint:firstMeaningfulPaint.timing,
 firstMeaningfulPaintTs:firstMeaningfulPaint.timestamp,
-firstCPUIdle:firstCPUIdle.timing,
-firstCPUIdleTs:firstCPUIdle.timestamp,
-interactive:interactive.timing,
-interactiveTs:interactive.timestamp,
-speedIndex:speedIndex.timing,
-speedIndexTs:speedIndex.timestamp,
+firstCPUIdle:firstCPUIdle&&firstCPUIdle.timing,
+firstCPUIdleTs:firstCPUIdle&&firstCPUIdle.timestamp,
+interactive:interactive&&interactive.timing,
+interactiveTs:interactive&&interactive.timestamp,
+speedIndex:speedIndex&&speedIndex.timing,
+speedIndexTs:speedIndex&&speedIndex.timestamp,
 estimatedInputLatency:estimatedInputLatency.timing,
 estimatedInputLatencyTs:estimatedInputLatency.timestamp,
 
@@ -7222,11 +7484,15 @@
 }
 
 
-const details={items:[metrics]};
+const details={
+type:'diagnostic',
+
+items:[metrics]};
+
 
 return{
 score:1,
-rawValue:interactive.timing,
+rawValue:interactive&&interactive.timing||0,
 details};
 
 }}
@@ -7268,11 +7534,9 @@
 
 
 
-
-
 module.exports=Metrics;
 
-},{"../computed/metrics/estimated-input-latency.js":14,"../computed/metrics/first-contentful-paint.js":15,"../computed/metrics/first-cpu-idle.js":16,"../computed/metrics/first-meaningful-paint.js":17,"../computed/metrics/interactive.js":18,"../computed/metrics/speed-index.js":27,"../computed/speedline.js":32,"../computed/trace-of-tab.js":33,"./audit":3}],"../audits/mixed-content":[function(require,module,exports){
+},{"../computed/metrics/estimated-input-latency.js":14,"../computed/metrics/first-contentful-paint.js":15,"../computed/metrics/first-cpu-idle.js":16,"../computed/metrics/first-meaningful-paint.js":17,"../computed/metrics/interactive.js":18,"../computed/metrics/speed-index.js":29,"../computed/speedline.js":34,"../computed/trace-of-tab.js":35,"./audit":3}],"../audits/mixed-content":[function(require,module,exports){
 
 
 
@@ -7404,6 +7668,7 @@
 const displayValue=`${Util.formatNumber(upgradeableResources.length)}
           ${upgradeableResources.length===1?'request':'requests'}`;
 
+
 const headings=[
 {key:'fullUrl',itemType:'url',text:'URL'}];
 
@@ -7424,7 +7689,7 @@
 
 module.exports=MixedContent;
 
-},{"../computed/network-records.js":29,"../lib/url-shim":"url","../report/html/renderer/util":75,"./audit":3}],"../audits/network-requests":[function(require,module,exports){
+},{"../computed/network-records.js":31,"../lib/url-shim":"url","../report/html/renderer/util":80,"./audit":3}],"../audits/network-requests":[function(require,module,exports){
 
 
 
@@ -7473,12 +7738,14 @@
 startTime:timeToMs(record.startTime),
 endTime:timeToMs(record.endTime),
 transferSize:record.transferSize,
+resourceSize:record.resourceSize,
 statusCode:record.statusCode,
 mimeType:record.mimeType,
 resourceType:record.resourceType};
 
 });
 
+
 const headings=[
 {key:'url',itemType:'url',text:'URL'},
 {key:'startTime',itemType:'ms',granularity:1,text:'Start Time'},
@@ -7490,6 +7757,13 @@
 granularity:1,
 text:'Transfer Size'},
 
+{
+key:'resourceSize',
+itemType:'bytes',
+displayUnit:'kb',
+granularity:1,
+text:'Resource Size'},
+
 {key:'statusCode',itemType:'text',text:'Status Code'},
 {key:'mimeType',itemType:'text',text:'MIME Type'},
 {key:'resourceType',itemType:'text',text:'Resource Type'}];
@@ -7508,7 +7782,174 @@
 
 module.exports=NetworkRequests;
 
-},{"../computed/network-records.js":29,"../lib/url-shim":"url","./audit":3}],"../audits/offline-start-url":[function(require,module,exports){
+},{"../computed/network-records.js":31,"../lib/url-shim":"url","./audit":3}],"../audits/network-rtt":[function(require,module,exports){
+(function(__filename){
+
+
+
+
+
+'use strict';
+
+const Audit=require('./audit.js');
+const i18n=require('../lib/i18n/i18n.js');
+const NetworkAnalysisComputed=require('../computed/network-analysis.js');
+
+const UIStrings={
+
+title:'Network Round Trip Times',
+
+description:'Network round trip times (RTT) have a large impact on performance. '+
+'If the RTT to an origin is high, it\'s an indication that servers closer to the user could '+
+'improve performance. [Learn more](https://hpbn.co/primer-on-latency-and-bandwidth/).'};
+
+
+const str_=i18n.createMessageInstanceIdFn(__filename,UIStrings);
+
+class NetworkRTT extends Audit{
+
+
+
+static get meta(){
+return{
+id:'network-rtt',
+scoreDisplayMode:Audit.SCORING_MODES.INFORMATIVE,
+title:str_(UIStrings.title),
+description:str_(UIStrings.description),
+requiredArtifacts:['devtoolsLogs']};
+
+}
+
+
+
+
+
+
+static async audit(artifacts,context){
+const devtoolsLog=artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
+const analysis=await NetworkAnalysisComputed.request(devtoolsLog,context);
+
+
+let maxRtt=0;
+const baseRtt=analysis.rtt;
+
+const results=[];
+for(const[origin,additionalRtt]of analysis.additionalRttByOrigin.entries()){
+
+if(!origin.startsWith('http'))continue;
+
+const rtt=additionalRtt+baseRtt;
+results.push({origin,rtt});
+maxRtt=Number.isFinite(rtt)?Math.max(rtt,maxRtt):maxRtt;
+}
+
+results.sort((a,b)=>b.rtt-a.rtt);
+
+
+const headings=[
+{key:'origin',itemType:'text',text:str_(i18n.UIStrings.columnURL)},
+{key:'rtt',itemType:'ms',granularity:1,text:str_(i18n.UIStrings.columnTimeSpent)}];
+
+
+const tableDetails=Audit.makeTableDetails(headings,results);
+
+return{
+score:1,
+rawValue:maxRtt,
+displayValue:str_(i18n.UIStrings.ms,{timeInMs:maxRtt}),
+details:tableDetails};
+
+}}
+
+
+module.exports=NetworkRTT;
+module.exports.UIStrings=UIStrings;
+
+}).call(this,"/lighthouse-core/audits/network-rtt.js");
+},{"../computed/network-analysis.js":30,"../lib/i18n/i18n.js":63,"./audit.js":3}],"../audits/network-server-latency":[function(require,module,exports){
+(function(__filename){
+
+
+
+
+
+'use strict';
+
+const Audit=require('./audit');
+const i18n=require('../lib/i18n/i18n.js');
+const NetworkAnalysisComputed=require('../computed/network-analysis.js');
+
+const UIStrings={
+
+title:'Server Backend Latencies',
+
+description:'Server latencies can impact web performance. '+
+'If the server latency of an origin is high, it\'s an indication the server is overloaded '+
+'or has poor backend performance. [Learn more](https://hpbn.co/primer-on-web-performance/#analyzing-the-resource-waterfall).'};
+
+
+const str_=i18n.createMessageInstanceIdFn(__filename,UIStrings);
+
+class NetworkServerLatency extends Audit{
+
+
+
+static get meta(){
+return{
+id:'network-server-latency',
+scoreDisplayMode:Audit.SCORING_MODES.INFORMATIVE,
+title:str_(UIStrings.title),
+description:str_(UIStrings.description),
+requiredArtifacts:['devtoolsLogs']};
+
+}
+
+
+
+
+
+
+static async audit(artifacts,context){
+const devtoolsLog=artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
+const analysis=await NetworkAnalysisComputed.request(devtoolsLog,context);
+
+
+let maxLatency=0;
+
+const results=[];
+for(const[origin,serverReponseTime]of analysis.serverResponseTimeByOrigin.entries()){
+
+if(!origin.startsWith('http'))continue;
+
+maxLatency=Math.max(serverReponseTime,maxLatency);
+results.push({origin,serverReponseTime});
+}
+
+results.sort((a,b)=>b.serverReponseTime-a.serverReponseTime);
+
+
+const headings=[
+{key:'origin',itemType:'text',text:str_(i18n.UIStrings.columnURL)},
+{key:'serverReponseTime',itemType:'ms',granularity:1,
+text:str_(i18n.UIStrings.columnTimeSpent)}];
+
+
+const tableDetails=Audit.makeTableDetails(headings,results);
+
+return{
+score:Math.max(1-maxLatency/500,0),
+rawValue:maxLatency,
+displayValue:str_(i18n.UIStrings.ms,{timeInMs:maxLatency}),
+details:tableDetails};
+
+}}
+
+
+module.exports=NetworkServerLatency;
+module.exports.UIStrings=UIStrings;
+
+}).call(this,"/lighthouse-core/audits/network-server-latency.js");
+},{"../computed/network-analysis.js":30,"../lib/i18n/i18n.js":63,"./audit":3}],"../audits/offline-start-url":[function(require,module,exports){
 
 
 
@@ -7528,7 +7969,7 @@
 title:'start_url responds with a 200 when offline',
 failureTitle:'start_url does not respond with a 200 when offline',
 description:'A service worker enables your web app to be reliable in unpredictable network conditions. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/http-200-when-offline).',
-requiredArtifacts:['Manifest','StartUrl']};
+requiredArtifacts:['WebAppManifest','StartUrl']};
 
 }
 
@@ -7540,7 +7981,7 @@
 
 
 const warnings=[];
-const manifest=artifacts.Manifest;
+const manifest=artifacts.WebAppManifest;
 if(manifest&&manifest.value&&manifest.value.start_url.warning){
 const manifestWarning=manifest.value.start_url.warning;
 warnings.push('We couldn\'t read the start_url from the manifest. As a result, the '+
@@ -7652,14 +8093,18 @@
 score,
 rawValue:values.roughEstimateOfTTI,
 displayValue:Util.formatMilliseconds(values.roughEstimateOfTTI),
-details:{items:[values]}};
+details:{
+type:'diagnostic',
+
+items:[values]}};
+
 
 }}
 
 
 module.exports=PredictivePerf;
 
-},{"../computed/metrics/lantern-estimated-input-latency.js":19,"../computed/metrics/lantern-first-contentful-paint.js":20,"../computed/metrics/lantern-first-cpu-idle.js":21,"../computed/metrics/lantern-first-meaningful-paint.js":22,"../computed/metrics/lantern-interactive.js":23,"../computed/metrics/lantern-speed-index.js":25,"../report/html/renderer/util":75,"./audit":3}],"../audits/redirects-http":[function(require,module,exports){
+},{"../computed/metrics/lantern-estimated-input-latency.js":19,"../computed/metrics/lantern-first-contentful-paint.js":20,"../computed/metrics/lantern-first-cpu-idle.js":21,"../computed/metrics/lantern-first-meaningful-paint.js":22,"../computed/metrics/lantern-interactive.js":23,"../computed/metrics/lantern-speed-index.js":26,"../report/html/renderer/util":80,"./audit":3}],"../audits/redirects-http":[function(require,module,exports){
 
 
 
@@ -7827,7 +8272,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/redirects.js");
-},{"../computed/main-resource.js":11,"../computed/metrics/lantern-interactive.js":23,"../computed/network-records.js":29,"../computed/trace-of-tab.js":33,"../lib/i18n/i18n.js":60,"./audit":3,"./byte-efficiency/byte-efficiency-audit":4}],"../audits/screenshot-thumbnails":[function(require,module,exports){
+},{"../computed/main-resource.js":11,"../computed/metrics/lantern-interactive.js":23,"../computed/network-records.js":31,"../computed/trace-of-tab.js":35,"../lib/i18n/i18n.js":63,"./audit":3,"./byte-efficiency/byte-efficiency-audit":4}],"../audits/screenshot-thumbnails":[function(require,module,exports){
 
 
 
@@ -7947,8 +8392,9 @@
 });
 }
 let base64Data;
-if(cachedThumbnails.has(frameForTimestamp)){
-base64Data=cachedThumbnails.get(frameForTimestamp);
+const cachedThumbnail=cachedThumbnails.get(frameForTimestamp);
+if(cachedThumbnail){
+base64Data=cachedThumbnail;
 }else{
 const imageData=frameForTimestamp.getParsedImage();
 const thumbnailImageData=ScreenshotThumbnails.scaleImageToThumbnail(imageData);
@@ -7976,7 +8422,8 @@
 
 module.exports=ScreenshotThumbnails;
 
-},{"../computed/metrics/interactive.js":18,"../computed/speedline.js":32,"../lib/lh-error":64,"./audit":3,"jpeg-js":111}],"../audits/seo/canonical":[function(require,module,exports){
+},{"../computed/metrics/interactive.js":18,"../computed/speedline.js":34,"../lib/lh-error":67,"./audit":3,"jpeg-js":117}],"../audits/seo/canonical":[function(require,module,exports){
+(function(__filename){
 
 
 
@@ -7989,6 +8436,32 @@
 const URL=require('../../lib/url-shim');
 const MainResource=require('../../computed/main-resource.js');
 const LINK_HEADER='link';
+const i18n=require('../../lib/i18n/i18n.js');
+
+const UIStrings={
+
+title:'Document has a valid `rel=canonical`',
+
+failureTitle:'Document does not have a valid `rel=canonical`',
+
+description:'Canonical links suggest which URL to show in search results. '+
+'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/canonical).',
+
+explanationConflict:'Multiple conflicting URLs ({urlList})',
+
+explanationInvalid:'Invalid URL ({url})',
+
+explanationRelative:'Relative URL ({url})',
+
+explanationPointsElsewhere:'Points to another `hreflang` location ({url})',
+
+explanationDifferentDomain:'Points to a different domain ({url})',
+
+explanationRoot:'Points to the domain\'s root URL (the homepage), '+
+'instead of an equivalent page of content'};
+
+
+const str_=i18n.createMessageInstanceIdFn(__filename,UIStrings);
 
 
 
@@ -8041,10 +8514,9 @@
 static get meta(){
 return{
 id:'canonical',
-title:'Document has a valid `rel=canonical`',
-failureTitle:'Document does not have a valid `rel=canonical`',
-description:'Canonical links suggest which URL to show in search results. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/canonical).',
+title:str_(UIStrings.title),
+failureTitle:str_(UIStrings.failureTitle),
+description:str_(UIStrings.description),
 requiredArtifacts:['Canonical','Hreflang','URL']};
 
 }
@@ -8097,7 +8569,7 @@
 if(canonicals.length>1){
 return{
 rawValue:false,
-explanation:`Multiple conflicting URLs (${canonicals.join(', ')})`};
+explanation:str_(UIStrings.explanationConflict,{urlList:canonicals.join(', ')})};
 
 }
 
@@ -8106,14 +8578,14 @@
 if(!isValidRelativeOrAbsoluteURL(canonical)){
 return{
 rawValue:false,
-explanation:`Invalid URL (${canonical})`};
+explanation:str_(UIStrings.explanationInvalid,{url:canonical})};
 
 }
 
 if(!URL.isValid(canonical)){
 return{
 rawValue:false,
-explanation:`Relative URL (${canonical})`};
+explanation:str_(UIStrings.explanationRelative,{url:canonical})};
 
 }
 
@@ -8124,7 +8596,7 @@
 baseURL.href!==canonicalURL.href){
 return{
 rawValue:false,
-explanation:`Points to another hreflang location (${baseURL.href})`};
+explanation:str_(UIStrings.explanationPointsElsewhere,{url:baseURL.href})};
 
 }
 
@@ -8133,7 +8605,7 @@
 if(getPrimaryDomain(canonicalURL)!==getPrimaryDomain(baseURL)){
 return{
 rawValue:false,
-explanation:`Points to a different domain (${canonicalURL})`};
+explanation:str_(UIStrings.explanationDifferentDomain,{url:canonicalURL})};
 
 }
 
@@ -8142,7 +8614,7 @@
 canonicalURL.pathname==='/'&&baseURL.pathname!=='/'){
 return{
 rawValue:false,
-explanation:'Points to a root of the same origin'};
+explanation:str_(UIStrings.explanationRoot)};
 
 }
 
@@ -8154,8 +8626,10 @@
 
 
 module.exports=Canonical;
+module.exports.UIStrings=UIStrings;
 
-},{"../../computed/main-resource.js":11,"../../lib/url-shim":"url","../audit":3,"http-link-header":95}],"../audits/seo/font-size":[function(require,module,exports){
+}).call(this,"/lighthouse-core/audits/seo/canonical.js");
+},{"../../computed/main-resource.js":11,"../../lib/i18n/i18n.js":63,"../../lib/url-shim":"url","../audit":3,"http-link-header":101}],"../audits/seo/font-size":[function(require,module,exports){
 (function(__filename){
 
 
@@ -8169,7 +8643,7 @@
 const URL=require('../../lib/url-shim');
 const i18n=require('../../lib/i18n/i18n.js');
 const Audit=require('../audit');
-const ViewportAudit=require('../viewport');
+const ComputedViewportMeta=require('../../computed/viewport-meta.js');
 const MINIMAL_PERCENTAGE_OF_LEGIBLE_TEXT=60;
 
 const UIStrings={
@@ -8180,7 +8654,15 @@
 
 description:'Font sizes less than 12px are too small to be legible and require mobile visitors to “pinch to zoom” in order to read. Strive to have >60% of page text ≥12px. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/font-sizes).',
 
-displayValue:'{decimalProportion, number, extendedPercent} legible text'};
+displayValue:'{decimalProportion, number, extendedPercent} legible text',
+
+explanationViewport:'Text is illegible because there\'s no viewport meta tag optimized '+
+'for mobile screens.',
+
+explanation:'{decimalProportion, number, extendedPercent} of text is too small.',
+
+explanationWithDisclaimer:'{decimalProportion, number, extendedPercent} of text is too '+
+'small (based on {decimalProportionVisited, number, extendedPercent} sample).'};
 
 
 const str_=i18n.createMessageInstanceIdFn(__filename,UIStrings);
@@ -8358,7 +8840,7 @@
 title:str_(UIStrings.title),
 failureTitle:str_(UIStrings.failureTitle),
 description:str_(UIStrings.description),
-requiredArtifacts:['FontSize','URL','MetaElements']};
+requiredArtifacts:['FontSize','URL','MetaElements','TestedAsMobileDevice']};
 
 }
 
@@ -8366,12 +8848,21 @@
 
 
 
-static audit(artifacts){
-const hasViewportSet=ViewportAudit.audit(artifacts).rawValue;
-if(!hasViewportSet){
+
+static async audit(artifacts,context){
+if(!artifacts.TestedAsMobileDevice){
+
+return{
+rawValue:true,
+notApplicable:true};
+
+}
+
+const viewportMeta=await ComputedViewportMeta.request(artifacts,context);
+if(!viewportMeta.isMobileOptimized){
 return{
 rawValue:false,
-explanation:'Text is illegible because of a missing viewport config'};
+explanation:str_(UIStrings.explanationViewport)};
 
 }
 
@@ -8394,6 +8885,7 @@
 (visitedTextLength-failingTextLength)/visitedTextLength*100;
 const pageUrl=artifacts.URL.finalUrl;
 
+
 const headings=[
 {key:'source',itemType:'url',text:'Source'},
 {key:'selector',itemType:'code',text:'Selector'},
@@ -8444,16 +8936,20 @@
 
 let explanation;
 if(!passed){
-const percentageOfFailingText=parseFloat((100-percentageOfPassingText).toFixed(2));
-let disclaimer='';
+const percentageOfFailingText=(100-percentageOfPassingText)/100;
 
 
 if(visitedTextLength<totalTextLength){
-const percentageOfVisitedText=visitedTextLength/totalTextLength*100;
-disclaimer=` (based on ${percentageOfVisitedText.toFixed()}% sample)`;
-}
+const percentageOfVisitedText=visitedTextLength/totalTextLength;
+explanation=str_(UIStrings.explanationWithDisclaimer,
+{
+decimalProportion:percentageOfFailingText,
+decimalProportionVisited:percentageOfVisitedText});
 
-explanation=`${percentageOfFailingText}% of text is too small${disclaimer}.`;
+}else{
+explanation=str_(UIStrings.explanation,
+{decimalProportion:percentageOfFailingText});
+}
 }
 
 return{
@@ -8469,8 +8965,8 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/seo/font-size.js");
-},{"../../lib/i18n/i18n.js":60,"../../lib/url-shim":"url","../audit":3,"../viewport":"../audits/viewport"}],"../audits/seo/hreflang":[function(require,module,exports){
-(function(global){
+},{"../../computed/viewport-meta.js":37,"../../lib/i18n/i18n.js":63,"../../lib/url-shim":"url","../audit":3}],"../audits/seo/hreflang":[function(require,module,exports){
+(function(global,__filename){
 
 
 
@@ -8484,6 +8980,20 @@
 const VALID_LANGS=importValidLangs();
 const LINK_HEADER='link';
 const NO_LANGUAGE='x-default';
+const i18n=require('../../lib/i18n/i18n.js');
+
+const UIStrings={
+
+title:'Document has a valid `hreflang`',
+
+failureTitle:'Document doesn\'t have a valid `hreflang`',
+
+description:'hreflang links tell search engines what version of a page they should '+
+'list in search results for a given language or region. [Learn more]'+
+'(https://developers.google.com/web/tools/lighthouse/audits/hreflang).'};
+
+
+const str_=i18n.createMessageInstanceIdFn(__filename,UIStrings);
 
 
 
@@ -8538,11 +9048,9 @@
 static get meta(){
 return{
 id:'hreflang',
-title:'Document has a valid `hreflang`',
-failureTitle:'Document doesn\'t have a valid `hreflang`',
-description:'hreflang links tell search engines what version of a page they should '+
-'list in search results for a given language or region. [Learn more]'+
-'(https://developers.google.com/web/tools/lighthouse/audits/hreflang).',
+title:str_(UIStrings.title),
+failureTitle:str_(UIStrings.failureTitle),
+description:str_(UIStrings.description),
 requiredArtifacts:['Hreflang','URL']};
 
 }
@@ -8578,6 +9086,7 @@
 filter(h=>h.name.toLowerCase()===LINK_HEADER&&!headerHasValidHreflangs(h.value)).
 forEach(h=>invalidHreflangs.push({source:`${h.name}: ${h.value}`}));
 
+
 const headings=[
 {key:'source',itemType:'code',text:'Source'}];
 
@@ -8592,9 +9101,11 @@
 
 
 module.exports=Hreflang;
+module.exports.UIStrings=UIStrings;
 
-}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"../../computed/main-resource.js":11,"../audit":3,"axe-core/lib/commons/utils/valid-langs.js":83,"http-link-header":95}],"../audits/seo/http-status-code":[function(require,module,exports){
+}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{},"/lighthouse-core/audits/seo/hreflang.js");
+},{"../../computed/main-resource.js":11,"../../lib/i18n/i18n.js":63,"../audit":3,"axe-core/lib/commons/utils/valid-langs.js":88,"http-link-header":101}],"../audits/seo/http-status-code":[function(require,module,exports){
+(function(__filename){
 
 
 
@@ -8606,6 +9117,20 @@
 const MainResource=require('../../computed/main-resource.js');
 const HTTP_UNSUCCESSFUL_CODE_LOW=400;
 const HTTP_UNSUCCESSFUL_CODE_HIGH=599;
+const i18n=require('../../lib/i18n/i18n.js');
+
+const UIStrings={
+
+title:'Page has successful HTTP status code',
+
+failureTitle:'Page has unsuccessful HTTP status code',
+
+description:'Pages with unsuccessful HTTP status codes may not be indexed properly. '+
+'[Learn more]'+
+'(https://developers.google.com/web/tools/lighthouse/audits/successful-http-code).'};
+
+
+const str_=i18n.createMessageInstanceIdFn(__filename,UIStrings);
 
 class HTTPStatusCode extends Audit{
 
@@ -8614,11 +9139,9 @@
 static get meta(){
 return{
 id:'http-status-code',
-title:'Page has successful HTTP status code',
-failureTitle:'Page has unsuccessful HTTP status code',
-description:'Pages with unsuccessful HTTP status codes may not be indexed properly. '+
-'[Learn more]'+
-'(https://developers.google.com/web/tools/lighthouse/audits/successful-http-code).',
+title:str_(UIStrings.title),
+failureTitle:str_(UIStrings.failureTitle),
+description:str_(UIStrings.description),
 requiredArtifacts:['devtoolsLogs','URL']};
 
 }
@@ -8652,8 +9175,11 @@
 
 
 module.exports=HTTPStatusCode;
+module.exports.UIStrings=UIStrings;
 
-},{"../../computed/main-resource.js":11,"../audit":3}],"../audits/seo/is-crawlable":[function(require,module,exports){
+}).call(this,"/lighthouse-core/audits/seo/http-status-code.js");
+},{"../../computed/main-resource.js":11,"../../lib/i18n/i18n.js":63,"../audit":3}],"../audits/seo/is-crawlable":[function(require,module,exports){
+(function(__filename){
 
 
 
@@ -8671,6 +9197,20 @@
 
 const ROBOTS_HEADER='x-robots-tag';
 const UNAVAILABLE_AFTER='unavailable_after';
+const i18n=require('../../lib/i18n/i18n.js');
+
+const UIStrings={
+
+title:'Page isn’t blocked from indexing',
+
+failureTitle:'Page is blocked from indexing',
+
+description:'Search engines are unable to include your pages in search results '+
+'if they don\'t have permission to crawl them. [Learn '+
+'more](https://developers.google.com/web/tools/lighthouse/audits/indexing).'};
+
+
+const str_=i18n.createMessageInstanceIdFn(__filename,UIStrings);
 
 
 
@@ -8720,11 +9260,9 @@
 static get meta(){
 return{
 id:'is-crawlable',
-title:'Page isn’t blocked from indexing',
-failureTitle:'Page is blocked from indexing',
-description:'Search engines are unable to include your pages in search results '+
-'if they don\'t have permission to crawl them. [Learn '+
-'more](https://developers.google.com/web/tools/lighthouse/audits/indexing).',
+title:str_(UIStrings.title),
+failureTitle:str_(UIStrings.failureTitle),
+description:str_(UIStrings.description),
 requiredArtifacts:['MetaElements','RobotsTxt','URL']};
 
 }
@@ -8776,6 +9314,7 @@
 }
 }
 
+
 const headings=[
 {key:'source',itemType:'code',text:'Blocking Directive Source'}];
 
@@ -8790,8 +9329,11 @@
 
 
 module.exports=IsCrawlable;
+module.exports.UIStrings=UIStrings;
 
-},{"../../computed/main-resource.js":11,"../../lib/url-shim":"url","../audit":3,"robots-parser":150}],"../audits/seo/link-text":[function(require,module,exports){
+}).call(this,"/lighthouse-core/audits/seo/is-crawlable.js");
+},{"../../computed/main-resource.js":11,"../../lib/i18n/i18n.js":63,"../../lib/url-shim":"url","../audit":3,"robots-parser":142}],"../audits/seo/link-text":[function(require,module,exports){
+(function(__filename){
 
 
 
@@ -8812,6 +9354,24 @@
 'more',
 'learn more']);
 
+const i18n=require('../../lib/i18n/i18n.js');
+
+const UIStrings={
+
+title:'Links have descriptive text',
+
+failureTitle:'Links do not have descriptive text',
+
+description:'Descriptive link text helps search engines understand your content. '+
+'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/descriptive-link-text).',
+
+displayValue:`{itemCount, plural,
+    =1 {1 link found}
+    other {# links found}
+    }`};
+
+
+const str_=i18n.createMessageInstanceIdFn(__filename,UIStrings);
 
 class LinkText extends Audit{
 
@@ -8820,11 +9380,10 @@
 static get meta(){
 return{
 id:'link-text',
-title:'Links have descriptive text',
-failureTitle:'Links do not have descriptive text',
-description:'Descriptive link text helps search engines understand your content. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/descriptive-link-text).',
-requiredArtifacts:['URL','CrawlableLinks']};
+title:str_(UIStrings.title),
+failureTitle:str_(UIStrings.failureTitle),
+description:str_(UIStrings.description),
+requiredArtifacts:['URL','AnchorElements']};
 
 }
 
@@ -8833,7 +9392,8 @@
 
 
 static audit(artifacts){
-const failingLinks=artifacts.CrawlableLinks.
+const failingLinks=artifacts.AnchorElements.
+filter(link=>link.href&&!link.rel.includes('nofollow')).
 filter(link=>{
 const href=link.href.toLowerCase();
 if(
@@ -8853,6 +9413,7 @@
 
 });
 
+
 const headings=[
 {key:'href',itemType:'url',text:'Link destination'},
 {key:'text',itemType:'text',text:'Link Text'}];
@@ -8862,8 +9423,7 @@
 let displayValue;
 
 if(failingLinks.length){
-displayValue=failingLinks.length>1?
-`${failingLinks.length} links found`:'1 link found';
+displayValue=str_(UIStrings.displayValue,{itemCount:failingLinks.length});
 }
 
 return{
@@ -8875,8 +9435,11 @@
 
 
 module.exports=LinkText;
+module.exports.UIStrings=UIStrings;
 
-},{"../../lib/url-shim":"url","../audit":3}],"../audits/seo/manual/mobile-friendly":[function(require,module,exports){
+}).call(this,"/lighthouse-core/audits/seo/link-text.js");
+},{"../../lib/i18n/i18n.js":63,"../../lib/url-shim":"url","../audit":3}],"../audits/seo/manual/structured-data":[function(require,module,exports){
+(function(__filename){
 
 
 
@@ -8885,35 +9448,16 @@
 'use strict';
 
 const ManualAudit=require('../../manual/manual-audit');
+const i18n=require('../../../lib/i18n/i18n.js');
+
+const UIStrings={
+
+description:'Run the [Structured Data Testing Tool](https://search.google.com/structured-data/testing-tool/) and the [Structured Data Linter](http://linter.structured-data.org/) to validate structured data. [Learn more](https://developers.google.com/search/docs/guides/mark-up-content).',
+
+title:'Structured data is valid'};
 
 
-
-
-
-class MobileFriendly extends ManualAudit{
-
-
-
-static get meta(){
-return Object.assign({
-id:'mobile-friendly',
-description:'Take the [Mobile-Friendly Test](https://search.google.com/test/mobile-friendly) to check for audits not covered by Lighthouse, like sizing tap targets appropriately. [Learn more](https://developers.google.com/search/mobile-sites/).',
-title:'Page is mobile friendly'},
-super.partialMeta);
-}}
-
-
-module.exports=MobileFriendly;
-
-},{"../../manual/manual-audit":5}],"../audits/seo/manual/structured-data":[function(require,module,exports){
-
-
-
-
-
-'use strict';
-
-const ManualAudit=require('../../manual/manual-audit');
+const str_=i18n.createMessageInstanceIdFn(__filename,UIStrings);
 
 
 
@@ -8926,15 +9470,18 @@
 static get meta(){
 return Object.assign({
 id:'structured-data',
-description:'Run the [Structured Data Testing Tool](https://search.google.com/structured-data/testing-tool/) and the [Structured Data Linter](http://linter.structured-data.org/) to validate structured data. [Learn more](https://developers.google.com/search/docs/guides/mark-up-content).',
-title:'Structured data is valid'},
+description:str_(UIStrings.description),
+title:str_(UIStrings.title)},
 super.partialMeta);
 }}
 
 
 module.exports=StructuredData;
+module.exports.UIStrings=UIStrings;
 
-},{"../../manual/manual-audit":5}],"../audits/seo/meta-description":[function(require,module,exports){
+}).call(this,"/lighthouse-core/audits/seo/manual/structured-data.js");
+},{"../../../lib/i18n/i18n.js":63,"../../manual/manual-audit":5}],"../audits/seo/meta-description":[function(require,module,exports){
+(function(__filename){
 
 
 
@@ -8943,6 +9490,22 @@
 'use strict';
 
 const Audit=require('../audit');
+const i18n=require('../../lib/i18n/i18n.js');
+
+const UIStrings={
+
+title:'Document has a meta description',
+
+failureTitle:'Document does not have a meta description',
+
+description:'Meta descriptions may be included in search results to concisely summarize '+
+'page content. '+
+'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/description).',
+
+explanation:'Description text is empty.'};
+
+
+const str_=i18n.createMessageInstanceIdFn(__filename,UIStrings);
 
 class Description extends Audit{
 
@@ -8951,11 +9514,9 @@
 static get meta(){
 return{
 id:'meta-description',
-title:'Document has a meta description',
-failureTitle:'Document does not have a meta description',
-description:'Meta descriptions may be included in search results to concisely summarize '+
-'page content. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/description).',
+title:str_(UIStrings.title),
+failureTitle:str_(UIStrings.failureTitle),
+description:str_(UIStrings.description),
 requiredArtifacts:['MetaElements']};
 
 }
@@ -8976,7 +9537,7 @@
 if(description.trim().length===0){
 return{
 rawValue:false,
-explanation:'Description text is empty.'};
+explanation:str_(UIStrings.explanation)};
 
 }
 
@@ -8987,8 +9548,11 @@
 
 
 module.exports=Description;
+module.exports.UIStrings=UIStrings;
 
-},{"../audit":3}],"../audits/seo/plugins":[function(require,module,exports){
+}).call(this,"/lighthouse-core/audits/seo/meta-description.js");
+},{"../../lib/i18n/i18n.js":63,"../audit":3}],"../audits/seo/plugins":[function(require,module,exports){
+(function(__filename){
 
 
 
@@ -9022,6 +9586,20 @@
 'source',
 'src']);
 
+const i18n=require('../../lib/i18n/i18n.js');
+
+const UIStrings={
+
+title:'Document avoids plugins',
+
+failureTitle:'Document uses plugins',
+
+description:'Search engines can\'t index plugin content, and '+
+'many devices restrict plugins or don\'t support them. '+
+'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/plugins).'};
+
+
+const str_=i18n.createMessageInstanceIdFn(__filename,UIStrings);
 
 
 
@@ -9064,11 +9642,9 @@
 static get meta(){
 return{
 id:'plugins',
-title:'Document avoids plugins',
-failureTitle:'Document uses plugins',
-description:'Search engines can\'t index plugin content, and '+
-'many devices restrict plugins or don\'t support them. '+
-'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/plugins).',
+title:str_(UIStrings.title),
+failureTitle:str_(UIStrings.failureTitle),
+description:str_(UIStrings.description),
 requiredArtifacts:['EmbeddedContent']};
 
 }
@@ -9131,6 +9707,7 @@
 
 });
 
+
 const headings=[
 {key:'source',itemType:'code',text:'Element source'}];
 
@@ -9145,8 +9722,11 @@
 
 
 module.exports=Plugins;
+module.exports.UIStrings=UIStrings;
 
-},{"../../lib/url-shim":"url","../audit":3}],"../audits/seo/robots-txt":[function(require,module,exports){
+}).call(this,"/lighthouse-core/audits/seo/plugins.js");
+},{"../../lib/i18n/i18n.js":63,"../../lib/url-shim":"url","../audit":3}],"../audits/seo/robots-txt":[function(require,module,exports){
+(function(__filename){
 
 
 
@@ -9180,6 +9760,28 @@
 'request-rate','visit-time','noindex']);
 
 const SITEMAP_VALID_PROTOCOLS=new Set(['https:','http:','ftp:']);
+const i18n=require('../../lib/i18n/i18n.js');
+
+const UIStrings={
+
+title:'robots.txt is valid',
+
+failureTitle:'robots.txt is not valid',
+
+description:'If your robots.txt file is malformed, crawlers may not be able to understand '+
+'how you want your website to be crawled or indexed.',
+
+displayValueHttpBadCode:'request for robots.txt returned HTTP status: {statusCode}',
+
+displayValueValidationError:`{itemCount, plural,
+    =1 {1 error found}
+    other {# errors found}
+    }`,
+
+explanation:'Lighthouse was unable to download a robots.txt file'};
+
+
+const str_=i18n.createMessageInstanceIdFn(__filename,UIStrings);
 
 
 
@@ -9310,10 +9912,9 @@
 static get meta(){
 return{
 id:'robots-txt',
-title:'robots.txt is valid',
-failureTitle:'robots.txt is not valid',
-description:'If your robots.txt file is malformed, crawlers may not be able to understand '+
-'how you want your website to be crawled or indexed.',
+title:str_(UIStrings.title),
+failureTitle:str_(UIStrings.failureTitle),
+description:str_(UIStrings.description),
 requiredArtifacts:['RobotsTxt']};
 
 }
@@ -9331,14 +9932,14 @@
 if(!status){
 return{
 rawValue:false,
-explanation:'Lighthouse was unable to download your robots.txt file'};
+explanation:str_(UIStrings.explanation)};
 
 }
 
 if(status>=HTTP_SERVER_ERROR_CODE_LOW){
 return{
 rawValue:false,
-displayValue:`request for robots.txt returned HTTP${status}`};
+displayValue:str_(UIStrings.displayValueHttpBadCode,{statusCode:status})};
 
 }else if(status>=HTTP_CLIENT_ERROR_CODE_LOW||content===''){
 return{
@@ -9354,6 +9955,7 @@
 
 const validationErrors=validateRobots(content);
 
+
 const headings=[
 {key:'index',itemType:'text',text:'Line #'},
 {key:'line',itemType:'code',text:'Content'},
@@ -9364,8 +9966,8 @@
 let displayValue;
 
 if(validationErrors.length){
-displayValue=validationErrors.length>1?
-`${validationErrors.length} errors found`:'1 error found';
+displayValue=
+str_(UIStrings.displayValueValidationError,{itemCount:validationErrors.length});
 }
 
 return{
@@ -9377,8 +9979,382 @@
 
 
 module.exports=RobotsTxt;
+module.exports.UIStrings=UIStrings;
 
-},{"../../lib/url-shim":"url","../audit":3}],"../audits/service-worker":[function(require,module,exports){
+}).call(this,"/lighthouse-core/audits/seo/robots-txt.js");
+},{"../../lib/i18n/i18n.js":63,"../../lib/url-shim":"url","../audit":3}],"../audits/seo/tap-targets":[function(require,module,exports){
+(function(__filename){
+
+
+
+
+
+'use strict';
+
+
+
+
+
+const Audit=require('../audit');
+const ComputedViewportMeta=require('../../computed/viewport-meta.js');
+const{
+rectsTouchOrOverlap,
+getRectOverlapArea,
+getRectAtCenter,
+allRectsContainedWithinEachOther,
+getLargestRect,
+getBoundingRectWithPadding}=
+require('../../lib/rect-helpers');
+const{getTappableRectsFromClientRects}=require('../../lib/tappable-rects');
+const i18n=require('../../lib/i18n/i18n.js');
+
+const UIStrings={
+
+title:'Tap targets are sized appropriately',
+
+failureTitle:'Tap targets are not sized appropriately',
+
+description:'Interactive elements like buttons and links should be large enough (48x48px), and have enough space around them, to be easy enough to tap without overlapping onto other elements. [Learn more](https://developers.google.com/web/fundamentals/accessibility/accessible-styles#multi-device_responsive_design).',
+
+tapTargetHeader:'Tap Target',
+
+sizeHeader:'Size',
+
+overlappingTargetHeader:'Overlapping Target',
+
+
+explanationViewportMetaNotOptimized:'Tap targets are too small because there\'s no viewport meta tag optimized for mobile screens',
+
+displayValue:'{decimalProportion, number, percent} appropriately sized tap targets'};
+
+
+const str_=i18n.createMessageInstanceIdFn(__filename,UIStrings);
+
+const FINGER_SIZE_PX=48;
+
+
+const MAX_ACCEPTABLE_OVERLAP_SCORE_RATIO=0.25;
+
+
+
+
+
+
+
+function getBoundedTapTargets(targets){
+return targets.map(tapTarget=>{
+return{
+tapTarget,
+paddedBoundsRect:getBoundingRectWithPadding(tapTarget.clientRects,FINGER_SIZE_PX)};
+
+});
+}
+
+
+
+
+function clientRectBelowMinimumSize(cr){
+return cr.width<FINGER_SIZE_PX||cr.height<FINGER_SIZE_PX;
+}
+
+
+
+
+
+
+function getTooSmallTargets(targets){
+return targets.filter(target=>{
+return target.tapTarget.clientRects.every(clientRectBelowMinimumSize);
+});
+}
+
+
+
+
+
+
+function getAllOverlapFailures(tooSmallTargets,allTargets){
+
+const failures=[];
+
+tooSmallTargets.forEach(target=>{
+
+const tappableRects=getTappableRectsFromClientRects(target.tapTarget.clientRects);
+
+for(const maybeOverlappingTarget of allTargets){
+if(maybeOverlappingTarget===target){
+
+continue;
+}
+
+if(!rectsTouchOrOverlap(target.paddedBoundsRect,maybeOverlappingTarget.paddedBoundsRect)){
+
+continue;
+}
+
+if(target.tapTarget.href===maybeOverlappingTarget.tapTarget.href){
+const isHttpOrHttpsLink=/https?:\/\//.test(target.tapTarget.href);
+if(isHttpOrHttpsLink){
+
+continue;
+}
+}
+
+const maybeOverlappingRects=maybeOverlappingTarget.tapTarget.clientRects;
+if(allRectsContainedWithinEachOther(tappableRects,maybeOverlappingRects)){
+
+
+
+
+continue;
+}
+
+const rectFailure=getOverlapFailureForTargetPair(tappableRects,maybeOverlappingRects);
+if(rectFailure){
+failures.push({
+...rectFailure,
+tapTarget:target.tapTarget,
+overlappingTarget:maybeOverlappingTarget.tapTarget});
+
+}
+}
+});
+
+return failures;
+}
+
+
+
+
+
+
+function getOverlapFailureForTargetPair(tappableRects,maybeOverlappingRects){
+
+let greatestFailure=null;
+
+for(const targetCR of tappableRects){
+const fingerRect=getRectAtCenter(targetCR,FINGER_SIZE_PX);
+
+
+const tapTargetScore=getRectOverlapArea(fingerRect,targetCR);
+
+for(const maybeOverlappingCR of maybeOverlappingRects){
+const overlappingTargetScore=getRectOverlapArea(fingerRect,maybeOverlappingCR);
+
+const overlapScoreRatio=overlappingTargetScore/tapTargetScore;
+if(overlapScoreRatio<MAX_ACCEPTABLE_OVERLAP_SCORE_RATIO){
+
+
+continue;
+}
+
+
+if(!greatestFailure||overlapScoreRatio>greatestFailure.overlapScoreRatio){
+greatestFailure={
+overlapScoreRatio,
+tapTargetScore,
+overlappingTargetScore};
+
+}
+}
+}
+return greatestFailure;
+}
+
+
+
+
+
+
+function mergeSymmetricFailures(overlapFailures){
+
+const failuresAfterMerging=[];
+
+overlapFailures.forEach((failure,overlapFailureIndex)=>{
+const symmetricFailure=overlapFailures.find(f=>
+f.tapTarget===failure.overlappingTarget&&
+f.overlappingTarget===failure.tapTarget);
+
+
+if(!symmetricFailure){
+failuresAfterMerging.push(failure);
+return;
+}
+
+const{overlapScoreRatio:failureOSR}=failure;
+const{overlapScoreRatio:symmetricOSR}=symmetricFailure;
+
+
+
+
+if(failureOSR>symmetricOSR||
+failureOSR===symmetricOSR&&
+overlapFailureIndex<overlapFailures.indexOf(symmetricFailure))
+{
+failuresAfterMerging.push(failure);
+}
+});
+
+return failuresAfterMerging;
+}
+
+
+
+
+
+function getTableItems(overlapFailures){
+const tableItems=overlapFailures.map(failure=>{
+const largestCR=getLargestRect(failure.tapTarget.clientRects);
+const width=Math.floor(largestCR.width);
+const height=Math.floor(largestCR.height);
+const size=width+'x'+height;
+return{
+tapTarget:targetToTableNode(failure.tapTarget),
+overlappingTarget:targetToTableNode(failure.overlappingTarget),
+tapTargetScore:failure.tapTargetScore,
+overlappingTargetScore:failure.overlappingTargetScore,
+overlapScoreRatio:failure.overlapScoreRatio,
+size,
+width,
+height};
+
+});
+
+tableItems.sort((a,b)=>{
+return b.overlapScoreRatio-a.overlapScoreRatio;
+});
+
+return tableItems;
+}
+
+
+
+
+
+function targetToTableNode(target){
+return{
+type:'node',
+snippet:target.snippet,
+path:target.path,
+selector:target.selector};
+
+}
+
+class TapTargets extends Audit{
+
+
+
+static get meta(){
+return{
+id:'tap-targets',
+title:str_(UIStrings.title),
+failureTitle:str_(UIStrings.failureTitle),
+description:str_(UIStrings.description),
+requiredArtifacts:['MetaElements','TapTargets','TestedAsMobileDevice']};
+
+}
+
+
+
+
+
+
+static async audit(artifacts,context){
+if(!artifacts.TestedAsMobileDevice){
+
+
+return{
+rawValue:true,
+notApplicable:true};
+
+}
+
+const viewportMeta=await ComputedViewportMeta.request(artifacts,context);
+if(!viewportMeta.isMobileOptimized){
+return{
+rawValue:false,
+explanation:str_(UIStrings.explanationViewportMetaNotOptimized)};
+
+}
+
+
+const boundedTapTargets=getBoundedTapTargets(artifacts.TapTargets);
+
+const tooSmallTargets=getTooSmallTargets(boundedTapTargets);
+const overlapFailures=getAllOverlapFailures(tooSmallTargets,boundedTapTargets);
+const overlapFailuresForDisplay=mergeSymmetricFailures(overlapFailures);
+const tableItems=getTableItems(overlapFailuresForDisplay);
+
+
+const headings=[
+{key:'tapTarget',itemType:'node',text:str_(UIStrings.tapTargetHeader)},
+{key:'size',itemType:'text',text:str_(UIStrings.sizeHeader)},
+{key:'overlappingTarget',itemType:'node',text:str_(UIStrings.overlappingTargetHeader)}];
+
+
+const details=Audit.makeTableDetails(headings,tableItems);
+
+const tapTargetCount=artifacts.TapTargets.length;
+const failingTapTargetCount=new Set(overlapFailures.map(f=>f.tapTarget)).size;
+const passingTapTargetCount=tapTargetCount-failingTapTargetCount;
+
+let score=1;
+let passingTapTargetRatio=1;
+if(failingTapTargetCount>0){
+passingTapTargetRatio=passingTapTargetCount/tapTargetCount;
+
+
+score=passingTapTargetRatio*0.89;
+}
+const displayValue=str_(UIStrings.displayValue,{decimalProportion:passingTapTargetRatio});
+
+return{
+rawValue:tableItems.length===0,
+score,
+details,
+displayValue};
+
+}}
+
+
+TapTargets.FINGER_SIZE_PX=FINGER_SIZE_PX;
+
+module.exports=TapTargets;
+module.exports.UIStrings=UIStrings;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}).call(this,"/lighthouse-core/audits/seo/tap-targets.js");
+},{"../../computed/viewport-meta.js":37,"../../lib/i18n/i18n.js":63,"../../lib/rect-helpers":73,"../../lib/tappable-rects":76,"../audit":3}],"../audits/service-worker":[function(require,module,exports){
 
 
 
@@ -9401,7 +10377,7 @@
 description:'The service worker is the technology that enables your app to use many '+
 'Progressive Web App features, such as offline, add to homescreen, and push '+
 'notifications. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/registered-service-worker).',
-requiredArtifacts:['URL','ServiceWorker','Manifest']};
+requiredArtifacts:['URL','ServiceWorker','WebAppManifest']};
 
 }
 
@@ -9488,7 +10464,8 @@
 
 }
 
-const startUrlFailure=ServiceWorker.checkStartUrl(artifacts.Manifest,controllingScopeUrl);
+const startUrlFailure=ServiceWorker.checkStartUrl(artifacts.WebAppManifest,
+controllingScopeUrl);
 if(startUrlFailure){
 return{
 rawValue:false,
@@ -9541,7 +10518,7 @@
 description:'A themed splash screen ensures a high-quality experience when '+
 'users launch your app from their homescreens. [Learn '+
 'more](https://developers.google.com/web/tools/lighthouse/audits/custom-splash-screen).',
-requiredArtifacts:['Manifest']};
+requiredArtifacts:['WebAppManifest']};
 
 }
 
@@ -9580,7 +10557,7 @@
 
 const failures=[];
 
-const manifestValues=await ManifestValues.request(artifacts.Manifest,context);
+const manifestValues=await ManifestValues.request(artifacts.WebAppManifest,context);
 SplashScreen.assessManifest(manifestValues,failures);
 
 return{
@@ -9625,7 +10602,7 @@
 failureTitle:'Does not set an address-bar theme color',
 description:'The browser address bar can be themed to match your site. '+
 '[Learn more](https://developers.google.com/web/tools/lighthouse/audits/address-bar).',
-requiredArtifacts:['Manifest','MetaElements']};
+requiredArtifacts:['WebAppManifest','MetaElements']};
 
 }
 
@@ -9675,7 +10652,7 @@
 const failures=[];
 
 const themeColorMeta=artifacts.MetaElements.find(meta=>meta.name==='theme-color');
-const manifestValues=await ManifestValues.request(artifacts.Manifest,context);
+const manifestValues=await ManifestValues.request(artifacts.WebAppManifest,context);
 ThemedOmnibox.assessManifest(manifestValues,failures);
 ThemedOmnibox.assessMetaThemecolor(themeColorMeta,failures);
 
@@ -9689,7 +10666,7 @@
 
 module.exports=ThemedOmnibox;
 
-},{"../computed/manifest-values.js":13,"./multi-check-audit":6,"cssstyle/lib/parsers":91}],"../audits/time-to-first-byte":[function(require,module,exports){
+},{"../computed/manifest-values.js":13,"./multi-check-audit":6,"cssstyle/lib/parsers":97}],"../audits/time-to-first-byte":[function(require,module,exports){
 (function(__filename){
 
 
@@ -9779,7 +10756,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/time-to-first-byte.js");
-},{"../computed/main-resource.js":11,"../lib/i18n/i18n.js":60,"./audit":3}],"../audits/user-timings":[function(require,module,exports){
+},{"../computed/main-resource.js":11,"../lib/i18n/i18n.js":63,"./audit":3}],"../audits/user-timings":[function(require,module,exports){
 (function(__filename){
 
 
@@ -9877,6 +10854,7 @@
 }
 });
 
+
 const headings=[
 {key:'name',itemType:'text',text:str_(UIStrings.columnName)},
 {key:'timingType',itemType:'text',text:str_(UIStrings.columnType)},
@@ -9911,7 +10889,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/user-timings.js");
-},{"../computed/user-timings.js":34,"../lib/i18n/i18n.js":60,"./audit":3}],"../audits/uses-rel-preconnect":[function(require,module,exports){
+},{"../computed/user-timings.js":36,"../lib/i18n/i18n.js":63,"./audit":3}],"../audits/uses-rel-preconnect":[function(require,module,exports){
 (function(__filename){
 
 
@@ -10120,7 +11098,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/uses-rel-preconnect.js");
-},{"../computed/load-simulator.js":10,"../computed/main-resource.js":11,"../computed/network-records.js":29,"../lib/i18n/i18n.js":60,"../lib/url-shim.js":"url","./audit":3,"./byte-efficiency/byte-efficiency-audit":4}],"../audits/uses-rel-preload":[function(require,module,exports){
+},{"../computed/load-simulator.js":10,"../computed/main-resource.js":11,"../computed/network-records.js":31,"../lib/i18n/i18n.js":63,"../lib/url-shim.js":"url","./audit":3,"./byte-efficiency/byte-efficiency-audit":4}],"../audits/uses-rel-preload":[function(require,module,exports){
 (function(__filename){
 
 
@@ -10363,7 +11341,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/uses-rel-preload.js");
-},{"../computed/critical-request-chains.js":9,"../computed/load-simulator.js":10,"../computed/main-resource.js":11,"../computed/page-dependency-graph.js":30,"../lib/i18n/i18n.js":60,"../lib/url-shim":"url","./audit":3,"./byte-efficiency/byte-efficiency-audit":4}],"../audits/viewport":[function(require,module,exports){
+},{"../computed/critical-request-chains.js":9,"../computed/load-simulator.js":10,"../computed/main-resource.js":11,"../computed/page-dependency-graph.js":32,"../lib/i18n/i18n.js":63,"../lib/url-shim":"url","./audit":3,"./byte-efficiency/byte-efficiency-audit":4}],"../audits/viewport":[function(require,module,exports){
 
 
 
@@ -10372,7 +11350,7 @@
 'use strict';
 
 const Audit=require('./audit');
-const Parser=require('metaviewport-parser');
+const ComputedViewportMeta=require('../computed/viewport-meta.js');
 
 class Viewport extends Audit{
 
@@ -10394,38 +11372,27 @@
 
 
 
-static audit(artifacts){
-const viewportMeta=artifacts.MetaElements.find(meta=>meta.name==='viewport');
-if(!viewportMeta){
+
+static async audit(artifacts,context){
+const viewportMeta=await ComputedViewportMeta.request(artifacts,context);
+
+if(!viewportMeta.hasViewportTag){
 return{
-explanation:'No viewport meta tag found',
-rawValue:false};
+rawValue:false,
+explanation:'No viewport meta tag found'};
 
 }
 
-const warnings=[];
-const parsedProps=Parser.parseMetaViewPortContent(viewportMeta.content||'');
-
-if(Object.keys(parsedProps.unknownProperties).length){
-warnings.push(`Invalid properties found: ${JSON.stringify(parsedProps.unknownProperties)}`);
-}
-if(Object.keys(parsedProps.invalidValues).length){
-warnings.push(`Invalid values found: ${JSON.stringify(parsedProps.invalidValues)}`);
-}
-
-const viewportProps=parsedProps.validProperties;
-const hasMobileViewport=viewportProps.width||viewportProps['initial-scale'];
-
 return{
-rawValue:!!hasMobileViewport,
-warnings};
+rawValue:viewportMeta.isMobileOptimized,
+warnings:viewportMeta.parserWarnings};
 
 }}
 
 
 module.exports=Viewport;
 
-},{"./audit":3,"metaviewport-parser":118}],"../audits/without-javascript":[function(require,module,exports){
+},{"../computed/viewport-meta.js":37,"./audit":3}],"../audits/without-javascript":[function(require,module,exports){
 
 
 
@@ -10560,6 +11527,7 @@
 resultTypes:['violations','inapplicable'],
 rules:{
 'tabindex':{enabled:true},
+'accesskeys':{enabled:true},
 'table-fake-caption':{enabled:false},
 'td-has-header':{enabled:false},
 'marquee':{enabled:false},
@@ -10614,7 +11582,89 @@
 
 module.exports=Accessibility;
 
-},{"../../lib/page-functions":69,"./gatherer":45}],"../gather/gatherers/cache-contents":[function(require,module,exports){
+},{"../../lib/page-functions":72,"./gatherer":48}],"../gather/gatherers/anchor-elements":[function(require,module,exports){
+
+
+
+
+
+'use strict';
+
+const Gatherer=require('./gatherer.js');
+const pageFunctions=require('../../lib/page-functions.js');
+
+
+
+
+
+
+
+
+
+
+
+
+
+function collectAnchorElements(){
+
+const resolveURLOrEmpty=url=>{
+try{
+return new URL(url,window.location.href).href;
+}catch(_){
+return'';
+}
+};
+
+
+
+const anchorElements=getElementsInDocument('a');
+
+return anchorElements.map(node=>{
+
+const outerHTML=getOuterHTMLSnippet(node);
+
+if(node instanceof HTMLAnchorElement){
+return{
+href:node.href,
+text:node.innerText,
+rel:node.rel,
+target:node.target,
+outerHTML};
+
+}
+
+return{
+href:resolveURLOrEmpty(node.href.baseVal),
+text:node.textContent||'',
+rel:'',
+target:node.target.baseVal||'',
+outerHTML};
+
+});
+}
+
+class AnchorElements extends Gatherer{
+
+
+
+
+async afterPass(passContext){
+const driver=passContext.driver;
+const expression=`(() => {
+      ${pageFunctions.getOuterHTMLSnippetString};
+      ${pageFunctions.getElementsInDocumentString};
+
+      return (${collectAnchorElements})();
+    })()`;
+
+
+return driver.evaluateAsync(expression,{useIsolation:true});
+}}
+
+
+module.exports=AnchorElements;
+
+},{"../../lib/page-functions.js":72,"./gatherer.js":48}],"../gather/gatherers/cache-contents":[function(require,module,exports){
 
 
 
@@ -10675,7 +11725,7 @@
 
 module.exports=CacheContents;
 
-},{"./gatherer":45}],"../gather/gatherers/chrome-console-messages":[function(require,module,exports){
+},{"./gatherer":48}],"../gather/gatherers/chrome-console-messages":[function(require,module,exports){
 
 
 
@@ -10731,7 +11781,7 @@
 
 module.exports=ChromeConsoleMessages;
 
-},{"./gatherer":45}],"../gather/gatherers/css-usage":[function(require,module,exports){
+},{"./gatherer":48}],"../gather/gatherers/css-usage":[function(require,module,exports){
 
 
 
@@ -10792,43 +11842,7 @@
 
 module.exports=CSSUsage;
 
-},{"./gatherer":45}],"../gather/gatherers/dobetterweb/anchors-with-no-rel-noopener":[function(require,module,exports){
-
-
-
-
-
-'use strict';
-
-const Gatherer=require('../gatherer');
-const pageFunctions=require('../../../lib/page-functions.js');
-
-class AnchorsWithNoRelNoopener extends Gatherer{
-
-
-
-
-afterPass(passContext){
-const expression=`(function() {
-      ${pageFunctions.getOuterHTMLSnippetString};
-      ${pageFunctions.getElementsInDocumentString}; // define function on page
-      const selector = 'a[target="_blank"]:not([rel~="noopener"]):not([rel~="noreferrer"])';
-      const elements = getElementsInDocument(selector);
-      return elements.map(node => ({
-        href: node.href,
-        rel: node.getAttribute('rel'),
-        target: node.getAttribute('target'),
-        outerHTML: getOuterHTMLSnippet(node),
-      }));
-    })()`;
-
-return passContext.driver.evaluateAsync(expression);
-}}
-
-
-module.exports=AnchorsWithNoRelNoopener;
-
-},{"../../../lib/page-functions.js":69,"../gatherer":45}],"../gather/gatherers/dobetterweb/appcache":[function(require,module,exports){
+},{"./gatherer":48}],"../gather/gatherers/dobetterweb/appcache":[function(require,module,exports){
 
 
 
@@ -10855,7 +11869,7 @@
 
 module.exports=AppCacheManifest;
 
-},{"../gatherer":45}],"../gather/gatherers/dobetterweb/doctype":[function(require,module,exports){
+},{"../gatherer":48}],"../gather/gatherers/dobetterweb/doctype":[function(require,module,exports){
 
 
 
@@ -10893,7 +11907,7 @@
 
 module.exports=Doctype;
 
-},{"../gatherer":45}],"../gather/gatherers/dobetterweb/domstats":[function(require,module,exports){
+},{"../gatherer":48}],"../gather/gatherers/dobetterweb/domstats":[function(require,module,exports){
 
 
 
@@ -11053,7 +12067,7 @@
 
 module.exports=DOMStats;
 
-},{"../../../lib/page-functions":69,"../gatherer":45}],"../gather/gatherers/dobetterweb/js-libraries":[function(require,module,exports){
+},{"../../../lib/page-functions":72,"../gatherer":48}],"../gather/gatherers/dobetterweb/js-libraries":[function(require,module,exports){
 
 
 
@@ -11117,7 +12131,7 @@
 
 module.exports=JSLibraries;
 
-},{"../gatherer":45}],"../gather/gatherers/dobetterweb/optimized-images":[function(require,module,exports){
+},{"../gatherer":48}],"../gather/gatherers/dobetterweb/optimized-images":[function(require,module,exports){
 
 
 
@@ -11351,7 +12365,7 @@
 
 module.exports=OptimizedImages;
 
-},{"../../../lib/network-request":68,"../../../lib/sentry":70,"../../../lib/url-shim":"url","../../driver.js":43,"../gatherer":45}],"../gather/gatherers/dobetterweb/password-inputs-with-prevented-paste":[function(require,module,exports){
+},{"../../../lib/network-request":71,"../../../lib/sentry":74,"../../../lib/url-shim":"url","../../driver.js":46,"../gatherer":48}],"../gather/gatherers/dobetterweb/password-inputs-with-prevented-paste":[function(require,module,exports){
 
 
 
@@ -11398,7 +12412,7 @@
 
 module.exports=PasswordInputsWithPreventedPaste;
 
-},{"../../../lib/page-functions":69,"../gatherer":45}],"../gather/gatherers/dobetterweb/response-compression":[function(require,module,exports){
+},{"../../../lib/page-functions":72,"../gatherer":48}],"../gather/gatherers/dobetterweb/response-compression":[function(require,module,exports){
 (function(Buffer){
 
 
@@ -11521,7 +12535,7 @@
 module.exports=ResponseCompression;
 
 }).call(this,require("buffer").Buffer);
-},{"../../../lib/network-request":68,"../../../lib/sentry":70,"../../../lib/url-shim":"url","../gatherer":45,"buffer":89,"zlib":87}],"../gather/gatherers/dobetterweb/tags-blocking-first-paint":[function(require,module,exports){
+},{"../../../lib/network-request":71,"../../../lib/sentry":74,"../../../lib/url-shim":"url","../gatherer":48,"buffer":94,"zlib":92}],"../gather/gatherers/dobetterweb/tags-blocking-first-paint":[function(require,module,exports){
 
 
 
@@ -11713,7 +12727,7 @@
 
 module.exports=TagsBlockingFirstPaint;
 
-},{"../../driver.js":43,"../gatherer":45}],"../gather/gatherers/html-without-javascript":[function(require,module,exports){
+},{"../../driver.js":46,"../gatherer":48}],"../gather/gatherers/html-without-javascript":[function(require,module,exports){
 
 
 
@@ -11771,7 +12785,7 @@
 
 module.exports=HTMLWithoutJavaScript;
 
-},{"./gatherer":45}],"../gather/gatherers/http-redirect":[function(require,module,exports){
+},{"./gatherer":48}],"../gather/gatherers/http-redirect":[function(require,module,exports){
 
 
 
@@ -11818,7 +12832,7 @@
 
 module.exports=HTTPRedirect;
 
-},{"./gatherer":45}],"../gather/gatherers/image-elements":[function(require,module,exports){
+},{"./gatherer":48}],"../gather/gatherers/image-elements":[function(require,module,exports){
 
 
 
@@ -11908,8 +12922,8 @@
 displayedHeight:element.clientHeight,
 clientRect:getClientRect(element),
 
-naturalWidth:Number.MAX_VALUE,
-naturalHeight:Number.MAX_VALUE,
+naturalWidth:0,
+naturalHeight:0,
 isCss:true,
 isPicture:false,
 usesObjectFit:false,
@@ -11952,11 +12966,13 @@
 const url=JSON.stringify(element.src);
 try{
 
+driver.setNextProtocolTimeout(250);
+
 const size=await driver.evaluateAsync(`(${determineNaturalSize.toString()})(${url})`);
 return Object.assign(element,size);
 }catch(_){
 
-return Object.assign(element,{naturalWidth:0,naturalHeight:0});
+return element;
 }
 }
 
@@ -11968,7 +12984,9 @@
 async afterPass(passContext,loadData){
 const driver=passContext.driver;
 const indexedNetworkRecords=loadData.networkRecords.reduce((map,record)=>{
-if(/^image/.test(record.mimeType)&&record.finished){
+
+
+if(/^image/.test(record.mimeType)&&record.finished&&record.statusCode===200){
 map[record.url]=record;
 }
 
@@ -11984,6 +13002,9 @@
 const elements=await driver.evaluateAsync(expression);
 
 const imageUsage=[];
+const top50Images=Object.values(indexedNetworkRecords).
+sort((a,b)=>b.resourceSize-a.resourceSize).
+slice(0,50);
 for(let element of elements){
 
 const networkRecord=indexedNetworkRecords[element.src]||{};
@@ -11999,7 +13020,12 @@
 
 
 
-if((element.isPicture||element.isCss)&&networkRecord){
+
+if(
+(element.isPicture||element.isCss)&&
+networkRecord&&
+top50Images.includes(networkRecord))
+{
 element=await this.fetchElementWithSizeInformation(driver,element);
 }
 
@@ -12012,7 +13038,7 @@
 
 module.exports=ImageElements;
 
-},{"../../lib/page-functions.js":69,"../driver.js":43,"./gatherer":45}],"../gather/gatherers/js-usage":[function(require,module,exports){
+},{"../../lib/page-functions.js":72,"../driver.js":46,"./gatherer":48}],"../gather/gatherers/js-usage":[function(require,module,exports){
 
 
 
@@ -12050,7 +13076,7 @@
 
 module.exports=JsUsage;
 
-},{"./gatherer":45}],"../gather/gatherers/link-elements":[function(require,module,exports){
+},{"./gatherer":48}],"../gather/gatherers/link-elements":[function(require,module,exports){
 
 
 
@@ -12088,32 +13114,7 @@
 
 module.exports=LinkElements;
 
-},{"../../lib/page-functions.js":69,"./gatherer.js":45}],"../gather/gatherers/manifest":[function(require,module,exports){
-
-
-
-
-
-'use strict';
-
-const Gatherer=require('./gatherer');
-
-
-
-
-class Manifest extends Gatherer{
-
-
-
-
-async afterPass(passContext){
-return passContext.baseArtifacts.WebAppManifest;
-}}
-
-
-module.exports=Manifest;
-
-},{"./gatherer":45}],"../gather/gatherers/meta-elements":[function(require,module,exports){
+},{"../../lib/page-functions.js":72,"./gatherer.js":48}],"../gather/gatherers/meta-elements":[function(require,module,exports){
 
 
 
@@ -12149,7 +13150,7 @@
 
 module.exports=MetaElements;
 
-},{"../../lib/page-functions.js":69,"./gatherer.js":45}],"../gather/gatherers/mixed-content":[function(require,module,exports){
+},{"../../lib/page-functions.js":72,"./gatherer.js":48}],"../gather/gatherers/mixed-content":[function(require,module,exports){
 (function(Buffer){
 
 
@@ -12273,7 +13274,7 @@
 module.exports=MixedContent;
 
 }).call(this,require("buffer").Buffer);
-},{"../../lib/url-shim":"url","../driver.js":43,"./gatherer":45,"buffer":89}],"../gather/gatherers/offline":[function(require,module,exports){
+},{"../../lib/url-shim":"url","../driver.js":46,"./gatherer":48,"buffer":94}],"../gather/gatherers/offline":[function(require,module,exports){
 
 
 
@@ -12310,7 +13311,7 @@
 
 module.exports=Offline;
 
-},{"../../lib/url-shim":"url","./gatherer":45}],"../gather/gatherers/runtime-exceptions":[function(require,module,exports){
+},{"../../lib/url-shim":"url","./gatherer":48}],"../gather/gatherers/runtime-exceptions":[function(require,module,exports){
 
 
 
@@ -12360,7 +13361,7 @@
 
 module.exports=RuntimeExceptions;
 
-},{"./gatherer":45}],"../gather/gatherers/scripts":[function(require,module,exports){
+},{"./gatherer":48}],"../gather/gatherers/scripts":[function(require,module,exports){
 
 
 
@@ -12368,8 +13369,11 @@
 
 'use strict';
 
+const log=require('lighthouse-logger');
 const Gatherer=require('./gatherer');
 const NetworkRequest=require('../../lib/network-request');
+const getElementsInDocumentString=require('../../lib/page-functions.js').getElementsInDocumentString;
+const URL=require('../../lib/url-shim.js');
 
 
 
@@ -12384,7 +13388,39 @@
 const driver=passContext.driver;
 
 
-const scriptContentMap={};
+const scripts=[];
+
+
+const inlineScripts=await driver.evaluateAsync(`(() => {
+      ${getElementsInDocumentString};
+
+      return getElementsInDocument('script')
+        .filter(script => !script.src && script.text.trim())
+        .map(script => script.text);
+    })()`,{useIsolation:true});
+
+if(inlineScripts.length){
+
+
+
+const mainResource=loadData.networkRecords.find(request=>
+passContext.url.startsWith(request.url)&&
+URL.equalWithExcludedFragments(request.url,passContext.url));
+if(!mainResource){
+log.warn('Scripts','could not locate mainResource');
+}
+const requestId=mainResource?mainResource.requestId:undefined;
+scripts.push(
+...inlineScripts.map(content=>{
+return{
+content,
+inline:true,
+requestId};
+
+}));
+
+}
+
 const scriptRecords=loadData.networkRecords.
 filter(record=>record.resourceType===NetworkRequest.TYPES.Script);
 
@@ -12392,18 +13428,22 @@
 try{
 const content=await driver.getRequestContent(record.requestId);
 if(content){
-scriptContentMap[record.requestId]=content;
+scripts.push({
+content,
+inline:false,
+requestId:record.requestId});
+
 }
 }catch(e){}
 }
 
-return scriptContentMap;
+return scripts;
 }}
 
 
 module.exports=Scripts;
 
-},{"../../lib/network-request":68,"./gatherer":45}],"../gather/gatherers/seo/canonical":[function(require,module,exports){
+},{"../../lib/network-request":71,"../../lib/page-functions.js":72,"../../lib/url-shim.js":"url","./gatherer":48,"lighthouse-logger":120}],"../gather/gatherers/seo/canonical":[function(require,module,exports){
 
 
 
@@ -12429,53 +13469,7 @@
 module.exports=Canonical;
 
 
-},{"../gatherer":45}],"../gather/gatherers/seo/crawlable-links":[function(require,module,exports){
-
-
-
-
-
-'use strict';
-
-const Gatherer=require('../gatherer');
-const pageFunctions=require('../../../lib/page-functions.js');
-
-class CrawlableLinks extends Gatherer{
-
-
-
-
-async afterPass(passContext){
-const expression=`(function() {
-      ${pageFunctions.getElementsInDocumentString}; // define function on page
-      const resolveURLOrNull = url => {
-        try { return new URL(url, window.location.href).href; }
-        catch (_) { return null; }
-      };
-
-      const selector = 'a[href]:not([rel~="nofollow"])';
-      const elements = getElementsInDocument(selector);
-      return elements
-        .map(node => ({
-          href: node.href instanceof SVGAnimatedString ?
-            resolveURLOrNull(node.href.baseVal) :
-            node.href,
-          text: node.href instanceof SVGAnimatedString ?
-            node.textContent :
-            node.innerText,
-        }));
-    })()`;
-
-
-const links=await passContext.driver.evaluateAsync(expression,{useIsolation:true});
-return links.filter(link=>typeof link.href==='string'&&link.href);
-}}
-
-
-module.exports=CrawlableLinks;
-
-
-},{"../../../lib/page-functions.js":69,"../gatherer":45}],"../gather/gatherers/seo/embedded-content":[function(require,module,exports){
+},{"../gatherer":48}],"../gather/gatherers/seo/embedded-content":[function(require,module,exports){
 
 
 
@@ -12518,7 +13512,7 @@
 
 module.exports=EmbeddedContent;
 
-},{"../../../lib/page-functions.js":69,"../gatherer":45}],"../gather/gatherers/seo/font-size":[function(require,module,exports){
+},{"../../../lib/page-functions.js":72,"../gatherer":48}],"../gather/gatherers/seo/font-size":[function(require,module,exports){
 
 
 
@@ -12881,7 +13875,7 @@
 module.exports.computeSelectorSpecificity=computeSelectorSpecificity;
 module.exports.getEffectiveFontRule=getEffectiveFontRule;
 
-},{"../../../lib/sentry.js":70,"../gatherer":45}],"../gather/gatherers/seo/hreflang":[function(require,module,exports){
+},{"../../../lib/sentry.js":74,"../gatherer":48}],"../gather/gatherers/seo/hreflang":[function(require,module,exports){
 
 
 
@@ -12918,7 +13912,7 @@
 module.exports=Hreflang;
 
 
-},{"../gatherer":45}],"../gather/gatherers/seo/robots-txt":[function(require,module,exports){
+},{"../gatherer":48}],"../gather/gatherers/seo/robots-txt":[function(require,module,exports){
 
 
 
@@ -12959,7 +13953,344 @@
 
 module.exports=RobotsTxt;
 
-},{"../gatherer":45}],"../gather/gatherers/service-worker":[function(require,module,exports){
+},{"../gatherer":48}],"../gather/gatherers/seo/tap-targets":[function(require,module,exports){
+
+
+
+
+
+'use strict';
+
+
+
+const Gatherer=require('../gatherer');
+const pageFunctions=require('../../../lib/page-functions.js');
+const{rectContainsString,rectContains}=require('../../../lib/rect-helpers');
+
+const TARGET_SELECTORS=[
+'button',
+'a',
+'input',
+'textarea',
+'select',
+'option',
+'[role=button]',
+'[role=checkbox]',
+'[role=link]',
+'[role=menuitem]',
+'[role=menuitemcheckbox]',
+'[role=menuitemradio]',
+'[role=option]',
+'[role=scrollbar]',
+'[role=slider]',
+'[role=spinbutton]'];
+
+const tapTargetsSelector=TARGET_SELECTORS.join(',');
+
+
+
+
+
+
+function elementIsVisible(element){
+const{overflowX,overflowY,display,visibility}=getComputedStyle(element);
+
+if(
+display==='none'||
+visibility==='collapse'&&['TR','TBODY','COL','COLGROUP'].includes(element.tagName))
+{
+
+return false;
+}
+
+
+if(display==='block'||display==='inline-block'){
+
+
+if(element.clientWidth===0&&overflowX==='hidden'||
+element.clientHeight===0&&overflowY==='hidden'){
+return false;
+}
+}
+
+const parent=element.parentElement;
+if(parent&&parent.tagName!=='BODY'){
+
+return elementIsVisible(parent);
+}
+
+return true;
+}
+
+
+
+
+
+function allClientRectsEmpty(clientRects){
+return clientRects.every(cr=>cr.width===0&&cr.height===0);
+}
+
+
+
+
+
+function getVisibleClientRects(element){
+if(!elementIsVisible(element)){
+return[];
+}
+
+let clientRects=getClientRects(element);
+
+if(allClientRectsEmpty(clientRects)){
+return[];
+}
+
+
+
+
+
+
+clientRects=filterClientRectsWithinAncestorsVisibleScrollArea(element,clientRects);
+
+return clientRects;
+}
+
+
+
+
+
+
+
+
+function filterClientRectsWithinAncestorsVisibleScrollArea(element,clientRects){
+const parent=element.parentElement;
+if(!parent){
+return clientRects;
+}
+if(getComputedStyle(parent).overflowY!=='visible'){
+const parentBCR=parent.getBoundingClientRect();
+clientRects=clientRects.filter(cr=>rectContains(parentBCR,cr));
+}
+if(parent.parentElement&&parent.parentElement.tagName!=='BODY'){
+return filterClientRectsWithinAncestorsVisibleScrollArea(
+parent,
+clientRects);
+
+}
+return clientRects;
+}
+
+
+
+
+
+
+function getClientRects(element){
+const clientRects=Array.from(
+element.getClientRects()).
+map(clientRect=>{
+
+
+const{width,height,left,top,right,bottom}=clientRect;
+return{width,height,left,top,right,bottom};
+});
+
+for(const child of element.children){
+clientRects.push(...getClientRects(child));
+}
+
+return clientRects;
+}
+
+
+
+
+
+
+function elementHasAncestorTapTarget(element){
+if(!element.parentElement){
+return false;
+}
+if(element.parentElement.matches(tapTargetsSelector)){
+return true;
+}
+return elementHasAncestorTapTarget(element.parentElement);
+}
+
+
+
+
+
+function hasTextNodeSiblingsFormingTextBlock(element){
+if(!element.parentElement){
+return false;
+}
+
+const parentElement=element.parentElement;
+
+const nodeText=element.textContent||'';
+const parentText=parentElement.textContent||'';
+if(parentText.length-nodeText.length<5){
+
+
+return false;
+}
+
+for(const sibling of element.parentElement.childNodes){
+if(sibling===element){
+continue;
+}
+const siblingTextContent=(sibling.textContent||'').trim();
+
+
+
+
+if(sibling.nodeType===Node.TEXT_NODE&&siblingTextContent.length>0){
+return true;
+}
+}
+
+return false;
+}
+
+
+
+
+
+
+
+
+
+function elementIsInTextBlock(element){
+const{display}=getComputedStyle(element);
+if(display!=='inline'&&display!=='inline-block'){
+return false;
+}
+
+if(hasTextNodeSiblingsFormingTextBlock(element)){
+return true;
+}else if(element.parentElement){
+return elementIsInTextBlock(element.parentElement);
+}else{
+return false;
+}
+}
+
+
+
+
+
+
+function elementIsPositionFixedOrAbsolute(element){
+const{position}=getComputedStyle(element);
+if(position==='fixed'||position==='absolute'){
+return true;
+}
+if(element.parentElement){
+return elementIsPositionFixedOrAbsolute(element.parentElement);
+}
+return false;
+}
+
+
+
+
+
+
+
+function truncate(str,maxLength){
+if(str.length<=maxLength){
+return str;
+}
+return str.slice(0,maxLength-1)+'…';
+}
+
+
+
+
+
+function gatherTapTargets(){
+
+const targets=[];
+
+
+
+const tapTargetElements=getElementsInDocument(tapTargetsSelector);
+
+tapTargetElements.forEach(tapTargetElement=>{
+
+if(elementHasAncestorTapTarget(tapTargetElement)){
+
+
+return;
+}
+if(elementIsInTextBlock(tapTargetElement)){
+
+
+return;
+}
+if(elementIsPositionFixedOrAbsolute(tapTargetElement)){
+
+
+
+
+
+return;
+}
+
+const visibleClientRects=getVisibleClientRects(tapTargetElement);
+if(visibleClientRects.length===0){
+return;
+}
+
+targets.push({
+clientRects:visibleClientRects,
+snippet:truncate(tapTargetElement.outerHTML,300),
+
+path:getNodePath(tapTargetElement),
+
+selector:getNodeSelector(tapTargetElement),
+href:tapTargetElement['href']||''});
+
+});
+
+return targets;
+}
+
+class TapTargets extends Gatherer{
+
+
+
+
+afterPass(passContext){
+const expression=`(function() {
+      const tapTargetsSelector = "${tapTargetsSelector}";
+      ${pageFunctions.getElementsInDocumentString};
+      ${filterClientRectsWithinAncestorsVisibleScrollArea.toString()};
+      ${elementIsPositionFixedOrAbsolute.toString()};
+      ${elementIsVisible.toString()};
+      ${elementHasAncestorTapTarget.toString()};
+      ${getVisibleClientRects.toString()};
+      ${truncate.toString()};
+      ${getClientRects.toString()};
+      ${hasTextNodeSiblingsFormingTextBlock.toString()};
+      ${elementIsInTextBlock.toString()};
+      ${allClientRectsEmpty.toString()};
+      ${rectContainsString};
+      ${pageFunctions.getNodePathString};
+      ${pageFunctions.getNodeSelectorString};
+      ${gatherTapTargets.toString()};
+
+      return gatherTapTargets();
+    })()`;
+
+return passContext.driver.evaluateAsync(expression,{useIsolation:true});
+}}
+
+
+module.exports=TapTargets;
+
+},{"../../../lib/page-functions.js":72,"../../../lib/rect-helpers":73,"../gatherer":48}],"../gather/gatherers/service-worker":[function(require,module,exports){
 
 
 
@@ -12987,7 +14318,7 @@
 
 module.exports=ServiceWorker;
 
-},{"./gatherer":45}],"../gather/gatherers/start-url":[function(require,module,exports){
+},{"./gatherer":48}],"../gather/gatherers/start-url":[function(require,module,exports){
 
 
 
@@ -13083,7 +14414,7 @@
 
 module.exports=StartUrl;
 
-},{"./gatherer":45}],"../gather/gatherers/viewport-dimensions":[function(require,module,exports){
+},{"./gatherer":48}],"../gather/gatherers/viewport-dimensions":[function(require,module,exports){
 
 
 
@@ -13136,7 +14467,7 @@
 
 module.exports=ViewportDimensions;
 
-},{"./gatherer":45}],1:[function(require,module,exports){
+},{"./gatherer":48}],1:[function(require,module,exports){
 
 
 
@@ -13203,7 +14534,7 @@
 self.listenForStatus=listenForStatus;
 }
 
-},{"../lighthouse-core/gather/connections/raw.js":41,"../lighthouse-core/index.js":46,"lighthouse-logger":114}],2:[function(require,module,exports){
+},{"../lighthouse-core/gather/connections/raw.js":44,"../lighthouse-core/index.js":49,"lighthouse-logger":120}],2:[function(require,module,exports){
 (function(__filename){
 
 
@@ -13265,16 +14596,27 @@
 
 }
 
+
 const headings=[
 {key:'node',itemType:'node',text:str_(UIStrings.failingElementsHeader)}];
 
 
+
+let diagnostic;
+if(impact||tags){
+diagnostic={
+type:'diagnostic',
+impact,
+tags};
+
+}
+
 return{
 rawValue:typeof rule==='undefined',
 extendedInfo:{
 value:rule},
 
-details:{...Audit.makeTableDetails(headings,items),impact,tags}};
+details:{...Audit.makeTableDetails(headings,items),diagnostic}};
 
 }}
 
@@ -13283,7 +14625,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/audits/accessibility/axe-audit.js");
-},{"../../lib/i18n/i18n.js":60,"../audit":3}],3:[function(require,module,exports){
+},{"../../lib/i18n/i18n.js":63,"../audit":3}],3:[function(require,module,exports){
 
 
 
@@ -13415,6 +14757,72 @@
 
 
 
+static makeListDetails(items){
+return{
+type:'list',
+items:items};
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+static makeSnippetDetails({
+content,
+title,
+lineMessages,
+generalMessages,
+node,
+maxLineLength=200,
+maxLinesAroundMessage=20})
+{
+const allLines=Audit._makeSnippetLinesArray(content,maxLineLength);
+const lines=Util.filterRelevantLines(allLines,lineMessages,maxLinesAroundMessage);
+return{
+type:'snippet',
+lines,
+title,
+lineMessages,
+generalMessages,
+lineCount:allLines.length,
+node};
+
+}
+
+
+
+
+
+
+static _makeSnippetLinesArray(content,maxLineLength){
+return content.split('\n').map((line,lineIndex)=>{
+const lineNumber=lineIndex+1;
+
+const lineDetail={
+content:line.slice(0,maxLineLength),
+lineNumber};
+
+if(line.length>maxLineLength){
+lineDetail.truncated=true;
+}
+return lineDetail;
+});
+}
+
+
+
+
+
 
 
 
@@ -13507,7 +14915,7 @@
 
 module.exports=Audit;
 
-},{"../lib/statistics":71,"../report/html/renderer/util":75}],4:[function(require,module,exports){
+},{"../lib/statistics":75,"../report/html/renderer/util":80}],4:[function(require,module,exports){
 (function(__filename){
 
 
@@ -13579,13 +14987,24 @@
 
 
 
-
-static estimateTransferSize(networkRecord,totalBytes,resourceType,compressionRatio=0.5){
+static estimateTransferSize(networkRecord,totalBytes,resourceType){
 if(!networkRecord){
 
 
 
-return Math.round(totalBytes*compressionRatio);
+
+switch(resourceType){
+case'Stylesheet':
+
+return Math.round(totalBytes*0.2);
+case'Script':
+case'Document':
+
+return Math.round(totalBytes*0.33);
+default:
+
+return Math.round(totalBytes*0.5);}
+
 }else if(networkRecord.resourceType===resourceType){
 
 return networkRecord.transferSize||0;
@@ -13740,7 +15159,7 @@
 module.exports=UnusedBytes;
 
 }).call(this,"/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js");
-},{"../../computed/load-simulator.js":10,"../../computed/metrics/lantern-interactive.js":23,"../../computed/network-records.js":29,"../../computed/page-dependency-graph.js":30,"../../lib/i18n/i18n.js":60,"../../lib/statistics":71,"../audit":3}],5:[function(require,module,exports){
+},{"../../computed/load-simulator.js":10,"../../computed/metrics/lantern-interactive.js":23,"../../computed/network-records.js":31,"../../computed/page-dependency-graph.js":32,"../../lib/i18n/i18n.js":63,"../../lib/statistics":75,"../audit":3}],5:[function(require,module,exports){
 
 
 
@@ -13823,7 +15242,13 @@
 });
 }
 
-const details={items:[detailsItem]};
+
+
+const details={
+type:'diagnostic',
+
+items:[detailsItem]};
+
 
 
 if(result.failures.length>0){
@@ -13948,7 +15373,7 @@
 
 module.exports=makeComputedArtifact;
 
-},{"../lib/arbitrary-equality-map.js":47,"lighthouse-logger":114}],9:[function(require,module,exports){
+},{"../lib/arbitrary-equality-map.js":50,"lighthouse-logger":120}],9:[function(require,module,exports){
 
 
 
@@ -14109,7 +15534,7 @@
 
 module.exports=makeComputedArtifact(CriticalRequestChains);
 
-},{"../lib/network-request.js":68,"./computed-artifact.js":8,"./main-resource.js":11,"./network-records.js":29,"assert":79}],10:[function(require,module,exports){
+},{"../lib/network-request.js":71,"./computed-artifact.js":8,"./main-resource.js":11,"./network-records.js":31,"assert":84}],10:[function(require,module,exports){
 
 
 
@@ -14129,7 +15554,7 @@
 
 
 static async compute_(data,context){
-const{throttlingMethod,throttling}=data.settings;
+const{throttlingMethod,throttling,precomputedLanternData}=data.settings;
 const networkAnalysis=await NetworkAnalysis.request(data.devtoolsLog,context);
 
 
@@ -14138,6 +15563,15 @@
 serverResponseTimeByOrigin:networkAnalysis.serverResponseTimeByOrigin};
 
 
+
+
+if(precomputedLanternData){
+options.additionalRttByOrigin=new Map(Object.entries(
+precomputedLanternData.additionalRttByOrigin));
+options.serverResponseTimeByOrigin=new Map(Object.entries(
+precomputedLanternData.serverResponseTimeByOrigin));
+}
+
 switch(throttlingMethod){
 case'provided':
 options.rtt=networkAnalysis.rtt;
@@ -14170,12 +15604,30 @@
 
 
 return new Simulator(options);
+}
+
+
+
+
+
+static convertAnalysisToSaveableLanternData(networkAnalysis){
+
+const lanternData={additionalRttByOrigin:{},serverResponseTimeByOrigin:{}};
+for(const[origin,value]of networkAnalysis.additionalRttByOrigin.entries()){
+if(origin.startsWith('http'))lanternData.additionalRttByOrigin[origin]=value;
+}
+
+for(const[origin,value]of networkAnalysis.serverResponseTimeByOrigin.entries()){
+if(origin.startsWith('http'))lanternData.serverResponseTimeByOrigin[origin]=value;
+}
+
+return lanternData;
 }}
 
 
 module.exports=makeComputedArtifact(LoadSimulator);
 
-},{"../config/constants.js":37,"../lib/dependency-graph/simulator/simulator.js":55,"./computed-artifact.js":8,"./network-analysis.js":28}],11:[function(require,module,exports){
+},{"../config/constants.js":40,"../lib/dependency-graph/simulator/simulator.js":58,"./computed-artifact.js":8,"./network-analysis.js":30}],11:[function(require,module,exports){
 
 
 
@@ -14214,7 +15666,7 @@
 
 module.exports=makeComputedArtifact(MainResource);
 
-},{"../lib/url-shim.js":"url","./computed-artifact.js":8,"./network-records.js":29}],12:[function(require,module,exports){
+},{"../lib/url-shim.js":"url","./computed-artifact.js":8,"./network-records.js":31}],12:[function(require,module,exports){
 
 
 
@@ -14258,6 +15710,8 @@
 
 
 
+
+
 class MainThreadTasks{
 
 
@@ -14290,7 +15744,8 @@
 
 
 
-static _createTasksFromEvents(mainThreadEvents){
+
+static _createTasksFromEvents(mainThreadEvents,priorTaskData){
 
 const tasks=[];
 
@@ -14298,6 +15753,14 @@
 
 for(const event of mainThreadEvents){
 
+if(event.name==='TimerInstall'&&currentTask){
+
+
+const timerId=event.args.data.timerId;
+priorTaskData.timers.set(timerId,currentTask);
+}
+
+
 if(event.ph!=='X'&&event.ph!=='B'&&event.ph!=='E')continue;
 
 
@@ -14359,10 +15822,12 @@
 
 
 
-static _computeRecursiveAttributableURLs(task,parentURLs){
+
+static _computeRecursiveAttributableURLs(task,parentURLs,priorTaskData){
 const argsData=task.event.args.data||{};
 const stackFrameURLs=(argsData.stackTrace||[]).map(entry=>entry.url);
 
+
 let taskURLs=[];
 switch(task.event.name){
 
@@ -14378,6 +15843,15 @@
 case'v8.compileModule':
 taskURLs=[task.event.args.fileName].concat(stackFrameURLs);
 break;
+case'TimerFire':{
+
+
+const timerId=task.event.args.data.timerId;
+const timerInstallerTaskNode=priorTaskData.timers.get(timerId);
+if(!timerInstallerTaskNode)break;
+taskURLs=timerInstallerTaskNode.attributableURLs.concat(stackFrameURLs);
+break;
+}
 default:
 taskURLs=stackFrameURLs;
 break;}
@@ -14395,7 +15869,7 @@
 
 task.attributableURLs=attributableURLs;
 task.children.forEach(child=>
-MainThreadTasks._computeRecursiveAttributableURLs(child,attributableURLs));
+MainThreadTasks._computeRecursiveAttributableURLs(child,attributableURLs,priorTaskData));
 }
 
 
@@ -14413,14 +15887,16 @@
 
 
 static getMainThreadTasks(traceEvents){
-const tasks=MainThreadTasks._createTasksFromEvents(traceEvents);
+const timers=new Map();
+const priorTaskData={timers};
+const tasks=MainThreadTasks._createTasksFromEvents(traceEvents,priorTaskData);
 
 
 for(const task of tasks){
 if(task.parent)continue;
 
 MainThreadTasks._computeRecursiveSelfTime(task);
-MainThreadTasks._computeRecursiveAttributableURLs(task,[]);
+MainThreadTasks._computeRecursiveAttributableURLs(task,[],priorTaskData);
 MainThreadTasks._computeRecursiveTaskGroup(task);
 }
 
@@ -14454,7 +15930,7 @@
 
 module.exports=makeComputedArtifact(MainThreadTasks);
 
-},{"../lib/task-groups.js":72,"./computed-artifact.js":8,"./trace-of-tab.js":33}],13:[function(require,module,exports){
+},{"../lib/task-groups.js":77,"./computed-artifact.js":8,"./trace-of-tab.js":35}],13:[function(require,module,exports){
 
 
 
@@ -14573,7 +16049,7 @@
 
 module.exports=makeComputedArtifact(ManifestValues);
 
-},{"../lib/icons.js":62,"./computed-artifact.js":8}],14:[function(require,module,exports){
+},{"../lib/icons.js":65,"./computed-artifact.js":8}],14:[function(require,module,exports){
 
 
 
@@ -14649,7 +16125,7 @@
 
 module.exports=makeComputedArtifact(EstimatedInputLatency);
 
-},{"../../lib/lh-error.js":64,"../../lib/traces/tracing-processor.js":74,"../computed-artifact.js":8,"./lantern-estimated-input-latency.js":19,"./metric.js":26}],15:[function(require,module,exports){
+},{"../../lib/lh-error.js":67,"../../lib/traces/tracing-processor.js":79,"../computed-artifact.js":8,"./lantern-estimated-input-latency.js":19,"./metric.js":28}],15:[function(require,module,exports){
 
 
 
@@ -14687,7 +16163,7 @@
 
 module.exports=makeComputedArtifact(FirstContentfulPaint);
 
-},{"../computed-artifact.js":8,"./lantern-first-contentful-paint.js":20,"./metric.js":26}],16:[function(require,module,exports){
+},{"../computed-artifact.js":8,"./lantern-first-contentful-paint.js":20,"./metric.js":28}],16:[function(require,module,exports){
 
 
 
@@ -14903,7 +16379,7 @@
 
 module.exports=makeComputedArtifact(FirstCPUIdle);
 
-},{"../../lib/lh-error.js":64,"../../lib/traces/tracing-processor.js":74,"../computed-artifact.js":8,"./lantern-first-cpu-idle.js":21,"./metric.js":26}],17:[function(require,module,exports){
+},{"../../lib/lh-error.js":67,"../../lib/traces/tracing-processor.js":79,"../computed-artifact.js":8,"./lantern-first-cpu-idle.js":21,"./metric.js":28}],17:[function(require,module,exports){
 
 
 
@@ -14946,7 +16422,7 @@
 
 module.exports=makeComputedArtifact(FirstMeaningfulPaint);
 
-},{"../../lib/lh-error.js":64,"../computed-artifact.js":8,"./lantern-first-meaningful-paint.js":22,"./metric.js":26}],18:[function(require,module,exports){
+},{"../../lib/lh-error.js":67,"../computed-artifact.js":8,"./lantern-first-meaningful-paint.js":22,"./metric.js":28}],18:[function(require,module,exports){
 
 
 
@@ -15137,7 +16613,7 @@
 
 
 
-},{"../../lib/lh-error.js":64,"../../lib/network-recorder.js":67,"../../lib/traces/tracing-processor.js":74,"../computed-artifact.js":8,"./lantern-interactive.js":23,"./metric.js":26}],19:[function(require,module,exports){
+},{"../../lib/lh-error.js":67,"../../lib/network-recorder.js":70,"../../lib/traces/tracing-processor.js":79,"../computed-artifact.js":8,"./lantern-interactive.js":23,"./metric.js":28}],19:[function(require,module,exports){
 
 
 
@@ -15240,7 +16716,7 @@
 
 module.exports=makeComputedArtifact(LanternEstimatedInputLatency);
 
-},{"../../lib/dependency-graph/base-node.js":49,"../computed-artifact.js":8,"./estimated-input-latency.js":14,"./lantern-first-meaningful-paint.js":22,"./lantern-metric.js":24}],20:[function(require,module,exports){
+},{"../../lib/dependency-graph/base-node.js":52,"../computed-artifact.js":8,"./estimated-input-latency.js":14,"./lantern-first-meaningful-paint.js":22,"./lantern-metric.js":25}],20:[function(require,module,exports){
 
 
 
@@ -15317,7 +16793,7 @@
 
 module.exports=makeComputedArtifact(LanternFirstContentfulPaint);
 
-},{"../../lib/dependency-graph/base-node.js":49,"../computed-artifact.js":8,"./lantern-metric.js":24}],21:[function(require,module,exports){
+},{"../../lib/dependency-graph/base-node.js":52,"../computed-artifact.js":8,"./lantern-metric.js":25}],21:[function(require,module,exports){
 
 
 
@@ -15390,7 +16866,7 @@
 
 module.exports=makeComputedArtifact(LanternFirstCPUIdle);
 
-},{"../../lib/dependency-graph/base-node.js":49,"../computed-artifact.js":8,"./first-cpu-idle":16,"./lantern-interactive.js":23}],22:[function(require,module,exports){
+},{"../../lib/dependency-graph/base-node.js":52,"../computed-artifact.js":8,"./first-cpu-idle":16,"./lantern-interactive.js":23}],22:[function(require,module,exports){
 
 
 
@@ -15490,7 +16966,7 @@
 
 module.exports=makeComputedArtifact(LanternFirstMeaningfulPaint);
 
-},{"../../lib/dependency-graph/base-node.js":49,"../../lib/lh-error.js":64,"../computed-artifact.js":8,"./lantern-first-contentful-paint.js":20,"./lantern-metric.js":24}],23:[function(require,module,exports){
+},{"../../lib/dependency-graph/base-node.js":52,"../../lib/lh-error.js":67,"../computed-artifact.js":8,"./lantern-first-contentful-paint.js":20,"./lantern-metric.js":25}],23:[function(require,module,exports){
 
 
 
@@ -15600,7 +17076,97 @@
 
 module.exports=makeComputedArtifact(LanternInteractive);
 
-},{"../../lib/dependency-graph/base-node.js":49,"../../lib/network-request.js":68,"../computed-artifact.js":8,"./lantern-first-meaningful-paint.js":22,"./lantern-metric.js":24}],24:[function(require,module,exports){
+},{"../../lib/dependency-graph/base-node.js":52,"../../lib/network-request.js":71,"../computed-artifact.js":8,"./lantern-first-meaningful-paint.js":22,"./lantern-metric.js":25}],24:[function(require,module,exports){
+
+
+
+
+
+'use strict';
+
+const makeComputedArtifact=require('../computed-artifact.js');
+const LanternMetricArtifact=require('./lantern-metric');
+const BaseNode=require('../../lib/dependency-graph/base-node');
+const LanternFirstContentfulPaint=require('./lantern-first-contentful-paint.js');
+
+
+
+class LanternMaxPotentialFID extends LanternMetricArtifact{
+
+
+
+static get COEFFICIENTS(){
+return{
+intercept:0,
+optimistic:0.5,
+pessimistic:0.5};
+
+}
+
+
+
+
+
+static getOptimisticGraph(dependencyGraph){
+return dependencyGraph;
+}
+
+
+
+
+
+static getPessimisticGraph(dependencyGraph){
+return dependencyGraph;
+}
+
+
+
+
+
+
+static getEstimateFromSimulation(simulation,extras){
+
+
+const fcpTimeInMs=extras.optimistic?
+extras.fcpResult.pessimisticEstimate.timeInMs:
+extras.fcpResult.optimisticEstimate.timeInMs;
+
+const timings=LanternMaxPotentialFID.getTimingsAfterFCP(
+simulation.nodeTimings,
+fcpTimeInMs);
+
+
+return{
+timeInMs:Math.max(...timings.map(timing=>timing.duration),16),
+nodeTimings:simulation.nodeTimings};
+
+}
+
+
+
+
+
+
+static async compute_(data,context){
+const fcpResult=await LanternFirstContentfulPaint.request(data,context);
+return super.computeMetricWithGraphs(data,context,{fcpResult});
+}
+
+
+
+
+
+
+static getTimingsAfterFCP(nodeTimings,fcpTimeInMs){
+return Array.from(nodeTimings.entries()).
+filter(([node,timing])=>node.type===BaseNode.TYPES.CPU&&timing.endTime>fcpTimeInMs).
+map(([_,timing])=>timing);
+}}
+
+
+module.exports=makeComputedArtifact(LanternMaxPotentialFID);
+
+},{"../../lib/dependency-graph/base-node":52,"../computed-artifact.js":8,"./lantern-first-contentful-paint.js":20,"./lantern-metric":25}],25:[function(require,module,exports){
 
 
 
@@ -15753,7 +17319,7 @@
 
 module.exports=LanternMetricArtifact;
 
-},{"../../lib/dependency-graph/base-node.js":49,"../../lib/network-request.js":68,"../load-simulator.js":10,"../page-dependency-graph.js":30,"../trace-of-tab.js":33}],25:[function(require,module,exports){
+},{"../../lib/dependency-graph/base-node.js":52,"../../lib/network-request.js":71,"../load-simulator.js":10,"../page-dependency-graph.js":32,"../trace-of-tab.js":35}],26:[function(require,module,exports){
 
 
 
@@ -15901,7 +17467,54 @@
 
 module.exports=makeComputedArtifact(LanternSpeedIndex);
 
-},{"../../config/constants.js":37,"../../lib/dependency-graph/base-node.js":49,"../computed-artifact.js":8,"../speedline.js":32,"./lantern-first-contentful-paint.js":20,"./lantern-metric.js":24}],26:[function(require,module,exports){
+},{"../../config/constants.js":40,"../../lib/dependency-graph/base-node.js":52,"../computed-artifact.js":8,"../speedline.js":34,"./lantern-first-contentful-paint.js":20,"./lantern-metric.js":25}],27:[function(require,module,exports){
+
+
+
+
+
+'use strict';
+
+const makeComputedArtifact=require('../computed-artifact.js');
+const MetricArtifact=require('./metric');
+const LanternMaxPotentialFID=require('./lantern-max-potential-fid.js');
+const LHError=require('../../lib/lh-error');
+const TracingProcessor=require('../../lib/traces/tracing-processor');
+
+class MaxPotentialFID extends MetricArtifact{
+
+
+
+
+
+static computeSimulatedMetric(data,context){
+return LanternMaxPotentialFID.request(data,context);
+}
+
+
+
+
+
+static computeObservedMetric(data){
+const{firstContentfulPaint}=data.traceOfTab.timings;
+if(!firstContentfulPaint){
+throw new LHError(LHError.errors.NO_FCP);
+}
+
+const events=TracingProcessor.getMainThreadTopLevelEvents(
+data.traceOfTab,
+firstContentfulPaint).
+filter(evt=>evt.duration>=1);
+
+return Promise.resolve({
+timing:Math.max(...events.map(evt=>evt.duration),16)});
+
+}}
+
+
+module.exports=makeComputedArtifact(MaxPotentialFID);
+
+},{"../../lib/lh-error":67,"../../lib/traces/tracing-processor":79,"../computed-artifact.js":8,"./lantern-max-potential-fid.js":24,"./metric":28}],28:[function(require,module,exports){
 
 
 
@@ -15975,7 +17588,7 @@
 
 module.exports=ComputedMetric;
 
-},{"../../lib/traces/tracing-processor.js":74,"../network-records.js":29,"../trace-of-tab.js":33}],27:[function(require,module,exports){
+},{"../../lib/traces/tracing-processor.js":79,"../network-records.js":31,"../trace-of-tab.js":35}],29:[function(require,module,exports){
 
 
 
@@ -16013,7 +17626,7 @@
 
 module.exports=makeComputedArtifact(SpeedIndex);
 
-},{"../computed-artifact.js":8,"../speedline.js":32,"./lantern-speed-index.js":25,"./metric.js":26}],28:[function(require,module,exports){
+},{"../computed-artifact.js":8,"../speedline.js":34,"./lantern-speed-index.js":26,"./metric.js":28}],30:[function(require,module,exports){
 
 
 
@@ -16052,8 +17665,7 @@
 const serverResponseTimeByOrigin=new Map();
 for(const[origin,summary]of responseTimeSummaries.entries()){
 
-
-const rttForOrigin=rttByOrigin.get(origin);
+const rttForOrigin=rttByOrigin.get(origin)||minimumRtt;
 additionalRttByOrigin.set(origin,rttForOrigin-minimumRtt);
 serverResponseTimeByOrigin.set(origin,summary.median);
 }
@@ -16080,7 +17692,7 @@
 
 module.exports=makeComputedArtifact(NetworkAnalysis);
 
-},{"../lib/dependency-graph/simulator/network-analyzer.js":54,"./computed-artifact.js":8,"./network-records.js":29}],29:[function(require,module,exports){
+},{"../lib/dependency-graph/simulator/network-analyzer.js":57,"./computed-artifact.js":8,"./network-records.js":31}],31:[function(require,module,exports){
 
 
 
@@ -16103,7 +17715,7 @@
 
 module.exports=makeComputedArtifact(NetworkRecords);
 
-},{"../lib/network-recorder.js":67,"./computed-artifact.js":8}],30:[function(require,module,exports){
+},{"../lib/network-recorder.js":70,"./computed-artifact.js":8}],32:[function(require,module,exports){
 
 
 
@@ -16463,7 +18075,7 @@
 
 
 
-},{"../lib/dependency-graph/cpu-node.js":50,"../lib/dependency-graph/network-node.js":51,"../lib/dependency-graph/simulator/network-analyzer.js":54,"../lib/network-request.js":68,"../lib/traces/tracing-processor.js":74,"./computed-artifact.js":8,"./network-records.js":29,"./trace-of-tab.js":33}],31:[function(require,module,exports){
+},{"../lib/dependency-graph/cpu-node.js":53,"../lib/dependency-graph/network-node.js":54,"../lib/dependency-graph/simulator/network-analyzer.js":57,"../lib/network-request.js":71,"../lib/traces/tracing-processor.js":79,"./computed-artifact.js":8,"./network-records.js":31,"./trace-of-tab.js":35}],33:[function(require,module,exports){
 
 
 
@@ -16494,7 +18106,7 @@
 
 module.exports=makeComputedArtifact(Screenshots);
 
-},{"./computed-artifact.js":8}],32:[function(require,module,exports){
+},{"./computed-artifact.js":8}],34:[function(require,module,exports){
 
 
 
@@ -16550,7 +18162,7 @@
 
 module.exports=makeComputedArtifact(Speedline);
 
-},{"../lib/lh-error.js":64,"./computed-artifact.js":8,"./trace-of-tab.js":33,"speedline-core":154}],33:[function(require,module,exports){
+},{"../lib/lh-error.js":67,"./computed-artifact.js":8,"./trace-of-tab.js":35,"speedline-core":146}],35:[function(require,module,exports){
 
 
 
@@ -16744,7 +18356,7 @@
 
 module.exports=makeComputedArtifact(TraceOfTab);
 
-},{"../lib/lh-error.js":64,"../lib/sentry.js":70,"../lib/traces/tracing-processor.js":74,"./computed-artifact.js":8,"lighthouse-logger":114}],34:[function(require,module,exports){
+},{"../lib/lh-error.js":67,"../lib/sentry.js":74,"../lib/traces/tracing-processor.js":79,"./computed-artifact.js":8,"lighthouse-logger":120}],36:[function(require,module,exports){
 
 
 
@@ -16829,7 +18441,65 @@
 
 module.exports=makeComputedArtifact(UserTimings);
 
-},{"./computed-artifact.js":8,"./trace-of-tab.js":33}],35:[function(require,module,exports){
+},{"./computed-artifact.js":8,"./trace-of-tab.js":35}],37:[function(require,module,exports){
+
+
+
+
+
+'use strict';
+
+const Parser=require('metaviewport-parser');
+
+const makeComputedArtifact=require('./computed-artifact.js');
+
+class ViewportMeta{
+
+
+
+
+static async compute_({MetaElements}){
+const viewportMeta=MetaElements.find(meta=>meta.name==='viewport');
+
+if(!viewportMeta){
+return{
+hasViewportTag:false,
+isMobileOptimized:false,
+parserWarnings:[]};
+
+}
+
+const warnings=[];
+const parsedProps=Parser.parseMetaViewPortContent(viewportMeta.content||'');
+
+if(Object.keys(parsedProps.unknownProperties).length){
+warnings.push(`Invalid properties found: ${JSON.stringify(parsedProps.unknownProperties)}`);
+}
+if(Object.keys(parsedProps.invalidValues).length){
+warnings.push(`Invalid values found: ${JSON.stringify(parsedProps.invalidValues)}`);
+}
+
+const viewportProps=parsedProps.validProperties;
+const isMobileOptimized=Boolean(viewportProps.width||viewportProps['initial-scale']);
+
+return{
+hasViewportTag:true,
+isMobileOptimized,
+parserWarnings:warnings};
+
+}}
+
+
+module.exports=makeComputedArtifact(ViewportMeta);
+
+
+
+
+
+
+
+
+},{"./computed-artifact.js":8,"metaviewport-parser":124}],38:[function(require,module,exports){
 
 
 
@@ -16916,7 +18586,7 @@
 }
 
 return auditRefsJson.map(auditRefJson=>{
-const{id,weight,...invalidRest}=auditRefJson;
+const{id,weight,group,...invalidRest}=auditRefJson;
 assertNoExcessProperties(invalidRest,pluginName,'auditRef');
 
 if(typeof id!=='string'){
@@ -16925,10 +18595,15 @@
 if(typeof weight!=='number'){
 throw new Error(`${pluginName} has an invalid auditRef weight.`);
 }
+if(typeof group!=='string'&&typeof group!=='undefined'){
+throw new Error(`${pluginName} has an invalid auditRef group.`);
+}
 
+const prependedGroup=group?`${pluginName}-${group}`:group;
 return{
 id,
-weight};
+weight,
+group:prependedGroup};
 
 });
 }
@@ -16980,6 +18655,47 @@
 
 
 
+static _parseGroups(groupsJson,pluginName){
+if(groupsJson===undefined){
+return undefined;
+}
+
+if(!isObjectOfUnknownProperties(groupsJson)){
+throw new Error(`${pluginName} groups json is not defined as an object.`);
+}
+
+const groups=Object.entries(groupsJson);
+
+
+const parsedGroupsJson={};
+groups.forEach(([groupId,groupJson])=>{
+if(!isObjectOfUnknownProperties(groupJson)){
+throw new Error(`${pluginName} has a group not defined as an object.`);
+}
+const{title,description,...invalidRest}=groupJson;
+assertNoExcessProperties(invalidRest,pluginName,'group');
+
+if(typeof title!=='string'){
+throw new Error(`${pluginName} has an invalid group title.`);
+}
+if(typeof description!=='string'&&typeof description!=='undefined'){
+throw new Error(`${pluginName} has an invalid group description.`);
+}
+parsedGroupsJson[`${pluginName}-${groupId}`]={
+title,
+description};
+
+});
+return parsedGroupsJson;
+}
+
+
+
+
+
+
+
+
 static parsePlugin(pluginJson,pluginName){
 
 pluginJson=JSON.parse(JSON.stringify(pluginJson));
@@ -16990,6 +18706,7 @@
 const{
 audits:pluginAuditsJson,
 category:pluginCategoryJson,
+groups:pluginGroupsJson,
 ...invalidRest}=
 pluginJson;
 
@@ -16998,15 +18715,16 @@
 return{
 audits:ConfigPlugin._parseAuditsList(pluginAuditsJson,pluginName),
 categories:{
-[pluginName]:ConfigPlugin._parseCategory(pluginCategoryJson,pluginName)}};
+[pluginName]:ConfigPlugin._parseCategory(pluginCategoryJson,pluginName)},
 
+groups:ConfigPlugin._parseGroups(pluginGroupsJson,pluginName)};
 
 }}
 
 
 module.exports=ConfigPlugin;
 
-},{}],36:[function(require,module,exports){
+},{}],39:[function(require,module,exports){
 (function(process,__dirname){
 
 
@@ -17972,7 +19690,7 @@
 module.exports=Config;
 
 }).call(this,require('_process'),"/lighthouse-core/config");
-},{"../audits/audit.js":3,"../runner.js":77,"./../lib/i18n/i18n.js":60,"./config-plugin.js":35,"./constants.js":37,"./default-config.js":38,"./full-config.js":39,"_process":131,"lighthouse-logger":114,"lodash.isequal":115,"path":129}],37:[function(require,module,exports){
+},{"../audits/audit.js":3,"../runner.js":82,"./../lib/i18n/i18n.js":63,"./config-plugin.js":38,"./constants.js":40,"./default-config.js":41,"./full-config.js":42,"_process":137,"lighthouse-logger":120,"lodash.isequal":121,"path":135}],40:[function(require,module,exports){
 
 
 
@@ -17985,6 +19703,7 @@
 
 
 
+
 const DEVTOOLS_RTT_ADJUSTMENT_FACTOR=3.75;
 const DEVTOOLS_THROUGHPUT_ADJUSTMENT_FACTOR=0.9;
 
@@ -18017,6 +19736,7 @@
 
 const defaultSettings={
 output:'json',
+maxWaitForFcp:15*1000,
 maxWaitForLoad:45*1000,
 throttlingMethod:'simulate',
 throttling:throttling.mobileSlow4G,
@@ -18032,6 +19752,7 @@
 blockedUrlPatterns:null,
 additionalTraceCategories:null,
 extraHeaders:null,
+precomputedLanternData:null,
 onlyAudits:null,
 onlyCategories:null,
 skipAudits:null};
@@ -18063,7 +19784,7 @@
 nonSimulatedPassConfigOverrides};
 
 
-},{}],38:[function(require,module,exports){
+},{}],41:[function(require,module,exports){
 (function(__filename){
 
 
@@ -18099,37 +19820,51 @@
 
 diagnosticsGroupDescription:'More information about the performance of your application.',
 
-a11yColorContrastGroupTitle:'Color Contrast Is Satisfactory',
+a11yCategoryTitle:'Accessibility',
+
+a11yCategoryDescription:'These checks highlight opportunities to [improve the accessibility of your web app](https://developers.google.com/web/fundamentals/accessibility). Only a subset of accessibility issues can be automatically detected so manual testing is also encouraged.',
+
+a11yCategoryManualDescription:'These items address areas which an automated testing tool cannot cover. Learn more in our guide on [conducting an accessibility review](https://developers.google.com/web/fundamentals/accessibility/how-to-review).',
+
+a11yBestPracticesGroupTitle:'Best practices',
+
+a11yBestPracticesGroupDescription:'These items highlight common accessibility best practices.',
+
+a11yColorContrastGroupTitle:'Contrast',
 
 a11yColorContrastGroupDescription:'These are opportunities to improve the legibility of your content.',
 
-a11yDescribeContentsGroupTitle:'Elements Describe Contents Well',
+a11yNamesLabelsGroupTitle:'Names and labels',
 
-a11yDescribeContentsGroupDescription:'These are opportunities to make your content easier to understand for a user of assistive technology, like a screen reader.',
+a11yNamesLabelsGroupDescription:'These are opportunities to improve the semantics of the controls in your application. This may enhance the experience for users of assistive technology, like a screen reader.',
 
-a11yWellStructuredGroupTitle:'Elements Are Well Structured',
+a11yNavigationGroupTitle:'Navigation',
 
-a11yWellStructuredGroupDescription:'These are opportunities to make sure your HTML is appropriately structured.',
+a11yNavigationGroupDescription:'These are opportunities to improve keyboard navigation in your application.',
 
-a11yAriaGroupTitle:'ARIA Attributes Follow Best Practices',
+a11yAriaGroupTitle:'ARIA',
 
 a11yAriaGroupDescription:'These are opportunities to improve the usage of ARIA in your application which may enhance the experience for users of assistive technology, like a screen reader.',
 
-a11yCorrectAttributesGroupTitle:'Elements Use Attributes Correctly',
-
-a11yCorrectAttributesGroupDescription:'These are opportunities to improve the configuration of your HTML elements.',
-
-a11yElementNamesGroupTitle:'Elements Have Discernible Names',
-
-a11yElementNamesGroupDescription:'These are opportunities to improve the semantics of the controls in your application. This may enhance the experience for users of assistive technology, like a screen reader.',
-
-a11yLanguageGroupTitle:'Page Specifies Valid Language',
+a11yLanguageGroupTitle:'Internationalization and localization',
 
 a11yLanguageGroupDescription:'These are opportunities to improve the interpretation of your content by users in different locales.',
 
-a11yMetaGroupTitle:'Meta Tags Used Properly',
+a11yAudioVideoGroupTitle:'Audio and video',
 
-a11yMetaGroupDescription:'These are opportunities to improve the user experience of your site.',
+a11yAudioVideoGroupDescription:'These are opportunities to provide alternative content for audio and video. This may improve the experience for users with hearing or vision impairments.',
+
+a11yTablesListsVideoGroupTitle:'Tables and lists',
+
+a11yTablesListsVideoGroupDescription:'These are opportunities to to improve the experience of reading tabular or list data using assistive technology, like a screen reader.',
+
+seoCategoryTitle:'SEO',
+
+seoCategoryDescription:'These checks ensure that your page is optimized for search engine results ranking. '+
+'There are additional factors Lighthouse does not check that may affect your search ranking. '+
+'[Learn more](https://support.google.com/webmasters/answer/35769).',
+
+seoCategoryManualDescription:'Run these additional validators on your site to check additional SEO best practices.',
 
 pwaFastReliableGroupTitle:'Fast and reliable',
 
@@ -18154,14 +19889,13 @@
 'scripts',
 'css-usage',
 'viewport-dimensions',
-'manifest',
 'runtime-exceptions',
 'chrome-console-messages',
 'accessibility',
+'anchor-elements',
 'image-elements',
 'link-elements',
 'meta-elements',
-'dobetterweb/anchors-with-no-rel-noopener',
 'dobetterweb/appcache',
 'dobetterweb/doctype',
 'dobetterweb/domstats',
@@ -18171,11 +19905,11 @@
 'dobetterweb/response-compression',
 'dobetterweb/tags-blocking-first-paint',
 'seo/font-size',
-'seo/crawlable-links',
 'seo/hreflang',
 'seo/embedded-content',
 'seo/canonical',
-'seo/robots-txt']},
+'seo/robots-txt',
+'seo/tap-targets']},
 
 
 {
@@ -18209,6 +19943,7 @@
 'screenshot-thumbnails',
 'final-screenshot',
 'metrics/estimated-input-latency',
+'metrics/max-potential-fid',
 'errors-in-console',
 'time-to-first-byte',
 'metrics/first-cpu-idle',
@@ -18227,12 +19962,17 @@
 'uses-rel-preload',
 'uses-rel-preconnect',
 'font-display',
+'diagnostics',
 'network-requests',
+'network-rtt',
+'network-server-latency',
+'main-thread-tasks',
 'metrics',
 'offline-start-url',
 'manual/pwa-cross-browser',
 'manual/pwa-page-transitions',
 'manual/pwa-each-page-has-url',
+'accessibility/accesskeys',
 'accessibility/aria-allowed-attr',
 'accessibility/aria-required-attr',
 'accessibility/aria-required-children',
@@ -18267,7 +20007,6 @@
 'accessibility/valid-lang',
 'accessibility/video-caption',
 'accessibility/video-description',
-'accessibility/manual/accesskeys',
 'accessibility/manual/custom-controls-labels',
 'accessibility/manual/custom-controls-roles',
 'accessibility/manual/focus-traps',
@@ -18309,10 +20048,10 @@
 'seo/link-text',
 'seo/is-crawlable',
 'seo/robots-txt',
+'seo/tap-targets',
 'seo/hreflang',
 'seo/plugins',
 'seo/canonical',
-'seo/manual/mobile-friendly',
 'seo/manual/structured-data'],
 
 
@@ -18337,37 +20076,37 @@
 'pwa-optimized':{
 title:str_(UIStrings.pwaOptimizedGroupTitle)},
 
+'a11y-best-practices':{
+title:str_(UIStrings.a11yBestPracticesGroupTitle),
+description:str_(UIStrings.a11yBestPracticesGroupDescription)},
+
 'a11y-color-contrast':{
 title:str_(UIStrings.a11yColorContrastGroupTitle),
 description:str_(UIStrings.a11yColorContrastGroupDescription)},
 
-'a11y-describe-contents':{
-title:str_(UIStrings.a11yDescribeContentsGroupTitle),
-description:str_(UIStrings.a11yDescribeContentsGroupDescription)},
+'a11y-names-labels':{
+title:str_(UIStrings.a11yNamesLabelsGroupTitle),
+description:str_(UIStrings.a11yNamesLabelsGroupDescription)},
 
-'a11y-well-structured':{
-title:str_(UIStrings.a11yWellStructuredGroupTitle),
-description:str_(UIStrings.a11yWellStructuredGroupDescription)},
+'a11y-navigation':{
+title:str_(UIStrings.a11yNavigationGroupTitle),
+description:str_(UIStrings.a11yNavigationGroupDescription)},
 
 'a11y-aria':{
 title:str_(UIStrings.a11yAriaGroupTitle),
 description:str_(UIStrings.a11yAriaGroupDescription)},
 
-'a11y-correct-attributes':{
-title:str_(UIStrings.a11yCorrectAttributesGroupTitle),
-description:str_(UIStrings.a11yCorrectAttributesGroupDescription)},
-
-'a11y-element-names':{
-title:str_(UIStrings.a11yElementNamesGroupTitle),
-description:str_(UIStrings.a11yElementNamesGroupDescription)},
-
 'a11y-language':{
 title:str_(UIStrings.a11yLanguageGroupTitle),
 description:str_(UIStrings.a11yLanguageGroupDescription)},
 
-'a11y-meta':{
-title:str_(UIStrings.a11yMetaGroupTitle),
-description:str_(UIStrings.a11yMetaGroupDescription)},
+'a11y-audio-video':{
+title:str_(UIStrings.a11yAudioVideoGroupTitle),
+description:str_(UIStrings.a11yAudioVideoGroupDescription)},
+
+'a11y-tables-lists':{
+title:str_(UIStrings.a11yTablesListsVideoGroupTitle),
+description:str_(UIStrings.a11yTablesListsVideoGroupDescription)},
 
 'seo-mobile':{
 title:'Mobile Friendly',
@@ -18393,6 +20132,7 @@
 {id:'interactive',weight:5,group:'metrics'},
 {id:'first-cpu-idle',weight:2,group:'metrics'},
 {id:'estimated-input-latency',weight:0,group:'metrics'},
+{id:'max-potential-fid',weight:0},
 
 {id:'render-blocking-resources',weight:0,group:'load-opportunities'},
 {id:'uses-responsive-images',weight:0,group:'load-opportunities'},
@@ -18412,21 +20152,27 @@
 {id:'uses-long-cache-ttl',weight:0,group:'diagnostics'},
 {id:'dom-size',weight:0,group:'diagnostics'},
 {id:'critical-request-chains',weight:0,group:'diagnostics'},
-{id:'network-requests',weight:0},
-{id:'metrics',weight:0},
 {id:'user-timings',weight:0,group:'diagnostics'},
 {id:'bootup-time',weight:0,group:'diagnostics'},
-{id:'screenshot-thumbnails',weight:0},
-{id:'final-screenshot',weight:0},
 {id:'mainthread-work-breakdown',weight:0,group:'diagnostics'},
-{id:'font-display',weight:0,group:'diagnostics'}]},
+{id:'font-display',weight:0,group:'diagnostics'},
+
+{id:'network-requests',weight:0},
+{id:'network-rtt',weight:0},
+{id:'network-server-latency',weight:0},
+{id:'main-thread-tasks',weight:0},
+{id:'diagnostics',weight:0},
+{id:'metrics',weight:0},
+{id:'screenshot-thumbnails',weight:0},
+{id:'final-screenshot',weight:0}]},
 
 
 'accessibility':{
-title:'Accessibility',
-description:'These checks highlight opportunities to [improve the accessibility of your web app](https://developers.google.com/web/fundamentals/accessibility). Only a subset of accessibility issues can be automatically detected so manual testing is also encouraged.',
-manualDescription:'These items address areas which an automated testing tool cannot cover. Learn more in our guide on [conducting an accessibility review](https://developers.google.com/web/fundamentals/accessibility/how-to-review).',
+title:str_(UIStrings.a11yCategoryTitle),
+description:str_(UIStrings.a11yCategoryDescription),
+manualDescription:str_(UIStrings.a11yCategoryManualDescription),
 auditRefs:[
+{id:'accesskeys',weight:3,group:'a11y-navigation'},
 {id:'aria-allowed-attr',weight:3,group:'a11y-aria'},
 {id:'aria-required-attr',weight:2,group:'a11y-aria'},
 {id:'aria-required-children',weight:5,group:'a11y-aria'},
@@ -18434,35 +20180,34 @@
 {id:'aria-roles',weight:3,group:'a11y-aria'},
 {id:'aria-valid-attr-value',weight:2,group:'a11y-aria'},
 {id:'aria-valid-attr',weight:5,group:'a11y-aria'},
-{id:'audio-caption',weight:4,group:'a11y-correct-attributes'},
-{id:'button-name',weight:10,group:'a11y-element-names'},
-{id:'bypass',weight:10,group:'a11y-describe-contents'},
+{id:'audio-caption',weight:4,group:'a11y-audio-video'},
+{id:'button-name',weight:10,group:'a11y-names-labels'},
+{id:'bypass',weight:10,group:'a11y-navigation'},
 {id:'color-contrast',weight:6,group:'a11y-color-contrast'},
-{id:'definition-list',weight:1,group:'a11y-well-structured'},
-{id:'dlitem',weight:1,group:'a11y-well-structured'},
-{id:'document-title',weight:2,group:'a11y-describe-contents'},
-{id:'duplicate-id',weight:5,group:'a11y-well-structured'},
-{id:'frame-title',weight:5,group:'a11y-describe-contents'},
+{id:'definition-list',weight:1,group:'a11y-tables-lists'},
+{id:'dlitem',weight:1,group:'a11y-tables-lists'},
+{id:'document-title',weight:2,group:'a11y-names-labels'},
+{id:'duplicate-id',weight:5,group:'a11y-best-practices'},
+{id:'frame-title',weight:5,group:'a11y-names-labels'},
 {id:'html-has-lang',weight:4,group:'a11y-language'},
 {id:'html-lang-valid',weight:1,group:'a11y-language'},
-{id:'image-alt',weight:8,group:'a11y-correct-attributes'},
-{id:'input-image-alt',weight:1,group:'a11y-correct-attributes'},
-{id:'label',weight:10,group:'a11y-describe-contents'},
-{id:'layout-table',weight:1,group:'a11y-describe-contents'},
-{id:'link-name',weight:9,group:'a11y-element-names'},
-{id:'list',weight:5,group:'a11y-well-structured'},
-{id:'listitem',weight:4,group:'a11y-well-structured'},
-{id:'meta-refresh',weight:1,group:'a11y-meta'},
-{id:'meta-viewport',weight:3,group:'a11y-meta'},
-{id:'object-alt',weight:4,group:'a11y-describe-contents'},
-{id:'tabindex',weight:4,group:'a11y-correct-attributes'},
-{id:'td-headers-attr',weight:1,group:'a11y-correct-attributes'},
-{id:'th-has-data-cells',weight:1,group:'a11y-correct-attributes'},
+{id:'image-alt',weight:8,group:'a11y-names-labels'},
+{id:'input-image-alt',weight:1,group:'a11y-names-labels'},
+{id:'label',weight:10,group:'a11y-names-labels'},
+{id:'layout-table',weight:1,group:'a11y-tables-lists'},
+{id:'link-name',weight:9,group:'a11y-names-labels'},
+{id:'list',weight:5,group:'a11y-tables-lists'},
+{id:'listitem',weight:4,group:'a11y-tables-lists'},
+{id:'meta-refresh',weight:1,group:'a11y-best-practices'},
+{id:'meta-viewport',weight:3,group:'a11y-best-practices'},
+{id:'object-alt',weight:4,group:'a11y-names-labels'},
+{id:'tabindex',weight:4,group:'a11y-navigation'},
+{id:'td-headers-attr',weight:1,group:'a11y-tables-lists'},
+{id:'th-has-data-cells',weight:1,group:'a11y-tables-lists'},
 {id:'valid-lang',weight:1,group:'a11y-language'},
-{id:'video-caption',weight:4,group:'a11y-describe-contents'},
-{id:'video-description',weight:3,group:'a11y-describe-contents'},
+{id:'video-caption',weight:4,group:'a11y-audio-video'},
+{id:'video-description',weight:3,group:'a11y-audio-video'},
 
-{id:'accesskeys',weight:0},
 {id:'logical-tab-order',weight:0},
 {id:'focusable-controls',weight:0},
 {id:'interactive-element-affordance',weight:0},
@@ -18497,11 +20242,9 @@
 
 
 'seo':{
-title:'SEO',
-description:'These checks ensure that your page is optimized for search engine results ranking. '+
-'There are additional factors Lighthouse does not check that may affect your search ranking. '+
-'[Learn more](https://support.google.com/webmasters/answer/35769).',
-manualDescription:'Run these additional validators on your site to check additional SEO best practices.',
+title:str_(UIStrings.seoCategoryTitle),
+description:str_(UIStrings.seoCategoryDescription),
+manualDescription:str_(UIStrings.seoCategoryManualDescription),
 auditRefs:[
 {id:'viewport',weight:1,group:'seo-mobile'},
 {id:'document-title',weight:1,group:'seo-content'},
@@ -18514,8 +20257,8 @@
 {id:'canonical',weight:1,group:'seo-content'},
 {id:'font-size',weight:1,group:'seo-mobile'},
 {id:'plugins',weight:1,group:'seo-content'},
+{id:'tap-targets',weight:1,group:'seo-mobile'},
 
-{id:'mobile-friendly',weight:0},
 {id:'structured-data',weight:0}]},
 
 
@@ -18559,7 +20302,7 @@
 
 
 }).call(this,"/lighthouse-core/config/default-config.js");
-},{"../lib/i18n/i18n.js":60,"./constants":37}],39:[function(require,module,exports){
+},{"../lib/i18n/i18n.js":63,"./constants":40}],42:[function(require,module,exports){
 
 
 
@@ -18594,7 +20337,7 @@
 
 module.exports=fullConfig;
 
-},{}],40:[function(require,module,exports){
+},{}],43:[function(require,module,exports){
 
 
 
@@ -18757,7 +20500,7 @@
 
 module.exports=Connection;
 
-},{"../../lib/lh-error":64,"events":94,"lighthouse-logger":114}],41:[function(require,module,exports){
+},{"../../lib/lh-error":67,"events":100,"lighthouse-logger":120}],44:[function(require,module,exports){
 
 
 
@@ -18817,7 +20560,7 @@
 
 module.exports=RawConnection;
 
-},{"./connection.js":40}],42:[function(require,module,exports){
+},{"./connection.js":43}],45:[function(require,module,exports){
 
 
 
@@ -18874,7 +20617,7 @@
 
 module.exports=DevtoolsLog;
 
-},{}],43:[function(require,module,exports){
+},{}],46:[function(require,module,exports){
 (function(Buffer){
 
 
@@ -18926,7 +20669,7 @@
 this._eventEmitter=new EventEmitter();
 this._connection=connection;
 
-this._devtoolsLog=new DevtoolsLog(/^(Page|Network)\./);
+this._devtoolsLog=new DevtoolsLog(/^(Page|Network|Target)\./);
 this.online=true;
 
 this._domainEnabledCounts=new Map();
@@ -18947,6 +20690,20 @@
 
 this._monitoredUrl=null;
 
+let targetProxyMessageId=0;
+this.on('Target.attachedToTarget',event=>{
+targetProxyMessageId++;
+
+if(event.targetInfo.type!=='iframe')return;
+
+
+
+this.sendCommand('Target.sendMessageToTarget',{
+message:JSON.stringify({id:targetProxyMessageId,method:'Network.enable'}),
+sessionId:event.sessionId});
+
+});
+
 connection.on('protocolevent',event=>{
 this._devtoolsLog.record(event);
 if(this._networkStatusMonitor){
@@ -19231,6 +20988,10 @@
 
 
 async _evaluateInContext(expression,contextId){
+
+
+const timeout=this._nextProtocolTimeout===DEFAULT_PROTOCOL_TIMEOUT?
+60000:this._nextProtocolTimeout;
 const evaluationParams={
 
 
@@ -19250,11 +21011,11 @@
 includeCommandLineAPI:true,
 awaitPromise:true,
 returnByValue:true,
-timeout:60000,
+timeout,
 contextId};
 
 
-this.setNextProtocolTimeout(60000);
+this.setNextProtocolTimeout(timeout);
 const response=await this.sendCommand('Runtime.evaluate',evaluationParams);
 if(response.exceptionDetails){
 
@@ -19278,7 +21039,20 @@
 
 async getAppManifest(){
 this.setNextProtocolTimeout(3000);
-const response=await this.sendCommand('Page.getAppManifest');
+let response;
+try{
+response=await this.sendCommand('Page.getAppManifest');
+}catch(err){
+if(err.code==='PROTOCOL_TIMEOUT'){
+
+
+log.error('Driver','Failed fetching manifest',err);
+return null;
+}
+
+throw err;
+}
+
 let data=response.data;
 
 
@@ -19411,18 +21185,23 @@
 
 
 
-_waitForFCP(){
+
+_waitForFCP(maxWaitForFCPMs){
 
 let cancel=()=>{
 throw new Error('_waitForFCP.cancel() called before it was defined');
 };
 
-const promise=new Promise(resolve=>{
+const promise=new Promise((resolve,reject)=>{
+const maxWaitTimeout=setTimeout(()=>{
+reject(new LHError(LHError.errors.NO_FCP));
+},maxWaitForFCPMs);
+
 
 const lifecycleListener=e=>{
 if(e.name==='firstContentfulPaint'){
-cancel();
 resolve();
+cancel();
 }
 };
 
@@ -19433,6 +21212,8 @@
 if(canceled)return;
 canceled=true;
 this.off('Page.lifecycleEvent',lifecycleListener);
+maxWaitTimeout&&clearTimeout(maxWaitTimeout);
+reject(new Error('Wait for FCP canceled'));
 };
 });
 
@@ -19709,12 +21490,12 @@
 
 
 async _waitForFullyLoaded(pauseAfterLoadMs,networkQuietThresholdMs,cpuQuietThresholdMs,
-maxWaitForLoadedMs,shouldWaitForFCP){
+maxWaitForLoadedMs,maxWaitForFCPMs){
 
 let maxTimeoutHandle;
 
 
-const waitForFCP=shouldWaitForFCP?this._waitForFCP():this._waitForNothing();
+const waitForFCP=maxWaitForFCPMs?this._waitForFCP(maxWaitForFCPMs):this._waitForNothing();
 
 const waitForLoadEvent=this._waitForLoadEvent(pauseAfterLoadMs);
 
@@ -19742,6 +21523,11 @@
 return function(){
 log.verbose('Driver','loadEventFired and network considered idle');
 };
+}).catch(err=>{
+
+return function(){
+throw err;
+};
 });
 
 
@@ -19876,6 +21662,12 @@
 await this._beginNetworkStatusMonitoring(url);
 await this._clearIsolatedContextId();
 
+
+await this.sendCommand('Target.setAutoAttach',{
+autoAttach:true,
+waitForDebuggerOnStart:false});
+
+
 await this.sendCommand('Page.enable');
 await this.sendCommand('Page.setLifecycleEventsEnabled',{enabled:true});
 await this.sendCommand('Emulation.setScriptExecutionDisabled',{value:disableJS});
@@ -19888,16 +21680,19 @@
 const passConfig=passContext.passConfig||{};
 let{pauseAfterLoadMs,networkQuietThresholdMs,cpuQuietThresholdMs}=passConfig;
 let maxWaitMs=passContext.settings&&passContext.settings.maxWaitForLoad;
+let maxFCPMs=passContext.settings&&passContext.settings.maxWaitForFcp;
 
 
 if(typeof pauseAfterLoadMs!=='number')pauseAfterLoadMs=DEFAULT_PAUSE_AFTER_LOAD;
 if(typeof networkQuietThresholdMs!=='number')networkQuietThresholdMs=DEFAULT_NETWORK_QUIET_THRESHOLD;
 if(typeof cpuQuietThresholdMs!=='number')cpuQuietThresholdMs=DEFAULT_CPU_QUIET_THRESHOLD;
 if(typeof maxWaitMs!=='number')maxWaitMs=constants.defaultSettings.maxWaitForLoad;
+if(typeof maxFCPMs!=='number')maxFCPMs=constants.defaultSettings.maxWaitForFcp;
 
 
+if(!waitForFCP)maxFCPMs=undefined;
 await this._waitForFullyLoaded(pauseAfterLoadMs,networkQuietThresholdMs,cpuQuietThresholdMs,
-maxWaitMs,waitForFCP);
+maxWaitMs,maxFCPMs);
 }
 
 
@@ -20285,7 +22080,7 @@
 module.exports=Driver;
 
 }).call(this,require("buffer").Buffer);
-},{"../config/constants":37,"../lib/element":57,"../lib/emulation":58,"../lib/lh-error":64,"../lib/network-recorder":67,"../lib/network-request":68,"../lib/page-functions.js":69,"../lib/url-shim":"url","./connections/connection.js":40,"./devtools-log":42,"buffer":89,"events":94,"lighthouse-logger":114}],44:[function(require,module,exports){
+},{"../config/constants":40,"../lib/element":60,"../lib/emulation":61,"../lib/lh-error":67,"../lib/network-recorder":70,"../lib/network-request":71,"../lib/page-functions.js":72,"../lib/url-shim":"url","./connections/connection.js":43,"./devtools-log":45,"buffer":94,"events":100,"lighthouse-logger":120}],47:[function(require,module,exports){
 
 
 
@@ -20683,10 +22478,19 @@
 
 
 static async getBaseArtifacts(options){
+const hostUserAgent=(await options.driver.getBrowserVersion()).userAgent;
+
+const{emulatedFormFactor}=options.settings;
+
+const IsMobileHost=hostUserAgent.includes('Android')||hostUserAgent.includes('Mobile');
+const TestedAsMobileDevice=emulatedFormFactor==='mobile'||
+emulatedFormFactor!=='desktop'&&IsMobileHost;
+
 return{
 fetchTime:new Date().toJSON(),
 LighthouseRunWarnings:[],
-HostUserAgent:(await options.driver.getBrowserVersion()).userAgent,
+TestedAsMobileDevice,
+HostUserAgent:hostUserAgent,
 NetworkUserAgent:'',
 BenchmarkIndex:0,
 WebAppManifest:null,
@@ -20800,7 +22604,7 @@
 
 module.exports=GatherRunner;
 
-},{"../config/constants.js":37,"../gather/driver.js":43,"../lib/lh-error.js":64,"../lib/manifest-parser.js":65,"../lib/network-recorder.js":67,"../lib/url-shim.js":"url","lighthouse-logger":114}],45:[function(require,module,exports){
+},{"../config/constants.js":40,"../gather/driver.js":46,"../lib/lh-error.js":67,"../lib/manifest-parser.js":68,"../lib/network-recorder.js":70,"../lib/url-shim.js":"url","lighthouse-logger":120}],48:[function(require,module,exports){
 
 
 
@@ -20861,7 +22665,7 @@
 
 module.exports=Gatherer;
 
-},{}],46:[function(require,module,exports){
+},{}],49:[function(require,module,exports){
 
 
 
@@ -20940,7 +22744,7 @@
 
 module.exports=lighthouse;
 
-},{"./audits/audit":3,"./config/config":36,"./gather/connections/cri.js":88,"./gather/driver":43,"./gather/gatherers/gatherer":45,"./lib/lh-error.js":64,"./lib/url-shim.js":"url","./runner":77,"lighthouse-logger":114}],47:[function(require,module,exports){
+},{"./audits/audit":3,"./config/config":39,"./gather/connections/cri.js":93,"./gather/driver":46,"./gather/gatherers/gatherer":48,"./lib/lh-error.js":67,"./lib/url-shim.js":"url","./runner":82,"lighthouse-logger":120}],50:[function(require,module,exports){
 
 
 
@@ -21023,7 +22827,7 @@
 
 module.exports=ArbitraryEqualityMap;
 
-},{"lodash.isequal":115}],48:[function(require,module,exports){
+},{"lodash.isequal":121}],51:[function(require,module,exports){
 (function(process){
 
 
@@ -21036,11 +22840,13 @@
 const path=require('path');
 const log=require('lighthouse-logger');
 const stream=require('stream');
-const Simulator=require('./dependency-graph/simulator/simulator');
-const lanternTraceSaver=require('./lantern-trace-saver');
-const Metrics=require('./traces/pwmetrics-events');
+const Simulator=require('./dependency-graph/simulator/simulator.js');
+const lanternTraceSaver=require('./lantern-trace-saver.js');
+const Metrics=require('./traces/pwmetrics-events.js');
 const rimraf=require('rimraf');
 const mkdirp=require('mkdirp');
+const NetworkAnalysisComputed=require('../computed/network-analysis.js');
+const LoadSimulatorComputed=require('../computed/load-simulator.js');
 
 const artifactsFilename='artifacts.json';
 const traceSuffix='.trace.json';
@@ -21299,17 +23105,31 @@
 });
 }
 
+
+
+
+
+
+async function saveLanternNetworkData(devtoolsLog,outputPath){
+const context={computedCache:new Map()};
+const networkAnalysis=await NetworkAnalysisComputed.request(devtoolsLog,context);
+const lanternData=LoadSimulatorComputed.convertAnalysisToSaveableLanternData(networkAnalysis);
+
+fs.writeFileSync(outputPath,JSON.stringify(lanternData));
+}
+
 module.exports={
 saveArtifacts,
 loadArtifacts,
 saveAssets,
 prepareAssets,
 saveTrace,
-logAssets};
+logAssets,
+saveLanternNetworkData};
 
 
 }).call(this,require('_process'));
-},{"./dependency-graph/simulator/simulator":55,"./lantern-trace-saver":63,"./traces/pwmetrics-events":73,"_process":131,"lighthouse-logger":114,"mkdirp":88,"path":129,"rimraf":88,"stream":156}],49:[function(require,module,exports){
+},{"../computed/load-simulator.js":10,"../computed/network-analysis.js":30,"./dependency-graph/simulator/simulator.js":58,"./lantern-trace-saver.js":66,"./traces/pwmetrics-events.js":78,"_process":137,"lighthouse-logger":120,"mkdirp":93,"path":135,"rimraf":93,"stream":148}],52:[function(require,module,exports){
 
 
 
@@ -21632,7 +23452,7 @@
 
 module.exports=BaseNode;
 
-},{}],50:[function(require,module,exports){
+},{}],53:[function(require,module,exports){
 
 
 
@@ -21718,7 +23538,7 @@
 
 module.exports=CPUNode;
 
-},{"./base-node":49}],51:[function(require,module,exports){
+},{"./base-node":52}],54:[function(require,module,exports){
 
 
 
@@ -21802,7 +23622,7 @@
 
 module.exports=NetworkNode;
 
-},{"../network-request":68,"./base-node":49}],52:[function(require,module,exports){
+},{"../network-request":71,"./base-node":52}],55:[function(require,module,exports){
 
 
 
@@ -21975,7 +23795,7 @@
 }};
 
 
-},{"./network-analyzer":54,"./tcp-connection":56}],53:[function(require,module,exports){
+},{"./network-analyzer":57,"./tcp-connection":59}],56:[function(require,module,exports){
 
 
 
@@ -22060,7 +23880,7 @@
 
 module.exports=DNSCache;
 
-},{}],54:[function(require,module,exports){
+},{}],57:[function(require,module,exports){
 
 
 
@@ -22526,7 +24346,7 @@
 
 
 
-},{"../../network-request":68}],55:[function(require,module,exports){
+},{"../../network-request":71}],58:[function(require,module,exports){
 
 
 
@@ -23039,7 +24859,7 @@
 
 
 
-},{"../../../config/constants":37,"../base-node":49,"./connection-pool":52,"./dns-cache":53,"./tcp-connection":56}],56:[function(require,module,exports){
+},{"../../../config/constants":40,"../base-node":52,"./connection-pool":55,"./dns-cache":56,"./tcp-connection":59}],59:[function(require,module,exports){
 
 
 
@@ -23247,7 +25067,7 @@
 
 
 
-},{}],57:[function(require,module,exports){
+},{}],60:[function(require,module,exports){
 
 
 
@@ -23320,7 +25140,7 @@
 
 module.exports=Element;
 
-},{"../gather/driver.js":43}],58:[function(require,module,exports){
+},{"../gather/driver.js":46}],61:[function(require,module,exports){
 
 
 
@@ -23361,15 +25181,10 @@
 deviceScaleFactor:1};
 
 
-const NEXUS5X_USERAGENT={
 
-userAgent:'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3559.0 Mobile Safari/537.36'};
+const NEXUS5X_USERAGENT='Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3694.0 Mobile Safari/537.36 Chrome-Lighthouse';
 
-
-const DESKTOP_USERAGENT={
-
-userAgent:'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3559.0 Safari/537.36'};
-
+const DESKTOP_USERAGENT='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3694.0 Safari/537.36 Chrome-Lighthouse';
 
 const OFFLINE_METRICS={
 offline:true,
@@ -23399,7 +25214,7 @@
 driver.sendCommand('Emulation.setDeviceMetricsOverride',NEXUS5X_EMULATION_METRICS),
 
 driver.sendCommand('Network.enable'),
-driver.sendCommand('Network.setUserAgentOverride',NEXUS5X_USERAGENT),
+driver.sendCommand('Network.setUserAgentOverride',{userAgent:NEXUS5X_USERAGENT}),
 driver.sendCommand('Emulation.setTouchEmulationEnabled',{enabled:true})]);
 
 }
@@ -23413,7 +25228,7 @@
 driver.sendCommand('Emulation.setDeviceMetricsOverride',DESKTOP_EMULATION_METRICS),
 
 driver.sendCommand('Network.enable'),
-driver.sendCommand('Network.setUserAgentOverride',DESKTOP_USERAGENT),
+driver.sendCommand('Network.setUserAgentOverride',{userAgent:DESKTOP_USERAGENT}),
 driver.sendCommand('Emulation.setTouchEmulationEnabled',{enabled:false})]);
 
 }
@@ -23479,11 +25294,25 @@
 clearAllNetworkEmulation,
 enableCPUThrottling,
 disableCPUThrottling,
-goOffline};
+goOffline,
+MOBILE_USERAGENT:NEXUS5X_USERAGENT,
+DESKTOP_USERAGENT};
 
 
-},{}],59:[function(require,module,exports){
+},{}],62:[function(require,module,exports){
 module.exports={
+"lighthouse-core/audits/accessibility/accesskeys.js | description":{
+"message":"Access keys let users quickly focus a part of the page. For proper navigation, each access key must be unique. [Learn more](https://dequeuniversity.com/rules/axe/3.1/accesskeys?application=lighthouse).",
+"description":"Description of a Lighthouse audit that tells the user *why* they should try to pass. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."},
+
+"lighthouse-core/audits/accessibility/accesskeys.js | failureTitle":{
+"message":"`[accesskey]` values are not unique",
+"description":"Title of an accesibility audit that evaluates if the ARIA HTML attributes are misaligned with the aria-role HTML attribute specificed on the element, such mismatches are invalid. This title is descriptive of the failing state and is shown to users when there is a failure that needs to be addressed."},
+
+"lighthouse-core/audits/accessibility/accesskeys.js | title":{
+"message":"`[accesskey]` values are unique",
+"description":"Title of an accesibility audit that evaluates if the accesskey HTML attribute values are unique across all elements. This title is descriptive of the successful state and is shown to users when no user action is required."},
+
 "lighthouse-core/audits/accessibility/aria-allowed-attr.js | description":{
 "message":"Each ARIA `role` supports a specific subset of `aria-*` attributes. Mismatching these invalidates the `aria-*` attributes. [Learn more](https://dequeuniversity.com/rules/axe/3.1/aria-allowed-attr?application=lighthouse).",
 "description":"Description of a Lighthouse audit that tells the user *why* they should try to pass. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."},
@@ -23788,14 +25617,6 @@
 "message":"List items (`<li>`) are contained within `<ul>` or `<ol>` parent elements",
 "description":"Title of an accesibility audit that evaluates if any list item elements do not have list parent elements. This title is descriptive of the successful state and is shown to users when no user action is required."},
 
-"lighthouse-core/audits/accessibility/manual/accesskeys.js | description":{
-"message":"Access keys let users quickly focus a part of the page. For proper navigation, each access key must be unique. [Learn more](https://dequeuniversity.com/rules/axe/3.1/accesskeys?application=lighthouse).",
-"description":"Description of a Lighthouse audit that tells the user *why* they should try to pass. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."},
-
-"lighthouse-core/audits/accessibility/manual/accesskeys.js | title":{
-"message":"`[accesskey]` values are unique",
-"description":"Title of an accesibility audit that evaluates if the accesskey HTML attribute values are unique across all elements. This title is descriptive of the successful state and is shown to users when no user action is required."},
-
 "lighthouse-core/audits/accessibility/meta-refresh.js | description":{
 "message":"Users do not expect a page to refresh automatically, and doing so will move focus back to the top of the page. This may create a frustrating or confusing experience. [Learn more](https://dequeuniversity.com/rules/axe/3.1/meta-refresh?application=lighthouse).",
 "description":"Description of a Lighthouse audit that tells the user *why* they should try to pass. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."},
@@ -23917,7 +25738,7 @@
 "description":"Label for a time column in a data table; entries will be the number of milliseconds spent parsing script files for every script loaded by the page."},
 
 "lighthouse-core/audits/bootup-time.js | columnTotal":{
-"message":"Total",
+"message":"Total CPU Time",
 "description":"Label for the total time column in a data table; entries will be the number of milliseconds spent executing per resource loaded by the page."},
 
 "lighthouse-core/audits/bootup-time.js | description":{
@@ -23989,11 +25810,11 @@
 "description":"Imperative title of a Lighthouse audit that tells the user to minify the page’s JS code to reduce file size. This is displayed in a list of audit titles that Lighthouse generates."},
 
 "lighthouse-core/audits/byte-efficiency/unused-css-rules.js | description":{
-"message":"Remove unused rules from stylesheets to reduce unnecessary bytes consumed by network activity. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/unused-css).",
+"message":"Remove dead rules from stylesheets and defer the loading of CSS not used for above-the-fold content to reduce unnecessary bytes consumed by network activity. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/unused-css).",
 "description":"Description of a Lighthouse audit that tells the user *why* they should defer loading any content in CSS that isn’t needed at page load. This is displayed after a user expands the section to see more. No word length limits. 'Learn More' becomes link text to additional documentation."},
 
 "lighthouse-core/audits/byte-efficiency/unused-css-rules.js | title":{
-"message":"Defer unused CSS",
+"message":"Remove unused CSS",
 "description":"Imperative title of a Lighthouse audit that tells the user to remove content from their CSS that isn’t needed immediately and instead load that content at a later time. This is displayed in a list of audit titles that Lighthouse generates."},
 
 "lighthouse-core/audits/byte-efficiency/unused-javascript.js | description":{
@@ -24122,11 +25943,11 @@
 
 "lighthouse-core/audits/load-fast-enough-for-pwa.js | displayValueText":{
 "message":"Interactive at {timeInMs, number, seconds} s",
-"description":"[ICU Syntax] Label for the audit identifying the time it took for the page to become interactive."},
+"description":"Label for the audit identifying the time it took for the page to become interactive."},
 
 "lighthouse-core/audits/load-fast-enough-for-pwa.js | displayValueTextWithOverride":{
 "message":"Interactive on simulated mobile network at {timeInMs, number, seconds} s",
-"description":"[ICU Syntax] Label for the audit identifying the time it took for the page to become interactive on a mobile network."},
+"description":"Label for the audit identifying the time it took for the page to become interactive on a mobile network."},
 
 "lighthouse-core/audits/load-fast-enough-for-pwa.js | failureTitle":{
 "message":"Page load is not fast enough on mobile networks",
@@ -24192,6 +26013,14 @@
 "message":"Time to Interactive",
 "description":"The name of the metric that marks the time at which the page is fully loaded and is able to quickly respond to user input (clicks, taps, and keypresses feel responsive). Shown to users as the label for the numeric metric value. Ideally fits within a ~40 character limit."},
 
+"lighthouse-core/audits/metrics/max-potential-fid.js | description":{
+"message":"The potential First Input Delay that your users could experience is the duration, in milliseconds, of the longest task.",
+"description":"Description of the Maximum Potential First Input Delay metric that marks the maximum estimated time between the page receiving input (a user clicking, tapping, or typing) and the page responding. This description is displayed within a tooltip when the user hovers on the metric name to see more. No character length limits. 'Learn More' becomes link text to additional documentation."},
+
+"lighthouse-core/audits/metrics/max-potential-fid.js | title":{
+"message":"Max Potential FID",
+"description":"The name of the metric \"Maximum Potential First Input Delay\" that marks the maximum estimated time between the page receiving input (a user clicking, tapping, or typing) and the page responding. Shown to users as the label for the numeric metric value. Ideally fits within a ~40 character limit."},
+
 "lighthouse-core/audits/metrics/speed-index.js | description":{
 "message":"Speed Index shows how quickly the contents of a page are visibly populated. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/speed-index).",
 "description":"Description of the Speed Index metric, which summarizes how quickly the page looked visually complete. This is displayed within a tooltip when the user hovers on the metric name to see more. No character length limits. 'Learn More' becomes link text to additional documentation."},
@@ -24200,6 +26029,22 @@
 "message":"Speed Index",
 "description":"The name of the metric that summarizes how quickly the page looked visually complete. The name of this metric is largely abstract and can be loosely translated. Shown to users as the label for the numeric metric value. Ideally fits within a ~40 character limit."},
 
+"lighthouse-core/audits/network-rtt.js | description":{
+"message":"Network round trip times (RTT) have a large impact on performance. If the RTT to an origin is high, it's an indication that servers closer to the user could improve performance. [Learn more](https://hpbn.co/primer-on-latency-and-bandwidth/).",
+"description":"Description of a Lighthouse audit that tells the user that a high network round trip time (RTT) can effect their website's performance because the server is physically far away from them thus making the RTT high. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."},
+
+"lighthouse-core/audits/network-rtt.js | title":{
+"message":"Network Round Trip Times",
+"description":"Descriptive title of a Lighthouse audit that tells the user the round trip times to each origin the page connected to. This is displayed in a list of audit titles that Lighthouse generates."},
+
+"lighthouse-core/audits/network-server-latency.js | description":{
+"message":"Server latencies can impact web performance. If the server latency of an origin is high, it's an indication the server is overloaded or has poor backend performance. [Learn more](https://hpbn.co/primer-on-web-performance/#analyzing-the-resource-waterfall).",
+"description":"Description of a Lighthouse audit that tells the user that server latency can effect their website's performance negatively. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."},
+
+"lighthouse-core/audits/network-server-latency.js | title":{
+"message":"Server Backend Latencies",
+"description":"Descriptive title of a Lighthouse audit that tells the user the server latencies observed from each origin the page connected to. This is displayed in a list of audit titles that Lighthouse generates."},
+
 "lighthouse-core/audits/redirects.js | description":{
 "message":"Redirects introduce additional delays before the page can be loaded. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/redirects).",
 "description":"Description of a Lighthouse audit that tells users why they should reduce the number of server-side redirects on their page. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."},
@@ -24208,21 +26053,213 @@
 "message":"Avoid multiple page redirects",
 "description":"Imperative title of a Lighthouse audit that tells the user to eliminate the redirects taken through multiple URLs to load the page. This is shown in a list of audits that Lighthouse generates."},
 
+"lighthouse-core/audits/seo/canonical.js | description":{
+"message":"Canonical links suggest which URL to show in search results. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/canonical).",
+"description":"Description of a Lighthouse audit that tells the user *why* they need to have a valid rel=canonical link. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."},
+
+"lighthouse-core/audits/seo/canonical.js | explanationConflict":{
+"message":"Multiple conflicting URLs ({urlList})",
+"description":"Explanatory message stating that there was a failure in an audit caused by multiple URLs conflicting with each other. \"urlList\" will be replaced by a list of URLs (e.g. https://example.com, https://example2.com, etc )."},
+
+"lighthouse-core/audits/seo/canonical.js | explanationDifferentDomain":{
+"message":"Points to a different domain ({url})",
+"description":"Explanatory message stating that there was a failure in an audit caused by a URL pointing to a different domain. \"url\" will be replaced by the invalid URL (e.g. https://example.com)."},
+
+"lighthouse-core/audits/seo/canonical.js | explanationInvalid":{
+"message":"Invalid URL ({url})",
+"description":"Explanatory message stating that there was a failure in an audit caused by a URL being invalid. \"url\" will be replaced by the invalid URL (e.g. https://example.com)."},
+
+"lighthouse-core/audits/seo/canonical.js | explanationPointsElsewhere":{
+"message":"Points to another `hreflang` location ({url})",
+"description":"Explanatory message stating that there was a failure in an audit caused by a URL pointing to a different hreflang than the current context. \"url\" will be replaced by the invalid URL (e.g. https://example.com). 'hreflang' is an HTML attribute and should not be translated."},
+
+"lighthouse-core/audits/seo/canonical.js | explanationRelative":{
+"message":"Relative URL ({url})",
+"description":"Explanatory message stating that there was a failure in an audit caused by a URL being relative instead of absolute. \"url\" will be replaced by the invalid URL (e.g. https://example.com)."},
+
+"lighthouse-core/audits/seo/canonical.js | explanationRoot":{
+"message":"Points to the domain's root URL (the homepage), instead of an equivalent page of content",
+"description":"Explanatory message stating that the page's canonical URL was pointing to the domain's root URL, which is a common mistake. \"points\" refers to the action of the 'rel=canonical' referencing another link. \"root\" refers to the starting/home page of the website. \"domain\" refers to the registered domain name of the website."},
+
+"lighthouse-core/audits/seo/canonical.js | failureTitle":{
+"message":"Document does not have a valid `rel=canonical`",
+"description":"Title of a Lighthouse audit that provides detail on a page's rel=canonical link. This descriptive title is shown to users when the rel=canonical link is invalid and should be fixed. \"rel=canonical\" is an HTML attribute and value and so should not be translated."},
+
+"lighthouse-core/audits/seo/canonical.js | title":{
+"message":"Document has a valid `rel=canonical`",
+"description":"Title of a Lighthouse audit that provides detail on a page's rel=canonical link. This descriptive title is shown to users when the rel=canonical link is valid. \"rel=canonical\" is an HTML attribute and value and so should not be translated."},
+
 "lighthouse-core/audits/seo/font-size.js | description":{
 "message":"Font sizes less than 12px are too small to be legible and require mobile visitors to “pinch to zoom” in order to read. Strive to have >60% of page text ≥12px. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/font-sizes).",
 "description":"Description of a Lighthouse audit that tells the user *why* they need to use a larger font size. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."},
 
 "lighthouse-core/audits/seo/font-size.js | displayValue":{
 "message":"{decimalProportion, number, extendedPercent} legible text",
-"description":"[ICU Syntax] Label for the audit identifying font sizes that are too small."},
+"description":"Label for the audit identifying font sizes that are too small."},
+
+"lighthouse-core/audits/seo/font-size.js | explanation":{
+"message":"{decimalProportion, number, extendedPercent} of text is too small.",
+"description":"Explanatory message stating that there was a failure in an audit caused by a certain percentage of the text on the page being too small. \"decimalProportion\" will be replaced by a percentage between 0 and 100%."},
+
+"lighthouse-core/audits/seo/font-size.js | explanationViewport":{
+"message":"Text is illegible because there's no viewport meta tag optimized for mobile screens.",
+"description":"Explanatory message stating that there was a failure in an audit caused by a missing page viewport meta tag configuration. \"viewport\" and \"meta\" are HTML terms and should not be translated."},
+
+"lighthouse-core/audits/seo/font-size.js | explanationWithDisclaimer":{
+"message":"{decimalProportion, number, extendedPercent} of text is too small (based on {decimalProportionVisited, number, extendedPercent} sample).",
+"description":"Explanatory message stating that there was a failure in an audit caused by a certain percentage of the text on the page being too small, based on a sample size of text that was less than 100% of the text on the page. \"decimalProportion\" will be replaced by a percentage between 0 and 100%."},
 
 "lighthouse-core/audits/seo/font-size.js | failureTitle":{
 "message":"Document doesn't use legible font sizes",
-"description":"Imperative title of a Lighthouse audit that tells the user that they should use font sizes that are easily read by the user. This imperative title is shown to users when there is a font that is too small to be read by the user."},
+"description":"Title of a Lighthouse audit that provides detail on the font sizes used on the page. This descriptive title is shown to users when there is a font that may be too small to be read by users."},
 
 "lighthouse-core/audits/seo/font-size.js | title":{
 "message":"Document uses legible font sizes",
-"description":"Imperative title of a Lighthouse audit that tells the user that they should use font sizes that are easily read by the user. This is displayed in a list of audit titles that Lighthouse generates."},
+"description":"Title of a Lighthouse audit that provides detail on the font sizes used on the page. This descriptive title is shown to users when the fonts used on the page are large enough to be considered legible."},
+
+"lighthouse-core/audits/seo/hreflang.js | description":{
+"message":"hreflang links tell search engines what version of a page they should list in search results for a given language or region. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/hreflang).",
+"description":"Description of a Lighthouse audit that tells the user *why* they need to have an hreflang link on their page. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. \"hreflang\" is an HTML attribute and should not be translated."},
+
+"lighthouse-core/audits/seo/hreflang.js | failureTitle":{
+"message":"Document doesn't have a valid `hreflang`",
+"description":"Title of a Lighthouse audit that provides detail on the `hreflang` attribute on a page. This descriptive title is shown when the page's `hreflang` attribute is not valid and needs to be fixed. \"hreflang\" is an HTML attribute and should not be translated."},
+
+"lighthouse-core/audits/seo/hreflang.js | title":{
+"message":"Document has a valid `hreflang`",
+"description":"Title of a Lighthouse audit that provides detail on the `hreflang` attribute on a page. This descriptive title is shown when the page's `hreflang` attribute is configured correctly. \"hreflang\" is an HTML attribute and should not be translated."},
+
+"lighthouse-core/audits/seo/http-status-code.js | description":{
+"message":"Pages with unsuccessful HTTP status codes may not be indexed properly. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/successful-http-code).",
+"description":"Description of a Lighthouse audit that tells the user *why* they need to serve pages with a valid HTTP status code. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."},
+
+"lighthouse-core/audits/seo/http-status-code.js | failureTitle":{
+"message":"Page has unsuccessful HTTP status code",
+"description":"Descriptive title of a Lighthouse audit that provides detail on the HTTP status code a page responds with. This descriptive title is shown when the page responds to requests with an HTTP status code that indicates the request was unsuccessful."},
+
+"lighthouse-core/audits/seo/http-status-code.js | title":{
+"message":"Page has successful HTTP status code",
+"description":"Title of a Lighthouse audit that provides detail on the HTTP status code a page responds with. This descriptive title is shown when the page has responded with a valid HTTP status code."},
+
+"lighthouse-core/audits/seo/is-crawlable.js | description":{
+"message":"Search engines are unable to include your pages in search results if they don't have permission to crawl them. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/indexing).",
+"description":"Description of a Lighthouse audit that tells the user *why* allowing search-engine crawling of their page is beneficial. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."},
+
+"lighthouse-core/audits/seo/is-crawlable.js | failureTitle":{
+"message":"Page is blocked from indexing",
+"description":"Title of a Lighthouse audit that provides detail on if search-engine crawlers are blocked from indexing the page. This title is shown when the page has been configured to block indexing and therefore cannot be indexed by search engines."},
+
+"lighthouse-core/audits/seo/is-crawlable.js | title":{
+"message":"Page isn’t blocked from indexing",
+"description":"Title of a Lighthouse audit that provides detail on if search-engine crawlers are blocked from indexing the page. This title is shown when the page is not blocked from indexing and can be crawled."},
+
+"lighthouse-core/audits/seo/link-text.js | description":{
+"message":"Descriptive link text helps search engines understand your content. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/descriptive-link-text).",
+"description":"Description of a Lighthouse audit that tells the user *why* they need to have descriptive text on the links in their page. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."},
+
+"lighthouse-core/audits/seo/link-text.js | displayValue":{
+"message":"{itemCount, plural,\n    =1 {1 link found}\n    other {# links found}\n    }",
+"description":"[ICU Syntax] Label for the audit identifying the number of links found. \"link\" here refers to the links in a web page to other web pages."},
+
+"lighthouse-core/audits/seo/link-text.js | failureTitle":{
+"message":"Links do not have descriptive text",
+"description":"Title of a Lighthouse audit that tests if each link on a page contains a sufficient description of what a user will find when they click it. Generic, non-descriptive text like \"click here\" doesn't give an indication of what the link leads to. This descriptive title is shown when one or more links on the page contain generic, non-descriptive text."},
+
+"lighthouse-core/audits/seo/link-text.js | title":{
+"message":"Links have descriptive text",
+"description":"Title of a Lighthouse audit that tests if each link on a page contains a sufficient description of what a user will find when they click it. Generic, non-descriptive text like \"click here\" doesn't give an indication of what the link leads to. This descriptive title is shown when all links on the page have sufficient textual descriptions."},
+
+"lighthouse-core/audits/seo/manual/structured-data.js | description":{
+"message":"Run the [Structured Data Testing Tool](https://search.google.com/structured-data/testing-tool/) and the [Structured Data Linter](http://linter.structured-data.org/) to validate structured data. [Learn more](https://developers.google.com/search/docs/guides/mark-up-content).",
+"description":"Description of a Lighthouse audit that provides detail on the structured data in a page. \"Structured data\" is a standardized data format on a page that helps a search engine categorize and understand its contents. This description is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."},
+
+"lighthouse-core/audits/seo/manual/structured-data.js | title":{
+"message":"Structured data is valid",
+"description":"Title of a Lighthouse audit that prompts users to manually check their page for valid structured data. \"Structured data\" is a standardized data format on a page that helps a search engine categorize and understand its contents."},
+
+"lighthouse-core/audits/seo/meta-description.js | description":{
+"message":"Meta descriptions may be included in search results to concisely summarize page content. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/description).",
+"description":"Description of a Lighthouse audit that tells the user *why* they need to have meta descriptions on their page. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."},
+
+"lighthouse-core/audits/seo/meta-description.js | explanation":{
+"message":"Description text is empty.",
+"description":"Explanatory message stating that there was a failure in an audit caused by the page's meta description text being empty."},
+
+"lighthouse-core/audits/seo/meta-description.js | failureTitle":{
+"message":"Document does not have a meta description",
+"description":"Title of a Lighthouse audit that provides detail on the web page's document meta description. This descriptive title is shown when the document does not have a meta description. \"meta\" should be left untranslated because it refers to an HTML element."},
+
+"lighthouse-core/audits/seo/meta-description.js | title":{
+"message":"Document has a meta description",
+"description":"Title of a Lighthouse audit that provides detail on the web page's document meta description. This descriptive title is shown when the document has a meta description. \"meta\" should be left untranslated because it refers to an HTML element."},
+
+"lighthouse-core/audits/seo/plugins.js | description":{
+"message":"Search engines can't index plugin content, and many devices restrict plugins or don't support them. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/plugins).",
+"description":"Description of a Lighthouse audit that tells the user *why* they need to avoid using browser plugins in their content. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."},
+
+"lighthouse-core/audits/seo/plugins.js | failureTitle":{
+"message":"Document uses plugins",
+"description":"Descriptive title of a Lighthouse audit that provides detail on the browser plugins used by the page. This title is shown when there is plugin content on the page."},
+
+"lighthouse-core/audits/seo/plugins.js | title":{
+"message":"Document avoids plugins",
+"description":"Title of a Lighthouse audit that provides detail on the browser plugins used by the page. This descriptive title is shown when there is no plugin content on the page that would restrict search indexing."},
+
+"lighthouse-core/audits/seo/robots-txt.js | description":{
+"message":"If your robots.txt file is malformed, crawlers may not be able to understand how you want your website to be crawled or indexed.",
+"description":"Description of a Lighthouse audit that tells the user *why* they need to have a valid robots.txt file. Note: \"robots.txt\" is a canonical filename and should not be translated. This is displayed after a user expands the section to see more. No character length limits."},
+
+"lighthouse-core/audits/seo/robots-txt.js | displayValueHttpBadCode":{
+"message":"request for robots.txt returned HTTP status: {statusCode}",
+"description":"Label for the audit identifying that the robots.txt request has returned a specific HTTP status code. Note: \"robots.txt\" is a canonical filename and should not be translated. \"statusCode\" will be replaced with a 3 digit integer which represents the status of the HTTP connectiong for this page."},
+
+"lighthouse-core/audits/seo/robots-txt.js | displayValueValidationError":{
+"message":"{itemCount, plural,\n    =1 {1 error found}\n    other {# errors found}\n    }",
+"description":"[ICU Syntax] Label for the audit identifying the number of errors that occured while validating the robots.txt file. \"itemCount\" will be replaced by the integer count of errors encountered."},
+
+"lighthouse-core/audits/seo/robots-txt.js | explanation":{
+"message":"Lighthouse was unable to download a robots.txt file",
+"description":"Explanatory message stating that there was a failure in an audit caused by Lighthouse not being able to download the robots.txt file for the site.  Note: \"robots.txt\" is a canonical filename and should not be translated."},
+
+"lighthouse-core/audits/seo/robots-txt.js | failureTitle":{
+"message":"robots.txt is not valid",
+"description":"Title of a Lighthouse audit that provides detail on the site's robots.txt file. Note: \"robots.txt\" is a canonical filename and should not be translated. This descriptive title is shown when the robots.txt file is misconfigured, which makes the page hard or impossible to scan via web crawler."},
+
+"lighthouse-core/audits/seo/robots-txt.js | title":{
+"message":"robots.txt is valid",
+"description":"Title of a Lighthouse audit that provides detail on the site's robots.txt file. Note: \"robots.txt\" is a canonical filename and should not be translated. This descriptive title is shown when the robots.txt file is present and configured correctly."},
+
+"lighthouse-core/audits/seo/tap-targets.js | description":{
+"message":"Interactive elements like buttons and links should be large enough (48x48px), and have enough space around them, to be easy enough to tap without overlapping onto other elements. [Learn more](https://developers.google.com/web/fundamentals/accessibility/accessible-styles#multi-device_responsive_design).",
+"description":"Description of a Lighthouse audit that tells the user why buttons and links need to be big enough and what 'big enough' means. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation."},
+
+"lighthouse-core/audits/seo/tap-targets.js | displayValue":{
+"message":"{decimalProportion, number, percent} appropriately sized tap targets",
+"description":"Explanatory message stating that a certain percentage of the tap targets (like buttons and links) on the page are of an appropriately large size."},
+
+"lighthouse-core/audits/seo/tap-targets.js | explanationViewportMetaNotOptimized":{
+"message":"Tap targets are too small because there's no viewport meta tag optimized for mobile screens",
+"description":"Explanatory message stating that there was a failure in an audit caused by the viewport meta tag not being optimized for mobile screens, which caused tap targets like buttons and links to be too small to tap on."},
+
+"lighthouse-core/audits/seo/tap-targets.js | failureTitle":{
+"message":"Tap targets are not sized appropriately",
+"description":"Descriptive title of a Lighthouse audit that provides detail on whether tap targets (like buttons and links) on a page are big enough so they can easily be tapped on a mobile device. This descriptive title is shown when tap targets are not easy to tap on."},
+
+"lighthouse-core/audits/seo/tap-targets.js | overlappingTargetHeader":{
+"message":"Overlapping Target",
+"description":"Label of a table column that identifies a tap target (like a link or button) that overlaps with another tap target."},
+
+"lighthouse-core/audits/seo/tap-targets.js | sizeHeader":{
+"message":"Size",
+"description":"Label of a table column that specifies the size of tap targets like buttons and links."},
+
+"lighthouse-core/audits/seo/tap-targets.js | tapTargetHeader":{
+"message":"Tap Target",
+"description":"Label of a table column that identifies tap targets (like buttons and links) that have failed the audit and aren't easy to tap on."},
+
+"lighthouse-core/audits/seo/tap-targets.js | title":{
+"message":"Tap targets are sized appropriately",
+"description":"Title of a Lighthouse audit that provides detail on whether tap targets (like buttons and links) on a page are big enough so they can easily be tapped on a mobile device. This descriptive title is shown when tap targets are easy to tap on."},
 
 "lighthouse-core/audits/time-to-first-byte.js | description":{
 "message":"Time To First Byte identifies the time at which your server sends a response. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/ttfb).",
@@ -24297,64 +26334,76 @@
 "description":"Description of the ARIA validity section within the Accessibility category. Within this section are audits with descriptive titles that highlight if whether all the aria- HTML attributes have been used properly."},
 
 "lighthouse-core/config/default-config.js | a11yAriaGroupTitle":{
-"message":"ARIA Attributes Follow Best Practices",
+"message":"ARIA",
 "description":"Title of the ARIA validity section within the Accessibility category. Within this section are audits with descriptive titles that highlight if whether all the aria- HTML attributes have been used properly."},
 
+"lighthouse-core/config/default-config.js | a11yAudioVideoGroupDescription":{
+"message":"These are opportunities to provide alternative content for audio and video. This may improve the experience for users with hearing or vision impairments.",
+"description":"Description of the navigation section within the Accessibility category. Within this section are audits with descriptive titles that highlight opportunities to provide alternative content for audio and video."},
+
+"lighthouse-core/config/default-config.js | a11yAudioVideoGroupTitle":{
+"message":"Audio and video",
+"description":"Title of the navigation section within the Accessibility category. Within this section are audits with descriptive titles that highlight opportunities to provide alternative content for audio and video."},
+
+"lighthouse-core/config/default-config.js | a11yBestPracticesGroupDescription":{
+"message":"These items highlight common accessibility best practices.",
+"description":"Description of the best practices section within the Accessibility category. Within this section are audits with descriptive titles that highlight common accessibility best practices."},
+
+"lighthouse-core/config/default-config.js | a11yBestPracticesGroupTitle":{
+"message":"Best practices",
+"description":"Title of the best practices section of the Accessibility category. Within this section are audits with descriptive titles that highlight common accessibility best practices."},
+
+"lighthouse-core/config/default-config.js | a11yCategoryDescription":{
+"message":"These checks highlight opportunities to [improve the accessibility of your web app](https://developers.google.com/web/fundamentals/accessibility). Only a subset of accessibility issues can be automatically detected so manual testing is also encouraged.",
+"description":"Description of the Accessibility category. This is displayed at the top of a list of audits focused on making web content accessible to all users. No character length limits. 'improve the accessibility of your web app' becomes link text to additional documentation."},
+
+"lighthouse-core/config/default-config.js | a11yCategoryManualDescription":{
+"message":"These items address areas which an automated testing tool cannot cover. Learn more in our guide on [conducting an accessibility review](https://developers.google.com/web/fundamentals/accessibility/how-to-review).",
+"description":"Description of the Accessibility manual checks category. This description is displayed above a list of accessibility audits that currently have no automated test and so must be verified manually by the user. No character length limits. 'conducting an accessibility review' becomes link text to additional documentation."},
+
+"lighthouse-core/config/default-config.js | a11yCategoryTitle":{
+"message":"Accessibility",
+"description":"Title of the Accessibility category of audits. This section contains audits focused on making web content accessible to all users. Also used as a label of a score gauge; try to limit to 20 characters."},
+
 "lighthouse-core/config/default-config.js | a11yColorContrastGroupDescription":{
 "message":"These are opportunities to improve the legibility of your content.",
 "description":"Description of the color contrast section within the Accessibility category. Within this section are audits with descriptive titles that highlight the color and vision aspects of the page's accessibility that are passing or failing."},
 
 "lighthouse-core/config/default-config.js | a11yColorContrastGroupTitle":{
-"message":"Color Contrast Is Satisfactory",
+"message":"Contrast",
 "description":"Title of the color contrast section within the Accessibility category. Within this section are audits with descriptive titles that highlight the color and vision aspects of the page's accessibility that are passing or failing."},
 
-"lighthouse-core/config/default-config.js | a11yCorrectAttributesGroupDescription":{
-"message":"These are opportunities to improve the configuration of your HTML elements.",
-"description":"Description of the HTML attribute validity section within the Accessibility category. Within this section are audits with descriptive titles that highlight if the HTML attribute values on the page are used correctly."},
-
-"lighthouse-core/config/default-config.js | a11yCorrectAttributesGroupTitle":{
-"message":"Elements Use Attributes Correctly",
-"description":"Title of the HTML attribute validity section within the Accessibility category. Within this section are audits with descriptive titles that highlight if the HTML attribute values on the page are used correctly. 'Elements' refers to HTML elements."},
-
-"lighthouse-core/config/default-config.js | a11yDescribeContentsGroupDescription":{
-"message":"These are opportunities to make your content easier to understand for a user of assistive technology, like a screen reader.",
-"description":"Description of the screen reader annotation section within the Accessibility category. Within this section are audits with descriptive titles that highlight the screen reader readability aspects of the page's accessibility that are passing or failing."},
-
-"lighthouse-core/config/default-config.js | a11yDescribeContentsGroupTitle":{
-"message":"Elements Describe Contents Well",
-"description":"Title of the screen reader annotation section within the Accessibility category. Within this section are audits with descriptive titles that highlight the screen reader readability aspects of the page's accessibility that are passing or failing. 'Elements' refers to HTML elements."},
-
-"lighthouse-core/config/default-config.js | a11yElementNamesGroupDescription":{
-"message":"These are opportunities to improve the semantics of the controls in your application. This may enhance the experience for users of assistive technology, like a screen reader.",
-"description":"Description of the HTML element naming section within the Accessibility category. Within this section are audits with descriptive titles that highlight if the non-textual HTML elements on the page have names discernible by a screen reader."},
-
-"lighthouse-core/config/default-config.js | a11yElementNamesGroupTitle":{
-"message":"Elements Have Discernible Names",
-"description":"Title of the HTML element naming section within the Accessibility category. Within this section are audits with descriptive titles that highlight if the non-textual HTML elements on the page have names discernible by a screen reader."},
-
 "lighthouse-core/config/default-config.js | a11yLanguageGroupDescription":{
 "message":"These are opportunities to improve the interpretation of your content by users in different locales.",
 "description":"Description of the language section within the Accessibility category. Within this section are audits with descriptive titles that highlight if the language has been annotated in the correct HTML attributes on the page."},
 
 "lighthouse-core/config/default-config.js | a11yLanguageGroupTitle":{
-"message":"Page Specifies Valid Language",
+"message":"Internationalization and localization",
 "description":"Title of the language section within the Accessibility category. Within this section are audits with descriptive titles that highlight if the language has been annotated in the correct HTML attributes on the page."},
 
-"lighthouse-core/config/default-config.js | a11yMetaGroupDescription":{
-"message":"These are opportunities to improve the user experience of your site.",
-"description":"Description of the meta tag section within the Accessibility category. Within this section are audits with descriptive titles that highlight if meta tags on the page have been used properly and if any important ones are missing."},
+"lighthouse-core/config/default-config.js | a11yNamesLabelsGroupDescription":{
+"message":"These are opportunities to improve the semantics of the controls in your application. This may enhance the experience for users of assistive technology, like a screen reader.",
+"description":"Description of the HTML element naming section within the Accessibility category. Within this section are audits with descriptive titles that highlight if the non-textual HTML elements on the page have names discernible by a screen reader."},
 
-"lighthouse-core/config/default-config.js | a11yMetaGroupTitle":{
-"message":"Meta Tags Used Properly",
-"description":"Title of the meta tag section within the Accessibility category. Within this section are audits with descriptive titles that highlight if meta tags on the page have been used properly and if any important ones are missing."},
+"lighthouse-core/config/default-config.js | a11yNamesLabelsGroupTitle":{
+"message":"Names and labels",
+"description":"Title of the HTML element naming section within the Accessibility category. Within this section are audits with descriptive titles that highlight if the non-textual HTML elements on the page have names discernible by a screen reader."},
 
-"lighthouse-core/config/default-config.js | a11yWellStructuredGroupDescription":{
-"message":"These are opportunities to make sure your HTML is appropriately structured.",
-"description":"Description of the HTML validity section within the Accessibility category. Within this section are audits with descriptive titles that highlight structural HTML aspects of the page's accessibility that are passing or failing."},
+"lighthouse-core/config/default-config.js | a11yNavigationGroupDescription":{
+"message":"These are opportunities to improve keyboard navigation in your application.",
+"description":"Description of the navigation section within the Accessibility category. Within this section are audits with descriptive titles that highlight opportunities to improve keyboard navigation."},
 
-"lighthouse-core/config/default-config.js | a11yWellStructuredGroupTitle":{
-"message":"Elements Are Well Structured",
-"description":"Title of the HTML validity section within the Accessibility category. Within this section are audits with descriptive titles that highlight structural HTML aspects of the page's accessibility that are passing or failing (i.e. that list items are contained within list parents, etc). 'Elements' refers to HTML elements."},
+"lighthouse-core/config/default-config.js | a11yNavigationGroupTitle":{
+"message":"Navigation",
+"description":"Title of the navigation section within the Accessibility category. Within this section are audits with descriptive titles that highlight opportunities to improve keyboard navigation."},
+
+"lighthouse-core/config/default-config.js | a11yTablesListsVideoGroupDescription":{
+"message":"These are opportunities to to improve the experience of reading tabular or list data using assistive technology, like a screen reader.",
+"description":"Description of the navigation section within the Accessibility category. Within this section are audits with descriptive titles that highlight opportunities to improve the experience of reading tabular or list data using assistive technology."},
+
+"lighthouse-core/config/default-config.js | a11yTablesListsVideoGroupTitle":{
+"message":"Tables and lists",
+"description":"Title of the navigation section within the Accessibility category. Within this section are audits with descriptive titles that highlight opportunities to improve the experience of reading tabular or list data using assistive technology."},
 
 "lighthouse-core/config/default-config.js | diagnosticsGroupDescription":{
 "message":"More information about the performance of your application.",
@@ -24408,6 +26457,18 @@
 "message":"PWA Optimized",
 "description":"Title of the \"PWA Optimized\" section of the web app category. Within this section are audits that check if the developer has taken advantage of features to make their web page more enjoyable and engaging for the user."},
 
+"lighthouse-core/config/default-config.js | seoCategoryDescription":{
+"message":"These checks ensure that your page is optimized for search engine results ranking. There are additional factors Lighthouse does not check that may affect your search ranking. [Learn more](https://support.google.com/webmasters/answer/35769).",
+"description":"Description of the Search Engine Optimization (SEO) category. This is displayed at the top of a list of audits focused on optimizing a website for indexing by search engines. No character length limits. 'Learn More' becomes link text to additional documentation."},
+
+"lighthouse-core/config/default-config.js | seoCategoryManualDescription":{
+"message":"Run these additional validators on your site to check additional SEO best practices.",
+"description":"Description of the Search Engine Optimization (SEO) manual checks category, the additional validators must be run by hand in order to check all SEO best practices. This is displayed at the top of a list of manually run audits focused on optimizing a website for indexing by search engines. No character length limits."},
+
+"lighthouse-core/config/default-config.js | seoCategoryTitle":{
+"message":"SEO",
+"description":"Title of the Search Engine Optimization (SEO) category of audits. This is displayed at the top of a list of audits focused on topics related to optimizing a website for indexing by search engines. Also used as a label of a score gauge; try to limit to 20 characters."},
+
 "lighthouse-core/lib/i18n/i18n.js | columnCacheTTL":{
 "message":"Cache TTL",
 "description":"Label for the TTL column in data tables, entries will be the time to live value of the cache header on a web resource"},
@@ -24552,6 +26613,14 @@
 "message":"Score scale:",
 "description":"Label preceding a pictorial explanation of the scoring scale: 0-50 is red (bad), 50-90 is orange (ok), 90-100 is green (good). These colors are used throughout the report to provide context for how good/bad a particular result is."},
 
+"lighthouse-core/report/html/renderer/util.js | snippetCollapseButtonLabel":{
+"message":"Collapse snippet",
+"description":"Label for button that only shows a few lines of the snippet when clicked"},
+
+"lighthouse-core/report/html/renderer/util.js | snippetExpandButtonLabel":{
+"message":"Expand snippet",
+"description":"Label for button that shows all lines of the snippet when clicked"},
+
 "lighthouse-core/report/html/renderer/util.js | toplevelWarningsMessage":{
 "message":"There were issues affecting this run of Lighthouse:",
 "description":"Label shown preceding any important warnings that may have invalidated the entire report. For example, if the user has Chrome extensions installed, they may add enough performance overhead that Lighthouse's performance metrics are unreliable. If shown, this will be displayed at the top of the report UI."},
@@ -24570,7 +26639,7 @@
 
 
 
-},{}],60:[function(require,module,exports){
+},{}],63:[function(require,module,exports){
 (function(__filename,__dirname){
 
 
@@ -24910,7 +26979,7 @@
 
 
 }).call(this,"/lighthouse-core/lib/i18n/i18n.js","/lighthouse-core/lib/i18n");
-},{"./locales.js":61,"intl":88,"intl-messageformat":103,"intl-messageformat-parser":101,"lighthouse-logger":114,"lodash.isequal":115,"lookup-closest-locale":116,"path":129}],61:[function(require,module,exports){
+},{"./locales.js":64,"intl":93,"intl-messageformat":109,"intl-messageformat-parser":107,"lighthouse-logger":120,"lodash.isequal":121,"lookup-closest-locale":122,"path":135}],64:[function(require,module,exports){
 
 
 
@@ -24998,7 +27067,7 @@
 
 module.exports=locales;
 
-},{"./en-US.json":59,"./locales/ar-XB.json":88,"./locales/ar.json":88,"./locales/bg.json":88,"./locales/ca.json":88,"./locales/cs.json":88,"./locales/da.json":88,"./locales/de.json":88,"./locales/el.json":88,"./locales/en-GB.json":88,"./locales/en-XA.json":88,"./locales/es.json":88,"./locales/fi.json":88,"./locales/fil.json":88,"./locales/fr.json":88,"./locales/he.json":88,"./locales/hi.json":88,"./locales/hr.json":88,"./locales/hu.json":88,"./locales/id.json":88,"./locales/it.json":88,"./locales/ja.json":88,"./locales/ko.json":88,"./locales/lt.json":88,"./locales/lv.json":88,"./locales/nl.json":88,"./locales/no.json":88,"./locales/pl.json":88,"./locales/pt-PT.json":88,"./locales/pt.json":88,"./locales/ro.json":88,"./locales/ru.json":88,"./locales/sk.json":88,"./locales/sl.json":88,"./locales/sr-Latn.json":88,"./locales/sr.json":88,"./locales/sv.json":88,"./locales/ta.json":88,"./locales/te.json":88,"./locales/th.json":88,"./locales/tr.json":88,"./locales/uk.json":88,"./locales/vi.json":88,"./locales/zh-HK.json":88,"./locales/zh-TW.json":88,"./locales/zh.json":88}],62:[function(require,module,exports){
+},{"./en-US.json":62,"./locales/ar-XB.json":93,"./locales/ar.json":93,"./locales/bg.json":93,"./locales/ca.json":93,"./locales/cs.json":93,"./locales/da.json":93,"./locales/de.json":93,"./locales/el.json":93,"./locales/en-GB.json":93,"./locales/en-XA.json":93,"./locales/es.json":93,"./locales/fi.json":93,"./locales/fil.json":93,"./locales/fr.json":93,"./locales/he.json":93,"./locales/hi.json":93,"./locales/hr.json":93,"./locales/hu.json":93,"./locales/id.json":93,"./locales/it.json":93,"./locales/ja.json":93,"./locales/ko.json":93,"./locales/lt.json":93,"./locales/lv.json":93,"./locales/nl.json":93,"./locales/no.json":93,"./locales/pl.json":93,"./locales/pt-PT.json":93,"./locales/pt.json":93,"./locales/ro.json":93,"./locales/ru.json":93,"./locales/sk.json":93,"./locales/sl.json":93,"./locales/sr-Latn.json":93,"./locales/sr.json":93,"./locales/sv.json":93,"./locales/ta.json":93,"./locales/te.json":93,"./locales/th.json":93,"./locales/tr.json":93,"./locales/uk.json":93,"./locales/vi.json":93,"./locales/zh-HK.json":93,"./locales/zh-TW.json":93,"./locales/zh.json":93}],65:[function(require,module,exports){
 
 
 
@@ -25072,7 +27141,7 @@
 pngSizedAtLeast};
 
 
-},{"./url-shim.js":"url"}],63:[function(require,module,exports){
+},{"./url-shim.js":"url"}],66:[function(require,module,exports){
 
 
 
@@ -25280,7 +27349,7 @@
 convertNodeTimingsToTrace};
 
 
-},{}],64:[function(require,module,exports){
+},{}],67:[function(require,module,exports){
 (function(__filename){
 
 
@@ -25522,7 +27591,7 @@
 module.exports.UIStrings=UIStrings;
 
 }).call(this,"/lighthouse-core/lib/lh-error.js");
-},{"./i18n/i18n.js":60}],65:[function(require,module,exports){
+},{"./i18n/i18n.js":63}],68:[function(require,module,exports){
 
 
 
@@ -25994,7 +28063,7 @@
 
 module.exports=parse;
 
-},{"./url-shim":"url","cssstyle/lib/parsers":91}],66:[function(require,module,exports){
+},{"./url-shim":"url","cssstyle/lib/parsers":97}],69:[function(require,module,exports){
 
 
 
@@ -26165,7 +28234,7 @@
 
 module.exports={computeJSTokenLength,computeCSSTokenLength};
 
-},{}],67:[function(require,module,exports){
+},{}],70:[function(require,module,exports){
 
 
 
@@ -26475,11 +28544,22 @@
 
 
 
-dispatch(event){
-if(!event.method.startsWith('Network.')){
-return;
+
+onReceivedMessageFromTarget(data){
+
+const protocolMessage=JSON.parse(data.message);
+
+
+if('id'in protocolMessage)return;
+
+this.dispatch(protocolMessage);
 }
 
+
+
+
+
+dispatch(event){
 switch(event.method){
 case'Network.requestWillBeSent':return this.onRequestWillBeSent(event.params);
 case'Network.requestServedFromCache':return this.onRequestServedFromCache(event.params);
@@ -26488,6 +28568,7 @@
 case'Network.loadingFinished':return this.onLoadingFinished(event.params);
 case'Network.loadingFailed':return this.onLoadingFailed(event.params);
 case'Network.resourceChangedPriority':return this.onResourceChangedPriority(event.params);
+case'Target.receivedMessageFromTarget':return this.onReceivedMessageFromTarget(event.params);
 default:return;}
 
 }
@@ -26536,7 +28617,9 @@
 for(const record of records){
 const stackFrames=record.initiator.stack&&record.initiator.stack.callFrames||[];
 const initiatorURL=record.initiator.url||stackFrames[0]&&stackFrames[0].url;
-const initiator=recordsByURL.get(initiatorURL)||record.redirectSource;
+
+
+const initiator=record.redirectSource||recordsByURL.get(initiatorURL);
 if(initiator){
 record.setInitiatorRequest(initiator);
 }
@@ -26563,7 +28646,7 @@
 
 module.exports=NetworkRecorder;
 
-},{"./network-request":68,"events":94,"lighthouse-logger":114}],68:[function(require,module,exports){
+},{"./network-request":71,"events":100,"lighthouse-logger":120}],71:[function(require,module,exports){
 (function(global){
 
 
@@ -26910,7 +28993,7 @@
 
 
 }).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./url-shim":"url"}],69:[function(require,module,exports){
+},{"./url-shim":"url"}],72:[function(require,module,exports){
 
 
 
@@ -27145,7 +29228,269 @@
 getNodeSelector:getNodeSelector};
 
 
-},{}],70:[function(require,module,exports){
+},{}],73:[function(require,module,exports){
+
+
+
+
+
+'use strict';
+
+
+
+
+
+function rectContainsPoint(rect,{x,y}){
+return rect.left<=x&&rect.right>=x&&rect.top<=y&&rect.bottom>=y;
+}
+
+
+
+
+
+
+
+
+
+
+function rectContains(rect1,rect2){
+return rect2.top>=rect1.top&&
+rect2.right<=rect1.right&&
+rect2.bottom<=rect1.bottom&&
+rect2.left>=rect1.left;
+}
+
+
+const rectContainsString=`
+  ${rectContains.toString()};
+`;
+
+
+
+
+
+function filterOutTinyRects(rects){
+return rects.filter(
+rect=>rect.width>1&&rect.height>1);
+
+}
+
+
+
+
+
+function filterOutRectsContainedByOthers(rects){
+const rectsToKeep=new Set(rects);
+
+for(const rect of rects){
+for(const possiblyContainingRect of rects){
+if(rect===possiblyContainingRect)continue;
+if(!rectsToKeep.has(possiblyContainingRect))continue;
+if(rectContains(possiblyContainingRect,rect)){
+rectsToKeep.delete(rect);
+break;
+}
+}
+}
+
+return Array.from(rectsToKeep);
+}
+
+
+
+
+function getRectCenterPoint(rect){
+return{
+x:rect.left+rect.width/2,
+y:rect.top+rect.height/2};
+
+}
+
+
+
+
+
+
+function rectsTouchOrOverlap(rectA,rectB){
+
+return(
+rectA.left<=rectB.right&&
+rectB.left<=rectA.right&&
+rectA.top<=rectB.bottom&&
+rectB.top<=rectA.bottom);
+
+}
+
+
+
+
+
+
+
+
+function getBoundingRectWithPadding(rects,minimumSize){
+if(rects.length===0){
+throw new Error('No rects to take bounds of');
+}
+
+let left=Number.MAX_VALUE;
+let right=-Number.MAX_VALUE;
+let top=Number.MAX_VALUE;
+let bottom=-Number.MAX_VALUE;
+for(const rect of rects){
+left=Math.min(left,rect.left);
+right=Math.max(right,rect.right);
+top=Math.min(top,rect.top);
+bottom=Math.max(bottom,rect.bottom);
+}
+
+
+const halfMinSize=minimumSize/2;
+left-=halfMinSize;
+right+=halfMinSize;
+top-=halfMinSize;
+bottom+=halfMinSize;
+
+return{
+left,
+right,
+top,
+bottom,
+width:right-left,
+height:bottom-top};
+
+}
+
+
+
+
+
+function getBoundingRect(rectA,rectB){
+const left=Math.min(rectA.left,rectB.left);
+const right=Math.max(rectA.right,rectB.right);
+const top=Math.min(rectA.top,rectB.top);
+const bottom=Math.max(rectA.bottom,rectB.bottom);
+return addRectWidthAndHeight({
+left,
+right,
+top,
+bottom});
+
+}
+
+
+
+
+
+function addRectWidthAndHeight({left,top,right,bottom}){
+return{
+left,
+top,
+right,
+bottom,
+width:right-left,
+height:bottom-top};
+
+}
+
+
+
+
+
+function addRectTopAndBottom({x,y,width,height}){
+return{
+left:x,
+top:y,
+right:x+width,
+bottom:y+height,
+width,
+height};
+
+}
+
+
+
+
+
+function getRectOverlapArea(rect1,rect2){
+
+const rectYOverlap=Math.min(rect1.bottom,rect2.bottom)-Math.max(rect1.top,rect2.top);
+if(rectYOverlap<=0)return 0;
+
+const rectXOverlap=Math.min(rect1.right,rect2.right)-Math.max(rect1.left,rect2.left);
+if(rectXOverlap<=0)return 0;
+
+return rectXOverlap*rectYOverlap;
+}
+
+
+
+
+
+function getRectAtCenter(rect,centerRectSize){
+return addRectWidthAndHeight({
+left:rect.left+rect.width/2-centerRectSize/2,
+top:rect.top+rect.height/2-centerRectSize/2,
+right:rect.right-rect.width/2+centerRectSize/2,
+bottom:rect.bottom-rect.height/2+centerRectSize/2});
+
+}
+
+
+
+
+function getRectArea(rect){
+return rect.width*rect.height;
+}
+
+
+
+
+function getLargestRect(rects){
+let largestRect=rects[0];
+for(const rect of rects){
+if(getRectArea(rect)>getRectArea(largestRect)){
+largestRect=rect;
+}
+}
+return largestRect;
+}
+
+
+
+
+
+
+function allRectsContainedWithinEachOther(rectListA,rectListB){
+for(const rectA of rectListA){
+for(const rectB of rectListB){
+if(!rectContains(rectA,rectB)&&!rectContains(rectB,rectA)){
+return false;
+}
+}
+}
+return true;
+}
+
+module.exports={
+rectContainsPoint,
+rectContains,
+rectContainsString,
+addRectWidthAndHeight,
+addRectTopAndBottom,
+getRectOverlapArea,
+getRectAtCenter,
+getLargestRect,
+getRectCenterPoint,
+getBoundingRect,
+getBoundingRectWithPadding,
+rectsTouchOrOverlap,
+allRectsContainedWithinEachOther,
+filterOutRectsContainedByOthers,
+filterOutTinyRects};
+
+
+},{}],74:[function(require,module,exports){
 
 
 
@@ -27273,7 +29618,7 @@
 
 module.exports=sentryDelegate;
 
-},{"lighthouse-logger":114,"raven":88}],71:[function(require,module,exports){
+},{"lighthouse-logger":120,"raven":93}],75:[function(require,module,exports){
 
 
 
@@ -27354,7 +29699,115 @@
 getLogNormalDistribution};
 
 
-},{}],72:[function(require,module,exports){
+},{}],76:[function(require,module,exports){
+
+
+
+
+
+'use strict';
+
+const{
+filterOutRectsContainedByOthers,
+filterOutTinyRects,
+rectsTouchOrOverlap,
+rectContainsPoint,
+getBoundingRect,
+getRectCenterPoint}=
+require('./rect-helpers');
+
+
+
+
+
+
+
+function getTappableRectsFromClientRects(clientRects){
+
+
+clientRects=filterOutTinyRects(clientRects);
+clientRects=filterOutRectsContainedByOthers(clientRects);
+clientRects=mergeTouchingClientRects(clientRects);
+return clientRects;
+}
+
+
+
+
+
+
+
+
+
+function almostEqual(a,b){
+return Math.abs(a-b)<=10;
+}
+
+
+
+
+
+
+function mergeTouchingClientRects(clientRects){
+for(let i=0;i<clientRects.length;i++){
+for(let j=i+1;j<clientRects.length;j++){
+const crA=clientRects[i];
+const crB=clientRects[j];
+
+
+
+
+
+
+
+
+
+
+
+
+const rectsLineUpHorizontally=
+almostEqual(crA.top,crB.top)||almostEqual(crA.bottom,crB.bottom);
+const rectsLineUpVertically=
+almostEqual(crA.left,crB.left)||almostEqual(crA.right,crB.right);
+const canMerge=
+rectsTouchOrOverlap(crA,crB)&&(
+rectsLineUpHorizontally||rectsLineUpVertically);
+
+if(canMerge){
+const replacementClientRect=getBoundingRect(crA,crB);
+const mergedRectCenter=getRectCenterPoint(replacementClientRect);
+
+if(
+!(
+rectContainsPoint(crA,mergedRectCenter)||
+rectContainsPoint(crB,mergedRectCenter)))
+
+{
+
+
+
+continue;
+}
+
+
+clientRects=clientRects.filter(cr=>cr!==crA&&cr!==crB);
+clientRects.push(replacementClientRect);
+
+
+
+return mergeTouchingClientRects(clientRects);
+}
+}
+}
+
+return clientRects;
+}
+
+module.exports={
+getTappableRectsFromClientRects};
+
+
+},{"./rect-helpers":73}],77:[function(require,module,exports){
 
 
 
@@ -27467,7 +29920,7 @@
 taskNameToGroup};
 
 
-},{}],73:[function(require,module,exports){
+},{}],78:[function(require,module,exports){
 
 
 
@@ -27485,7 +29938,7 @@
 
 function getUberMetrics(auditResults){
 const metricsAudit=auditResults.metrics;
-if(!metricsAudit||!metricsAudit.details||!metricsAudit.details.items)return;
+if(!metricsAudit||!metricsAudit.details||!('items'in metricsAudit.details))return;
 
 return metricsAudit.details.items[0];
 }
@@ -27672,7 +30125,7 @@
 
 module.exports=Metrics;
 
-},{"lighthouse-logger":114}],74:[function(require,module,exports){
+},{"lighthouse-logger":120}],79:[function(require,module,exports){
 
 
 
@@ -27936,7 +30389,7 @@
 
 module.exports=TraceProcessor;
 
-},{"../lh-error":64}],75:[function(require,module,exports){
+},{"../lh-error":67}],80:[function(require,module,exports){
 
 
 
@@ -28387,6 +30840,48 @@
 
 
 if(Util.numberDateLocale==='en-XA')Util.numberDateLocale='de';
+}
+
+
+
+
+
+
+
+
+
+static filterRelevantLines(lines,lineMessages,surroundingLineCount){
+if(lineMessages.length===0){
+
+return lines.slice(0,surroundingLineCount*2+1);
+}
+
+const minGapSize=3;
+const lineNumbersToKeep=new Set();
+
+
+lineMessages=lineMessages.sort((a,b)=>(a.lineNumber||0)-(b.lineNumber||0));
+lineMessages.forEach(({lineNumber})=>{
+let firstSurroundingLineNumber=lineNumber-surroundingLineCount;
+let lastSurroundingLineNumber=lineNumber+surroundingLineCount;
+
+while(firstSurroundingLineNumber<1){
+
+firstSurroundingLineNumber++;
+lastSurroundingLineNumber++;
+}
+
+
+if(lineNumbersToKeep.has(firstSurroundingLineNumber-minGapSize-1)){
+firstSurroundingLineNumber-=minGapSize;
+}
+for(let i=firstSurroundingLineNumber;i<=lastSurroundingLineNumber;i++){
+const surroundingLineNumber=i;
+lineNumbersToKeep.add(surroundingLineNumber);
+}
+});
+
+return lines.filter(line=>lineNumbersToKeep.has(line.lineNumber));
 }}
 
 
@@ -28436,6 +30931,11 @@
 crcLongestDurationLabel:'Maximum critical path latency:',
 
 
+snippetExpandButtonLabel:'Expand snippet',
+
+snippetCollapseButtonLabel:'Collapse snippet',
+
+
 lsPerformanceCategoryDescription:'[Lighthouse](https://developers.google.com/web/tools/lighthouse/) analysis of the current page on an emulated mobile network. Values are estimated and may vary.',
 
 labDataTitle:'Lab Data'};
@@ -28447,7 +30947,7 @@
 self.Util=Util;
 }
 
-},{}],76:[function(require,module,exports){
+},{}],81:[function(require,module,exports){
 
 
 
@@ -28567,7 +31067,7 @@
 
 module.exports=ReportGenerator;
 
-},{"./html/html-report-assets":88}],77:[function(require,module,exports){
+},{"./html/html-report-assets":93}],82:[function(require,module,exports){
 (function(process){
 
 
@@ -28916,10 +31416,7 @@
 }
 }
 
-return{
-code:LHError.NO_ERROR,
-message:''};
-
+return undefined;
 }
 
 
@@ -28937,14 +31434,14 @@
 
 
 const fileList=[
-...["accessibility","audit.js","bootup-time.js","byte-efficiency","content-width.js","critical-request-chains.js","deprecations.js","dobetterweb","errors-in-console.js","final-screenshot.js","font-display.js","image-aspect-ratio.js","installable-manifest.js","is-on-https.js","load-fast-enough-for-pwa.js","mainthread-work-breakdown.js","manual","metrics","metrics.js","mixed-content.js","multi-check-audit.js","network-requests.js","offline-start-url.js","predictive-perf.js","redirects-http.js","redirects.js","screenshot-thumbnails.js","seo","service-worker.js","splash-screen.js","themed-omnibox.js","time-to-first-byte.js","user-timings.js","uses-rel-preconnect.js","uses-rel-preload.js","viewport.js","violation-audit.js","without-javascript.js","works-offline.js"],
+...["accessibility","audit.js","bootup-time.js","byte-efficiency","content-width.js","critical-request-chains.js","deprecations.js","diagnostics.js","dobetterweb","errors-in-console.js","final-screenshot.js","font-display.js","image-aspect-ratio.js","installable-manifest.js","is-on-https.js","load-fast-enough-for-pwa.js","main-thread-tasks.js","mainthread-work-breakdown.js","manual","metrics","metrics.js","mixed-content.js","multi-check-audit.js","network-requests.js","network-rtt.js","network-server-latency.js","offline-start-url.js","predictive-perf.js","redirects-http.js","redirects.js","screenshot-thumbnails.js","seo","service-worker.js","splash-screen.js","themed-omnibox.js","time-to-first-byte.js","user-timings.js","uses-rel-preconnect.js","uses-rel-preload.js","viewport.js","violation-audit.js","without-javascript.js","works-offline.js"],
 ...["appcache-manifest.js","doctype.js","dom-size.js","external-anchors-use-rel-noopener.js","geolocation-on-start.js","js-libraries.js","no-document-write.js","no-vulnerable-libraries.js","notification-on-start.js","password-inputs-can-be-pasted-into.js","uses-http2.js","uses-passive-event-listeners.js"].map(f=>`dobetterweb/${f}`),
-...["estimated-input-latency.js","first-contentful-paint-3g.js","first-contentful-paint.js","first-cpu-idle.js","first-meaningful-paint.js","interactive.js","speed-index.js"].map(f=>`metrics/${f}`),
-...["canonical.js","font-size.js","hreflang.js","http-status-code.js","is-crawlable.js","link-text.js","manual","meta-description.js","plugins.js","robots-txt.js"].map(f=>`seo/${f}`),
-...["mobile-friendly.js","structured-data.js"].map(f=>`seo/manual/${f}`),
-...["aria-allowed-attr.js","aria-required-attr.js","aria-required-children.js","aria-required-parent.js","aria-roles.js","aria-valid-attr-value.js","aria-valid-attr.js","audio-caption.js","axe-audit.js","button-name.js","bypass.js","color-contrast.js","definition-list.js","dlitem.js","document-title.js","duplicate-id.js","frame-title.js","html-has-lang.js","html-lang-valid.js","image-alt.js","input-image-alt.js","label.js","layout-table.js","link-name.js","list.js","listitem.js","manual","meta-refresh.js","meta-viewport.js","object-alt.js","tabindex.js","td-headers-attr.js","th-has-data-cells.js","valid-lang.js","video-caption.js","video-description.js"].
+...["estimated-input-latency.js","first-contentful-paint-3g.js","first-contentful-paint.js","first-cpu-idle.js","first-meaningful-paint.js","interactive.js","max-potential-fid.js","speed-index.js"].map(f=>`metrics/${f}`),
+...["canonical.js","font-size.js","hreflang.js","http-status-code.js","is-crawlable.js","link-text.js","manual","meta-description.js","plugins.js","robots-txt.js","tap-targets.js"].map(f=>`seo/${f}`),
+...["structured-data.js"].map(f=>`seo/manual/${f}`),
+...["accesskeys.js","aria-allowed-attr.js","aria-required-attr.js","aria-required-children.js","aria-required-parent.js","aria-roles.js","aria-valid-attr-value.js","aria-valid-attr.js","audio-caption.js","axe-audit.js","button-name.js","bypass.js","color-contrast.js","definition-list.js","dlitem.js","document-title.js","duplicate-id.js","frame-title.js","html-has-lang.js","html-lang-valid.js","image-alt.js","input-image-alt.js","label.js","layout-table.js","link-name.js","list.js","listitem.js","manual","meta-refresh.js","meta-viewport.js","object-alt.js","tabindex.js","td-headers-attr.js","th-has-data-cells.js","valid-lang.js","video-caption.js","video-description.js"].
 map(f=>`accessibility/${f}`),
-...["accesskeys.js","custom-controls-labels.js","custom-controls-roles.js","focus-traps.js","focusable-controls.js","heading-levels.js","interactive-element-affordance.js","logical-tab-order.js","managed-focus.js","offscreen-content-hidden.js","use-landmarks.js","visual-order-follows-dom.js"].
+...["custom-controls-labels.js","custom-controls-roles.js","focus-traps.js","focusable-controls.js","heading-levels.js","interactive-element-affordance.js","logical-tab-order.js","managed-focus.js","offscreen-content-hidden.js","use-landmarks.js","visual-order-follows-dom.js"].
 map(f=>`accessibility/manual/${f}`),
 ...["byte-efficiency-audit.js","efficient-animated-content.js","offscreen-images.js","render-blocking-resources.js","total-byte-weight.js","unminified-css.js","unminified-javascript.js","unused-css-rules.js","unused-javascript.js","uses-long-cache-ttl.js","uses-optimized-images.js","uses-responsive-images.js","uses-text-compression.js","uses-webp-images.js"].
 map(f=>`byte-efficiency/${f}`),
@@ -28961,9 +31458,9 @@
 
 static getGathererList(){
 const fileList=[
-...["accessibility.js","cache-contents.js","chrome-console-messages.js","css-usage.js","dobetterweb","gatherer.js","html-without-javascript.js","http-redirect.js","image-elements.js","js-usage.js","link-elements.js","manifest.js","meta-elements.js","mixed-content.js","offline.js","runtime-exceptions.js","scripts.js","seo","service-worker.js","start-url.js","viewport-dimensions.js"],
-...["canonical.js","crawlable-links.js","embedded-content.js","font-size.js","hreflang.js","robots-txt.js"].map(f=>`seo/${f}`),
-...["anchors-with-no-rel-noopener.js","appcache.js","doctype.js","domstats.js","js-libraries.js","optimized-images.js","password-inputs-with-prevented-paste.js","response-compression.js","tags-blocking-first-paint.js"].
+...["accessibility.js","anchor-elements.js","cache-contents.js","chrome-console-messages.js","css-usage.js","dobetterweb","gatherer.js","html-without-javascript.js","http-redirect.js","image-elements.js","js-usage.js","link-elements.js","meta-elements.js","mixed-content.js","offline.js","runtime-exceptions.js","scripts.js","seo","service-worker.js","start-url.js","viewport-dimensions.js"],
+...["canonical.js","embedded-content.js","font-size.js","hreflang.js","robots-txt.js","tap-targets.js"].map(f=>`seo/${f}`),
+...["appcache.js","doctype.js","domstats.js","js-libraries.js","optimized-images.js","password-inputs-with-prevented-paste.js","response-compression.js","tags-blocking-first-paint.js"].
 map(f=>`dobetterweb/${f}`)];
 
 return fileList.filter(f=>/\.js$/.test(f)&&f!=='gatherer.js').sort();
@@ -28988,7 +31485,7 @@
 module.exports=Runner;
 
 }).call(this,require('_process'));
-},{"../package.json":161,"./audits/audit":3,"./gather/driver.js":43,"./gather/gather-runner":44,"./lib/asset-saver":48,"./lib/i18n/i18n.js":60,"./lib/lh-error.js":64,"./lib/sentry":70,"./lib/url-shim":"url","./report/report-generator":76,"./scoring":78,"_process":131,"lighthouse-logger":114,"lodash.isequal":115,"path":129}],78:[function(require,module,exports){
+},{"../package.json":167,"./audits/audit":3,"./gather/driver.js":46,"./gather/gather-runner":47,"./lib/asset-saver":51,"./lib/i18n/i18n.js":63,"./lib/lh-error.js":67,"./lib/sentry":74,"./lib/url-shim":"url","./report/report-generator":81,"./scoring":83,"_process":137,"lighthouse-logger":120,"lodash.isequal":121,"path":135}],83:[function(require,module,exports){
 
 
 
@@ -29083,7 +31580,7 @@
 
 module.exports=ReportScoring;
 
-},{"./audits/audit":3}],79:[function(require,module,exports){
+},{"./audits/audit":3}],84:[function(require,module,exports){
 (function(global){
 'use strict';
 
@@ -29577,7 +32074,7 @@
 };
 
 }).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"util/":82}],80:[function(require,module,exports){
+},{"util/":87}],85:[function(require,module,exports){
 if(typeof Object.create==='function'){
 
 module.exports=function inherits(ctor,superCtor){
@@ -29602,14 +32099,14 @@
 };
 }
 
-},{}],81:[function(require,module,exports){
+},{}],86:[function(require,module,exports){
 module.exports=function isBuffer(arg){
 return arg&&typeof arg==='object'&&
 typeof arg.copy==='function'&&
 typeof arg.fill==='function'&&
 typeof arg.readUInt8==='function';
 };
-},{}],82:[function(require,module,exports){
+},{}],87:[function(require,module,exports){
 (function(process,global){
 
 
@@ -30199,7 +32696,7 @@
 }
 
 }).call(this,require('_process'),typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./support/isBuffer":81,"_process":131,"inherits":80}],83:[function(require,module,exports){
+},{"./support/isBuffer":86,"_process":137,"inherits":85}],88:[function(require,module,exports){
 
 
 var langs=[
@@ -38355,7 +40852,7 @@
 return langs;
 };
 
-},{}],84:[function(require,module,exports){
+},{}],89:[function(require,module,exports){
 'use strict';
 
 exports.byteLength=byteLength;
@@ -38508,9 +41005,9 @@
 return parts.join('');
 }
 
-},{}],85:[function(require,module,exports){
+},{}],90:[function(require,module,exports){
 
-},{}],86:[function(require,module,exports){
+},{}],91:[function(require,module,exports){
 (function(process,Buffer){
 'use strict';
 
@@ -38922,7 +41419,7 @@
 
 exports.Zlib=Zlib;
 }).call(this,require('_process'),require("buffer").Buffer);
-},{"_process":131,"assert":79,"buffer":89,"pako/lib/zlib/constants":122,"pako/lib/zlib/deflate.js":124,"pako/lib/zlib/inflate.js":88,"pako/lib/zlib/zstream":127}],87:[function(require,module,exports){
+},{"_process":137,"assert":84,"buffer":94,"pako/lib/zlib/constants":128,"pako/lib/zlib/deflate.js":130,"pako/lib/zlib/inflate.js":93,"pako/lib/zlib/zstream":133}],92:[function(require,module,exports){
 (function(process){
 'use strict';
 
@@ -39534,9 +42031,10 @@
 util.inherits(InflateRaw,Zlib);
 util.inherits(Unzip,Zlib);
 }).call(this,require('_process'));
-},{"./binding":86,"_process":131,"assert":79,"buffer":89,"stream":156,"util":160}],88:[function(require,module,exports){
-arguments[4][85][0].apply(exports,arguments);
-},{"dup":85}],89:[function(require,module,exports){
+},{"./binding":91,"_process":137,"assert":84,"buffer":94,"stream":148,"util":166}],93:[function(require,module,exports){
+arguments[4][90][0].apply(exports,arguments);
+},{"dup":90}],94:[function(require,module,exports){
+(function(Buffer){
 
 
 
@@ -41315,7 +43813,8 @@
 return obj!==obj;
 }
 
-},{"base64-js":84,"ieee754":97}],90:[function(require,module,exports){
+}).call(this,require("buffer").Buffer);
+},{"base64-js":89,"buffer":94,"ieee754":103}],95:[function(require,module,exports){
 (function(Buffer){
 
 
@@ -41426,13 +43925,167 @@
 }
 
 }).call(this,{"isBuffer":require("../../insert-module-globals/node_modules/is-buffer/index.js")});
-},{"../../insert-module-globals/node_modules/is-buffer/index.js":100}],91:[function(require,module,exports){
+},{"../../insert-module-globals/node_modules/is-buffer/index.js":106}],96:[function(require,module,exports){
+module.exports=[
+"aliceblue",
+"antiquewhite",
+"aqua",
+"aquamarine",
+"azure",
+"beige",
+"bisque",
+"black",
+"blanchedalmond",
+"blue",
+"blueviolet",
+"brown",
+"burlywood",
+"cadetblue",
+"chartreuse",
+"chocolate",
+"coral",
+"cornflowerblue",
+"cornsilk",
+"crimson",
+"cyan",
+"darkblue",
+"darkcyan",
+"darkgoldenrod",
+"darkgray",
+"darkgreen",
+"darkgrey",
+"darkkhaki",
+"darkmagenta",
+"darkolivegreen",
+"darkorange",
+"darkorchid",
+"darkred",
+"darksalmon",
+"darkseagreen",
+"darkslateblue",
+"darkslategray",
+"darkslategrey",
+"darkturquoise",
+"darkviolet",
+"deeppink",
+"deepskyblue",
+"dimgray",
+"dimgrey",
+"dodgerblue",
+"firebrick",
+"floralwhite",
+"forestgreen",
+"fuchsia",
+"gainsboro",
+"ghostwhite",
+"gold",
+"goldenrod",
+"gray",
+"green",
+"greenyellow",
+"grey",
+"honeydew",
+"hotpink",
+"indianred",
+"indigo",
+"ivory",
+"khaki",
+"lavender",
+"lavenderblush",
+"lawngreen",
+"lemonchiffon",
+"lightblue",
+"lightcoral",
+"lightcyan",
+"lightgoldenrodyellow",
+"lightgray",
+"lightgreen",
+"lightgrey",
+"lightpink",
+"lightsalmon",
+"lightseagreen",
+"lightskyblue",
+"lightslategray",
+"lightslategrey",
+"lightsteelblue",
+"lightyellow",
+"lime",
+"limegreen",
+"linen",
+"magenta",
+"maroon",
+"mediumaquamarine",
+"mediumblue",
+"mediumorchid",
+"mediumpurple",
+"mediumseagreen",
+"mediumslateblue",
+"mediumspringgreen",
+"mediumturquoise",
+"mediumvioletred",
+"midnightblue",
+"mintcream",
+"mistyrose",
+"moccasin",
+"navajowhite",
+"navy",
+"oldlace",
+"olive",
+"olivedrab",
+"orange",
+"orangered",
+"orchid",
+"palegoldenrod",
+"palegreen",
+"paleturquoise",
+"palevioletred",
+"papayawhip",
+"peachpuff",
+"peru",
+"pink",
+"plum",
+"powderblue",
+"purple",
+"rebeccapurple",
+"red",
+"rosybrown",
+"royalblue",
+"saddlebrown",
+"salmon",
+"sandybrown",
+"seagreen",
+"seashell",
+"sienna",
+"silver",
+"skyblue",
+"slateblue",
+"slategray",
+"slategrey",
+"snow",
+"springgreen",
+"steelblue",
+"tan",
+"teal",
+"thistle",
+"tomato",
+"turquoise",
+"violet",
+"wheat",
+"white",
+"whitesmoke",
+"yellow",
+"yellowgreen"];
+
+
+},{}],97:[function(require,module,exports){
 
 
 
 
 'use strict';
 
+const namedColors=require('./named_colors.json');
+
 exports.TYPES={
 INTEGER:1,
 NUMBER:2,
@@ -41447,18 +44100,17 @@
 
 
 
-
-var integerRegEx=/^[\-+]?[0-9]+$/;
-var numberRegEx=/^[\-+]?[0-9]*\.[0-9]+$/;
-var lengthRegEx=/^(0|[\-+]?[0-9]*\.?[0-9]+(in|cm|em|mm|pt|pc|px|ex|rem|vh|vw))$/;
-var percentRegEx=/^[\-+]?[0-9]*\.?[0-9]+%$/;
-var urlRegEx=/^url\(\s*([^\)]*)\s*\)$/;
-var stringRegEx=/^(\"[^\"]*\"|\'[^\']*\')$/;
+var integerRegEx=/^[-+]?[0-9]+$/;
+var numberRegEx=/^[-+]?[0-9]*\.[0-9]+$/;
+var lengthRegEx=/^(0|[-+]?[0-9]*\.?[0-9]+(in|cm|em|mm|pt|pc|px|ex|rem|vh|vw))$/;
+var percentRegEx=/^[-+]?[0-9]*\.?[0-9]+%$/;
+var urlRegEx=/^url\(\s*([^)]*)\s*\)$/;
+var stringRegEx=/^("[^"]*"|'[^']*')$/;
 var colorRegEx1=/^#[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])?$/;
-var colorRegEx2=/^rgb\(([^\)]*)\)$/;
-var colorRegEx3=/^rgba\(([^\)]*)\)$/;
-var angleRegEx=/^([\-+]?[0-9]*\.?[0-9]+)(deg|grad|rad)$/;
-
+var colorRegEx2=/^rgb\(([^)]*)\)$/;
+var colorRegEx3=/^rgba\(([^)]*)\)$/;
+var colorRegEx4=/^hsla?\(\s*(-?\d+|-?\d*.\d+)\s*,\s*(-?\d+|-?\d*.\d+)%\s*,\s*(-?\d+|-?\d*.\d+)%\s*(,\s*(-?\d+|-?\d*.\d+)\s*)?\)/;
+var angleRegEx=/^([-+]?[0-9]*\.?[0-9]+)(deg|grad|rad)$/;
 
 
 exports.valueType=function valueType(val){
@@ -41504,7 +44156,10 @@
 if(parts.length!==3){
 return undefined;
 }
-if(parts.every(percentRegEx.test.bind(percentRegEx))||parts.every(integerRegEx.test.bind(integerRegEx))){
+if(
+parts.every(percentRegEx.test.bind(percentRegEx))||
+parts.every(integerRegEx.test.bind(integerRegEx)))
+{
 return exports.TYPES.COLOR;
 }
 return undefined;
@@ -41515,7 +44170,10 @@
 if(parts.length!==4){
 return undefined;
 }
-if(parts.slice(0,3).every(percentRegEx.test.bind(percentRegEx))||parts.every(integerRegEx.test.bind(integerRegEx))){
+if(
+parts.slice(0,3).every(percentRegEx.test.bind(percentRegEx))||
+parts.every(integerRegEx.test.bind(integerRegEx)))
+{
 if(numberRegEx.test(parts[3])){
 return exports.TYPES.COLOR;
 }
@@ -41523,26 +44181,18 @@
 return undefined;
 }
 
+if(colorRegEx4.test(val)){
+return exports.TYPES.COLOR;
+}
+
 
 val=val.toLowerCase();
+
+if(namedColors.includes(val)){
+return exports.TYPES.COLOR;
+}
+
 switch(val){
-case'maroon':
-case'red':
-case'orange':
-case'yellow':
-case'olive':
-case'purple':
-case'fuchsia':
-case'white':
-case'lime':
-case'green':
-case'navy':
-case'blue':
-case'aqua':
-case'teal':
-case'black':
-case'silver':
-case'gray':
 
 case'activeborder':
 case'activecaption':
@@ -41708,7 +44358,13 @@
 if(type===exports.TYPES.NULL_OR_EMPTY_STR){
 return val;
 }
-var red,green,blue,alpha=1;
+var red,
+green,
+blue,
+hue,
+saturation,
+lightness,
+alpha=1;
 var parts;
 var res=colorRegEx1.exec(val);
 
@@ -41778,6 +44434,25 @@
 return'rgba('+red+', '+green+', '+blue+', '+alpha+')';
 }
 
+res=colorRegEx4.exec(val);
+if(res){
+const[,_hue,_saturation,_lightness,_alphaString='']=res;
+const _alpha=parseFloat(_alphaString.replace(',','').trim());
+if(!_hue||!_saturation||!_lightness){
+return undefined;
+}
+hue=parseFloat(_hue);
+saturation=parseInt(_saturation,10);
+lightness=parseInt(_lightness,10);
+if(_alpha&&numberRegEx.test(_alpha)){
+alpha=parseFloat(_alpha);
+}
+if(!_alphaString||alpha===1){
+return'hsl('+hue+', '+saturation+'%, '+lightness+'%)';
+}
+return'hsla('+hue+', '+saturation+'%, '+lightness+'%, '+alpha+')';
+}
+
 if(type===exports.TYPES.COLOR){
 return val;
 }
@@ -41845,8 +44520,8 @@
 exports.dashedToCamelCase=dashedToCamelCase;
 
 var is_space=/\s/;
-var opening_deliminators=['"','\'','('];
-var closing_deliminators=['"','\'',')'];
+var opening_deliminators=['"',"'",'('];
+var closing_deliminators=['"',"'",')'];
 
 var getParts=function(str){
 var deliminator_stack=[];
@@ -41874,7 +44549,10 @@
 current_part+=str[i];
 }else{
 current_part+=str[i];
-if(closing_index!==-1&&closing_index===deliminator_stack[deliminator_stack.length-1]){
+if(
+closing_index!==-1&&
+closing_index===deliminator_stack[deliminator_stack.length-1])
+{
 deliminator_stack.pop();
 }else if(opening_index!==-1){
 deliminator_stack.push(opening_index);
@@ -41917,10 +44595,10 @@
 }
 var parts=getParts(v);
 var valid=true;
-parts.forEach(function(part){
+parts.forEach(function(part,i){
 var part_valid=false;
 Object.keys(shorthand_for).forEach(function(property){
-if(shorthand_for[property].isValid(part)){
+if(shorthand_for[property].isValid(part,i)){
 part_valid=true;
 obj[property]=part;
 }
@@ -41976,11 +44654,14 @@
 if(this._values[property]!==undefined){
 return this.getPropertyValue(property);
 }
-return Object.keys(shorthand_for).map(function(subprop){
+return Object.keys(shorthand_for).
+map(function(subprop){
 return this.getPropertyValue(subprop);
-},this).filter(function(value){
+},this).
+filter(function(value){
 return value!=='';
-}).join(' ');
+}).
+join(' ');
 };
 };
 
@@ -41994,7 +44675,7 @@
 if(property_after!==''){
 property_after='-'+property_after;
 }
-var part_names=["top","right","bottom","left"];
+var part_names=['top','right','bottom','left'];
 
 return function(v){
 if(typeof v==='number'){
@@ -42032,7 +44713,7 @@
 }
 
 for(var i=0;i<4;i++){
-var property=property_before+"-"+part_names[i]+property_after;
+var property=property_before+'-'+part_names[i]+property_after;
 this.removeProperty(property);
 if(parts[i]!==''){
 this._values[property]=parts[i];
@@ -42050,7 +44731,7 @@
 
 exports.subImplicitSetter=function(prefix,part,isValid,parser){
 var property=prefix+'-'+part;
-var subparts=[prefix+"-top",prefix+"-right",prefix+"-bottom",prefix+"-left"];
+var subparts=[prefix+'-top',prefix+'-right',prefix+'-bottom',prefix+'-left'];
 
 return function(v){
 if(typeof v==='number'){
@@ -42076,17 +44757,14 @@
 this.removeProperty(subparts[i]);
 this._values[subparts[i]]=parts[i];
 }
-this._setProperty(prefix,parts.join(" "));
+this._setProperty(prefix,parts.join(' '));
 }
 return v;
 };
 };
 
-
 var camel_to_dashed=/[A-Z]/g;
-
-var first_segment=/^\([^\-]\)-/;
-
+var first_segment=/^\([^-]\)-/;
 var vendor_prefixes=['o','moz','ms','webkit'];
 exports.camelToDashed=function(camel_case){
 var match;
@@ -42098,7 +44776,7 @@
 return dashed;
 };
 
-},{}],92:[function(require,module,exports){
+},{"./named_colors.json":96}],98:[function(require,module,exports){
 (function(process){
 
 
@@ -42287,7 +44965,7 @@
 }
 
 }).call(this,require('_process'));
-},{"./debug":93,"_process":131}],93:[function(require,module,exports){
+},{"./debug":99,"_process":137}],99:[function(require,module,exports){
 
 
 
@@ -42491,7 +45169,7 @@
 return val;
 }
 
-},{"ms":119}],94:[function(require,module,exports){
+},{"ms":125}],100:[function(require,module,exports){
 
 
 
@@ -43016,7 +45694,7 @@
 };
 }
 
-},{}],95:[function(require,module,exports){
+},{}],101:[function(require,module,exports){
 (function(Buffer){
 var querystring=require('querystring');
 var trim=require('./trim');
@@ -43323,12 +46001,12 @@
 module.exports=Link;
 
 }).call(this,{"isBuffer":require("../../insert-module-globals/node_modules/is-buffer/index.js")});
-},{"../../insert-module-globals/node_modules/is-buffer/index.js":100,"./trim":96,"querystring":134}],96:[function(require,module,exports){
+},{"../../insert-module-globals/node_modules/is-buffer/index.js":106,"./trim":102,"querystring":140}],102:[function(require,module,exports){
 module.exports=function trim(value){
 return value.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,'');
 };
 
-},{}],97:[function(require,module,exports){
+},{}],103:[function(require,module,exports){
 exports.read=function(buffer,offset,isLE,mLen,nBytes){
 var e,m;
 var eLen=nBytes*8-mLen-1;
@@ -43414,7 +46092,7 @@
 buffer[offset+i-d]|=s*128;
 };
 
-},{}],98:[function(require,module,exports){
+},{}],104:[function(require,module,exports){
 
 
 
@@ -43559,9 +46237,9 @@
 })(ImageSSIM||(ImageSSIM={}));
 module.exports=ImageSSIM;
 
-},{}],99:[function(require,module,exports){
-arguments[4][80][0].apply(exports,arguments);
-},{"dup":80}],100:[function(require,module,exports){
+},{}],105:[function(require,module,exports){
+arguments[4][85][0].apply(exports,arguments);
+},{"dup":85}],106:[function(require,module,exports){
 
 
 
@@ -43584,13 +46262,13 @@
 return typeof obj.readFloatLE==='function'&&typeof obj.slice==='function'&&isBuffer(obj.slice(0,0));
 }
 
-},{}],101:[function(require,module,exports){
+},{}],107:[function(require,module,exports){
 'use strict';
 
 exports=module.exports=require('./lib/parser')['default'];
 exports['default']=exports;
 
-},{"./lib/parser":102}],102:[function(require,module,exports){
+},{"./lib/parser":108}],108:[function(require,module,exports){
 "use strict";
 
 exports["default"]=function(){
@@ -44992,7 +47670,7 @@
 }();
 
 
-},{}],103:[function(require,module,exports){
+},{}],109:[function(require,module,exports){
 
 
 'use strict';
@@ -45009,7 +47687,7 @@
 exports=module.exports=IntlMessageFormat;
 exports['default']=exports;
 
-},{"./lib/locales":85,"./lib/main":108}],104:[function(require,module,exports){
+},{"./lib/locales":90,"./lib/main":114}],110:[function(require,module,exports){
 
 
 
@@ -45219,7 +47897,7 @@
 };
 
 
-},{}],105:[function(require,module,exports){
+},{}],111:[function(require,module,exports){
 
 
 
@@ -45497,13 +48175,13 @@
 };
 
 
-},{"./compiler":104,"./es5":107,"./utils":109,"intl-messageformat-parser":101}],106:[function(require,module,exports){
+},{"./compiler":110,"./es5":113,"./utils":115,"intl-messageformat-parser":107}],112:[function(require,module,exports){
 
 "use strict";
 exports["default"]={"locale":"en","pluralRuleFunction":function(n,ord){var s=String(n).split("."),v0=!s[1],t0=Number(s[0])==n,n10=t0&&s[0].slice(-1),n100=t0&&s[0].slice(-2);if(ord)return n10==1&&n100!=11?"one":n10==2&&n100!=12?"two":n10==3&&n100!=13?"few":"other";return n==1&&v0?"one":"other";}};
 
 
-},{}],107:[function(require,module,exports){
+},{}],113:[function(require,module,exports){
 
 
 
@@ -45554,7 +48232,7 @@
 exports.defineProperty=defineProperty,exports.objCreate=objCreate;
 
 
-},{"./utils":109}],108:[function(require,module,exports){
+},{"./utils":115}],114:[function(require,module,exports){
 
 
 "use strict";
@@ -45566,7 +48244,7 @@
 exports["default"]=src$core$$["default"];
 
 
-},{"./core":105,"./en":106}],109:[function(require,module,exports){
+},{"./core":111,"./en":112}],115:[function(require,module,exports){
 
 
 
@@ -45599,14 +48277,14 @@
 exports.hop=hop;
 
 
-},{}],110:[function(require,module,exports){
+},{}],116:[function(require,module,exports){
 var toString={}.toString;
 
 module.exports=Array.isArray||function(arr){
 return toString.call(arr)=='[object Array]';
 };
 
-},{}],111:[function(require,module,exports){
+},{}],117:[function(require,module,exports){
 var encode=require('./lib/encoder'),
 decode=require('./lib/decoder');
 
@@ -45615,7 +48293,7 @@
 decode:decode};
 
 
-},{"./lib/decoder":112,"./lib/encoder":113}],112:[function(require,module,exports){
+},{"./lib/decoder":118,"./lib/encoder":119}],118:[function(require,module,exports){
 (function(Buffer){
 
 
@@ -46605,7 +49283,7 @@
 }
 
 }).call(this,require("buffer").Buffer);
-},{"buffer":89}],113:[function(require,module,exports){
+},{"buffer":94}],119:[function(require,module,exports){
 (function(Buffer){
 
 
@@ -47375,7 +50053,7 @@
 }
 
 }).call(this,require("buffer").Buffer);
-},{"buffer":89}],114:[function(require,module,exports){
+},{"buffer":94}],120:[function(require,module,exports){
 (function(process){
 
 
@@ -47620,7 +50298,7 @@
 module.exports=Log;
 
 }).call(this,require('_process'));
-},{"_process":131,"debug":92,"events":94,"marky":117}],115:[function(require,module,exports){
+},{"_process":137,"debug":98,"events":100,"marky":123}],121:[function(require,module,exports){
 (function(global){
 
 
@@ -49472,7 +52150,7 @@
 module.exports=isEqual;
 
 }).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{}],116:[function(require,module,exports){
+},{}],122:[function(require,module,exports){
 
 
 
@@ -49489,7 +52167,7 @@
 }
 };
 
-},{}],117:[function(require,module,exports){
+},{}],123:[function(require,module,exports){
 'use strict';
 
 Object.defineProperty(exports,'__esModule',{value:true});
@@ -49569,7 +52247,7 @@
 exports.clear=function(){entries=[];};
 }
 
-},{}],118:[function(require,module,exports){
+},{}],124:[function(require,module,exports){
 exports.getRenderingDataFromViewport=function(viewportProperties,uaDeviceWidth,uaDeviceHeight,uaMaxZoom,uaMinZoom){
 
 var vw=uaDeviceWidth/100;
@@ -49912,7 +52590,7 @@
 "viewport-fit":["auto","cover"]};
 
 
-},{}],119:[function(require,module,exports){
+},{}],125:[function(require,module,exports){
 
 
 
@@ -50066,7 +52744,7 @@
 return Math.ceil(ms/n)+' '+name+'s';
 }
 
-},{}],120:[function(require,module,exports){
+},{}],126:[function(require,module,exports){
 'use strict';
 
 
@@ -50173,7 +52851,7 @@
 
 exports.setTyped(TYPED_OK);
 
-},{}],121:[function(require,module,exports){
+},{}],127:[function(require,module,exports){
 'use strict';
 
 
@@ -50226,7 +52904,7 @@
 
 module.exports=adler32;
 
-},{}],122:[function(require,module,exports){
+},{}],128:[function(require,module,exports){
 'use strict';
 
 
@@ -50296,7 +52974,7 @@
 
 
 
-},{}],123:[function(require,module,exports){
+},{}],129:[function(require,module,exports){
 'use strict';
 
 
@@ -50357,7 +53035,7 @@
 
 module.exports=crc32;
 
-},{}],124:[function(require,module,exports){
+},{}],130:[function(require,module,exports){
 'use strict';
 
 
@@ -52233,7 +54911,7 @@
 
 
 
-},{"../utils/common":120,"./adler32":121,"./crc32":123,"./messages":125,"./trees":126}],125:[function(require,module,exports){
+},{"../utils/common":126,"./adler32":127,"./crc32":129,"./messages":131,"./trees":132}],131:[function(require,module,exports){
 'use strict';
 
 
@@ -52267,7 +54945,7 @@
 '-6':'incompatible version'};
 
 
-},{}],126:[function(require,module,exports){
+},{}],132:[function(require,module,exports){
 'use strict';
 
 
@@ -52289,6 +54967,8 @@
 
 
 
+
+
 var utils=require('../utils/common');
 
 
@@ -53489,7 +56169,7 @@
 exports._tr_tally=_tr_tally;
 exports._tr_align=_tr_align;
 
-},{"../utils/common":120}],127:[function(require,module,exports){
+},{"../utils/common":126}],133:[function(require,module,exports){
 'use strict';
 
 
@@ -53538,7 +56218,7 @@
 
 module.exports=ZStream;
 
-},{}],128:[function(require,module,exports){
+},{}],134:[function(require,module,exports){
 module.exports=function parseCacheControl(field){
 
 if(typeof field!=='string'){
@@ -53577,7 +56257,7 @@
 return err?null:header;
 };
 
-},{}],129:[function(require,module,exports){
+},{}],135:[function(require,module,exports){
 (function(process){
 
 
@@ -53883,7 +56563,7 @@
 
 
 }).call(this,require('_process'));
-},{"_process":131}],130:[function(require,module,exports){
+},{"_process":137}],136:[function(require,module,exports){
 (function(process){
 'use strict';
 
@@ -53930,7 +56610,7 @@
 }
 
 }).call(this,require('_process'));
-},{"_process":131}],131:[function(require,module,exports){
+},{"_process":137}],137:[function(require,module,exports){
 
 var process=module.exports={};
 
@@ -54116,7 +56796,7 @@
 };
 process.umask=function(){return 0;};
 
-},{}],132:[function(require,module,exports){
+},{}],138:[function(require,module,exports){
 
 
 
@@ -54202,7 +56882,7 @@
 return Object.prototype.toString.call(xs)==='[object Array]';
 };
 
-},{}],133:[function(require,module,exports){
+},{}],139:[function(require,module,exports){
 
 
 
@@ -54289,2523 +56969,13 @@
 return res;
 };
 
-},{}],134:[function(require,module,exports){
+},{}],140:[function(require,module,exports){
 'use strict';
 
 exports.decode=exports.parse=require('./decode');
 exports.encode=exports.stringify=require('./encode');
 
-},{"./decode":132,"./encode":133}],135:[function(require,module,exports){
-module.exports=require('./lib/_stream_duplex.js');
-
-},{"./lib/_stream_duplex.js":136}],136:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-'use strict';
-
-
-
-var processNextTick=require('process-nextick-args');
-
-
-
-var objectKeys=Object.keys||function(obj){
-var keys=[];
-for(var key in obj){
-keys.push(key);
-}return keys;
-};
-
-
-module.exports=Duplex;
-
-
-var util=require('core-util-is');
-util.inherits=require('inherits');
-
-
-var Readable=require('./_stream_readable');
-var Writable=require('./_stream_writable');
-
-util.inherits(Duplex,Readable);
-
-var keys=objectKeys(Writable.prototype);
-for(var v=0;v<keys.length;v++){
-var method=keys[v];
-if(!Duplex.prototype[method])Duplex.prototype[method]=Writable.prototype[method];
-}
-
-function Duplex(options){
-if(!(this instanceof Duplex))return new Duplex(options);
-
-Readable.call(this,options);
-Writable.call(this,options);
-
-if(options&&options.readable===false)this.readable=false;
-
-if(options&&options.writable===false)this.writable=false;
-
-this.allowHalfOpen=true;
-if(options&&options.allowHalfOpen===false)this.allowHalfOpen=false;
-
-this.once('end',onend);
-}
-
-
-function onend(){
-
-
-if(this.allowHalfOpen||this._writableState.ended)return;
-
-
-
-processNextTick(onEndNT,this);
-}
-
-function onEndNT(self){
-self.end();
-}
-
-Object.defineProperty(Duplex.prototype,'destroyed',{
-get:function(){
-if(this._readableState===undefined||this._writableState===undefined){
-return false;
-}
-return this._readableState.destroyed&&this._writableState.destroyed;
-},
-set:function(value){
-
-
-if(this._readableState===undefined||this._writableState===undefined){
-return;
-}
-
-
-
-this._readableState.destroyed=value;
-this._writableState.destroyed=value;
-}});
-
-
-Duplex.prototype._destroy=function(err,cb){
-this.push(null);
-this.end();
-
-processNextTick(cb,err);
-};
-
-function forEach(xs,f){
-for(var i=0,l=xs.length;i<l;i++){
-f(xs[i],i);
-}
-}
-},{"./_stream_readable":138,"./_stream_writable":140,"core-util-is":90,"inherits":99,"process-nextick-args":130}],137:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-'use strict';
-
-module.exports=PassThrough;
-
-var Transform=require('./_stream_transform');
-
-
-var util=require('core-util-is');
-util.inherits=require('inherits');
-
-
-util.inherits(PassThrough,Transform);
-
-function PassThrough(options){
-if(!(this instanceof PassThrough))return new PassThrough(options);
-
-Transform.call(this,options);
-}
-
-PassThrough.prototype._transform=function(chunk,encoding,cb){
-cb(null,chunk);
-};
-},{"./_stream_transform":139,"core-util-is":90,"inherits":99}],138:[function(require,module,exports){
-(function(process,global){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-'use strict';
-
-
-
-var processNextTick=require('process-nextick-args');
-
-
-module.exports=Readable;
-
-
-var isArray=require('isarray');
-
-
-
-var Duplex;
-
-
-Readable.ReadableState=ReadableState;
-
-
-var EE=require('events').EventEmitter;
-
-var EElistenerCount=function(emitter,type){
-return emitter.listeners(type).length;
-};
-
-
-
-var Stream=require('./internal/streams/stream');
-
-
-
-
-
-var Buffer=require('safe-buffer').Buffer;
-var OurUint8Array=global.Uint8Array||function(){};
-function _uint8ArrayToBuffer(chunk){
-return Buffer.from(chunk);
-}
-function _isUint8Array(obj){
-return Buffer.isBuffer(obj)||obj instanceof OurUint8Array;
-}
-
-
-
-var util=require('core-util-is');
-util.inherits=require('inherits');
-
-
-
-var debugUtil=require('util');
-var debug=void 0;
-if(debugUtil&&debugUtil.debuglog){
-debug=debugUtil.debuglog('stream');
-}else{
-debug=function(){};
-}
-
-
-var BufferList=require('./internal/streams/BufferList');
-var destroyImpl=require('./internal/streams/destroy');
-var StringDecoder;
-
-util.inherits(Readable,Stream);
-
-var kProxyEvents=['error','close','destroy','pause','resume'];
-
-function prependListener(emitter,event,fn){
-
-
-if(typeof emitter.prependListener==='function'){
-return emitter.prependListener(event,fn);
-}else{
-
-
-
-
-if(!emitter._events||!emitter._events[event])emitter.on(event,fn);else if(isArray(emitter._events[event]))emitter._events[event].unshift(fn);else emitter._events[event]=[fn,emitter._events[event]];
-}
-}
-
-function ReadableState(options,stream){
-Duplex=Duplex||require('./_stream_duplex');
-
-options=options||{};
-
-
-
-this.objectMode=!!options.objectMode;
-
-if(stream instanceof Duplex)this.objectMode=this.objectMode||!!options.readableObjectMode;
-
-
-
-var hwm=options.highWaterMark;
-var defaultHwm=this.objectMode?16:16*1024;
-this.highWaterMark=hwm||hwm===0?hwm:defaultHwm;
-
-
-this.highWaterMark=Math.floor(this.highWaterMark);
-
-
-
-
-this.buffer=new BufferList();
-this.length=0;
-this.pipes=null;
-this.pipesCount=0;
-this.flowing=null;
-this.ended=false;
-this.endEmitted=false;
-this.reading=false;
-
-
-
-
-
-this.sync=true;
-
-
-
-this.needReadable=false;
-this.emittedReadable=false;
-this.readableListening=false;
-this.resumeScheduled=false;
-
-
-this.destroyed=false;
-
-
-
-
-this.defaultEncoding=options.defaultEncoding||'utf8';
-
-
-this.awaitDrain=0;
-
-
-this.readingMore=false;
-
-this.decoder=null;
-this.encoding=null;
-if(options.encoding){
-if(!StringDecoder)StringDecoder=require('string_decoder/').StringDecoder;
-this.decoder=new StringDecoder(options.encoding);
-this.encoding=options.encoding;
-}
-}
-
-function Readable(options){
-Duplex=Duplex||require('./_stream_duplex');
-
-if(!(this instanceof Readable))return new Readable(options);
-
-this._readableState=new ReadableState(options,this);
-
-
-this.readable=true;
-
-if(options){
-if(typeof options.read==='function')this._read=options.read;
-
-if(typeof options.destroy==='function')this._destroy=options.destroy;
-}
-
-Stream.call(this);
-}
-
-Object.defineProperty(Readable.prototype,'destroyed',{
-get:function(){
-if(this._readableState===undefined){
-return false;
-}
-return this._readableState.destroyed;
-},
-set:function(value){
-
-
-if(!this._readableState){
-return;
-}
-
-
-
-this._readableState.destroyed=value;
-}});
-
-
-Readable.prototype.destroy=destroyImpl.destroy;
-Readable.prototype._undestroy=destroyImpl.undestroy;
-Readable.prototype._destroy=function(err,cb){
-this.push(null);
-cb(err);
-};
-
-
-
-
-
-Readable.prototype.push=function(chunk,encoding){
-var state=this._readableState;
-var skipChunkCheck;
-
-if(!state.objectMode){
-if(typeof chunk==='string'){
-encoding=encoding||state.defaultEncoding;
-if(encoding!==state.encoding){
-chunk=Buffer.from(chunk,encoding);
-encoding='';
-}
-skipChunkCheck=true;
-}
-}else{
-skipChunkCheck=true;
-}
-
-return readableAddChunk(this,chunk,encoding,false,skipChunkCheck);
-};
-
-
-Readable.prototype.unshift=function(chunk){
-return readableAddChunk(this,chunk,null,true,false);
-};
-
-function readableAddChunk(stream,chunk,encoding,addToFront,skipChunkCheck){
-var state=stream._readableState;
-if(chunk===null){
-state.reading=false;
-onEofChunk(stream,state);
-}else{
-var er;
-if(!skipChunkCheck)er=chunkInvalid(state,chunk);
-if(er){
-stream.emit('error',er);
-}else if(state.objectMode||chunk&&chunk.length>0){
-if(typeof chunk!=='string'&&!state.objectMode&&Object.getPrototypeOf(chunk)!==Buffer.prototype){
-chunk=_uint8ArrayToBuffer(chunk);
-}
-
-if(addToFront){
-if(state.endEmitted)stream.emit('error',new Error('stream.unshift() after end event'));else addChunk(stream,state,chunk,true);
-}else if(state.ended){
-stream.emit('error',new Error('stream.push() after EOF'));
-}else{
-state.reading=false;
-if(state.decoder&&!encoding){
-chunk=state.decoder.write(chunk);
-if(state.objectMode||chunk.length!==0)addChunk(stream,state,chunk,false);else maybeReadMore(stream,state);
-}else{
-addChunk(stream,state,chunk,false);
-}
-}
-}else if(!addToFront){
-state.reading=false;
-}
-}
-
-return needMoreData(state);
-}
-
-function addChunk(stream,state,chunk,addToFront){
-if(state.flowing&&state.length===0&&!state.sync){
-stream.emit('data',chunk);
-stream.read(0);
-}else{
-
-state.length+=state.objectMode?1:chunk.length;
-if(addToFront)state.buffer.unshift(chunk);else state.buffer.push(chunk);
-
-if(state.needReadable)emitReadable(stream);
-}
-maybeReadMore(stream,state);
-}
-
-function chunkInvalid(state,chunk){
-var er;
-if(!_isUint8Array(chunk)&&typeof chunk!=='string'&&chunk!==undefined&&!state.objectMode){
-er=new TypeError('Invalid non-string/buffer chunk');
-}
-return er;
-}
-
-
-
-
-
-
-
-
-function needMoreData(state){
-return!state.ended&&(state.needReadable||state.length<state.highWaterMark||state.length===0);
-}
-
-Readable.prototype.isPaused=function(){
-return this._readableState.flowing===false;
-};
-
-
-Readable.prototype.setEncoding=function(enc){
-if(!StringDecoder)StringDecoder=require('string_decoder/').StringDecoder;
-this._readableState.decoder=new StringDecoder(enc);
-this._readableState.encoding=enc;
-return this;
-};
-
-
-var MAX_HWM=0x800000;
-function computeNewHighWaterMark(n){
-if(n>=MAX_HWM){
-n=MAX_HWM;
-}else{
-
-
-n--;
-n|=n>>>1;
-n|=n>>>2;
-n|=n>>>4;
-n|=n>>>8;
-n|=n>>>16;
-n++;
-}
-return n;
-}
-
-
-
-function howMuchToRead(n,state){
-if(n<=0||state.length===0&&state.ended)return 0;
-if(state.objectMode)return 1;
-if(n!==n){
-
-if(state.flowing&&state.length)return state.buffer.head.data.length;else return state.length;
-}
-
-if(n>state.highWaterMark)state.highWaterMark=computeNewHighWaterMark(n);
-if(n<=state.length)return n;
-
-if(!state.ended){
-state.needReadable=true;
-return 0;
-}
-return state.length;
-}
-
-
-Readable.prototype.read=function(n){
-debug('read',n);
-n=parseInt(n,10);
-var state=this._readableState;
-var nOrig=n;
-
-if(n!==0)state.emittedReadable=false;
-
-
-
-
-if(n===0&&state.needReadable&&(state.length>=state.highWaterMark||state.ended)){
-debug('read: emitReadable',state.length,state.ended);
-if(state.length===0&&state.ended)endReadable(this);else emitReadable(this);
-return null;
-}
-
-n=howMuchToRead(n,state);
-
-
-if(n===0&&state.ended){
-if(state.length===0)endReadable(this);
-return null;
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-var doRead=state.needReadable;
-debug('need readable',doRead);
-
-
-if(state.length===0||state.length-n<state.highWaterMark){
-doRead=true;
-debug('length less than watermark',doRead);
-}
-
-
-
-if(state.ended||state.reading){
-doRead=false;
-debug('reading or ended',doRead);
-}else if(doRead){
-debug('do read');
-state.reading=true;
-state.sync=true;
-
-if(state.length===0)state.needReadable=true;
-
-this._read(state.highWaterMark);
-state.sync=false;
-
-
-if(!state.reading)n=howMuchToRead(nOrig,state);
-}
-
-var ret;
-if(n>0)ret=fromList(n,state);else ret=null;
-
-if(ret===null){
-state.needReadable=true;
-n=0;
-}else{
-state.length-=n;
-}
-
-if(state.length===0){
-
-
-if(!state.ended)state.needReadable=true;
-
-
-if(nOrig!==n&&state.ended)endReadable(this);
-}
-
-if(ret!==null)this.emit('data',ret);
-
-return ret;
-};
-
-function onEofChunk(stream,state){
-if(state.ended)return;
-if(state.decoder){
-var chunk=state.decoder.end();
-if(chunk&&chunk.length){
-state.buffer.push(chunk);
-state.length+=state.objectMode?1:chunk.length;
-}
-}
-state.ended=true;
-
-
-emitReadable(stream);
-}
-
-
-
-
-function emitReadable(stream){
-var state=stream._readableState;
-state.needReadable=false;
-if(!state.emittedReadable){
-debug('emitReadable',state.flowing);
-state.emittedReadable=true;
-if(state.sync)processNextTick(emitReadable_,stream);else emitReadable_(stream);
-}
-}
-
-function emitReadable_(stream){
-debug('emit readable');
-stream.emit('readable');
-flow(stream);
-}
-
-
-
-
-
-
-
-function maybeReadMore(stream,state){
-if(!state.readingMore){
-state.readingMore=true;
-processNextTick(maybeReadMore_,stream,state);
-}
-}
-
-function maybeReadMore_(stream,state){
-var len=state.length;
-while(!state.reading&&!state.flowing&&!state.ended&&state.length<state.highWaterMark){
-debug('maybeReadMore read 0');
-stream.read(0);
-if(len===state.length)
-
-break;else len=state.length;
-}
-state.readingMore=false;
-}
-
-
-
-
-
-Readable.prototype._read=function(n){
-this.emit('error',new Error('_read() is not implemented'));
-};
-
-Readable.prototype.pipe=function(dest,pipeOpts){
-var src=this;
-var state=this._readableState;
-
-switch(state.pipesCount){
-case 0:
-state.pipes=dest;
-break;
-case 1:
-state.pipes=[state.pipes,dest];
-break;
-default:
-state.pipes.push(dest);
-break;}
-
-state.pipesCount+=1;
-debug('pipe count=%d opts=%j',state.pipesCount,pipeOpts);
-
-var doEnd=(!pipeOpts||pipeOpts.end!==false)&&dest!==process.stdout&&dest!==process.stderr;
-
-var endFn=doEnd?onend:unpipe;
-if(state.endEmitted)processNextTick(endFn);else src.once('end',endFn);
-
-dest.on('unpipe',onunpipe);
-function onunpipe(readable,unpipeInfo){
-debug('onunpipe');
-if(readable===src){
-if(unpipeInfo&&unpipeInfo.hasUnpiped===false){
-unpipeInfo.hasUnpiped=true;
-cleanup();
-}
-}
-}
-
-function onend(){
-debug('onend');
-dest.end();
-}
-
-
-
-
-
-var ondrain=pipeOnDrain(src);
-dest.on('drain',ondrain);
-
-var cleanedUp=false;
-function cleanup(){
-debug('cleanup');
-
-dest.removeListener('close',onclose);
-dest.removeListener('finish',onfinish);
-dest.removeListener('drain',ondrain);
-dest.removeListener('error',onerror);
-dest.removeListener('unpipe',onunpipe);
-src.removeListener('end',onend);
-src.removeListener('end',unpipe);
-src.removeListener('data',ondata);
-
-cleanedUp=true;
-
-
-
-
-
-
-if(state.awaitDrain&&(!dest._writableState||dest._writableState.needDrain))ondrain();
-}
-
-
-
-
-
-var increasedAwaitDrain=false;
-src.on('data',ondata);
-function ondata(chunk){
-debug('ondata');
-increasedAwaitDrain=false;
-var ret=dest.write(chunk);
-if(false===ret&&!increasedAwaitDrain){
-
-
-
-
-if((state.pipesCount===1&&state.pipes===dest||state.pipesCount>1&&indexOf(state.pipes,dest)!==-1)&&!cleanedUp){
-debug('false write response, pause',src._readableState.awaitDrain);
-src._readableState.awaitDrain++;
-increasedAwaitDrain=true;
-}
-src.pause();
-}
-}
-
-
-
-function onerror(er){
-debug('onerror',er);
-unpipe();
-dest.removeListener('error',onerror);
-if(EElistenerCount(dest,'error')===0)dest.emit('error',er);
-}
-
-
-prependListener(dest,'error',onerror);
-
-
-function onclose(){
-dest.removeListener('finish',onfinish);
-unpipe();
-}
-dest.once('close',onclose);
-function onfinish(){
-debug('onfinish');
-dest.removeListener('close',onclose);
-unpipe();
-}
-dest.once('finish',onfinish);
-
-function unpipe(){
-debug('unpipe');
-src.unpipe(dest);
-}
-
-
-dest.emit('pipe',src);
-
-
-if(!state.flowing){
-debug('pipe resume');
-src.resume();
-}
-
-return dest;
-};
-
-function pipeOnDrain(src){
-return function(){
-var state=src._readableState;
-debug('pipeOnDrain',state.awaitDrain);
-if(state.awaitDrain)state.awaitDrain--;
-if(state.awaitDrain===0&&EElistenerCount(src,'data')){
-state.flowing=true;
-flow(src);
-}
-};
-}
-
-Readable.prototype.unpipe=function(dest){
-var state=this._readableState;
-var unpipeInfo={hasUnpiped:false};
-
-
-if(state.pipesCount===0)return this;
-
-
-if(state.pipesCount===1){
-
-if(dest&&dest!==state.pipes)return this;
-
-if(!dest)dest=state.pipes;
-
-
-state.pipes=null;
-state.pipesCount=0;
-state.flowing=false;
-if(dest)dest.emit('unpipe',this,unpipeInfo);
-return this;
-}
-
-
-
-if(!dest){
-
-var dests=state.pipes;
-var len=state.pipesCount;
-state.pipes=null;
-state.pipesCount=0;
-state.flowing=false;
-
-for(var i=0;i<len;i++){
-dests[i].emit('unpipe',this,unpipeInfo);
-}return this;
-}
-
-
-var index=indexOf(state.pipes,dest);
-if(index===-1)return this;
-
-state.pipes.splice(index,1);
-state.pipesCount-=1;
-if(state.pipesCount===1)state.pipes=state.pipes[0];
-
-dest.emit('unpipe',this,unpipeInfo);
-
-return this;
-};
-
-
-
-Readable.prototype.on=function(ev,fn){
-var res=Stream.prototype.on.call(this,ev,fn);
-
-if(ev==='data'){
-
-if(this._readableState.flowing!==false)this.resume();
-}else if(ev==='readable'){
-var state=this._readableState;
-if(!state.endEmitted&&!state.readableListening){
-state.readableListening=state.needReadable=true;
-state.emittedReadable=false;
-if(!state.reading){
-processNextTick(nReadingNextTick,this);
-}else if(state.length){
-emitReadable(this);
-}
-}
-}
-
-return res;
-};
-Readable.prototype.addListener=Readable.prototype.on;
-
-function nReadingNextTick(self){
-debug('readable nexttick read 0');
-self.read(0);
-}
-
-
-
-Readable.prototype.resume=function(){
-var state=this._readableState;
-if(!state.flowing){
-debug('resume');
-state.flowing=true;
-resume(this,state);
-}
-return this;
-};
-
-function resume(stream,state){
-if(!state.resumeScheduled){
-state.resumeScheduled=true;
-processNextTick(resume_,stream,state);
-}
-}
-
-function resume_(stream,state){
-if(!state.reading){
-debug('resume read 0');
-stream.read(0);
-}
-
-state.resumeScheduled=false;
-state.awaitDrain=0;
-stream.emit('resume');
-flow(stream);
-if(state.flowing&&!state.reading)stream.read(0);
-}
-
-Readable.prototype.pause=function(){
-debug('call pause flowing=%j',this._readableState.flowing);
-if(false!==this._readableState.flowing){
-debug('pause');
-this._readableState.flowing=false;
-this.emit('pause');
-}
-return this;
-};
-
-function flow(stream){
-var state=stream._readableState;
-debug('flow',state.flowing);
-while(state.flowing&&stream.read()!==null){}
-}
-
-
-
-
-Readable.prototype.wrap=function(stream){
-var state=this._readableState;
-var paused=false;
-
-var self=this;
-stream.on('end',function(){
-debug('wrapped end');
-if(state.decoder&&!state.ended){
-var chunk=state.decoder.end();
-if(chunk&&chunk.length)self.push(chunk);
-}
-
-self.push(null);
-});
-
-stream.on('data',function(chunk){
-debug('wrapped data');
-if(state.decoder)chunk=state.decoder.write(chunk);
-
-
-if(state.objectMode&&(chunk===null||chunk===undefined))return;else if(!state.objectMode&&(!chunk||!chunk.length))return;
-
-var ret=self.push(chunk);
-if(!ret){
-paused=true;
-stream.pause();
-}
-});
-
-
-
-for(var i in stream){
-if(this[i]===undefined&&typeof stream[i]==='function'){
-this[i]=function(method){
-return function(){
-return stream[method].apply(stream,arguments);
-};
-}(i);
-}
-}
-
-
-for(var n=0;n<kProxyEvents.length;n++){
-stream.on(kProxyEvents[n],self.emit.bind(self,kProxyEvents[n]));
-}
-
-
-
-self._read=function(n){
-debug('wrapped _read',n);
-if(paused){
-paused=false;
-stream.resume();
-}
-};
-
-return self;
-};
-
-
-Readable._fromList=fromList;
-
-
-
-
-
-function fromList(n,state){
-
-if(state.length===0)return null;
-
-var ret;
-if(state.objectMode)ret=state.buffer.shift();else if(!n||n>=state.length){
-
-if(state.decoder)ret=state.buffer.join('');else if(state.buffer.length===1)ret=state.buffer.head.data;else ret=state.buffer.concat(state.length);
-state.buffer.clear();
-}else{
-
-ret=fromListPartial(n,state.buffer,state.decoder);
-}
-
-return ret;
-}
-
-
-
-
-function fromListPartial(n,list,hasStrings){
-var ret;
-if(n<list.head.data.length){
-
-ret=list.head.data.slice(0,n);
-list.head.data=list.head.data.slice(n);
-}else if(n===list.head.data.length){
-
-ret=list.shift();
-}else{
-
-ret=hasStrings?copyFromBufferString(n,list):copyFromBuffer(n,list);
-}
-return ret;
-}
-
-
-
-
-
-function copyFromBufferString(n,list){
-var p=list.head;
-var c=1;
-var ret=p.data;
-n-=ret.length;
-while(p=p.next){
-var str=p.data;
-var nb=n>str.length?str.length:n;
-if(nb===str.length)ret+=str;else ret+=str.slice(0,n);
-n-=nb;
-if(n===0){
-if(nb===str.length){
-++c;
-if(p.next)list.head=p.next;else list.head=list.tail=null;
-}else{
-list.head=p;
-p.data=str.slice(nb);
-}
-break;
-}
-++c;
-}
-list.length-=c;
-return ret;
-}
-
-
-
-
-function copyFromBuffer(n,list){
-var ret=Buffer.allocUnsafe(n);
-var p=list.head;
-var c=1;
-p.data.copy(ret);
-n-=p.data.length;
-while(p=p.next){
-var buf=p.data;
-var nb=n>buf.length?buf.length:n;
-buf.copy(ret,ret.length-n,0,nb);
-n-=nb;
-if(n===0){
-if(nb===buf.length){
-++c;
-if(p.next)list.head=p.next;else list.head=list.tail=null;
-}else{
-list.head=p;
-p.data=buf.slice(nb);
-}
-break;
-}
-++c;
-}
-list.length-=c;
-return ret;
-}
-
-function endReadable(stream){
-var state=stream._readableState;
-
-
-
-if(state.length>0)throw new Error('"endReadable()" called on non-empty stream');
-
-if(!state.endEmitted){
-state.ended=true;
-processNextTick(endReadableNT,state,stream);
-}
-}
-
-function endReadableNT(state,stream){
-
-if(!state.endEmitted&&state.length===0){
-state.endEmitted=true;
-stream.readable=false;
-stream.emit('end');
-}
-}
-
-function forEach(xs,f){
-for(var i=0,l=xs.length;i<l;i++){
-f(xs[i],i);
-}
-}
-
-function indexOf(xs,x){
-for(var i=0,l=xs.length;i<l;i++){
-if(xs[i]===x)return i;
-}
-return-1;
-}
-}).call(this,require('_process'),typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{"./_stream_duplex":136,"./internal/streams/BufferList":141,"./internal/streams/destroy":142,"./internal/streams/stream":143,"_process":131,"core-util-is":90,"events":94,"inherits":99,"isarray":110,"process-nextick-args":130,"safe-buffer":151,"string_decoder/":144,"util":85}],139:[function(require,module,exports){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-'use strict';
-
-module.exports=Transform;
-
-var Duplex=require('./_stream_duplex');
-
-
-var util=require('core-util-is');
-util.inherits=require('inherits');
-
-
-util.inherits(Transform,Duplex);
-
-function TransformState(stream){
-this.afterTransform=function(er,data){
-return afterTransform(stream,er,data);
-};
-
-this.needTransform=false;
-this.transforming=false;
-this.writecb=null;
-this.writechunk=null;
-this.writeencoding=null;
-}
-
-function afterTransform(stream,er,data){
-var ts=stream._transformState;
-ts.transforming=false;
-
-var cb=ts.writecb;
-
-if(!cb){
-return stream.emit('error',new Error('write callback called multiple times'));
-}
-
-ts.writechunk=null;
-ts.writecb=null;
-
-if(data!==null&&data!==undefined)stream.push(data);
-
-cb(er);
-
-var rs=stream._readableState;
-rs.reading=false;
-if(rs.needReadable||rs.length<rs.highWaterMark){
-stream._read(rs.highWaterMark);
-}
-}
-
-function Transform(options){
-if(!(this instanceof Transform))return new Transform(options);
-
-Duplex.call(this,options);
-
-this._transformState=new TransformState(this);
-
-var stream=this;
-
-
-this._readableState.needReadable=true;
-
-
-
-
-this._readableState.sync=false;
-
-if(options){
-if(typeof options.transform==='function')this._transform=options.transform;
-
-if(typeof options.flush==='function')this._flush=options.flush;
-}
-
-
-this.once('prefinish',function(){
-if(typeof this._flush==='function')this._flush(function(er,data){
-done(stream,er,data);
-});else done(stream);
-});
-}
-
-Transform.prototype.push=function(chunk,encoding){
-this._transformState.needTransform=false;
-return Duplex.prototype.push.call(this,chunk,encoding);
-};
-
-
-
-
-
-
-
-
-
-
-
-Transform.prototype._transform=function(chunk,encoding,cb){
-throw new Error('_transform() is not implemented');
-};
-
-Transform.prototype._write=function(chunk,encoding,cb){
-var ts=this._transformState;
-ts.writecb=cb;
-ts.writechunk=chunk;
-ts.writeencoding=encoding;
-if(!ts.transforming){
-var rs=this._readableState;
-if(ts.needTransform||rs.needReadable||rs.length<rs.highWaterMark)this._read(rs.highWaterMark);
-}
-};
-
-
-
-
-Transform.prototype._read=function(n){
-var ts=this._transformState;
-
-if(ts.writechunk!==null&&ts.writecb&&!ts.transforming){
-ts.transforming=true;
-this._transform(ts.writechunk,ts.writeencoding,ts.afterTransform);
-}else{
-
-
-ts.needTransform=true;
-}
-};
-
-Transform.prototype._destroy=function(err,cb){
-var _this=this;
-
-Duplex.prototype._destroy.call(this,err,function(err2){
-cb(err2);
-_this.emit('close');
-});
-};
-
-function done(stream,er,data){
-if(er)return stream.emit('error',er);
-
-if(data!==null&&data!==undefined)stream.push(data);
-
-
-
-var ws=stream._writableState;
-var ts=stream._transformState;
-
-if(ws.length)throw new Error('Calling transform done when ws.length != 0');
-
-if(ts.transforming)throw new Error('Calling transform done when still transforming');
-
-return stream.push(null);
-}
-},{"./_stream_duplex":136,"core-util-is":90,"inherits":99}],140:[function(require,module,exports){
-(function(process,global,setImmediate){
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-'use strict';
-
-
-
-var processNextTick=require('process-nextick-args');
-
-
-module.exports=Writable;
-
-
-function WriteReq(chunk,encoding,cb){
-this.chunk=chunk;
-this.encoding=encoding;
-this.callback=cb;
-this.next=null;
-}
-
-
-
-function CorkedRequest(state){
-var _this=this;
-
-this.next=null;
-this.entry=null;
-this.finish=function(){
-onCorkedFinish(_this,state);
-};
-}
-
-
-
-var asyncWrite=!process.browser&&['v0.10','v0.9.'].indexOf(process.version.slice(0,5))>-1?setImmediate:processNextTick;
-
-
-
-var Duplex;
-
-
-Writable.WritableState=WritableState;
-
-
-var util=require('core-util-is');
-util.inherits=require('inherits');
-
-
-
-var internalUtil={
-deprecate:require('util-deprecate')};
-
-
-
-
-var Stream=require('./internal/streams/stream');
-
-
-
-var Buffer=require('safe-buffer').Buffer;
-var OurUint8Array=global.Uint8Array||function(){};
-function _uint8ArrayToBuffer(chunk){
-return Buffer.from(chunk);
-}
-function _isUint8Array(obj){
-return Buffer.isBuffer(obj)||obj instanceof OurUint8Array;
-}
-
-
-var destroyImpl=require('./internal/streams/destroy');
-
-util.inherits(Writable,Stream);
-
-function nop(){}
-
-function WritableState(options,stream){
-Duplex=Duplex||require('./_stream_duplex');
-
-options=options||{};
-
-
-
-this.objectMode=!!options.objectMode;
-
-if(stream instanceof Duplex)this.objectMode=this.objectMode||!!options.writableObjectMode;
-
-
-
-
-var hwm=options.highWaterMark;
-var defaultHwm=this.objectMode?16:16*1024;
-this.highWaterMark=hwm||hwm===0?hwm:defaultHwm;
-
-
-this.highWaterMark=Math.floor(this.highWaterMark);
-
-
-this.finalCalled=false;
-
-
-this.needDrain=false;
-
-this.ending=false;
-
-this.ended=false;
-
-this.finished=false;
-
-
-this.destroyed=false;
-
-
-
-
-var noDecode=options.decodeStrings===false;
-this.decodeStrings=!noDecode;
-
-
-
-
-this.defaultEncoding=options.defaultEncoding||'utf8';
-
-
-
-
-this.length=0;
-
-
-this.writing=false;
-
-
-this.corked=0;
-
-
-
-
-
-this.sync=true;
-
-
-
-
-this.bufferProcessing=false;
-
-
-this.onwrite=function(er){
-onwrite(stream,er);
-};
-
-
-this.writecb=null;
-
-
-this.writelen=0;
-
-this.bufferedRequest=null;
-this.lastBufferedRequest=null;
-
-
-
-this.pendingcb=0;
-
-
-
-this.prefinished=false;
-
-
-this.errorEmitted=false;
-
-
-this.bufferedRequestCount=0;
-
-
-
-this.corkedRequestsFree=new CorkedRequest(this);
-}
-
-WritableState.prototype.getBuffer=function getBuffer(){
-var current=this.bufferedRequest;
-var out=[];
-while(current){
-out.push(current);
-current=current.next;
-}
-return out;
-};
-
-(function(){
-try{
-Object.defineProperty(WritableState.prototype,'buffer',{
-get:internalUtil.deprecate(function(){
-return this.getBuffer();
-},'_writableState.buffer is deprecated. Use _writableState.getBuffer '+'instead.','DEP0003')});
-
-}catch(_){}
-})();
-
-
-
-var realHasInstance;
-if(typeof Symbol==='function'&&Symbol.hasInstance&&typeof Function.prototype[Symbol.hasInstance]==='function'){
-realHasInstance=Function.prototype[Symbol.hasInstance];
-Object.defineProperty(Writable,Symbol.hasInstance,{
-value:function(object){
-if(realHasInstance.call(this,object))return true;
-
-return object&&object._writableState instanceof WritableState;
-}});
-
-}else{
-realHasInstance=function(object){
-return object instanceof this;
-};
-}
-
-function Writable(options){
-Duplex=Duplex||require('./_stream_duplex');
-
-
-
-
-
-
-
-
-if(!realHasInstance.call(Writable,this)&&!(this instanceof Duplex)){
-return new Writable(options);
-}
-
-this._writableState=new WritableState(options,this);
-
-
-this.writable=true;
-
-if(options){
-if(typeof options.write==='function')this._write=options.write;
-
-if(typeof options.writev==='function')this._writev=options.writev;
-
-if(typeof options.destroy==='function')this._destroy=options.destroy;
-
-if(typeof options.final==='function')this._final=options.final;
-}
-
-Stream.call(this);
-}
-
-
-Writable.prototype.pipe=function(){
-this.emit('error',new Error('Cannot pipe, not readable'));
-};
-
-function writeAfterEnd(stream,cb){
-var er=new Error('write after end');
-
-stream.emit('error',er);
-processNextTick(cb,er);
-}
-
-
-
-
-function validChunk(stream,state,chunk,cb){
-var valid=true;
-var er=false;
-
-if(chunk===null){
-er=new TypeError('May not write null values to stream');
-}else if(typeof chunk!=='string'&&chunk!==undefined&&!state.objectMode){
-er=new TypeError('Invalid non-string/buffer chunk');
-}
-if(er){
-stream.emit('error',er);
-processNextTick(cb,er);
-valid=false;
-}
-return valid;
-}
-
-Writable.prototype.write=function(chunk,encoding,cb){
-var state=this._writableState;
-var ret=false;
-var isBuf=_isUint8Array(chunk)&&!state.objectMode;
-
-if(isBuf&&!Buffer.isBuffer(chunk)){
-chunk=_uint8ArrayToBuffer(chunk);
-}
-
-if(typeof encoding==='function'){
-cb=encoding;
-encoding=null;
-}
-
-if(isBuf)encoding='buffer';else if(!encoding)encoding=state.defaultEncoding;
-
-if(typeof cb!=='function')cb=nop;
-
-if(state.ended)writeAfterEnd(this,cb);else if(isBuf||validChunk(this,state,chunk,cb)){
-state.pendingcb++;
-ret=writeOrBuffer(this,state,isBuf,chunk,encoding,cb);
-}
-
-return ret;
-};
-
-Writable.prototype.cork=function(){
-var state=this._writableState;
-
-state.corked++;
-};
-
-Writable.prototype.uncork=function(){
-var state=this._writableState;
-
-if(state.corked){
-state.corked--;
-
-if(!state.writing&&!state.corked&&!state.finished&&!state.bufferProcessing&&state.bufferedRequest)clearBuffer(this,state);
-}
-};
-
-Writable.prototype.setDefaultEncoding=function setDefaultEncoding(encoding){
-
-if(typeof encoding==='string')encoding=encoding.toLowerCase();
-if(!(['hex','utf8','utf-8','ascii','binary','base64','ucs2','ucs-2','utf16le','utf-16le','raw'].indexOf((encoding+'').toLowerCase())>-1))throw new TypeError('Unknown encoding: '+encoding);
-this._writableState.defaultEncoding=encoding;
-return this;
-};
-
-function decodeChunk(state,chunk,encoding){
-if(!state.objectMode&&state.decodeStrings!==false&&typeof chunk==='string'){
-chunk=Buffer.from(chunk,encoding);
-}
-return chunk;
-}
-
-
-
-
-function writeOrBuffer(stream,state,isBuf,chunk,encoding,cb){
-if(!isBuf){
-var newChunk=decodeChunk(state,chunk,encoding);
-if(chunk!==newChunk){
-isBuf=true;
-encoding='buffer';
-chunk=newChunk;
-}
-}
-var len=state.objectMode?1:chunk.length;
-
-state.length+=len;
-
-var ret=state.length<state.highWaterMark;
-
-if(!ret)state.needDrain=true;
-
-if(state.writing||state.corked){
-var last=state.lastBufferedRequest;
-state.lastBufferedRequest={
-chunk:chunk,
-encoding:encoding,
-isBuf:isBuf,
-callback:cb,
-next:null};
-
-if(last){
-last.next=state.lastBufferedRequest;
-}else{
-state.bufferedRequest=state.lastBufferedRequest;
-}
-state.bufferedRequestCount+=1;
-}else{
-doWrite(stream,state,false,len,chunk,encoding,cb);
-}
-
-return ret;
-}
-
-function doWrite(stream,state,writev,len,chunk,encoding,cb){
-state.writelen=len;
-state.writecb=cb;
-state.writing=true;
-state.sync=true;
-if(writev)stream._writev(chunk,state.onwrite);else stream._write(chunk,encoding,state.onwrite);
-state.sync=false;
-}
-
-function onwriteError(stream,state,sync,er,cb){
---state.pendingcb;
-
-if(sync){
-
-
-processNextTick(cb,er);
-
-
-processNextTick(finishMaybe,stream,state);
-stream._writableState.errorEmitted=true;
-stream.emit('error',er);
-}else{
-
-
-cb(er);
-stream._writableState.errorEmitted=true;
-stream.emit('error',er);
-
-
-finishMaybe(stream,state);
-}
-}
-
-function onwriteStateUpdate(state){
-state.writing=false;
-state.writecb=null;
-state.length-=state.writelen;
-state.writelen=0;
-}
-
-function onwrite(stream,er){
-var state=stream._writableState;
-var sync=state.sync;
-var cb=state.writecb;
-
-onwriteStateUpdate(state);
-
-if(er)onwriteError(stream,state,sync,er,cb);else{
-
-var finished=needFinish(state);
-
-if(!finished&&!state.corked&&!state.bufferProcessing&&state.bufferedRequest){
-clearBuffer(stream,state);
-}
-
-if(sync){
-
-asyncWrite(afterWrite,stream,state,finished,cb);
-
-}else{
-afterWrite(stream,state,finished,cb);
-}
-}
-}
-
-function afterWrite(stream,state,finished,cb){
-if(!finished)onwriteDrain(stream,state);
-state.pendingcb--;
-cb();
-finishMaybe(stream,state);
-}
-
-
-
-
-function onwriteDrain(stream,state){
-if(state.length===0&&state.needDrain){
-state.needDrain=false;
-stream.emit('drain');
-}
-}
-
-
-function clearBuffer(stream,state){
-state.bufferProcessing=true;
-var entry=state.bufferedRequest;
-
-if(stream._writev&&entry&&entry.next){
-
-var l=state.bufferedRequestCount;
-var buffer=new Array(l);
-var holder=state.corkedRequestsFree;
-holder.entry=entry;
-
-var count=0;
-var allBuffers=true;
-while(entry){
-buffer[count]=entry;
-if(!entry.isBuf)allBuffers=false;
-entry=entry.next;
-count+=1;
-}
-buffer.allBuffers=allBuffers;
-
-doWrite(stream,state,true,state.length,buffer,'',holder.finish);
-
-
-
-state.pendingcb++;
-state.lastBufferedRequest=null;
-if(holder.next){
-state.corkedRequestsFree=holder.next;
-holder.next=null;
-}else{
-state.corkedRequestsFree=new CorkedRequest(state);
-}
-}else{
-
-while(entry){
-var chunk=entry.chunk;
-var encoding=entry.encoding;
-var cb=entry.callback;
-var len=state.objectMode?1:chunk.length;
-
-doWrite(stream,state,false,len,chunk,encoding,cb);
-entry=entry.next;
-
-
-
-
-if(state.writing){
-break;
-}
-}
-
-if(entry===null)state.lastBufferedRequest=null;
-}
-
-state.bufferedRequestCount=0;
-state.bufferedRequest=entry;
-state.bufferProcessing=false;
-}
-
-Writable.prototype._write=function(chunk,encoding,cb){
-cb(new Error('_write() is not implemented'));
-};
-
-Writable.prototype._writev=null;
-
-Writable.prototype.end=function(chunk,encoding,cb){
-var state=this._writableState;
-
-if(typeof chunk==='function'){
-cb=chunk;
-chunk=null;
-encoding=null;
-}else if(typeof encoding==='function'){
-cb=encoding;
-encoding=null;
-}
-
-if(chunk!==null&&chunk!==undefined)this.write(chunk,encoding);
-
-
-if(state.corked){
-state.corked=1;
-this.uncork();
-}
-
-
-if(!state.ending&&!state.finished)endWritable(this,state,cb);
-};
-
-function needFinish(state){
-return state.ending&&state.length===0&&state.bufferedRequest===null&&!state.finished&&!state.writing;
-}
-function callFinal(stream,state){
-stream._final(function(err){
-state.pendingcb--;
-if(err){
-stream.emit('error',err);
-}
-state.prefinished=true;
-stream.emit('prefinish');
-finishMaybe(stream,state);
-});
-}
-function prefinish(stream,state){
-if(!state.prefinished&&!state.finalCalled){
-if(typeof stream._final==='function'){
-state.pendingcb++;
-state.finalCalled=true;
-processNextTick(callFinal,stream,state);
-}else{
-state.prefinished=true;
-stream.emit('prefinish');
-}
-}
-}
-
-function finishMaybe(stream,state){
-var need=needFinish(state);
-if(need){
-prefinish(stream,state);
-if(state.pendingcb===0){
-state.finished=true;
-stream.emit('finish');
-}
-}
-return need;
-}
-
-function endWritable(stream,state,cb){
-state.ending=true;
-finishMaybe(stream,state);
-if(cb){
-if(state.finished)processNextTick(cb);else stream.once('finish',cb);
-}
-state.ended=true;
-stream.writable=false;
-}
-
-function onCorkedFinish(corkReq,state,err){
-var entry=corkReq.entry;
-corkReq.entry=null;
-while(entry){
-var cb=entry.callback;
-state.pendingcb--;
-cb(err);
-entry=entry.next;
-}
-if(state.corkedRequestsFree){
-state.corkedRequestsFree.next=corkReq;
-}else{
-state.corkedRequestsFree=corkReq;
-}
-}
-
-Object.defineProperty(Writable.prototype,'destroyed',{
-get:function(){
-if(this._writableState===undefined){
-return false;
-}
-return this._writableState.destroyed;
-},
-set:function(value){
-
-
-if(!this._writableState){
-return;
-}
-
-
-
-this._writableState.destroyed=value;
-}});
-
-
-Writable.prototype.destroy=destroyImpl.destroy;
-Writable.prototype._undestroy=destroyImpl.undestroy;
-Writable.prototype._destroy=function(err,cb){
-this.end();
-cb(err);
-};
-}).call(this,require('_process'),typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{},require("timers").setImmediate);
-},{"./_stream_duplex":136,"./internal/streams/destroy":142,"./internal/streams/stream":143,"_process":131,"core-util-is":90,"inherits":99,"process-nextick-args":130,"safe-buffer":151,"timers":157,"util-deprecate":158}],141:[function(require,module,exports){
-'use strict';
-
-
-
-function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor)){throw new TypeError("Cannot call a class as a function");}}
-
-var Buffer=require('safe-buffer').Buffer;
-
-
-function copyBuffer(src,target,offset){
-src.copy(target,offset);
-}
-
-module.exports=function(){
-function BufferList(){
-_classCallCheck(this,BufferList);
-
-this.head=null;
-this.tail=null;
-this.length=0;
-}
-
-BufferList.prototype.push=function push(v){
-var entry={data:v,next:null};
-if(this.length>0)this.tail.next=entry;else this.head=entry;
-this.tail=entry;
-++this.length;
-};
-
-BufferList.prototype.unshift=function unshift(v){
-var entry={data:v,next:this.head};
-if(this.length===0)this.tail=entry;
-this.head=entry;
-++this.length;
-};
-
-BufferList.prototype.shift=function shift(){
-if(this.length===0)return;
-var ret=this.head.data;
-if(this.length===1)this.head=this.tail=null;else this.head=this.head.next;
---this.length;
-return ret;
-};
-
-BufferList.prototype.clear=function clear(){
-this.head=this.tail=null;
-this.length=0;
-};
-
-BufferList.prototype.join=function join(s){
-if(this.length===0)return'';
-var p=this.head;
-var ret=''+p.data;
-while(p=p.next){
-ret+=s+p.data;
-}return ret;
-};
-
-BufferList.prototype.concat=function concat(n){
-if(this.length===0)return Buffer.alloc(0);
-if(this.length===1)return this.head.data;
-var ret=Buffer.allocUnsafe(n>>>0);
-var p=this.head;
-var i=0;
-while(p){
-copyBuffer(p.data,ret,i);
-i+=p.data.length;
-p=p.next;
-}
-return ret;
-};
-
-return BufferList;
-}();
-},{"safe-buffer":151}],142:[function(require,module,exports){
-'use strict';
-
-
-
-var processNextTick=require('process-nextick-args');
-
-
-
-function destroy(err,cb){
-var _this=this;
-
-var readableDestroyed=this._readableState&&this._readableState.destroyed;
-var writableDestroyed=this._writableState&&this._writableState.destroyed;
-
-if(readableDestroyed||writableDestroyed){
-if(cb){
-cb(err);
-}else if(err&&(!this._writableState||!this._writableState.errorEmitted)){
-processNextTick(emitErrorNT,this,err);
-}
-return;
-}
-
-
-
-
-if(this._readableState){
-this._readableState.destroyed=true;
-}
-
-
-if(this._writableState){
-this._writableState.destroyed=true;
-}
-
-this._destroy(err||null,function(err){
-if(!cb&&err){
-processNextTick(emitErrorNT,_this,err);
-if(_this._writableState){
-_this._writableState.errorEmitted=true;
-}
-}else if(cb){
-cb(err);
-}
-});
-}
-
-function undestroy(){
-if(this._readableState){
-this._readableState.destroyed=false;
-this._readableState.reading=false;
-this._readableState.ended=false;
-this._readableState.endEmitted=false;
-}
-
-if(this._writableState){
-this._writableState.destroyed=false;
-this._writableState.ended=false;
-this._writableState.ending=false;
-this._writableState.finished=false;
-this._writableState.errorEmitted=false;
-}
-}
-
-function emitErrorNT(self,err){
-self.emit('error',err);
-}
-
-module.exports={
-destroy:destroy,
-undestroy:undestroy};
-
-},{"process-nextick-args":130}],143:[function(require,module,exports){
-module.exports=require('events').EventEmitter;
-
-},{"events":94}],144:[function(require,module,exports){
-'use strict';
-
-var Buffer=require('safe-buffer').Buffer;
-
-var isEncoding=Buffer.isEncoding||function(encoding){
-encoding=''+encoding;
-switch(encoding&&encoding.toLowerCase()){
-case'hex':case'utf8':case'utf-8':case'ascii':case'binary':case'base64':case'ucs2':case'ucs-2':case'utf16le':case'utf-16le':case'raw':
-return true;
-default:
-return false;}
-
-};
-
-function _normalizeEncoding(enc){
-if(!enc)return'utf8';
-var retried;
-while(true){
-switch(enc){
-case'utf8':
-case'utf-8':
-return'utf8';
-case'ucs2':
-case'ucs-2':
-case'utf16le':
-case'utf-16le':
-return'utf16le';
-case'latin1':
-case'binary':
-return'latin1';
-case'base64':
-case'ascii':
-case'hex':
-return enc;
-default:
-if(retried)return;
-enc=(''+enc).toLowerCase();
-retried=true;}
-
-}
-};
-
-
-
-function normalizeEncoding(enc){
-var nenc=_normalizeEncoding(enc);
-if(typeof nenc!=='string'&&(Buffer.isEncoding===isEncoding||!isEncoding(enc)))throw new Error('Unknown encoding: '+enc);
-return nenc||enc;
-}
-
-
-
-
-exports.StringDecoder=StringDecoder;
-function StringDecoder(encoding){
-this.encoding=normalizeEncoding(encoding);
-var nb;
-switch(this.encoding){
-case'utf16le':
-this.text=utf16Text;
-this.end=utf16End;
-nb=4;
-break;
-case'utf8':
-this.fillLast=utf8FillLast;
-nb=4;
-break;
-case'base64':
-this.text=base64Text;
-this.end=base64End;
-nb=3;
-break;
-default:
-this.write=simpleWrite;
-this.end=simpleEnd;
-return;}
-
-this.lastNeed=0;
-this.lastTotal=0;
-this.lastChar=Buffer.allocUnsafe(nb);
-}
-
-StringDecoder.prototype.write=function(buf){
-if(buf.length===0)return'';
-var r;
-var i;
-if(this.lastNeed){
-r=this.fillLast(buf);
-if(r===undefined)return'';
-i=this.lastNeed;
-this.lastNeed=0;
-}else{
-i=0;
-}
-if(i<buf.length)return r?r+this.text(buf,i):this.text(buf,i);
-return r||'';
-};
-
-StringDecoder.prototype.end=utf8End;
-
-
-StringDecoder.prototype.text=utf8Text;
-
-
-StringDecoder.prototype.fillLast=function(buf){
-if(this.lastNeed<=buf.length){
-buf.copy(this.lastChar,this.lastTotal-this.lastNeed,0,this.lastNeed);
-return this.lastChar.toString(this.encoding,0,this.lastTotal);
-}
-buf.copy(this.lastChar,this.lastTotal-this.lastNeed,0,buf.length);
-this.lastNeed-=buf.length;
-};
-
-
-
-function utf8CheckByte(byte){
-if(byte<=0x7F)return 0;else if(byte>>5===0x06)return 2;else if(byte>>4===0x0E)return 3;else if(byte>>3===0x1E)return 4;
-return-1;
-}
-
-
-
-
-function utf8CheckIncomplete(self,buf,i){
-var j=buf.length-1;
-if(j<i)return 0;
-var nb=utf8CheckByte(buf[j]);
-if(nb>=0){
-if(nb>0)self.lastNeed=nb-1;
-return nb;
-}
-if(--j<i)return 0;
-nb=utf8CheckByte(buf[j]);
-if(nb>=0){
-if(nb>0)self.lastNeed=nb-2;
-return nb;
-}
-if(--j<i)return 0;
-nb=utf8CheckByte(buf[j]);
-if(nb>=0){
-if(nb>0){
-if(nb===2)nb=0;else self.lastNeed=nb-3;
-}
-return nb;
-}
-return 0;
-}
-
-
-
-
-
-
-
-
-
-function utf8CheckExtraBytes(self,buf,p){
-if((buf[0]&0xC0)!==0x80){
-self.lastNeed=0;
-return'\ufffd'.repeat(p);
-}
-if(self.lastNeed>1&&buf.length>1){
-if((buf[1]&0xC0)!==0x80){
-self.lastNeed=1;
-return'\ufffd'.repeat(p+1);
-}
-if(self.lastNeed>2&&buf.length>2){
-if((buf[2]&0xC0)!==0x80){
-self.lastNeed=2;
-return'\ufffd'.repeat(p+2);
-}
-}
-}
-}
-
-
-function utf8FillLast(buf){
-var p=this.lastTotal-this.lastNeed;
-var r=utf8CheckExtraBytes(this,buf,p);
-if(r!==undefined)return r;
-if(this.lastNeed<=buf.length){
-buf.copy(this.lastChar,p,0,this.lastNeed);
-return this.lastChar.toString(this.encoding,0,this.lastTotal);
-}
-buf.copy(this.lastChar,p,0,buf.length);
-this.lastNeed-=buf.length;
-}
-
-
-
-
-function utf8Text(buf,i){
-var total=utf8CheckIncomplete(this,buf,i);
-if(!this.lastNeed)return buf.toString('utf8',i);
-this.lastTotal=total;
-var end=buf.length-(total-this.lastNeed);
-buf.copy(this.lastChar,0,end);
-return buf.toString('utf8',i,end);
-}
-
-
-
-function utf8End(buf){
-var r=buf&&buf.length?this.write(buf):'';
-if(this.lastNeed)return r+'\ufffd'.repeat(this.lastTotal-this.lastNeed);
-return r;
-}
-
-
-
-
-
-function utf16Text(buf,i){
-if((buf.length-i)%2===0){
-var r=buf.toString('utf16le',i);
-if(r){
-var c=r.charCodeAt(r.length-1);
-if(c>=0xD800&&c<=0xDBFF){
-this.lastNeed=2;
-this.lastTotal=4;
-this.lastChar[0]=buf[buf.length-2];
-this.lastChar[1]=buf[buf.length-1];
-return r.slice(0,-1);
-}
-}
-return r;
-}
-this.lastNeed=1;
-this.lastTotal=2;
-this.lastChar[0]=buf[buf.length-1];
-return buf.toString('utf16le',i,buf.length-1);
-}
-
-
-
-function utf16End(buf){
-var r=buf&&buf.length?this.write(buf):'';
-if(this.lastNeed){
-var end=this.lastTotal-this.lastNeed;
-return r+this.lastChar.toString('utf16le',0,end);
-}
-return r;
-}
-
-function base64Text(buf,i){
-var n=(buf.length-i)%3;
-if(n===0)return buf.toString('base64',i);
-this.lastNeed=3-n;
-this.lastTotal=3;
-if(n===1){
-this.lastChar[0]=buf[buf.length-1];
-}else{
-this.lastChar[0]=buf[buf.length-2];
-this.lastChar[1]=buf[buf.length-1];
-}
-return buf.toString('base64',i,buf.length-n);
-}
-
-function base64End(buf){
-var r=buf&&buf.length?this.write(buf):'';
-if(this.lastNeed)return r+this.lastChar.toString('base64',0,3-this.lastNeed);
-return r;
-}
-
-
-function simpleWrite(buf){
-return buf.toString(this.encoding);
-}
-
-function simpleEnd(buf){
-return buf&&buf.length?this.write(buf):'';
-}
-},{"safe-buffer":151}],145:[function(require,module,exports){
-module.exports=require('./readable').PassThrough;
-
-},{"./readable":146}],146:[function(require,module,exports){
-exports=module.exports=require('./lib/_stream_readable.js');
-exports.Stream=exports;
-exports.Readable=exports;
-exports.Writable=require('./lib/_stream_writable.js');
-exports.Duplex=require('./lib/_stream_duplex.js');
-exports.Transform=require('./lib/_stream_transform.js');
-exports.PassThrough=require('./lib/_stream_passthrough.js');
-
-},{"./lib/_stream_duplex.js":136,"./lib/_stream_passthrough.js":137,"./lib/_stream_readable.js":138,"./lib/_stream_transform.js":139,"./lib/_stream_writable.js":140}],147:[function(require,module,exports){
-module.exports=require('./readable').Transform;
-
-},{"./readable":146}],148:[function(require,module,exports){
-module.exports=require('./lib/_stream_writable.js');
-
-},{"./lib/_stream_writable.js":140}],149:[function(require,module,exports){
+},{"./decode":138,"./encode":139}],141:[function(require,module,exports){
 var URL=require('url').URL;
 
 
@@ -57228,13 +57398,13 @@
 
 module.exports=Robots;
 
-},{"url":"url"}],150:[function(require,module,exports){
+},{"url":"url"}],142:[function(require,module,exports){
 var Robots=require('./Robots');
 
 module.exports=function(url,contents){
 return new Robots(url,contents);
 };
-},{"./Robots":149}],151:[function(require,module,exports){
+},{"./Robots":141}],143:[function(require,module,exports){
 
 var buffer=require('buffer');
 var Buffer=buffer.Buffer;
@@ -57298,7 +57468,7 @@
 return buffer.SlowBuffer(size);
 };
 
-},{"buffer":89}],152:[function(require,module,exports){
+},{"buffer":94}],144:[function(require,module,exports){
 (function(process){
 exports=module.exports=SemVer;
 
@@ -58505,7 +58675,7 @@
 }
 
 }).call(this,require('_process'));
-},{"_process":131}],153:[function(require,module,exports){
+},{"_process":137}],145:[function(require,module,exports){
 (function(Buffer){
 'use strict';
 
@@ -58747,7 +58917,7 @@
 
 
 }).call(this,require("buffer").Buffer);
-},{"buffer":89,"jpeg-js":111}],154:[function(require,module,exports){
+},{"buffer":94,"jpeg-js":117}],146:[function(require,module,exports){
 'use strict';
 
 const frame=require('./frame');
@@ -58817,7 +58987,7 @@
 });
 };
 
-},{"./frame":153,"./speed-index":155}],155:[function(require,module,exports){
+},{"./frame":145,"./speed-index":147}],147:[function(require,module,exports){
 'use strict';
 
 const imageSSIM=require('image-ssim');
@@ -59089,7 +59259,7 @@
 calculateSpeedIndexes};
 
 
-},{"image-ssim":98}],156:[function(require,module,exports){
+},{"image-ssim":104}],148:[function(require,module,exports){
 
 
 
@@ -59218,7 +59388,2517 @@
 return dest;
 };
 
-},{"events":94,"inherits":99,"readable-stream/duplex.js":135,"readable-stream/passthrough.js":145,"readable-stream/readable.js":146,"readable-stream/transform.js":147,"readable-stream/writable.js":148}],157:[function(require,module,exports){
+},{"events":100,"inherits":105,"readable-stream/duplex.js":149,"readable-stream/passthrough.js":158,"readable-stream/readable.js":159,"readable-stream/transform.js":160,"readable-stream/writable.js":161}],149:[function(require,module,exports){
+module.exports=require('./lib/_stream_duplex.js');
+
+},{"./lib/_stream_duplex.js":150}],150:[function(require,module,exports){
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+'use strict';
+
+
+
+var processNextTick=require('process-nextick-args');
+
+
+
+var objectKeys=Object.keys||function(obj){
+var keys=[];
+for(var key in obj){
+keys.push(key);
+}return keys;
+};
+
+
+module.exports=Duplex;
+
+
+var util=require('core-util-is');
+util.inherits=require('inherits');
+
+
+var Readable=require('./_stream_readable');
+var Writable=require('./_stream_writable');
+
+util.inherits(Duplex,Readable);
+
+var keys=objectKeys(Writable.prototype);
+for(var v=0;v<keys.length;v++){
+var method=keys[v];
+if(!Duplex.prototype[method])Duplex.prototype[method]=Writable.prototype[method];
+}
+
+function Duplex(options){
+if(!(this instanceof Duplex))return new Duplex(options);
+
+Readable.call(this,options);
+Writable.call(this,options);
+
+if(options&&options.readable===false)this.readable=false;
+
+if(options&&options.writable===false)this.writable=false;
+
+this.allowHalfOpen=true;
+if(options&&options.allowHalfOpen===false)this.allowHalfOpen=false;
+
+this.once('end',onend);
+}
+
+
+function onend(){
+
+
+if(this.allowHalfOpen||this._writableState.ended)return;
+
+
+
+processNextTick(onEndNT,this);
+}
+
+function onEndNT(self){
+self.end();
+}
+
+Object.defineProperty(Duplex.prototype,'destroyed',{
+get:function(){
+if(this._readableState===undefined||this._writableState===undefined){
+return false;
+}
+return this._readableState.destroyed&&this._writableState.destroyed;
+},
+set:function(value){
+
+
+if(this._readableState===undefined||this._writableState===undefined){
+return;
+}
+
+
+
+this._readableState.destroyed=value;
+this._writableState.destroyed=value;
+}});
+
+
+Duplex.prototype._destroy=function(err,cb){
+this.push(null);
+this.end();
+
+processNextTick(cb,err);
+};
+
+function forEach(xs,f){
+for(var i=0,l=xs.length;i<l;i++){
+f(xs[i],i);
+}
+}
+},{"./_stream_readable":152,"./_stream_writable":154,"core-util-is":95,"inherits":105,"process-nextick-args":136}],151:[function(require,module,exports){
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+'use strict';
+
+module.exports=PassThrough;
+
+var Transform=require('./_stream_transform');
+
+
+var util=require('core-util-is');
+util.inherits=require('inherits');
+
+
+util.inherits(PassThrough,Transform);
+
+function PassThrough(options){
+if(!(this instanceof PassThrough))return new PassThrough(options);
+
+Transform.call(this,options);
+}
+
+PassThrough.prototype._transform=function(chunk,encoding,cb){
+cb(null,chunk);
+};
+},{"./_stream_transform":153,"core-util-is":95,"inherits":105}],152:[function(require,module,exports){
+(function(process,global){
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+'use strict';
+
+
+
+var processNextTick=require('process-nextick-args');
+
+
+module.exports=Readable;
+
+
+var isArray=require('isarray');
+
+
+
+var Duplex;
+
+
+Readable.ReadableState=ReadableState;
+
+
+var EE=require('events').EventEmitter;
+
+var EElistenerCount=function(emitter,type){
+return emitter.listeners(type).length;
+};
+
+
+
+var Stream=require('./internal/streams/stream');
+
+
+
+
+
+var Buffer=require('safe-buffer').Buffer;
+var OurUint8Array=global.Uint8Array||function(){};
+function _uint8ArrayToBuffer(chunk){
+return Buffer.from(chunk);
+}
+function _isUint8Array(obj){
+return Buffer.isBuffer(obj)||obj instanceof OurUint8Array;
+}
+
+
+
+var util=require('core-util-is');
+util.inherits=require('inherits');
+
+
+
+var debugUtil=require('util');
+var debug=void 0;
+if(debugUtil&&debugUtil.debuglog){
+debug=debugUtil.debuglog('stream');
+}else{
+debug=function(){};
+}
+
+
+var BufferList=require('./internal/streams/BufferList');
+var destroyImpl=require('./internal/streams/destroy');
+var StringDecoder;
+
+util.inherits(Readable,Stream);
+
+var kProxyEvents=['error','close','destroy','pause','resume'];
+
+function prependListener(emitter,event,fn){
+
+
+if(typeof emitter.prependListener==='function'){
+return emitter.prependListener(event,fn);
+}else{
+
+
+
+
+if(!emitter._events||!emitter._events[event])emitter.on(event,fn);else if(isArray(emitter._events[event]))emitter._events[event].unshift(fn);else emitter._events[event]=[fn,emitter._events[event]];
+}
+}
+
+function ReadableState(options,stream){
+Duplex=Duplex||require('./_stream_duplex');
+
+options=options||{};
+
+
+
+this.objectMode=!!options.objectMode;
+
+if(stream instanceof Duplex)this.objectMode=this.objectMode||!!options.readableObjectMode;
+
+
+
+var hwm=options.highWaterMark;
+var defaultHwm=this.objectMode?16:16*1024;
+this.highWaterMark=hwm||hwm===0?hwm:defaultHwm;
+
+
+this.highWaterMark=Math.floor(this.highWaterMark);
+
+
+
+
+this.buffer=new BufferList();
+this.length=0;
+this.pipes=null;
+this.pipesCount=0;
+this.flowing=null;
+this.ended=false;
+this.endEmitted=false;
+this.reading=false;
+
+
+
+
+
+this.sync=true;
+
+
+
+this.needReadable=false;
+this.emittedReadable=false;
+this.readableListening=false;
+this.resumeScheduled=false;
+
+
+this.destroyed=false;
+
+
+
+
+this.defaultEncoding=options.defaultEncoding||'utf8';
+
+
+this.awaitDrain=0;
+
+
+this.readingMore=false;
+
+this.decoder=null;
+this.encoding=null;
+if(options.encoding){
+if(!StringDecoder)StringDecoder=require('string_decoder/').StringDecoder;
+this.decoder=new StringDecoder(options.encoding);
+this.encoding=options.encoding;
+}
+}
+
+function Readable(options){
+Duplex=Duplex||require('./_stream_duplex');
+
+if(!(this instanceof Readable))return new Readable(options);
+
+this._readableState=new ReadableState(options,this);
+
+
+this.readable=true;
+
+if(options){
+if(typeof options.read==='function')this._read=options.read;
+
+if(typeof options.destroy==='function')this._destroy=options.destroy;
+}
+
+Stream.call(this);
+}
+
+Object.defineProperty(Readable.prototype,'destroyed',{
+get:function(){
+if(this._readableState===undefined){
+return false;
+}
+return this._readableState.destroyed;
+},
+set:function(value){
+
+
+if(!this._readableState){
+return;
+}
+
+
+
+this._readableState.destroyed=value;
+}});
+
+
+Readable.prototype.destroy=destroyImpl.destroy;
+Readable.prototype._undestroy=destroyImpl.undestroy;
+Readable.prototype._destroy=function(err,cb){
+this.push(null);
+cb(err);
+};
+
+
+
+
+
+Readable.prototype.push=function(chunk,encoding){
+var state=this._readableState;
+var skipChunkCheck;
+
+if(!state.objectMode){
+if(typeof chunk==='string'){
+encoding=encoding||state.defaultEncoding;
+if(encoding!==state.encoding){
+chunk=Buffer.from(chunk,encoding);
+encoding='';
+}
+skipChunkCheck=true;
+}
+}else{
+skipChunkCheck=true;
+}
+
+return readableAddChunk(this,chunk,encoding,false,skipChunkCheck);
+};
+
+
+Readable.prototype.unshift=function(chunk){
+return readableAddChunk(this,chunk,null,true,false);
+};
+
+function readableAddChunk(stream,chunk,encoding,addToFront,skipChunkCheck){
+var state=stream._readableState;
+if(chunk===null){
+state.reading=false;
+onEofChunk(stream,state);
+}else{
+var er;
+if(!skipChunkCheck)er=chunkInvalid(state,chunk);
+if(er){
+stream.emit('error',er);
+}else if(state.objectMode||chunk&&chunk.length>0){
+if(typeof chunk!=='string'&&!state.objectMode&&Object.getPrototypeOf(chunk)!==Buffer.prototype){
+chunk=_uint8ArrayToBuffer(chunk);
+}
+
+if(addToFront){
+if(state.endEmitted)stream.emit('error',new Error('stream.unshift() after end event'));else addChunk(stream,state,chunk,true);
+}else if(state.ended){
+stream.emit('error',new Error('stream.push() after EOF'));
+}else{
+state.reading=false;
+if(state.decoder&&!encoding){
+chunk=state.decoder.write(chunk);
+if(state.objectMode||chunk.length!==0)addChunk(stream,state,chunk,false);else maybeReadMore(stream,state);
+}else{
+addChunk(stream,state,chunk,false);
+}
+}
+}else if(!addToFront){
+state.reading=false;
+}
+}
+
+return needMoreData(state);
+}
+
+function addChunk(stream,state,chunk,addToFront){
+if(state.flowing&&state.length===0&&!state.sync){
+stream.emit('data',chunk);
+stream.read(0);
+}else{
+
+state.length+=state.objectMode?1:chunk.length;
+if(addToFront)state.buffer.unshift(chunk);else state.buffer.push(chunk);
+
+if(state.needReadable)emitReadable(stream);
+}
+maybeReadMore(stream,state);
+}
+
+function chunkInvalid(state,chunk){
+var er;
+if(!_isUint8Array(chunk)&&typeof chunk!=='string'&&chunk!==undefined&&!state.objectMode){
+er=new TypeError('Invalid non-string/buffer chunk');
+}
+return er;
+}
+
+
+
+
+
+
+
+
+function needMoreData(state){
+return!state.ended&&(state.needReadable||state.length<state.highWaterMark||state.length===0);
+}
+
+Readable.prototype.isPaused=function(){
+return this._readableState.flowing===false;
+};
+
+
+Readable.prototype.setEncoding=function(enc){
+if(!StringDecoder)StringDecoder=require('string_decoder/').StringDecoder;
+this._readableState.decoder=new StringDecoder(enc);
+this._readableState.encoding=enc;
+return this;
+};
+
+
+var MAX_HWM=0x800000;
+function computeNewHighWaterMark(n){
+if(n>=MAX_HWM){
+n=MAX_HWM;
+}else{
+
+
+n--;
+n|=n>>>1;
+n|=n>>>2;
+n|=n>>>4;
+n|=n>>>8;
+n|=n>>>16;
+n++;
+}
+return n;
+}
+
+
+
+function howMuchToRead(n,state){
+if(n<=0||state.length===0&&state.ended)return 0;
+if(state.objectMode)return 1;
+if(n!==n){
+
+if(state.flowing&&state.length)return state.buffer.head.data.length;else return state.length;
+}
+
+if(n>state.highWaterMark)state.highWaterMark=computeNewHighWaterMark(n);
+if(n<=state.length)return n;
+
+if(!state.ended){
+state.needReadable=true;
+return 0;
+}
+return state.length;
+}
+
+
+Readable.prototype.read=function(n){
+debug('read',n);
+n=parseInt(n,10);
+var state=this._readableState;
+var nOrig=n;
+
+if(n!==0)state.emittedReadable=false;
+
+
+
+
+if(n===0&&state.needReadable&&(state.length>=state.highWaterMark||state.ended)){
+debug('read: emitReadable',state.length,state.ended);
+if(state.length===0&&state.ended)endReadable(this);else emitReadable(this);
+return null;
+}
+
+n=howMuchToRead(n,state);
+
+
+if(n===0&&state.ended){
+if(state.length===0)endReadable(this);
+return null;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+var doRead=state.needReadable;
+debug('need readable',doRead);
+
+
+if(state.length===0||state.length-n<state.highWaterMark){
+doRead=true;
+debug('length less than watermark',doRead);
+}
+
+
+
+if(state.ended||state.reading){
+doRead=false;
+debug('reading or ended',doRead);
+}else if(doRead){
+debug('do read');
+state.reading=true;
+state.sync=true;
+
+if(state.length===0)state.needReadable=true;
+
+this._read(state.highWaterMark);
+state.sync=false;
+
+
+if(!state.reading)n=howMuchToRead(nOrig,state);
+}
+
+var ret;
+if(n>0)ret=fromList(n,state);else ret=null;
+
+if(ret===null){
+state.needReadable=true;
+n=0;
+}else{
+state.length-=n;
+}
+
+if(state.length===0){
+
+
+if(!state.ended)state.needReadable=true;
+
+
+if(nOrig!==n&&state.ended)endReadable(this);
+}
+
+if(ret!==null)this.emit('data',ret);
+
+return ret;
+};
+
+function onEofChunk(stream,state){
+if(state.ended)return;
+if(state.decoder){
+var chunk=state.decoder.end();
+if(chunk&&chunk.length){
+state.buffer.push(chunk);
+state.length+=state.objectMode?1:chunk.length;
+}
+}
+state.ended=true;
+
+
+emitReadable(stream);
+}
+
+
+
+
+function emitReadable(stream){
+var state=stream._readableState;
+state.needReadable=false;
+if(!state.emittedReadable){
+debug('emitReadable',state.flowing);
+state.emittedReadable=true;
+if(state.sync)processNextTick(emitReadable_,stream);else emitReadable_(stream);
+}
+}
+
+function emitReadable_(stream){
+debug('emit readable');
+stream.emit('readable');
+flow(stream);
+}
+
+
+
+
+
+
+
+function maybeReadMore(stream,state){
+if(!state.readingMore){
+state.readingMore=true;
+processNextTick(maybeReadMore_,stream,state);
+}
+}
+
+function maybeReadMore_(stream,state){
+var len=state.length;
+while(!state.reading&&!state.flowing&&!state.ended&&state.length<state.highWaterMark){
+debug('maybeReadMore read 0');
+stream.read(0);
+if(len===state.length)
+
+break;else len=state.length;
+}
+state.readingMore=false;
+}
+
+
+
+
+
+Readable.prototype._read=function(n){
+this.emit('error',new Error('_read() is not implemented'));
+};
+
+Readable.prototype.pipe=function(dest,pipeOpts){
+var src=this;
+var state=this._readableState;
+
+switch(state.pipesCount){
+case 0:
+state.pipes=dest;
+break;
+case 1:
+state.pipes=[state.pipes,dest];
+break;
+default:
+state.pipes.push(dest);
+break;}
+
+state.pipesCount+=1;
+debug('pipe count=%d opts=%j',state.pipesCount,pipeOpts);
+
+var doEnd=(!pipeOpts||pipeOpts.end!==false)&&dest!==process.stdout&&dest!==process.stderr;
+
+var endFn=doEnd?onend:unpipe;
+if(state.endEmitted)processNextTick(endFn);else src.once('end',endFn);
+
+dest.on('unpipe',onunpipe);
+function onunpipe(readable,unpipeInfo){
+debug('onunpipe');
+if(readable===src){
+if(unpipeInfo&&unpipeInfo.hasUnpiped===false){
+unpipeInfo.hasUnpiped=true;
+cleanup();
+}
+}
+}
+
+function onend(){
+debug('onend');
+dest.end();
+}
+
+
+
+
+
+var ondrain=pipeOnDrain(src);
+dest.on('drain',ondrain);
+
+var cleanedUp=false;
+function cleanup(){
+debug('cleanup');
+
+dest.removeListener('close',onclose);
+dest.removeListener('finish',onfinish);
+dest.removeListener('drain',ondrain);
+dest.removeListener('error',onerror);
+dest.removeListener('unpipe',onunpipe);
+src.removeListener('end',onend);
+src.removeListener('end',unpipe);
+src.removeListener('data',ondata);
+
+cleanedUp=true;
+
+
+
+
+
+
+if(state.awaitDrain&&(!dest._writableState||dest._writableState.needDrain))ondrain();
+}
+
+
+
+
+
+var increasedAwaitDrain=false;
+src.on('data',ondata);
+function ondata(chunk){
+debug('ondata');
+increasedAwaitDrain=false;
+var ret=dest.write(chunk);
+if(false===ret&&!increasedAwaitDrain){
+
+
+
+
+if((state.pipesCount===1&&state.pipes===dest||state.pipesCount>1&&indexOf(state.pipes,dest)!==-1)&&!cleanedUp){
+debug('false write response, pause',src._readableState.awaitDrain);
+src._readableState.awaitDrain++;
+increasedAwaitDrain=true;
+}
+src.pause();
+}
+}
+
+
+
+function onerror(er){
+debug('onerror',er);
+unpipe();
+dest.removeListener('error',onerror);
+if(EElistenerCount(dest,'error')===0)dest.emit('error',er);
+}
+
+
+prependListener(dest,'error',onerror);
+
+
+function onclose(){
+dest.removeListener('finish',onfinish);
+unpipe();
+}
+dest.once('close',onclose);
+function onfinish(){
+debug('onfinish');
+dest.removeListener('close',onclose);
+unpipe();
+}
+dest.once('finish',onfinish);
+
+function unpipe(){
+debug('unpipe');
+src.unpipe(dest);
+}
+
+
+dest.emit('pipe',src);
+
+
+if(!state.flowing){
+debug('pipe resume');
+src.resume();
+}
+
+return dest;
+};
+
+function pipeOnDrain(src){
+return function(){
+var state=src._readableState;
+debug('pipeOnDrain',state.awaitDrain);
+if(state.awaitDrain)state.awaitDrain--;
+if(state.awaitDrain===0&&EElistenerCount(src,'data')){
+state.flowing=true;
+flow(src);
+}
+};
+}
+
+Readable.prototype.unpipe=function(dest){
+var state=this._readableState;
+var unpipeInfo={hasUnpiped:false};
+
+
+if(state.pipesCount===0)return this;
+
+
+if(state.pipesCount===1){
+
+if(dest&&dest!==state.pipes)return this;
+
+if(!dest)dest=state.pipes;
+
+
+state.pipes=null;
+state.pipesCount=0;
+state.flowing=false;
+if(dest)dest.emit('unpipe',this,unpipeInfo);
+return this;
+}
+
+
+
+if(!dest){
+
+var dests=state.pipes;
+var len=state.pipesCount;
+state.pipes=null;
+state.pipesCount=0;
+state.flowing=false;
+
+for(var i=0;i<len;i++){
+dests[i].emit('unpipe',this,unpipeInfo);
+}return this;
+}
+
+
+var index=indexOf(state.pipes,dest);
+if(index===-1)return this;
+
+state.pipes.splice(index,1);
+state.pipesCount-=1;
+if(state.pipesCount===1)state.pipes=state.pipes[0];
+
+dest.emit('unpipe',this,unpipeInfo);
+
+return this;
+};
+
+
+
+Readable.prototype.on=function(ev,fn){
+var res=Stream.prototype.on.call(this,ev,fn);
+
+if(ev==='data'){
+
+if(this._readableState.flowing!==false)this.resume();
+}else if(ev==='readable'){
+var state=this._readableState;
+if(!state.endEmitted&&!state.readableListening){
+state.readableListening=state.needReadable=true;
+state.emittedReadable=false;
+if(!state.reading){
+processNextTick(nReadingNextTick,this);
+}else if(state.length){
+emitReadable(this);
+}
+}
+}
+
+return res;
+};
+Readable.prototype.addListener=Readable.prototype.on;
+
+function nReadingNextTick(self){
+debug('readable nexttick read 0');
+self.read(0);
+}
+
+
+
+Readable.prototype.resume=function(){
+var state=this._readableState;
+if(!state.flowing){
+debug('resume');
+state.flowing=true;
+resume(this,state);
+}
+return this;
+};
+
+function resume(stream,state){
+if(!state.resumeScheduled){
+state.resumeScheduled=true;
+processNextTick(resume_,stream,state);
+}
+}
+
+function resume_(stream,state){
+if(!state.reading){
+debug('resume read 0');
+stream.read(0);
+}
+
+state.resumeScheduled=false;
+state.awaitDrain=0;
+stream.emit('resume');
+flow(stream);
+if(state.flowing&&!state.reading)stream.read(0);
+}
+
+Readable.prototype.pause=function(){
+debug('call pause flowing=%j',this._readableState.flowing);
+if(false!==this._readableState.flowing){
+debug('pause');
+this._readableState.flowing=false;
+this.emit('pause');
+}
+return this;
+};
+
+function flow(stream){
+var state=stream._readableState;
+debug('flow',state.flowing);
+while(state.flowing&&stream.read()!==null){}
+}
+
+
+
+
+Readable.prototype.wrap=function(stream){
+var state=this._readableState;
+var paused=false;
+
+var self=this;
+stream.on('end',function(){
+debug('wrapped end');
+if(state.decoder&&!state.ended){
+var chunk=state.decoder.end();
+if(chunk&&chunk.length)self.push(chunk);
+}
+
+self.push(null);
+});
+
+stream.on('data',function(chunk){
+debug('wrapped data');
+if(state.decoder)chunk=state.decoder.write(chunk);
+
+
+if(state.objectMode&&(chunk===null||chunk===undefined))return;else if(!state.objectMode&&(!chunk||!chunk.length))return;
+
+var ret=self.push(chunk);
+if(!ret){
+paused=true;
+stream.pause();
+}
+});
+
+
+
+for(var i in stream){
+if(this[i]===undefined&&typeof stream[i]==='function'){
+this[i]=function(method){
+return function(){
+return stream[method].apply(stream,arguments);
+};
+}(i);
+}
+}
+
+
+for(var n=0;n<kProxyEvents.length;n++){
+stream.on(kProxyEvents[n],self.emit.bind(self,kProxyEvents[n]));
+}
+
+
+
+self._read=function(n){
+debug('wrapped _read',n);
+if(paused){
+paused=false;
+stream.resume();
+}
+};
+
+return self;
+};
+
+
+Readable._fromList=fromList;
+
+
+
+
+
+function fromList(n,state){
+
+if(state.length===0)return null;
+
+var ret;
+if(state.objectMode)ret=state.buffer.shift();else if(!n||n>=state.length){
+
+if(state.decoder)ret=state.buffer.join('');else if(state.buffer.length===1)ret=state.buffer.head.data;else ret=state.buffer.concat(state.length);
+state.buffer.clear();
+}else{
+
+ret=fromListPartial(n,state.buffer,state.decoder);
+}
+
+return ret;
+}
+
+
+
+
+function fromListPartial(n,list,hasStrings){
+var ret;
+if(n<list.head.data.length){
+
+ret=list.head.data.slice(0,n);
+list.head.data=list.head.data.slice(n);
+}else if(n===list.head.data.length){
+
+ret=list.shift();
+}else{
+
+ret=hasStrings?copyFromBufferString(n,list):copyFromBuffer(n,list);
+}
+return ret;
+}
+
+
+
+
+
+function copyFromBufferString(n,list){
+var p=list.head;
+var c=1;
+var ret=p.data;
+n-=ret.length;
+while(p=p.next){
+var str=p.data;
+var nb=n>str.length?str.length:n;
+if(nb===str.length)ret+=str;else ret+=str.slice(0,n);
+n-=nb;
+if(n===0){
+if(nb===str.length){
+++c;
+if(p.next)list.head=p.next;else list.head=list.tail=null;
+}else{
+list.head=p;
+p.data=str.slice(nb);
+}
+break;
+}
+++c;
+}
+list.length-=c;
+return ret;
+}
+
+
+
+
+function copyFromBuffer(n,list){
+var ret=Buffer.allocUnsafe(n);
+var p=list.head;
+var c=1;
+p.data.copy(ret);
+n-=p.data.length;
+while(p=p.next){
+var buf=p.data;
+var nb=n>buf.length?buf.length:n;
+buf.copy(ret,ret.length-n,0,nb);
+n-=nb;
+if(n===0){
+if(nb===buf.length){
+++c;
+if(p.next)list.head=p.next;else list.head=list.tail=null;
+}else{
+list.head=p;
+p.data=buf.slice(nb);
+}
+break;
+}
+++c;
+}
+list.length-=c;
+return ret;
+}
+
+function endReadable(stream){
+var state=stream._readableState;
+
+
+
+if(state.length>0)throw new Error('"endReadable()" called on non-empty stream');
+
+if(!state.endEmitted){
+state.ended=true;
+processNextTick(endReadableNT,state,stream);
+}
+}
+
+function endReadableNT(state,stream){
+
+if(!state.endEmitted&&state.length===0){
+state.endEmitted=true;
+stream.readable=false;
+stream.emit('end');
+}
+}
+
+function forEach(xs,f){
+for(var i=0,l=xs.length;i<l;i++){
+f(xs[i],i);
+}
+}
+
+function indexOf(xs,x){
+for(var i=0,l=xs.length;i<l;i++){
+if(xs[i]===x)return i;
+}
+return-1;
+}
+}).call(this,require('_process'),typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
+},{"./_stream_duplex":150,"./internal/streams/BufferList":155,"./internal/streams/destroy":156,"./internal/streams/stream":157,"_process":137,"core-util-is":95,"events":100,"inherits":105,"isarray":116,"process-nextick-args":136,"safe-buffer":143,"string_decoder/":162,"util":90}],153:[function(require,module,exports){
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+'use strict';
+
+module.exports=Transform;
+
+var Duplex=require('./_stream_duplex');
+
+
+var util=require('core-util-is');
+util.inherits=require('inherits');
+
+
+util.inherits(Transform,Duplex);
+
+function TransformState(stream){
+this.afterTransform=function(er,data){
+return afterTransform(stream,er,data);
+};
+
+this.needTransform=false;
+this.transforming=false;
+this.writecb=null;
+this.writechunk=null;
+this.writeencoding=null;
+}
+
+function afterTransform(stream,er,data){
+var ts=stream._transformState;
+ts.transforming=false;
+
+var cb=ts.writecb;
+
+if(!cb){
+return stream.emit('error',new Error('write callback called multiple times'));
+}
+
+ts.writechunk=null;
+ts.writecb=null;
+
+if(data!==null&&data!==undefined)stream.push(data);
+
+cb(er);
+
+var rs=stream._readableState;
+rs.reading=false;
+if(rs.needReadable||rs.length<rs.highWaterMark){
+stream._read(rs.highWaterMark);
+}
+}
+
+function Transform(options){
+if(!(this instanceof Transform))return new Transform(options);
+
+Duplex.call(this,options);
+
+this._transformState=new TransformState(this);
+
+var stream=this;
+
+
+this._readableState.needReadable=true;
+
+
+
+
+this._readableState.sync=false;
+
+if(options){
+if(typeof options.transform==='function')this._transform=options.transform;
+
+if(typeof options.flush==='function')this._flush=options.flush;
+}
+
+
+this.once('prefinish',function(){
+if(typeof this._flush==='function')this._flush(function(er,data){
+done(stream,er,data);
+});else done(stream);
+});
+}
+
+Transform.prototype.push=function(chunk,encoding){
+this._transformState.needTransform=false;
+return Duplex.prototype.push.call(this,chunk,encoding);
+};
+
+
+
+
+
+
+
+
+
+
+
+Transform.prototype._transform=function(chunk,encoding,cb){
+throw new Error('_transform() is not implemented');
+};
+
+Transform.prototype._write=function(chunk,encoding,cb){
+var ts=this._transformState;
+ts.writecb=cb;
+ts.writechunk=chunk;
+ts.writeencoding=encoding;
+if(!ts.transforming){
+var rs=this._readableState;
+if(ts.needTransform||rs.needReadable||rs.length<rs.highWaterMark)this._read(rs.highWaterMark);
+}
+};
+
+
+
+
+Transform.prototype._read=function(n){
+var ts=this._transformState;
+
+if(ts.writechunk!==null&&ts.writecb&&!ts.transforming){
+ts.transforming=true;
+this._transform(ts.writechunk,ts.writeencoding,ts.afterTransform);
+}else{
+
+
+ts.needTransform=true;
+}
+};
+
+Transform.prototype._destroy=function(err,cb){
+var _this=this;
+
+Duplex.prototype._destroy.call(this,err,function(err2){
+cb(err2);
+_this.emit('close');
+});
+};
+
+function done(stream,er,data){
+if(er)return stream.emit('error',er);
+
+if(data!==null&&data!==undefined)stream.push(data);
+
+
+
+var ws=stream._writableState;
+var ts=stream._transformState;
+
+if(ws.length)throw new Error('Calling transform done when ws.length != 0');
+
+if(ts.transforming)throw new Error('Calling transform done when still transforming');
+
+return stream.push(null);
+}
+},{"./_stream_duplex":150,"core-util-is":95,"inherits":105}],154:[function(require,module,exports){
+(function(process,global,setImmediate){
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+'use strict';
+
+
+
+var processNextTick=require('process-nextick-args');
+
+
+module.exports=Writable;
+
+
+function WriteReq(chunk,encoding,cb){
+this.chunk=chunk;
+this.encoding=encoding;
+this.callback=cb;
+this.next=null;
+}
+
+
+
+function CorkedRequest(state){
+var _this=this;
+
+this.next=null;
+this.entry=null;
+this.finish=function(){
+onCorkedFinish(_this,state);
+};
+}
+
+
+
+var asyncWrite=!process.browser&&['v0.10','v0.9.'].indexOf(process.version.slice(0,5))>-1?setImmediate:processNextTick;
+
+
+
+var Duplex;
+
+
+Writable.WritableState=WritableState;
+
+
+var util=require('core-util-is');
+util.inherits=require('inherits');
+
+
+
+var internalUtil={
+deprecate:require('util-deprecate')};
+
+
+
+
+var Stream=require('./internal/streams/stream');
+
+
+
+var Buffer=require('safe-buffer').Buffer;
+var OurUint8Array=global.Uint8Array||function(){};
+function _uint8ArrayToBuffer(chunk){
+return Buffer.from(chunk);
+}
+function _isUint8Array(obj){
+return Buffer.isBuffer(obj)||obj instanceof OurUint8Array;
+}
+
+
+var destroyImpl=require('./internal/streams/destroy');
+
+util.inherits(Writable,Stream);
+
+function nop(){}
+
+function WritableState(options,stream){
+Duplex=Duplex||require('./_stream_duplex');
+
+options=options||{};
+
+
+
+this.objectMode=!!options.objectMode;
+
+if(stream instanceof Duplex)this.objectMode=this.objectMode||!!options.writableObjectMode;
+
+
+
+
+var hwm=options.highWaterMark;
+var defaultHwm=this.objectMode?16:16*1024;
+this.highWaterMark=hwm||hwm===0?hwm:defaultHwm;
+
+
+this.highWaterMark=Math.floor(this.highWaterMark);
+
+
+this.finalCalled=false;
+
+
+this.needDrain=false;
+
+this.ending=false;
+
+this.ended=false;
+
+this.finished=false;
+
+
+this.destroyed=false;
+
+
+
+
+var noDecode=options.decodeStrings===false;
+this.decodeStrings=!noDecode;
+
+
+
+
+this.defaultEncoding=options.defaultEncoding||'utf8';
+
+
+
+
+this.length=0;
+
+
+this.writing=false;
+
+
+this.corked=0;
+
+
+
+
+
+this.sync=true;
+
+
+
+
+this.bufferProcessing=false;
+
+
+this.onwrite=function(er){
+onwrite(stream,er);
+};
+
+
+this.writecb=null;
+
+
+this.writelen=0;
+
+this.bufferedRequest=null;
+this.lastBufferedRequest=null;
+
+
+
+this.pendingcb=0;
+
+
+
+this.prefinished=false;
+
+
+this.errorEmitted=false;
+
+
+this.bufferedRequestCount=0;
+
+
+
+this.corkedRequestsFree=new CorkedRequest(this);
+}
+
+WritableState.prototype.getBuffer=function getBuffer(){
+var current=this.bufferedRequest;
+var out=[];
+while(current){
+out.push(current);
+current=current.next;
+}
+return out;
+};
+
+(function(){
+try{
+Object.defineProperty(WritableState.prototype,'buffer',{
+get:internalUtil.deprecate(function(){
+return this.getBuffer();
+},'_writableState.buffer is deprecated. Use _writableState.getBuffer '+'instead.','DEP0003')});
+
+}catch(_){}
+})();
+
+
+
+var realHasInstance;
+if(typeof Symbol==='function'&&Symbol.hasInstance&&typeof Function.prototype[Symbol.hasInstance]==='function'){
+realHasInstance=Function.prototype[Symbol.hasInstance];
+Object.defineProperty(Writable,Symbol.hasInstance,{
+value:function(object){
+if(realHasInstance.call(this,object))return true;
+
+return object&&object._writableState instanceof WritableState;
+}});
+
+}else{
+realHasInstance=function(object){
+return object instanceof this;
+};
+}
+
+function Writable(options){
+Duplex=Duplex||require('./_stream_duplex');
+
+
+
+
+
+
+
+
+if(!realHasInstance.call(Writable,this)&&!(this instanceof Duplex)){
+return new Writable(options);
+}
+
+this._writableState=new WritableState(options,this);
+
+
+this.writable=true;
+
+if(options){
+if(typeof options.write==='function')this._write=options.write;
+
+if(typeof options.writev==='function')this._writev=options.writev;
+
+if(typeof options.destroy==='function')this._destroy=options.destroy;
+
+if(typeof options.final==='function')this._final=options.final;
+}
+
+Stream.call(this);
+}
+
+
+Writable.prototype.pipe=function(){
+this.emit('error',new Error('Cannot pipe, not readable'));
+};
+
+function writeAfterEnd(stream,cb){
+var er=new Error('write after end');
+
+stream.emit('error',er);
+processNextTick(cb,er);
+}
+
+
+
+
+function validChunk(stream,state,chunk,cb){
+var valid=true;
+var er=false;
+
+if(chunk===null){
+er=new TypeError('May not write null values to stream');
+}else if(typeof chunk!=='string'&&chunk!==undefined&&!state.objectMode){
+er=new TypeError('Invalid non-string/buffer chunk');
+}
+if(er){
+stream.emit('error',er);
+processNextTick(cb,er);
+valid=false;
+}
+return valid;
+}
+
+Writable.prototype.write=function(chunk,encoding,cb){
+var state=this._writableState;
+var ret=false;
+var isBuf=_isUint8Array(chunk)&&!state.objectMode;
+
+if(isBuf&&!Buffer.isBuffer(chunk)){
+chunk=_uint8ArrayToBuffer(chunk);
+}
+
+if(typeof encoding==='function'){
+cb=encoding;
+encoding=null;
+}
+
+if(isBuf)encoding='buffer';else if(!encoding)encoding=state.defaultEncoding;
+
+if(typeof cb!=='function')cb=nop;
+
+if(state.ended)writeAfterEnd(this,cb);else if(isBuf||validChunk(this,state,chunk,cb)){
+state.pendingcb++;
+ret=writeOrBuffer(this,state,isBuf,chunk,encoding,cb);
+}
+
+return ret;
+};
+
+Writable.prototype.cork=function(){
+var state=this._writableState;
+
+state.corked++;
+};
+
+Writable.prototype.uncork=function(){
+var state=this._writableState;
+
+if(state.corked){
+state.corked--;
+
+if(!state.writing&&!state.corked&&!state.finished&&!state.bufferProcessing&&state.bufferedRequest)clearBuffer(this,state);
+}
+};
+
+Writable.prototype.setDefaultEncoding=function setDefaultEncoding(encoding){
+
+if(typeof encoding==='string')encoding=encoding.toLowerCase();
+if(!(['hex','utf8','utf-8','ascii','binary','base64','ucs2','ucs-2','utf16le','utf-16le','raw'].indexOf((encoding+'').toLowerCase())>-1))throw new TypeError('Unknown encoding: '+encoding);
+this._writableState.defaultEncoding=encoding;
+return this;
+};
+
+function decodeChunk(state,chunk,encoding){
+if(!state.objectMode&&state.decodeStrings!==false&&typeof chunk==='string'){
+chunk=Buffer.from(chunk,encoding);
+}
+return chunk;
+}
+
+
+
+
+function writeOrBuffer(stream,state,isBuf,chunk,encoding,cb){
+if(!isBuf){
+var newChunk=decodeChunk(state,chunk,encoding);
+if(chunk!==newChunk){
+isBuf=true;
+encoding='buffer';
+chunk=newChunk;
+}
+}
+var len=state.objectMode?1:chunk.length;
+
+state.length+=len;
+
+var ret=state.length<state.highWaterMark;
+
+if(!ret)state.needDrain=true;
+
+if(state.writing||state.corked){
+var last=state.lastBufferedRequest;
+state.lastBufferedRequest={
+chunk:chunk,
+encoding:encoding,
+isBuf:isBuf,
+callback:cb,
+next:null};
+
+if(last){
+last.next=state.lastBufferedRequest;
+}else{
+state.bufferedRequest=state.lastBufferedRequest;
+}
+state.bufferedRequestCount+=1;
+}else{
+doWrite(stream,state,false,len,chunk,encoding,cb);
+}
+
+return ret;
+}
+
+function doWrite(stream,state,writev,len,chunk,encoding,cb){
+state.writelen=len;
+state.writecb=cb;
+state.writing=true;
+state.sync=true;
+if(writev)stream._writev(chunk,state.onwrite);else stream._write(chunk,encoding,state.onwrite);
+state.sync=false;
+}
+
+function onwriteError(stream,state,sync,er,cb){
+--state.pendingcb;
+
+if(sync){
+
+
+processNextTick(cb,er);
+
+
+processNextTick(finishMaybe,stream,state);
+stream._writableState.errorEmitted=true;
+stream.emit('error',er);
+}else{
+
+
+cb(er);
+stream._writableState.errorEmitted=true;
+stream.emit('error',er);
+
+
+finishMaybe(stream,state);
+}
+}
+
+function onwriteStateUpdate(state){
+state.writing=false;
+state.writecb=null;
+state.length-=state.writelen;
+state.writelen=0;
+}
+
+function onwrite(stream,er){
+var state=stream._writableState;
+var sync=state.sync;
+var cb=state.writecb;
+
+onwriteStateUpdate(state);
+
+if(er)onwriteError(stream,state,sync,er,cb);else{
+
+var finished=needFinish(state);
+
+if(!finished&&!state.corked&&!state.bufferProcessing&&state.bufferedRequest){
+clearBuffer(stream,state);
+}
+
+if(sync){
+
+asyncWrite(afterWrite,stream,state,finished,cb);
+
+}else{
+afterWrite(stream,state,finished,cb);
+}
+}
+}
+
+function afterWrite(stream,state,finished,cb){
+if(!finished)onwriteDrain(stream,state);
+state.pendingcb--;
+cb();
+finishMaybe(stream,state);
+}
+
+
+
+
+function onwriteDrain(stream,state){
+if(state.length===0&&state.needDrain){
+state.needDrain=false;
+stream.emit('drain');
+}
+}
+
+
+function clearBuffer(stream,state){
+state.bufferProcessing=true;
+var entry=state.bufferedRequest;
+
+if(stream._writev&&entry&&entry.next){
+
+var l=state.bufferedRequestCount;
+var buffer=new Array(l);
+var holder=state.corkedRequestsFree;
+holder.entry=entry;
+
+var count=0;
+var allBuffers=true;
+while(entry){
+buffer[count]=entry;
+if(!entry.isBuf)allBuffers=false;
+entry=entry.next;
+count+=1;
+}
+buffer.allBuffers=allBuffers;
+
+doWrite(stream,state,true,state.length,buffer,'',holder.finish);
+
+
+
+state.pendingcb++;
+state.lastBufferedRequest=null;
+if(holder.next){
+state.corkedRequestsFree=holder.next;
+holder.next=null;
+}else{
+state.corkedRequestsFree=new CorkedRequest(state);
+}
+}else{
+
+while(entry){
+var chunk=entry.chunk;
+var encoding=entry.encoding;
+var cb=entry.callback;
+var len=state.objectMode?1:chunk.length;
+
+doWrite(stream,state,false,len,chunk,encoding,cb);
+entry=entry.next;
+
+
+
+
+if(state.writing){
+break;
+}
+}
+
+if(entry===null)state.lastBufferedRequest=null;
+}
+
+state.bufferedRequestCount=0;
+state.bufferedRequest=entry;
+state.bufferProcessing=false;
+}
+
+Writable.prototype._write=function(chunk,encoding,cb){
+cb(new Error('_write() is not implemented'));
+};
+
+Writable.prototype._writev=null;
+
+Writable.prototype.end=function(chunk,encoding,cb){
+var state=this._writableState;
+
+if(typeof chunk==='function'){
+cb=chunk;
+chunk=null;
+encoding=null;
+}else if(typeof encoding==='function'){
+cb=encoding;
+encoding=null;
+}
+
+if(chunk!==null&&chunk!==undefined)this.write(chunk,encoding);
+
+
+if(state.corked){
+state.corked=1;
+this.uncork();
+}
+
+
+if(!state.ending&&!state.finished)endWritable(this,state,cb);
+};
+
+function needFinish(state){
+return state.ending&&state.length===0&&state.bufferedRequest===null&&!state.finished&&!state.writing;
+}
+function callFinal(stream,state){
+stream._final(function(err){
+state.pendingcb--;
+if(err){
+stream.emit('error',err);
+}
+state.prefinished=true;
+stream.emit('prefinish');
+finishMaybe(stream,state);
+});
+}
+function prefinish(stream,state){
+if(!state.prefinished&&!state.finalCalled){
+if(typeof stream._final==='function'){
+state.pendingcb++;
+state.finalCalled=true;
+processNextTick(callFinal,stream,state);
+}else{
+state.prefinished=true;
+stream.emit('prefinish');
+}
+}
+}
+
+function finishMaybe(stream,state){
+var need=needFinish(state);
+if(need){
+prefinish(stream,state);
+if(state.pendingcb===0){
+state.finished=true;
+stream.emit('finish');
+}
+}
+return need;
+}
+
+function endWritable(stream,state,cb){
+state.ending=true;
+finishMaybe(stream,state);
+if(cb){
+if(state.finished)processNextTick(cb);else stream.once('finish',cb);
+}
+state.ended=true;
+stream.writable=false;
+}
+
+function onCorkedFinish(corkReq,state,err){
+var entry=corkReq.entry;
+corkReq.entry=null;
+while(entry){
+var cb=entry.callback;
+state.pendingcb--;
+cb(err);
+entry=entry.next;
+}
+if(state.corkedRequestsFree){
+state.corkedRequestsFree.next=corkReq;
+}else{
+state.corkedRequestsFree=corkReq;
+}
+}
+
+Object.defineProperty(Writable.prototype,'destroyed',{
+get:function(){
+if(this._writableState===undefined){
+return false;
+}
+return this._writableState.destroyed;
+},
+set:function(value){
+
+
+if(!this._writableState){
+return;
+}
+
+
+
+this._writableState.destroyed=value;
+}});
+
+
+Writable.prototype.destroy=destroyImpl.destroy;
+Writable.prototype._undestroy=destroyImpl.undestroy;
+Writable.prototype._destroy=function(err,cb){
+this.end();
+cb(err);
+};
+}).call(this,require('_process'),typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{},require("timers").setImmediate);
+},{"./_stream_duplex":150,"./internal/streams/destroy":156,"./internal/streams/stream":157,"_process":137,"core-util-is":95,"inherits":105,"process-nextick-args":136,"safe-buffer":143,"timers":163,"util-deprecate":164}],155:[function(require,module,exports){
+'use strict';
+
+
+
+function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor)){throw new TypeError("Cannot call a class as a function");}}
+
+var Buffer=require('safe-buffer').Buffer;
+
+
+function copyBuffer(src,target,offset){
+src.copy(target,offset);
+}
+
+module.exports=function(){
+function BufferList(){
+_classCallCheck(this,BufferList);
+
+this.head=null;
+this.tail=null;
+this.length=0;
+}
+
+BufferList.prototype.push=function push(v){
+var entry={data:v,next:null};
+if(this.length>0)this.tail.next=entry;else this.head=entry;
+this.tail=entry;
+++this.length;
+};
+
+BufferList.prototype.unshift=function unshift(v){
+var entry={data:v,next:this.head};
+if(this.length===0)this.tail=entry;
+this.head=entry;
+++this.length;
+};
+
+BufferList.prototype.shift=function shift(){
+if(this.length===0)return;
+var ret=this.head.data;
+if(this.length===1)this.head=this.tail=null;else this.head=this.head.next;
+--this.length;
+return ret;
+};
+
+BufferList.prototype.clear=function clear(){
+this.head=this.tail=null;
+this.length=0;
+};
+
+BufferList.prototype.join=function join(s){
+if(this.length===0)return'';
+var p=this.head;
+var ret=''+p.data;
+while(p=p.next){
+ret+=s+p.data;
+}return ret;
+};
+
+BufferList.prototype.concat=function concat(n){
+if(this.length===0)return Buffer.alloc(0);
+if(this.length===1)return this.head.data;
+var ret=Buffer.allocUnsafe(n>>>0);
+var p=this.head;
+var i=0;
+while(p){
+copyBuffer(p.data,ret,i);
+i+=p.data.length;
+p=p.next;
+}
+return ret;
+};
+
+return BufferList;
+}();
+},{"safe-buffer":143}],156:[function(require,module,exports){
+'use strict';
+
+
+
+var processNextTick=require('process-nextick-args');
+
+
+
+function destroy(err,cb){
+var _this=this;
+
+var readableDestroyed=this._readableState&&this._readableState.destroyed;
+var writableDestroyed=this._writableState&&this._writableState.destroyed;
+
+if(readableDestroyed||writableDestroyed){
+if(cb){
+cb(err);
+}else if(err&&(!this._writableState||!this._writableState.errorEmitted)){
+processNextTick(emitErrorNT,this,err);
+}
+return;
+}
+
+
+
+
+if(this._readableState){
+this._readableState.destroyed=true;
+}
+
+
+if(this._writableState){
+this._writableState.destroyed=true;
+}
+
+this._destroy(err||null,function(err){
+if(!cb&&err){
+processNextTick(emitErrorNT,_this,err);
+if(_this._writableState){
+_this._writableState.errorEmitted=true;
+}
+}else if(cb){
+cb(err);
+}
+});
+}
+
+function undestroy(){
+if(this._readableState){
+this._readableState.destroyed=false;
+this._readableState.reading=false;
+this._readableState.ended=false;
+this._readableState.endEmitted=false;
+}
+
+if(this._writableState){
+this._writableState.destroyed=false;
+this._writableState.ended=false;
+this._writableState.ending=false;
+this._writableState.finished=false;
+this._writableState.errorEmitted=false;
+}
+}
+
+function emitErrorNT(self,err){
+self.emit('error',err);
+}
+
+module.exports={
+destroy:destroy,
+undestroy:undestroy};
+
+},{"process-nextick-args":136}],157:[function(require,module,exports){
+module.exports=require('events').EventEmitter;
+
+},{"events":100}],158:[function(require,module,exports){
+module.exports=require('./readable').PassThrough;
+
+},{"./readable":159}],159:[function(require,module,exports){
+exports=module.exports=require('./lib/_stream_readable.js');
+exports.Stream=exports;
+exports.Readable=exports;
+exports.Writable=require('./lib/_stream_writable.js');
+exports.Duplex=require('./lib/_stream_duplex.js');
+exports.Transform=require('./lib/_stream_transform.js');
+exports.PassThrough=require('./lib/_stream_passthrough.js');
+
+},{"./lib/_stream_duplex.js":150,"./lib/_stream_passthrough.js":151,"./lib/_stream_readable.js":152,"./lib/_stream_transform.js":153,"./lib/_stream_writable.js":154}],160:[function(require,module,exports){
+module.exports=require('./readable').Transform;
+
+},{"./readable":159}],161:[function(require,module,exports){
+module.exports=require('./lib/_stream_writable.js');
+
+},{"./lib/_stream_writable.js":154}],162:[function(require,module,exports){
+'use strict';
+
+var Buffer=require('safe-buffer').Buffer;
+
+var isEncoding=Buffer.isEncoding||function(encoding){
+encoding=''+encoding;
+switch(encoding&&encoding.toLowerCase()){
+case'hex':case'utf8':case'utf-8':case'ascii':case'binary':case'base64':case'ucs2':case'ucs-2':case'utf16le':case'utf-16le':case'raw':
+return true;
+default:
+return false;}
+
+};
+
+function _normalizeEncoding(enc){
+if(!enc)return'utf8';
+var retried;
+while(true){
+switch(enc){
+case'utf8':
+case'utf-8':
+return'utf8';
+case'ucs2':
+case'ucs-2':
+case'utf16le':
+case'utf-16le':
+return'utf16le';
+case'latin1':
+case'binary':
+return'latin1';
+case'base64':
+case'ascii':
+case'hex':
+return enc;
+default:
+if(retried)return;
+enc=(''+enc).toLowerCase();
+retried=true;}
+
+}
+};
+
+
+
+function normalizeEncoding(enc){
+var nenc=_normalizeEncoding(enc);
+if(typeof nenc!=='string'&&(Buffer.isEncoding===isEncoding||!isEncoding(enc)))throw new Error('Unknown encoding: '+enc);
+return nenc||enc;
+}
+
+
+
+
+exports.StringDecoder=StringDecoder;
+function StringDecoder(encoding){
+this.encoding=normalizeEncoding(encoding);
+var nb;
+switch(this.encoding){
+case'utf16le':
+this.text=utf16Text;
+this.end=utf16End;
+nb=4;
+break;
+case'utf8':
+this.fillLast=utf8FillLast;
+nb=4;
+break;
+case'base64':
+this.text=base64Text;
+this.end=base64End;
+nb=3;
+break;
+default:
+this.write=simpleWrite;
+this.end=simpleEnd;
+return;}
+
+this.lastNeed=0;
+this.lastTotal=0;
+this.lastChar=Buffer.allocUnsafe(nb);
+}
+
+StringDecoder.prototype.write=function(buf){
+if(buf.length===0)return'';
+var r;
+var i;
+if(this.lastNeed){
+r=this.fillLast(buf);
+if(r===undefined)return'';
+i=this.lastNeed;
+this.lastNeed=0;
+}else{
+i=0;
+}
+if(i<buf.length)return r?r+this.text(buf,i):this.text(buf,i);
+return r||'';
+};
+
+StringDecoder.prototype.end=utf8End;
+
+
+StringDecoder.prototype.text=utf8Text;
+
+
+StringDecoder.prototype.fillLast=function(buf){
+if(this.lastNeed<=buf.length){
+buf.copy(this.lastChar,this.lastTotal-this.lastNeed,0,this.lastNeed);
+return this.lastChar.toString(this.encoding,0,this.lastTotal);
+}
+buf.copy(this.lastChar,this.lastTotal-this.lastNeed,0,buf.length);
+this.lastNeed-=buf.length;
+};
+
+
+
+function utf8CheckByte(byte){
+if(byte<=0x7F)return 0;else if(byte>>5===0x06)return 2;else if(byte>>4===0x0E)return 3;else if(byte>>3===0x1E)return 4;
+return-1;
+}
+
+
+
+
+function utf8CheckIncomplete(self,buf,i){
+var j=buf.length-1;
+if(j<i)return 0;
+var nb=utf8CheckByte(buf[j]);
+if(nb>=0){
+if(nb>0)self.lastNeed=nb-1;
+return nb;
+}
+if(--j<i)return 0;
+nb=utf8CheckByte(buf[j]);
+if(nb>=0){
+if(nb>0)self.lastNeed=nb-2;
+return nb;
+}
+if(--j<i)return 0;
+nb=utf8CheckByte(buf[j]);
+if(nb>=0){
+if(nb>0){
+if(nb===2)nb=0;else self.lastNeed=nb-3;
+}
+return nb;
+}
+return 0;
+}
+
+
+
+
+
+
+
+
+
+function utf8CheckExtraBytes(self,buf,p){
+if((buf[0]&0xC0)!==0x80){
+self.lastNeed=0;
+return'\ufffd'.repeat(p);
+}
+if(self.lastNeed>1&&buf.length>1){
+if((buf[1]&0xC0)!==0x80){
+self.lastNeed=1;
+return'\ufffd'.repeat(p+1);
+}
+if(self.lastNeed>2&&buf.length>2){
+if((buf[2]&0xC0)!==0x80){
+self.lastNeed=2;
+return'\ufffd'.repeat(p+2);
+}
+}
+}
+}
+
+
+function utf8FillLast(buf){
+var p=this.lastTotal-this.lastNeed;
+var r=utf8CheckExtraBytes(this,buf,p);
+if(r!==undefined)return r;
+if(this.lastNeed<=buf.length){
+buf.copy(this.lastChar,p,0,this.lastNeed);
+return this.lastChar.toString(this.encoding,0,this.lastTotal);
+}
+buf.copy(this.lastChar,p,0,buf.length);
+this.lastNeed-=buf.length;
+}
+
+
+
+
+function utf8Text(buf,i){
+var total=utf8CheckIncomplete(this,buf,i);
+if(!this.lastNeed)return buf.toString('utf8',i);
+this.lastTotal=total;
+var end=buf.length-(total-this.lastNeed);
+buf.copy(this.lastChar,0,end);
+return buf.toString('utf8',i,end);
+}
+
+
+
+function utf8End(buf){
+var r=buf&&buf.length?this.write(buf):'';
+if(this.lastNeed)return r+'\ufffd'.repeat(this.lastTotal-this.lastNeed);
+return r;
+}
+
+
+
+
+
+function utf16Text(buf,i){
+if((buf.length-i)%2===0){
+var r=buf.toString('utf16le',i);
+if(r){
+var c=r.charCodeAt(r.length-1);
+if(c>=0xD800&&c<=0xDBFF){
+this.lastNeed=2;
+this.lastTotal=4;
+this.lastChar[0]=buf[buf.length-2];
+this.lastChar[1]=buf[buf.length-1];
+return r.slice(0,-1);
+}
+}
+return r;
+}
+this.lastNeed=1;
+this.lastTotal=2;
+this.lastChar[0]=buf[buf.length-1];
+return buf.toString('utf16le',i,buf.length-1);
+}
+
+
+
+function utf16End(buf){
+var r=buf&&buf.length?this.write(buf):'';
+if(this.lastNeed){
+var end=this.lastTotal-this.lastNeed;
+return r+this.lastChar.toString('utf16le',0,end);
+}
+return r;
+}
+
+function base64Text(buf,i){
+var n=(buf.length-i)%3;
+if(n===0)return buf.toString('base64',i);
+this.lastNeed=3-n;
+this.lastTotal=3;
+if(n===1){
+this.lastChar[0]=buf[buf.length-1];
+}else{
+this.lastChar[0]=buf[buf.length-2];
+this.lastChar[1]=buf[buf.length-1];
+}
+return buf.toString('base64',i,buf.length-n);
+}
+
+function base64End(buf){
+var r=buf&&buf.length?this.write(buf):'';
+if(this.lastNeed)return r+this.lastChar.toString('base64',0,3-this.lastNeed);
+return r;
+}
+
+
+function simpleWrite(buf){
+return buf.toString(this.encoding);
+}
+
+function simpleEnd(buf){
+return buf&&buf.length?this.write(buf):'';
+}
+},{"safe-buffer":143}],163:[function(require,module,exports){
 (function(setImmediate,clearImmediate){
 var nextTick=require('process/browser.js').nextTick;
 var apply=Function.prototype.apply;
@@ -59297,7 +61977,7 @@
 delete immediateIds[id];
 };
 }).call(this,require("timers").setImmediate,require("timers").clearImmediate);
-},{"process/browser.js":131,"timers":157}],158:[function(require,module,exports){
+},{"process/browser.js":137,"timers":163}],164:[function(require,module,exports){
 (function(global){
 
 
@@ -59368,15 +62048,15 @@
 }
 
 }).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{});
-},{}],159:[function(require,module,exports){
-arguments[4][81][0].apply(exports,arguments);
-},{"dup":81}],160:[function(require,module,exports){
-arguments[4][82][0].apply(exports,arguments);
-},{"./support/isBuffer":159,"_process":131,"dup":82,"inherits":99}],161:[function(require,module,exports){
+},{}],165:[function(require,module,exports){
+arguments[4][86][0].apply(exports,arguments);
+},{"dup":86}],166:[function(require,module,exports){
+arguments[4][87][0].apply(exports,arguments);
+},{"./support/isBuffer":165,"_process":137,"dup":87,"inherits":105}],167:[function(require,module,exports){
 module.exports={
-"version":"4.1.0"};
+"version":"4.2.0"};
 
-},{}],162:[function(require,module,exports){
+},{}],168:[function(require,module,exports){
 module.exports={
 "npm":{
 "angular":[
@@ -59406,10 +62086,12 @@
 {"id":"npm:backbone:20110701","severity":"medium","semver":{"vulnerable":["<0.5.0"]}}],
 
 "bootstrap":[
+{"id":"SNYK-JS-BOOTSTRAP-173700","severity":"medium","semver":{"vulnerable":["<3.4.1",">=4.0.0 <4.3.1"]}},
+{"id":"SNYK-JS-BOOTSTRAP-73560","severity":"medium","semver":{"vulnerable":[">=4.0.0 <4.1.2"]}},
 {"id":"SNYK-JS-BOOTSTRAP-72890","severity":"medium","semver":{"vulnerable":["<3.4.0"]}},
 {"id":"SNYK-JS-BOOTSTRAP-72889","severity":"medium","semver":{"vulnerable":["<3.4.0"]}},
 {"id":"npm:bootstrap:20180529","severity":"medium","semver":{"vulnerable":["<3.4.0",">=4.0.0 <4.1.2"]}},
-{"id":"npm:bootstrap:20160627","severity":"medium","semver":{"vulnerable":["<3.4.0",">=4.0.0 <=4.0.0"]}},
+{"id":"npm:bootstrap:20160627","severity":"medium","semver":{"vulnerable":["<3.4.0",">=4.0.0-alpha <4.0.0-beta.2"]}},
 {"id":"npm:bootstrap:20120510","severity":"medium","semver":{"vulnerable":["<2.1.0"]}}],
 
 "dojo":[
@@ -59426,11 +62108,12 @@
 {"id":"npm:foundation-sites:20120717","severity":"medium","semver":{"vulnerable":["<3.0.6 >=3.0.0"]}}],
 
 "handlebars":[
+{"id":"SNYK-JS-HANDLEBARS-173692","severity":"high","semver":{"vulnerable":["<4.0.13"]}},
 {"id":"npm:handlebars:20151207","severity":"medium","semver":{"vulnerable":["<4.0.0"]}},
 {"id":"npm:handlebars:20110425","severity":"medium","semver":{"vulnerable":["<=1.0.0-beta.3"]}}],
 
 "highcharts":[
-{"id":"npm:highcharts:20180225","severity":"low","semver":{"vulnerable":["<6.1.0"]}}],
+{"id":"npm:highcharts:20180225","severity":"high","semver":{"vulnerable":["<6.1.0"]}}],
 
 "jquery":[
 {"id":"npm:jquery:20160529","severity":"low","semver":{"vulnerable":["=3.0.0-rc.1"]}},
@@ -59452,12 +62135,14 @@
 {"id":"npm:knockout:20130701","severity":"medium","semver":{"vulnerable":["<3.0.0 >=2.1.0-pre"]}}],
 
 "lodash":[
+{"id":"SNYK-JS-LODASH-73639","severity":"medium","semver":{"vulnerable":["<4.17.11"]}},
+{"id":"SNYK-JS-LODASH-73638","severity":"low","semver":{"vulnerable":["<4.17.11"]}},
 {"id":"npm:lodash:20180130","severity":"low","semver":{"vulnerable":["<4.17.5"]}}],
 
 "moment":[
 {"id":"npm:moment:20170905","severity":"low","semver":{"vulnerable":["<2.19.3"]}},
 {"id":"npm:moment:20161019","severity":"medium","semver":{"vulnerable":["<2.15.2"]}},
-{"id":"npm:moment:20160126","severity":"low","semver":{"vulnerable":["<=2.11.1"]}}],
+{"id":"npm:moment:20160126","severity":"medium","semver":{"vulnerable":["<=2.11.1"]}}],
 
 "mustache":[
 {"id":"npm:mustache:20151207","severity":"medium","semver":{"vulnerable":["<2.2.1"]}},
@@ -59707,4 +62392,4 @@
 
 module.exports=URLShim;
 
-},{"../report/html/renderer/util.js":75,"url":"url"}]},{},[1]);
\ No newline at end of file
+},{"../report/html/renderer/util.js":80,"url":"url"}]},{},[1]);
\ No newline at end of file
diff --git a/third_party/blink/renderer/devtools/front_end/help/ReleaseNoteText.js b/third_party/blink/renderer/devtools/front_end/help/ReleaseNoteText.js
index 7c3dd62d..aacb55d 100644
--- a/third_party/blink/renderer/devtools/front_end/help/ReleaseNoteText.js
+++ b/third_party/blink/renderer/devtools/front_end/help/ReleaseNoteText.js
@@ -13,6 +13,44 @@
 /** @type {!Array<!Help.ReleaseNote>} */
 Help.releaseNoteText = [
   {
+    version: 16,
+    header: 'Highlights from the Chrome 74 update',
+    highlights: [
+      {
+        title: 'Highlight all nodes affected by CSS property',
+        subtitle:
+            'Hover over a CSS property like padding or margin in the Styles pane to highlight all nodes affected by that declaration.',
+        link: 'https://developers.google.com/web/updates/2019/03/devtools#highlight',
+      },
+
+      {
+        title: 'Lighthouse v4 in the Audits panel',
+        subtitle:
+            'Featuring a new "tap targets" audit for checking that mobile links and buttons are properly sized, and a new UI for PWA reports.',
+        link: 'https://developers.google.com/web/updates/2019/03/devtools#lighthouse',
+      },
+      {
+        title: 'WebSocket binary message viewer',
+        subtitle:
+            'Click a WebSocket connection in the Network Log, go to the Messages tab, then click a binary message to view its contents.',
+        link: 'https://developers.google.com/web/updates/2019/03/devtools#binary',
+      },
+      {
+        title: 'Capture area screenshot in the Command Menu',
+        subtitle: 'Press ' + commandMenuShortcut +
+            ', run the "Capture area screenshot" command, and then drag your mouse to take a screenshot of part of the viewport.',
+        link: 'https://developers.google.com/web/updates/2019/03/devtools#screenshot',
+      },
+      {
+        title: 'Service worker filters in the Network panel',
+        subtitle:
+            'Type "is:service-worker-initiated" or "is:service-worker-intercepted" to only show service worker activity.',
+        link: 'https://developers.google.com/web/updates/2019/03/devtools#swfilters',
+      },
+    ],
+    link: 'https://developers.google.com/web/updates/2019/03/devtools',
+  },
+  {
     version: 15,
     header: 'Highlights from the Chrome 73 update',
     highlights: [
diff --git a/third_party/blink/renderer/modules/BUILD.gn b/third_party/blink/renderer/modules/BUILD.gn
index 38d14e4..efa8a76d 100644
--- a/third_party/blink/renderer/modules/BUILD.gn
+++ b/third_party/blink/renderer/modules/BUILD.gn
@@ -110,6 +110,7 @@
     "//third_party/blink/renderer/modules/filesystem",
     "//third_party/blink/renderer/modules/gamepad",
     "//third_party/blink/renderer/modules/geolocation",
+    "//third_party/blink/renderer/modules/hid",
     "//third_party/blink/renderer/modules/idle",
     "//third_party/blink/renderer/modules/imagecapture",
     "//third_party/blink/renderer/modules/indexeddb",
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
index 1de7ab0e..ca99704 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
@@ -730,7 +730,9 @@
     ChildrenChanged(element->parentNode());
   } else {
     // Refresh the focusable state on the exposed object.
-    MarkAXObjectDirty(obj, false);
+    // TODO(accessibility) find out why using MarkAXObjectDirty(obj, false)
+    // regresses Slack performance, see https://crbug.com/936944.
+    PostNotification(obj, ax::mojom::Event::kValueChanged);
   }
 }
 
diff --git a/third_party/blink/renderer/modules/event_target_modules_names.json5 b/third_party/blink/renderer/modules/event_target_modules_names.json5
index a1b3935..b2f6898 100644
--- a/third_party/blink/renderer/modules/event_target_modules_names.json5
+++ b/third_party/blink/renderer/modules/event_target_modules_names.json5
@@ -16,6 +16,7 @@
     "CookieStore",
     "MediaKeySession",
     "FileWriter",
+    "HID",
     "IdleStatus",
     "ImageCapture",
     "IDBDatabase",
diff --git a/third_party/blink/renderer/modules/hid/BUILD.gn b/third_party/blink/renderer/modules/hid/BUILD.gn
new file mode 100644
index 0000000..80c219b
--- /dev/null
+++ b/third_party/blink/renderer/modules/hid/BUILD.gn
@@ -0,0 +1,14 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/blink/renderer/modules/modules.gni")
+
+blink_modules_sources("hid") {
+  sources = [
+    "hid.cc",
+    "hid.h",
+    "navigator_hid.cc",
+    "navigator_hid.h",
+  ]
+}
diff --git a/third_party/blink/renderer/modules/hid/DEPS b/third_party/blink/renderer/modules/hid/DEPS
new file mode 100644
index 0000000..d5b52fa0
--- /dev/null
+++ b/third_party/blink/renderer/modules/hid/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+    "+mojo/public/cpp/bindings",
+]
diff --git a/third_party/blink/renderer/modules/hid/OWNERS b/third_party/blink/renderer/modules/hid/OWNERS
new file mode 100644
index 0000000..fd7080c6
--- /dev/null
+++ b/third_party/blink/renderer/modules/hid/OWNERS
@@ -0,0 +1,5 @@
+mattreynolds@chromium.org
+reillyg@chromium.org
+
+# TEAM: device-dev@chromium.org
+# COMPONENT: Blink>HID
diff --git a/third_party/blink/renderer/modules/hid/hid.cc b/third_party/blink/renderer/modules/hid/hid.cc
new file mode 100644
index 0000000..305480f2
--- /dev/null
+++ b/third_party/blink/renderer/modules/hid/hid.cc
@@ -0,0 +1,34 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/hid/hid.h"
+
+#include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink.h"
+#include "third_party/blink/renderer/modules/event_target_modules.h"
+
+namespace blink {
+
+HID::HID(ExecutionContext& context) : ContextLifecycleObserver(&context) {}
+
+HID::~HID() = default;
+
+ExecutionContext* HID::GetExecutionContext() const {
+  return ContextLifecycleObserver::GetExecutionContext();
+}
+
+const AtomicString& HID::InterfaceName() const {
+  return event_target_names::kHID;
+}
+
+FeatureEnabledState HID::GetFeatureEnabledState() const {
+  return GetExecutionContext()->GetSecurityContext().GetFeatureEnabledState(
+      mojom::FeaturePolicyFeature::kHid);
+}
+
+void HID::Trace(blink::Visitor* visitor) {
+  EventTargetWithInlineData::Trace(visitor);
+  ContextLifecycleObserver::Trace(visitor);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/hid/hid.h b/third_party/blink/renderer/modules/hid/hid.h
new file mode 100644
index 0000000..5ba091ca
--- /dev/null
+++ b/third_party/blink/renderer/modules/hid/hid.h
@@ -0,0 +1,35 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_HID_HID_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_HID_HID_H_
+
+#include "third_party/blink/renderer/core/dom/events/event_target.h"
+#include "third_party/blink/renderer/core/execution_context/context_lifecycle_observer.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/core/execution_context/security_context.h"
+
+namespace blink {
+
+class HID : public EventTargetWithInlineData, public ContextLifecycleObserver {
+  DEFINE_WRAPPERTYPEINFO();
+  USING_GARBAGE_COLLECTED_MIXIN(HID);
+
+ public:
+  explicit HID(ExecutionContext&);
+  ~HID() override;
+
+  // EventTarget overrides.
+  ExecutionContext* GetExecutionContext() const override;
+  const AtomicString& InterfaceName() const override;
+
+  void Trace(blink::Visitor*) override;
+
+ private:
+  FeatureEnabledState GetFeatureEnabledState() const;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_HID_HID_H_
diff --git a/third_party/blink/renderer/modules/hid/hid.idl b/third_party/blink/renderer/modules/hid/hid.idl
new file mode 100644
index 0000000..58fa330
--- /dev/null
+++ b/third_party/blink/renderer/modules/hid/hid.idl
@@ -0,0 +1,12 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Functionality for enumerating and connecting to devices within the HID
+// subsystem.
+// https://github.com/nondebug/webhid
+
+[
+    Exposed(Window WebHID),
+    SecureContext
+] interface HID : EventTarget {};
diff --git a/third_party/blink/renderer/modules/hid/navigator_hid.cc b/third_party/blink/renderer/modules/hid/navigator_hid.cc
new file mode 100644
index 0000000..9161859
--- /dev/null
+++ b/third_party/blink/renderer/modules/hid/navigator_hid.cc
@@ -0,0 +1,46 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/hid/navigator_hid.h"
+
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/frame/navigator.h"
+#include "third_party/blink/renderer/modules/hid/hid.h"
+
+namespace blink {
+
+NavigatorHID& NavigatorHID::From(Navigator& navigator) {
+  NavigatorHID* supplement =
+      Supplement<Navigator>::From<NavigatorHID>(navigator);
+  if (!supplement) {
+    supplement = MakeGarbageCollected<NavigatorHID>(navigator);
+    ProvideTo(navigator, supplement);
+  }
+  return *supplement;
+}
+
+HID* NavigatorHID::hid(Navigator& navigator) {
+  return NavigatorHID::From(navigator).hid();
+}
+
+HID* NavigatorHID::hid() {
+  return hid_;
+}
+
+void NavigatorHID::Trace(blink::Visitor* visitor) {
+  visitor->Trace(hid_);
+  Supplement<Navigator>::Trace(visitor);
+}
+
+NavigatorHID::NavigatorHID(Navigator& navigator) {
+  if (navigator.GetFrame()) {
+    DCHECK(navigator.GetFrame()->GetDocument());
+    hid_ = MakeGarbageCollected<HID>(*navigator.GetFrame()->GetDocument());
+  }
+}
+
+const char NavigatorHID::kSupplementName[] = "NavigatorHID";
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/hid/navigator_hid.h b/third_party/blink/renderer/modules/hid/navigator_hid.h
new file mode 100644
index 0000000..c7f56f33
--- /dev/null
+++ b/third_party/blink/renderer/modules/hid/navigator_hid.h
@@ -0,0 +1,41 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_MODULES_HID_NAVIGATOR_HID_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_HID_NAVIGATOR_HID_H_
+
+#include "third_party/blink/renderer/core/frame/navigator.h"
+#include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/supplementable.h"
+
+namespace blink {
+
+class Navigator;
+class HID;
+
+class NavigatorHID final : public GarbageCollected<NavigatorHID>,
+                           public Supplement<Navigator> {
+  USING_GARBAGE_COLLECTED_MIXIN(NavigatorHID);
+
+ public:
+  static const char kSupplementName[];
+
+  // Gets, or creates, NavigatorHID supplement on Navigator.
+  // See platform/Supplementable.h
+  static NavigatorHID& From(Navigator&);
+
+  static HID* hid(Navigator&);
+  HID* hid();
+
+  void Trace(blink::Visitor*) override;
+
+  explicit NavigatorHID(Navigator&);
+
+ private:
+  Member<HID> hid_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_HID_NAVIGATOR_HID_H_
diff --git a/third_party/blink/renderer/modules/hid/navigator_hid.idl b/third_party/blink/renderer/modules/hid/navigator_hid.idl
new file mode 100644
index 0000000..8e0cfca
--- /dev/null
+++ b/third_party/blink/renderer/modules/hid/navigator_hid.idl
@@ -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.
+
+// Partial Navigator interface for functionality related to HID subsystem
+// devices.
+// https://github.com/nondebug/webhid
+
+[
+    ImplementedAs=NavigatorHID,
+    RuntimeEnabled=WebHID,
+    SecureContext
+] partial interface Navigator {
+    [SameObject] readonly attribute HID hid;
+};
diff --git a/third_party/blink/renderer/modules/media_controls/elements/media_control_input_element.cc b/third_party/blink/renderer/modules/media_controls/elements/media_control_input_element.cc
index d8cce8a..ff72a98 100644
--- a/third_party/blink/renderer/modules/media_controls/elements/media_control_input_element.cc
+++ b/third_party/blink/renderer/modules/media_controls/elements/media_control_input_element.cc
@@ -61,25 +61,28 @@
   overflow_menu_text_->setInnerText(button->GetOverflowMenuString(),
                                     ASSERT_NO_EXCEPTION);
 
-  HTMLLabelElement* element = HTMLLabelElement::Create(GetDocument());
-  element->SetShadowPseudoId(
+  overflow_label_element_ = HTMLLabelElement::Create(GetDocument());
+  overflow_label_element_->SetShadowPseudoId(
       AtomicString("-internal-media-controls-overflow-menu-list-item"));
-  element->setAttribute(html_names::kRoleAttr, "menuitem");
+  overflow_label_element_->setAttribute(html_names::kRoleAttr, "menuitem");
   // Appending a button to a label element ensures that clicks on the label
   // are passed down to the button, performing the action we'd expect.
-  element->ParserAppendChild(button);
+  overflow_label_element_->ParserAppendChild(button);
 
   // Allows to focus the list entry instead of the button.
-  element->setTabIndex(0);
+  overflow_label_element_->setTabIndex(0);
   button->setTabIndex(-1);
 
   if (MediaControlsImpl::IsModern()) {
     overflow_menu_container_ = HTMLDivElement::Create(GetDocument());
     overflow_menu_container_->ParserAppendChild(overflow_menu_text_);
+    overflow_menu_container_->setAttribute(html_names::kAriaHiddenAttr, "true");
+    aria_label_ = button->getAttribute(html_names::kAriaLabelAttr) + " " +
+                  button->GetOverflowMenuString();
     UpdateOverflowSubtitleElement(button->GetOverflowMenuSubtitleString());
-    element->ParserAppendChild(overflow_menu_container_);
+    overflow_label_element_->ParserAppendChild(overflow_menu_container_);
   } else {
-    element->ParserAppendChild(overflow_menu_text_);
+    overflow_label_element_->ParserAppendChild(overflow_menu_text_);
   }
 
   // Initialize the internal states of the main element and the overflow one.
@@ -89,10 +92,11 @@
   // Keeping the element hidden by default. This is setting the style in
   // addition of calling ShouldShowButtonInOverflowMenu() to guarantee that the
   // internal state matches the CSS state.
-  element->SetInlineStyleProperty(CSSPropertyDisplay, CSSValueNone);
+  overflow_label_element_->SetInlineStyleProperty(CSSPropertyDisplay,
+                                                  CSSValueNone);
   SetOverflowElementIsWanted(false);
 
-  return element;
+  return overflow_label_element_;
 }
 
 void MediaControlInputElement::UpdateOverflowSubtitleElement(String text) {
@@ -101,6 +105,7 @@
   if (!text) {
     // If setting the text to null, we want to remove the element.
     RemoveOverflowSubtitleElement();
+    UpdateOverflowLabelAriaLabel("");
     return;
   }
 
@@ -117,6 +122,7 @@
     overflow_menu_container_->setAttribute(
         "class", kOverflowContainerWithSubtitleCSSClass);
   }
+  UpdateOverflowLabelAriaLabel(text);
 }
 
 void MediaControlInputElement::RemoveOverflowSubtitleElement() {
@@ -138,6 +144,12 @@
   overflow_element_->SetIsWanted(wanted);
 }
 
+void MediaControlInputElement::UpdateOverflowLabelAriaLabel(String subtitle) {
+  String full_aria_label = aria_label_ + " " + subtitle;
+  overflow_label_element_->setAttribute(html_names::kAriaLabelAttr,
+                                        WTF::AtomicString(full_aria_label));
+}
+
 void MediaControlInputElement::MaybeRecordDisplayed() {
   // Display is defined as wanted and fitting. Overflow elements will only be
   // displayed if their inline counterpart isn't displayed.
@@ -292,6 +304,7 @@
   visitor->Trace(overflow_menu_container_);
   visitor->Trace(overflow_menu_text_);
   visitor->Trace(overflow_menu_subtitle_);
+  visitor->Trace(overflow_label_element_);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/media_controls/elements/media_control_input_element.h b/third_party/blink/renderer/modules/media_controls/elements/media_control_input_element.h
index 5d99c2a8df..ac375a0 100644
--- a/third_party/blink/renderer/modules/media_controls/elements/media_control_input_element.h
+++ b/third_party/blink/renderer/modules/media_controls/elements/media_control_input_element.h
@@ -99,6 +99,9 @@
   // Remove the subtitle text from the overflow element.
   void RemoveOverflowSubtitleElement();
 
+  // Updates aria label on overflow_label_element_.
+  void UpdateOverflowLabelAriaLabel(String);
+
   // Used for histograms, do not reorder.
   enum class CTREvent {
     kDisplayed = 0,
@@ -113,6 +116,9 @@
   // Setting this pointer is optional so it may be null.
   Member<MediaControlInputElement> overflow_element_;
 
+  // The overflow label element for the overflow_element_;
+  Member<HTMLLabelElement> overflow_label_element_;
+
   // Contains the overflow text and its subtitle (if exists).
   Member<HTMLDivElement> overflow_menu_container_;
 
@@ -122,6 +128,9 @@
   // The subtitle of the text within the overflow menu.
   Member<HTMLSpanElement> overflow_menu_subtitle_;
 
+  // The aria label for the overflow element without subtitle text.
+  String aria_label_;
+
   // Keeps track if the button was created for the purpose of the overflow menu.
   bool is_overflow_element_ = false;
 
diff --git a/third_party/blink/renderer/modules/modules_idl_files.gni b/third_party/blink/renderer/modules/modules_idl_files.gni
index 321ac13..49018962 100644
--- a/third_party/blink/renderer/modules/modules_idl_files.gni
+++ b/third_party/blink/renderer/modules/modules_idl_files.gni
@@ -166,6 +166,7 @@
           "geolocation/geolocation.idl",
           "geolocation/position.idl",
           "geolocation/position_error.idl",
+          "hid/hid.idl",
           "idle/idle_manager.idl",
           "idle/idle_state.idl",
           "idle/idle_status.idl",
@@ -793,6 +794,7 @@
           "filesystem/window_file_system.idl",
           "gamepad/navigator_gamepad.idl",
           "geolocation/navigator_geolocation.idl",
+          "hid/navigator_hid.idl",
           "idle/navigator_idle.idl",
           "idle/worker_navigator_idle.idl",
           "indexeddb/window_indexed_database.idl",
diff --git a/third_party/blink/renderer/modules/service_worker/fetch_event.cc b/third_party/blink/renderer/modules/service_worker/fetch_event.cc
index 93034c5..6a7116168 100644
--- a/third_party/blink/renderer/modules/service_worker/fetch_event.cc
+++ b/third_party/blink/renderer/modules/service_worker/fetch_event.cc
@@ -205,7 +205,8 @@
   info->SetLoadResponseEnd(completion_time);
   info->SetInitialURL(request_->url());
   info->SetFinalResponse(resource_response);
-  info->AddFinalTransferSize(encoded_data_length);
+  info->AddFinalTransferSize(encoded_data_length == -1 ? 0
+                                                       : encoded_data_length);
   WorkerGlobalScopePerformance::performance(*worker_global_scope)
       ->GenerateAndAddResourceTiming(*info);
 }
diff --git a/third_party/blink/renderer/modules/storage/cached_storage_area.cc b/third_party/blink/renderer/modules/storage/cached_storage_area.cc
index 21ce9fe0..35a114e 100644
--- a/third_party/blink/renderer/modules/storage/cached_storage_area.cc
+++ b/third_party/blink/renderer/modules/storage/cached_storage_area.cc
@@ -4,7 +4,6 @@
 
 #include "third_party/blink/renderer/modules/storage/cached_storage_area.h"
 
-#include "base/debug/crash_logging.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/rand_util.h"
@@ -424,13 +423,6 @@
 void CachedStorageArea::EnsureLoaded() {
   if (map_)
     return;
-  // TODO(dmurph): Change this back to non-sync after the cause of the renderer
-  // hang is discovered. http://crbug.com/927534
-  static base::debug::CrashKeyString* dom_storage_type =
-      base::debug::AllocateCrashKeyString("dom_storage_type",
-                                          base::debug::CrashKeySize::Size32);
-  base::debug::ScopedCrashKeyString auto_clear(
-      dom_storage_type, IsSessionStorage() ? "ss" : "ls");
 
   // There might be something weird happening during the sync call that destroys
   // this object. Keep a reference to either fix or rule out that this is the
diff --git a/third_party/blink/renderer/modules/storage/storage_controller_test.cc b/third_party/blink/renderer/modules/storage/storage_controller_test.cc
index 7cc8ba1..3c49809 100644
--- a/third_party/blink/renderer/modules/storage/storage_controller_test.cc
+++ b/third_party/blink/renderer/modules/storage/storage_controller_test.cc
@@ -28,16 +28,12 @@
     : public mojom::blink::StoragePartitionService {
  public:
   void OpenLocalStorage(const scoped_refptr<const SecurityOrigin>& origin,
-                        mojom::blink::StorageAreaRequest request,
-                        OpenLocalStorageCallback done) override {
-    std::move(done).Run();
-  }
+                        mojom::blink::StorageAreaRequest request) override {}
 
-  void OpenSessionStorage(const String& namespace_id,
-                          mojom::blink::SessionStorageNamespaceRequest request,
-                          OpenSessionStorageCallback done) override {
+  void OpenSessionStorage(
+      const String& namespace_id,
+      mojom::blink::SessionStorageNamespaceRequest request) override {
     session_storage_opens++;
-    std::move(done).Run();
   }
 
   void GetSessionStorageUsage(int32_t* out) const {
diff --git a/third_party/blink/renderer/modules/storage/storage_namespace_test.cc b/third_party/blink/renderer/modules/storage/storage_namespace_test.cc
index 31dd9b6..24ed63a 100644
--- a/third_party/blink/renderer/modules/storage/storage_namespace_test.cc
+++ b/third_party/blink/renderer/modules/storage/storage_namespace_test.cc
@@ -22,16 +22,11 @@
     : public mojom::blink::StoragePartitionService {
  public:
   void OpenLocalStorage(const scoped_refptr<const SecurityOrigin>& origin,
-                        mojom::blink::StorageAreaRequest request,
-                        OpenLocalStorageCallback callback) override {
-    std::move(callback).Run();
-  }
+                        mojom::blink::StorageAreaRequest request) override {}
 
-  void OpenSessionStorage(const String& namespace_id,
-                          mojom::blink::SessionStorageNamespaceRequest request,
-                          OpenSessionStorageCallback callback) override {
-    std::move(callback).Run();
-  }
+  void OpenSessionStorage(
+      const String& namespace_id,
+      mojom::blink::SessionStorageNamespaceRequest request) override {}
 };
 
 }  // namespace
diff --git a/third_party/blink/renderer/modules/webaudio/audio_context.cc b/third_party/blink/renderer/modules/webaudio/audio_context.cc
index 53c7875..6ab33f2 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_context.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_context.cc
@@ -68,8 +68,14 @@
         WebAudioLatencyHint(context_options->latencyHint().GetAsDouble());
   }
 
+  base::Optional<float> sample_rate;
+  if (context_options->hasSampleRate()) {
+    sample_rate = context_options->sampleRate();
+  }
+
   AudioContext* audio_context =
-      MakeGarbageCollected<AudioContext>(document, latency_hint);
+      MakeGarbageCollected<AudioContext>(document, latency_hint, sample_rate);
+  ++g_hardware_context_count;
   audio_context->UpdateStateIfNeeded();
 
   if (!audio_utilities::IsValidAudioBufferSampleRate(
@@ -97,7 +103,6 @@
     audio_context->StartRendering();
     audio_context->SetContextState(kRunning);
   }
-  ++g_hardware_context_count;
 #if DEBUG_AUDIONODE_REFERENCES
   fprintf(stderr, "[%16p]: AudioContext::AudioContext(): %u #%u\n",
           audio_context, audio_context->context_id_, g_hardware_context_count);
@@ -117,10 +122,12 @@
 }
 
 AudioContext::AudioContext(Document& document,
-                           const WebAudioLatencyHint& latency_hint)
+                           const WebAudioLatencyHint& latency_hint,
+                           base::Optional<float> sample_rate)
     : BaseAudioContext(&document, kRealtimeContext),
       context_id_(g_context_id++) {
-  destination_node_ = RealtimeAudioDestinationNode::Create(this, latency_hint);
+  destination_node_ =
+      RealtimeAudioDestinationNode::Create(this, latency_hint, sample_rate);
 
   switch (GetAutoplayPolicy()) {
     case AutoplayPolicy::Type::kNoUserGestureRequired:
diff --git a/third_party/blink/renderer/modules/webaudio/audio_context.h b/third_party/blink/renderer/modules/webaudio/audio_context.h
index a99e57bb..1a64258 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_context.h
+++ b/third_party/blink/renderer/modules/webaudio/audio_context.h
@@ -37,7 +37,9 @@
                               const AudioContextOptions*,
                               ExceptionState&);
 
-  AudioContext(Document&, const WebAudioLatencyHint&);
+  AudioContext(Document&,
+               const WebAudioLatencyHint&,
+               base::Optional<float> sample_rate);
   ~AudioContext() override;
   void Trace(blink::Visitor*) override;
 
diff --git a/third_party/blink/renderer/modules/webaudio/audio_context_options.idl b/third_party/blink/renderer/modules/webaudio/audio_context_options.idl
index bfab28c..68c281a 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_context_options.idl
+++ b/third_party/blink/renderer/modules/webaudio/audio_context_options.idl
@@ -8,4 +8,5 @@
     // seconds, without taking into account double buffering (same as
     // AudioContext.baseLatency).
     (AudioContextLatencyCategory or double) latencyHint = "interactive";
+    float sampleRate;
 };
diff --git a/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.cc b/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.cc
index cb71afb1..1449ddb 100644
--- a/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.cc
@@ -39,17 +39,20 @@
 namespace blink {
 
 scoped_refptr<RealtimeAudioDestinationHandler>
-RealtimeAudioDestinationHandler::Create(
-    AudioNode& node,
-    const WebAudioLatencyHint& latency_hint) {
+RealtimeAudioDestinationHandler::Create(AudioNode& node,
+                                        const WebAudioLatencyHint& latency_hint,
+                                        base::Optional<float> sample_rate) {
   return base::AdoptRef(
-      new RealtimeAudioDestinationHandler(node, latency_hint));
+      new RealtimeAudioDestinationHandler(node, latency_hint, sample_rate));
 }
 
 RealtimeAudioDestinationHandler::RealtimeAudioDestinationHandler(
     AudioNode& node,
-    const WebAudioLatencyHint& latency_hint)
-    : AudioDestinationHandler(node), latency_hint_(latency_hint) {
+    const WebAudioLatencyHint& latency_hint,
+    base::Optional<float> sample_rate)
+    : AudioDestinationHandler(node),
+      latency_hint_(latency_hint),
+      sample_rate_(sample_rate) {
   // Node-specific default channel count and mixing rules.
   channel_count_ = 2;
   SetInternalChannelCountMode(kExplicit);
@@ -235,8 +238,8 @@
 }
 
 void RealtimeAudioDestinationHandler::CreatePlatformDestination() {
-  platform_destination_ =
-      AudioDestination::Create(*this, ChannelCount(), latency_hint_);
+  platform_destination_ = AudioDestination::Create(*this, ChannelCount(),
+                                                   latency_hint_, sample_rate_);
 }
 
 void RealtimeAudioDestinationHandler::StartPlatformDestination() {
@@ -267,16 +270,19 @@
 
 RealtimeAudioDestinationNode::RealtimeAudioDestinationNode(
     AudioContext& context,
-    const WebAudioLatencyHint& latency_hint)
+    const WebAudioLatencyHint& latency_hint,
+    base::Optional<float> sample_rate)
     : AudioDestinationNode(context) {
-  SetHandler(RealtimeAudioDestinationHandler::Create(*this, latency_hint));
+  SetHandler(RealtimeAudioDestinationHandler::Create(*this, latency_hint,
+                                                     sample_rate));
 }
 
 RealtimeAudioDestinationNode* RealtimeAudioDestinationNode::Create(
     AudioContext* context,
-    const WebAudioLatencyHint& latency_hint) {
-  return MakeGarbageCollected<RealtimeAudioDestinationNode>(*context,
-                                                            latency_hint);
+    const WebAudioLatencyHint& latency_hint,
+    base::Optional<float> sample_rate) {
+  return MakeGarbageCollected<RealtimeAudioDestinationNode>(
+      *context, latency_hint, sample_rate);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.h b/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.h
index 55381abb..e495ec6d 100644
--- a/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.h
+++ b/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.h
@@ -43,7 +43,8 @@
  public:
   static scoped_refptr<RealtimeAudioDestinationHandler> Create(
       AudioNode&,
-      const WebAudioLatencyHint&);
+      const WebAudioLatencyHint&,
+      base::Optional<float> sample_rate);
   ~RealtimeAudioDestinationHandler() override;
 
   // For AudioHandler.
@@ -80,7 +81,8 @@
 
  private:
   explicit RealtimeAudioDestinationHandler(AudioNode&,
-                                           const WebAudioLatencyHint&);
+                                           const WebAudioLatencyHint&,
+                                           base::Optional<float> sample_rate);
 
   void CreatePlatformDestination();
   void StartPlatformDestination();
@@ -90,17 +92,22 @@
 
   // Holds the audio device thread that runs the real time audio context.
   scoped_refptr<AudioDestination> platform_destination_;
+
+  base::Optional<float> sample_rate_;
 };
 
 // -----------------------------------------------------------------------------
 
 class RealtimeAudioDestinationNode final : public AudioDestinationNode {
  public:
-  static RealtimeAudioDestinationNode* Create(AudioContext*,
-                                              const WebAudioLatencyHint&);
+  static RealtimeAudioDestinationNode* Create(
+      AudioContext*,
+      const WebAudioLatencyHint&,
+      base::Optional<float> sample_rate);
 
   explicit RealtimeAudioDestinationNode(AudioContext&,
-                                        const WebAudioLatencyHint&);
+                                        const WebAudioLatencyHint&,
+                                        base::Optional<float> sample_rate);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/xr/xr_input_source.cc b/third_party/blink/renderer/modules/xr/xr_input_source.cc
index 125a9b23..61df35142 100644
--- a/third_party/blink/renderer/modules/xr/xr_input_source.cc
+++ b/third_party/blink/renderer/modules/xr/xr_input_source.cc
@@ -42,7 +42,7 @@
 
   switch (handedness_) {
     case kHandNone:
-      handedness_string_ = "";
+      handedness_string_ = "none";
       return;
     case kHandLeft:
       handedness_string_ = "left";
@@ -50,6 +50,9 @@
     case kHandRight:
       handedness_string_ = "right";
       return;
+    case kHandUninitialized:
+      NOTREACHED() << "Cannot set handedness to uninitialized";
+      return;
   }
 
   NOTREACHED() << "Unknown handedness: " << handedness_;
diff --git a/third_party/blink/renderer/modules/xr/xr_input_source.h b/third_party/blink/renderer/modules/xr/xr_input_source.h
index 1b93636..42f6445 100644
--- a/third_party/blink/renderer/modules/xr/xr_input_source.h
+++ b/third_party/blink/renderer/modules/xr/xr_input_source.h
@@ -19,7 +19,12 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
-  enum Handedness { kHandNone = 0, kHandLeft = 1, kHandRight = 2 };
+  enum Handedness {
+    kHandUninitialized = -1,
+    kHandNone = 0,
+    kHandLeft = 1,
+    kHandRight = 2
+  };
   enum TargetRayMode { kGaze = 1, kTrackedPointer = 2, kScreen = 3 };
 
   XRInputSource(XRSession*, uint32_t source_id);
@@ -51,7 +56,7 @@
   const Member<XRSession> session_;
   const uint32_t source_id_;
 
-  Handedness handedness_;
+  Handedness handedness_ = kHandUninitialized;
   String handedness_string_;
 
   TargetRayMode target_ray_mode_;
diff --git a/third_party/blink/renderer/modules/xr/xr_input_source.idl b/third_party/blink/renderer/modules/xr/xr_input_source.idl
index bd3a7bf..a018bd8 100644
--- a/third_party/blink/renderer/modules/xr/xr_input_source.idl
+++ b/third_party/blink/renderer/modules/xr/xr_input_source.idl
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 enum XRHandedness {
+  "none",
   "left",
   "right"
 };
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 532abc1..77b4b56 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -376,6 +376,8 @@
     "audio/iir_filter.h",
     "audio/mac/fft_frame_mac.cc",
     "audio/mac/vector_math_mac.h",
+    "audio/media_multi_channel_resampler.cc",
+    "audio/media_multi_channel_resampler.h",
     "audio/multi_channel_resampler.cc",
     "audio/multi_channel_resampler.h",
     "audio/panner.cc",
@@ -1651,6 +1653,7 @@
     "animation/compositor_float_animation_curve_test.cc",
     "animation/compositor_keyframe_model_test.cc",
     "animation/timing_function_test.cc",
+    "audio/audio_destination_test.cc",
     "audio/push_pull_fifo_multithread_test.cc",
     "audio/push_pull_fifo_test.cc",
     "audio/vector_math_test.cc",
diff --git a/third_party/blink/renderer/platform/audio/DEPS b/third_party/blink/renderer/platform/audio/DEPS
index e7d214c5..d245efd9 100644
--- a/third_party/blink/renderer/platform/audio/DEPS
+++ b/third_party/blink/renderer/platform/audio/DEPS
@@ -6,6 +6,8 @@
     "+third_party/blink/renderer/platform/audio",
 
     # Dependencies.
+    "+media/base/audio_bus.h",
+    "+media/base/multi_channel_resampler.h",
     "+third_party/blink/renderer/platform/cpu/mips/common_macros_msa.h",
     "+third_party/blink/renderer/platform/cross_thread_functional.h",
     "+third_party/blink/renderer/platform/geometry/float_point_3d.h",
diff --git a/third_party/blink/renderer/platform/audio/audio_destination.cc b/third_party/blink/renderer/platform/audio/audio_destination.cc
index ad35bf7..895bc61 100644
--- a/third_party/blink/renderer/platform/audio/audio_destination.cc
+++ b/third_party/blink/renderer/platform/audio/audio_destination.cc
@@ -32,6 +32,7 @@
 #include <memory>
 #include <utility>
 
+#include "media/base/audio_bus.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/web_audio_latency_hint.h"
 #include "third_party/blink/renderer/platform/audio/audio_utilities.h"
@@ -55,14 +56,16 @@
 scoped_refptr<AudioDestination> AudioDestination::Create(
     AudioIOCallback& callback,
     unsigned number_of_output_channels,
-    const WebAudioLatencyHint& latency_hint) {
-  return base::AdoptRef(
-      new AudioDestination(callback, number_of_output_channels, latency_hint));
+    const WebAudioLatencyHint& latency_hint,
+    base::Optional<float> context_sample_rate) {
+  return base::AdoptRef(new AudioDestination(
+      callback, number_of_output_channels, latency_hint, context_sample_rate));
 }
 
 AudioDestination::AudioDestination(AudioIOCallback& callback,
                                    unsigned number_of_output_channels,
-                                   const WebAudioLatencyHint& latency_hint)
+                                   const WebAudioLatencyHint& latency_hint,
+                                   base::Optional<float> context_sample_rate)
     : number_of_output_channels_(number_of_output_channels),
       play_state_(PlayState::kStopped),
       fifo_(
@@ -96,6 +99,26 @@
   if (!CheckBufferSize()) {
     NOTREACHED();
   }
+
+  if (context_sample_rate.has_value() &&
+      context_sample_rate.value() != web_audio_device_->SampleRate()) {
+    double scale_factor =
+        context_sample_rate.value() / web_audio_device_->SampleRate();
+    resampler_.reset(new MediaMultiChannelResampler(
+        MaxChannelCount(), scale_factor, audio_utilities::kRenderQuantumFrames,
+        ConvertToBaseCallback(
+            CrossThreadBind(&AudioDestination::ProvideResamplerInput,
+                            CrossThreadUnretained(this)))));
+    resampler_bus_ =
+        media::AudioBus::CreateWrapper(render_bus_->NumberOfChannels());
+    for (unsigned int i = 0; i < render_bus_->NumberOfChannels(); ++i) {
+      resampler_bus_->SetChannelData(i, render_bus_->Channel(i)->MutableData());
+    }
+    resampler_bus_->set_frames(render_bus_->length());
+    context_sample_rate_ = context_sample_rate.value();
+  } else {
+    context_sample_rate_ = web_audio_device_->SampleRate();
+  }
 }
 
 AudioDestination::~AudioDestination() {
@@ -161,17 +184,15 @@
                delay_timestamp);
 
   frames_elapsed_ -= std::min(frames_elapsed_, prior_frames_skipped);
-  AudioIOPosition output_position;
-  output_position.position =
+  output_position_.position =
       frames_elapsed_ / static_cast<double>(web_audio_device_->SampleRate()) -
       delay;
-  output_position.timestamp = delay_timestamp;
+  output_position_.timestamp = delay_timestamp;
 
   base::TimeTicks callback_request = base::TimeTicks::Now();
-  AudioIOCallbackMetric metric;
-  metric.callback_interval =
+  metric_.callback_interval =
       (callback_request - previous_callback_request_).InSecondsF();
-  metric.render_duration = previous_render_duration_.InSecondsF();
+  metric_.render_duration = previous_render_duration_.InSecondsF();
 
   for (size_t pushed_frames = 0; pushed_frames < frames_to_render;
        pushed_frames += audio_utilities::kRenderQuantumFrames) {
@@ -180,18 +201,23 @@
     // using the elapsed time from the moment it was initially obtained.
     if (callback_buffer_size_ > audio_utilities::kRenderQuantumFrames * 2) {
       double delta = (base::TimeTicks::Now() - callback_request).InSecondsF();
-      output_position.position += delta;
-      output_position.timestamp += delta;
+      output_position_.position += delta;
+      output_position_.timestamp += delta;
     }
 
     // Some implementations give only rough estimation of |delay| so
     // we might have negative estimation |outputPosition| value.
-    if (output_position.position < 0.0)
-      output_position.position = 0.0;
+    if (output_position_.position < 0.0)
+      output_position_.position = 0.0;
 
-    // Process WebAudio graph and push the rendered output to FIFO.
-    callback_.Render(render_bus_.get(), audio_utilities::kRenderQuantumFrames,
-                     output_position, metric);
+    if (resampler_) {
+      resampler_->Resample(audio_utilities::kRenderQuantumFrames,
+                           resampler_bus_.get());
+    } else {
+      // Process WebAudio graph and push the rendered output to FIFO.
+      callback_.Render(render_bus_.get(), audio_utilities::kRenderQuantumFrames,
+                       output_position_, metric_);
+    }
 
     fifo_->Push(render_bus_.get());
   }
@@ -306,4 +332,10 @@
             kFIFOSize);
   return is_buffer_size_valid;
 }
+
+void AudioDestination::ProvideResamplerInput(int resampler_frame_delay,
+                                             AudioBus* dest) {
+  callback_.Render(dest, audio_utilities::kRenderQuantumFrames,
+                   output_position_, metric_);
+}
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/audio/audio_destination.h b/third_party/blink/renderer/platform/audio/audio_destination.h
index 2abf9aa..a19445e 100644
--- a/third_party/blink/renderer/platform/audio/audio_destination.h
+++ b/third_party/blink/renderer/platform/audio/audio_destination.h
@@ -37,6 +37,7 @@
 #include "third_party/blink/public/platform/web_vector.h"
 #include "third_party/blink/renderer/platform/audio/audio_bus.h"
 #include "third_party/blink/renderer/platform/audio/audio_io_callback.h"
+#include "third_party/blink/renderer/platform/audio/media_multi_channel_resampler.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "third_party/blink/renderer/platform/wtf/thread_safe_ref_counted.h"
@@ -64,13 +65,15 @@
  public:
   AudioDestination(AudioIOCallback&,
                    unsigned number_of_output_channels,
-                   const WebAudioLatencyHint&);
+                   const WebAudioLatencyHint&,
+                   base::Optional<float> context_sample_rate);
   ~AudioDestination() override;
 
   static scoped_refptr<AudioDestination> Create(
       AudioIOCallback&,
       unsigned number_of_output_channels,
-      const WebAudioLatencyHint&);
+      const WebAudioLatencyHint&,
+      base::Optional<float> context_sample_rate);
 
   // The actual render function (WebAudioDevice::RenderCallback) isochronously
   // invoked by the media renderer. This is never called after Stop() is called.
@@ -102,8 +105,8 @@
   uint32_t CallbackBufferSize() const;
   bool IsPlaying();
 
-  // TODO(hongchan): this should not be called by the rendering thread.
-  double SampleRate() const { return web_audio_device_->SampleRate(); }
+  // This is the context sample rate, not the device one.
+  double SampleRate() const { return context_sample_rate_; }
 
   // Returns the audio buffer size in frames used by the underlying audio
   // hardware.
@@ -114,6 +117,9 @@
   static uint32_t MaxChannelCount();
 
  private:
+  // Provide input to the resampler (if used).
+  void ProvideResamplerInput(int resampler_frame_delay, AudioBus* dest);
+
   enum class PlayState { kStopped, kPlaying, kPaused };
 
   // Check if the buffer size chosen by the WebAudioDevice is too large.
@@ -155,6 +161,18 @@
   // Accessed by rendering thread.
   size_t frames_elapsed_;
 
+  // The sample rate used for rendering the Web Audio graph.
+  float context_sample_rate_;
+
+  // Used for resampling if the Web Audio sample rate differs from the platform
+  // one.
+  std::unique_ptr<MediaMultiChannelResampler> resampler_;
+  std::unique_ptr<media::AudioBus> resampler_bus_;
+
+  // Required for RequestRender and also in the resampling callback (if used).
+  AudioIOPosition output_position_;
+  AudioIOCallbackMetric metric_;
+
   DISALLOW_COPY_AND_ASSIGN(AudioDestination);
 };
 
diff --git a/third_party/blink/renderer/platform/audio/audio_destination_test.cc b/third_party/blink/renderer/platform/audio/audio_destination_test.cc
new file mode 100644
index 0000000..ffab6ad
--- /dev/null
+++ b/third_party/blink/renderer/platform/audio/audio_destination_test.cc
@@ -0,0 +1,109 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/platform/audio/audio_destination.h"
+
+#include <memory>
+#include <vector>
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/platform/web_audio_device.h"
+#include "third_party/blink/public/platform/web_audio_latency_hint.h"
+#include "third_party/blink/renderer/platform/audio/audio_io_callback.h"
+#include "third_party/blink/renderer/platform/audio/audio_utilities.h"
+#include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
+
+namespace blink {
+
+namespace {
+
+class MockWebAudioDevice : public WebAudioDevice {
+ public:
+  explicit MockWebAudioDevice(double sample_rate, int frames_per_buffer)
+      : sample_rate_(sample_rate), frames_per_buffer_(frames_per_buffer) {}
+
+  void Start() override {}
+  void Stop() override {}
+  void Pause() override {}
+  void Resume() override {}
+  double SampleRate() override { return sample_rate_; }
+  int FramesPerBuffer() override { return frames_per_buffer_; }
+
+ private:
+  double sample_rate_;
+  int frames_per_buffer_;
+};
+
+class TestPlatform : public TestingPlatformSupport {
+ public:
+  std::unique_ptr<WebAudioDevice> CreateAudioDevice(
+      unsigned number_of_input_channels,
+      unsigned number_of_channels,
+      const WebAudioLatencyHint& latency_hint,
+      WebAudioDevice::RenderCallback*,
+      const WebString& device_id) override {
+    return std::make_unique<MockWebAudioDevice>(AudioHardwareSampleRate(),
+                                                AudioHardwareBufferSize());
+  }
+
+  double AudioHardwareSampleRate() override { return 44100; }
+  size_t AudioHardwareBufferSize() override { return 512; }
+  unsigned AudioHardwareOutputChannels() override { return 2; }
+};
+
+class AudioCallback : public blink::AudioIOCallback {
+ public:
+  void Render(AudioBus* destination_bus,
+              uint32_t frames_to_process,
+              const AudioIOPosition& output_position,
+              const AudioIOCallbackMetric& metric) override {
+    frames_processed_ += frames_to_process;
+  }
+
+  AudioCallback() : frames_processed_(0) {}
+  int frames_processed_;
+};
+
+void CountWASamplesProcessedForRate(base::Optional<float> sample_rate) {
+  WebAudioLatencyHint latency_hint(WebAudioLatencyHint::kCategoryInteractive);
+  AudioCallback callback;
+
+  const int channel_count = Platform::Current()->AudioHardwareOutputChannels();
+  const size_t request_frames = Platform::Current()->AudioHardwareBufferSize();
+
+  scoped_refptr<AudioDestination> destination = AudioDestination::Create(
+      callback, channel_count, latency_hint, sample_rate);
+
+  std::vector<float> channels[channel_count];
+  WebVector<float*> dest_data(static_cast<size_t>(channel_count));
+  for (int i = 0; i < channel_count; ++i) {
+    channels[i].resize(request_frames);
+    dest_data[i] = channels[i].data();
+  }
+  destination->Render(dest_data, request_frames, 0, 0, 0);
+
+  int exact_frames_required =
+      std::ceil(request_frames * destination->SampleRate() /
+                Platform::Current()->AudioHardwareSampleRate());
+  int expected_frames_processed =
+      std::ceil(exact_frames_required /
+                static_cast<double>(audio_utilities::kRenderQuantumFrames)) *
+      audio_utilities::kRenderQuantumFrames;
+
+  EXPECT_EQ(expected_frames_processed, callback.frames_processed_);
+}
+
+TEST(AudioDestinationTest, ResamplingTest) {
+  ScopedTestingPlatformSupport<TestPlatform> platform;
+
+  CountWASamplesProcessedForRate(base::Optional<float>());
+  CountWASamplesProcessedForRate(8000);
+  CountWASamplesProcessedForRate(24000);
+  CountWASamplesProcessedForRate(44100);
+  CountWASamplesProcessedForRate(48000);
+  CountWASamplesProcessedForRate(384000);
+}
+
+}  // namespace
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/audio/media_multi_channel_resampler.cc b/third_party/blink/renderer/platform/audio/media_multi_channel_resampler.cc
new file mode 100644
index 0000000..4db2339
--- /dev/null
+++ b/third_party/blink/renderer/platform/audio/media_multi_channel_resampler.cc
@@ -0,0 +1,44 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/platform/audio/media_multi_channel_resampler.h"
+
+#include <memory>
+#include "base/bind.h"
+#include "media/base/audio_bus.h"
+#include "third_party/blink/renderer/platform/audio/audio_bus.h"
+
+namespace blink {
+
+MediaMultiChannelResampler::MediaMultiChannelResampler(
+    int channels,
+    double io_sample_rate_ratio,
+    size_t request_frames,
+    const ReadCB& read_cb)
+    : read_cb_(read_cb) {
+  resampler_.reset(new media::MultiChannelResampler(
+      channels, io_sample_rate_ratio, request_frames,
+      base::BindRepeating(&MediaMultiChannelResampler::ProvideResamplerInput,
+                          base::Unretained(this))));
+}
+
+void MediaMultiChannelResampler::Resample(int frames,
+                                          media::AudioBus* audio_bus) {
+  resampler_->Resample(audio_bus->frames(), audio_bus);
+}
+
+void MediaMultiChannelResampler::ProvideResamplerInput(
+    int resampler_frame_delay,
+    media::AudioBus* dest) {
+  // Create a blink::AudioBus wrapper around the memory provided by the
+  // media::AudioBus.
+  scoped_refptr<AudioBus> bus =
+      AudioBus::Create(dest->channels(), dest->frames(), false);
+  for (int i = 0; i < dest->channels(); ++i) {
+    bus->SetChannelMemory(i, dest->channel(i), dest->frames());
+  }
+  read_cb_.Run(resampler_frame_delay, bus.get());
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/audio/media_multi_channel_resampler.h b/third_party/blink/renderer/platform/audio/media_multi_channel_resampler.h
new file mode 100644
index 0000000..8fc5c04
--- /dev/null
+++ b/third_party/blink/renderer/platform/audio/media_multi_channel_resampler.h
@@ -0,0 +1,65 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_PLATFORM_AUDIO_MEDIA_MULTI_CHANNEL_RESAMPLER_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_AUDIO_MEDIA_MULTI_CHANNEL_RESAMPLER_H_
+
+#include <memory>
+#include "base/callback.h"
+#include "base/macros.h"
+#include "media/base/multi_channel_resampler.h"
+#include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+
+namespace media {
+class AudioBus;
+}  // namespace media
+
+namespace blink {
+
+class AudioBus;
+
+// This is a simple wrapper around the MultiChannelResampler provided by the
+// media layer.
+class PLATFORM_EXPORT MediaMultiChannelResampler {
+  USING_FAST_MALLOC(MediaMultiChannelResampler);
+
+  // Callback type for providing more data into the resampler.  Expects AudioBus
+  // to be completely filled with data upon return; zero padded if not enough
+  // frames are available to satisfy the request.  |frame_delay| is the number
+  // of output frames already processed and can be used to estimate delay.
+  typedef base::RepeatingCallback<void(int frame_delay, AudioBus* audio_bus)>
+      ReadCB;
+
+ public:
+  // Constructs a MultiChannelResampler with the specified |read_cb|, which is
+  // used to acquire audio data for resampling.  |io_sample_rate_ratio| is the
+  // ratio of input / output sample rates.  |request_frames| is the size in
+  // frames of the AudioBus to be filled by |read_cb|.
+  MediaMultiChannelResampler(int channels,
+                             double io_sample_rate_ratio,
+                             size_t request_frames,
+                             const ReadCB& read_cb);
+
+  // Resamples |frames| of data from |read_cb_| into AudioBus.
+  void Resample(int frames, media::AudioBus* audio_bus);
+
+ private:
+  // Wrapper method used to provide input to the media::MultiChannelResampler
+  // with a media::AudioBus rather than a blink::AudioBus.
+  void ProvideResamplerInput(int resampler_frame_delay, media::AudioBus* dest);
+
+  // The resampler being wrapped by this class.
+  std::unique_ptr<media::MultiChannelResampler> resampler_;
+
+  // The callback using a blink::AudioBus that will be called by
+  // ProvideResamplerInput().
+  ReadCB read_cb_;
+
+  DISALLOW_COPY_AND_ASSIGN(MediaMultiChannelResampler);
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_AUDIO_MEDIA_MULTI_CHANNEL_RESAMPLER_H_
diff --git a/third_party/blink/renderer/platform/bindings/v8_per_isolate_data.cc b/third_party/blink/renderer/platform/bindings/v8_per_isolate_data.cc
index ea8cbfa..3dfc6d43 100644
--- a/third_party/blink/renderer/platform/bindings/v8_per_isolate_data.cc
+++ b/third_party/blink/renderer/platform/bindings/v8_per_isolate_data.cc
@@ -67,7 +67,7 @@
   CHECK(!ScriptForbiddenScope::IsScriptForbidden());
 }
 
-static void MicrotasksCompletedCallback(v8::Isolate* isolate) {
+static void MicrotasksCompletedCallback(v8::Isolate* isolate, void*) {
   V8PerIsolateData::From(isolate)->RunEndOfScopeTasks();
 }
 
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 2e38732..75467c6 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
@@ -989,10 +989,12 @@
   for (const auto& layer : layers) {
     bool found_backdrop_filter = false;
     for (auto* effect = effect_tree.Node(layer->effect_tree_index());
+         // TODO(crbug.com/938679): Check
+         // has_potential_backdrop_filter_animation when we have it.
          !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 &&
+      if ((effect->opacity != 1.f || effect->has_potential_opacity_animation) &&
           (!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
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 8162b9d..134af9b 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
@@ -3289,14 +3289,23 @@
   EXPECT_TRUE(cc_transform_node->pre_local.IsIdentity());
 }
 
-enum { kNoRenderSurface, kHasRenderSurface };
+enum {
+  kNoRenderSurface = 0,
+  kHasRenderSurface = 1 << 0,
+  kHasOpacityAnimation = 1 << 1,
+  kHasFilterAnimation = 1 << 2,
+};
 
-#define EXPECT_OPACITY(effect_id, expected_opacity, expected_render_surface) \
-  do {                                                                       \
-    const auto* effect = GetPropertyTrees().effect_tree.Node(effect_id);     \
-    EXPECT_EQ(expected_opacity, effect->opacity);                            \
-    EXPECT_EQ(expected_render_surface == kHasRenderSurface,                  \
-              effect->has_render_surface);                                   \
+#define EXPECT_OPACITY(effect_id, expected_opacity, expected_flags)      \
+  do {                                                                   \
+    const auto* effect = GetPropertyTrees().effect_tree.Node(effect_id); \
+    EXPECT_EQ(expected_opacity, effect->opacity);                        \
+    EXPECT_EQ(!!((expected_flags)&kHasRenderSurface),                    \
+              effect->has_render_surface);                               \
+    EXPECT_EQ(!!((expected_flags)&kHasOpacityAnimation),                 \
+              effect->has_potential_opacity_animation);                  \
+    EXPECT_EQ(!!((expected_flags)&kHasFilterAnimation),                  \
+              effect->has_potential_filter_animation);                   \
   } while (false)
 
 TEST_P(PaintArtifactCompositorTest, OpacityRenderSurfaces) {
@@ -3309,16 +3318,16 @@
   //   L0  L1         L5
   auto e = CreateOpacityEffect(e0(), 0.1f);
   auto a = CreateOpacityEffect(*e, 0.2f);
-  auto b =
-      CreateOpacityEffect(*e, 0.3f, CompositingReason::kActiveOpacityAnimation);
-  auto c =
-      CreateOpacityEffect(*e, 0.4f, CompositingReason::kActiveOpacityAnimation);
-  auto aa =
-      CreateOpacityEffect(*a, 0.5f, CompositingReason::kActiveOpacityAnimation);
-  auto ab =
-      CreateOpacityEffect(*a, 0.6f, CompositingReason::kActiveOpacityAnimation);
-  auto ca =
-      CreateOpacityEffect(*c, 0.7f, CompositingReason::kActiveOpacityAnimation);
+  auto b = CreateOpacityEffect(*e, 0.3f,
+                               CompositingReason::kWillChangeCompositingHint);
+  auto c = CreateOpacityEffect(*e, 0.4f,
+                               CompositingReason::kWillChangeCompositingHint);
+  auto aa = CreateOpacityEffect(*a, 0.5f,
+                                CompositingReason::kWillChangeCompositingHint);
+  auto ab = CreateOpacityEffect(*a, 0.6f,
+                                CompositingReason::kWillChangeCompositingHint);
+  auto ca = CreateOpacityEffect(*c, 0.7f,
+                                CompositingReason::kWillChangeCompositingHint);
   auto t = CreateTransform(t0(), TransformationMatrix().Rotate(90),
                            FloatPoint3D(), CompositingReason::k3DTransform);
 
@@ -3365,6 +3374,70 @@
                  kHasRenderSurface);
 }
 
+TEST_P(PaintArtifactCompositorTest, OpacityAnimationRenderSurfaces) {
+  // The topologies of the effect tree and layer tree are the same as
+  // OpacityRencerSurfaces, except that the layers all have 1.f opacity and
+  // active opacity animations.
+  //            e
+  //         /  |  \
+  //       a    b    c -- L4
+  //     / \   / \    \
+  //    aa ab L2 L3   ca          (L = layer)
+  //    |   |          |
+  //   L0  L1         L5
+  auto e = CreateCompositedAnimatingOpacityEffect(e0(), 1.f);
+  auto a = CreateCompositedAnimatingOpacityEffect(*e, 1.f);
+  auto b = CreateCompositedAnimatingOpacityEffect(*e, 1.f);
+  auto c = CreateCompositedAnimatingOpacityEffect(*e, 1.f);
+  auto aa = CreateCompositedAnimatingOpacityEffect(*a, 1.f);
+  auto ab = CreateCompositedAnimatingOpacityEffect(*a, 1.f);
+  auto ca = CreateCompositedAnimatingOpacityEffect(*c, 1.f);
+  auto t = CreateTransform(t0(), TransformationMatrix().Rotate(90),
+                           FloatPoint3D(), CompositingReason::k3DTransform);
+
+  TestPaintArtifact artifact;
+  FloatRect r(150, 150, 100, 100);
+  artifact.Chunk(t0(), c0(), *aa).RectDrawing(r, Color::kWhite);
+  artifact.Chunk(t0(), c0(), *ab).RectDrawing(r, Color::kWhite);
+  artifact.Chunk(t0(), c0(), *b).RectDrawing(r, Color::kWhite);
+  artifact.Chunk(*t, c0(), *b).RectDrawing(r, Color::kWhite);
+  artifact.Chunk(t0(), c0(), *c).RectDrawing(r, Color::kWhite);
+  artifact.Chunk(t0(), c0(), *ca).RectDrawing(r, Color::kWhite);
+  Update(artifact.Build());
+  ASSERT_EQ(6u, ContentLayerCount());
+
+  int effect_ids[6];
+  for (size_t i = 0; i < ContentLayerCount(); i++)
+    effect_ids[i] = ContentLayerAt(i)->effect_tree_index();
+
+  // Effects of layer 0, 1, 5 each has one compositing layer, so don't have
+  // render surface.
+  EXPECT_OPACITY(effect_ids[0], 1.f, kHasOpacityAnimation);
+  EXPECT_OPACITY(effect_ids[1], 1.f, kHasOpacityAnimation);
+  EXPECT_OPACITY(effect_ids[5], 1.f, kHasOpacityAnimation);
+
+  // Layer 2 and 3 have the same effect state. The effect has render surface
+  // because it has two compositing layers.
+  EXPECT_EQ(effect_ids[2], effect_ids[3]);
+  EXPECT_OPACITY(effect_ids[2], 1.f, kHasRenderSurface | kHasOpacityAnimation);
+
+  // Effect |a| has two indirect compositing layers, so has render surface.
+  const auto& effect_tree = GetPropertyTrees().effect_tree;
+  int id_a = effect_tree.Node(effect_ids[0])->parent_id;
+  EXPECT_EQ(id_a, effect_tree.Node(effect_ids[1])->parent_id);
+  EXPECT_OPACITY(id_a, 1.f, kHasRenderSurface | kHasOpacityAnimation);
+
+  // Effect |c| has one direct and one indirect compositing layers, so has
+  // render surface.
+  EXPECT_OPACITY(effect_ids[4], 1.f, kHasRenderSurface | kHasOpacityAnimation);
+
+  // Though all children of effect |e| have render surfaces and |e| doesn't
+  // control any compositing layer, we still give it a render surface for
+  // simplicity of the algorithm.
+  EXPECT_OPACITY(effect_tree.Node(effect_ids[4])->parent_id, 1.f,
+                 kHasRenderSurface | kHasOpacityAnimation);
+}
+
 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
@@ -3413,10 +3486,10 @@
 TEST_P(PaintArtifactCompositorTest, OpacityIndirectlyAffectingTwoLayers) {
   auto opacity = CreateOpacityEffect(e0(), 0.5f);
   auto child_composited_effect = CreateOpacityEffect(
-      *opacity, 1.f, CompositingReason::kActiveOpacityAnimation);
+      *opacity, 1.f, CompositingReason::kWillChangeCompositingHint);
   auto grandchild_composited_effect =
       CreateOpacityEffect(*child_composited_effect, 1.f,
-                          CompositingReason::kActiveOpacityAnimation);
+                          CompositingReason::kWillChangeCompositingHint);
 
   TestPaintArtifact artifact;
   artifact.Chunk(t0(), c0(), *child_composited_effect)
@@ -3431,10 +3504,136 @@
   EXPECT_OPACITY(layer0_effect_id, 1.f, kNoRenderSurface);
   int layer1_effect_id = ContentLayerAt(1)->effect_tree_index();
   EXPECT_OPACITY(layer1_effect_id, 1.f, kNoRenderSurface);
+  // |opacity| affects both layer0 and layer1 which don't have render surfaces,
+  // so it should have a render surface.
   int opacity_id = effect_tree.Node(layer0_effect_id)->parent_id;
   EXPECT_OPACITY(opacity_id, 0.5f, kHasRenderSurface);
 }
 
+TEST_P(PaintArtifactCompositorTest,
+       OpacityIndirectlyAffectingTwoLayersWithOpacityAnimations) {
+  auto opacity = CreateCompositedAnimatingOpacityEffect(e0(), 1.f);
+  auto child_composited_effect =
+      CreateCompositedAnimatingOpacityEffect(*opacity, 1.f);
+  auto grandchild_composited_effect =
+      CreateCompositedAnimatingOpacityEffect(*child_composited_effect, 1.f);
+
+  TestPaintArtifact artifact;
+  artifact.Chunk(t0(), c0(), *child_composited_effect)
+      .RectDrawing(FloatRect(150, 150, 100, 100), Color::kWhite);
+  artifact.Chunk(t0(), c0(), *grandchild_composited_effect)
+      .RectDrawing(FloatRect(150, 150, 100, 100), Color::kGray);
+  Update(artifact.Build());
+  ASSERT_EQ(2u, ContentLayerCount());
+
+  const auto& effect_tree = GetPropertyTrees().effect_tree;
+  // layer0's opacity animation needs a render surfafce because it affects
+  // both layer0 and layer1.
+  int layer0_effect_id = ContentLayerAt(0)->effect_tree_index();
+  EXPECT_OPACITY(layer0_effect_id, 1.f,
+                 kHasRenderSurface | kHasOpacityAnimation);
+  // layer1's opacity animation doesn't need a render surface because it
+  // affects layer1 only.
+  int layer1_effect_id = ContentLayerAt(1)->effect_tree_index();
+  EXPECT_OPACITY(layer1_effect_id, 1.f,
+                 kNoRenderSurface | kHasOpacityAnimation);
+  // Though |opacity| affects both layer0 and layer1, layer0's effect has
+  // render surface, so |opacity| doesn't need a render surface.
+  int opacity_id = effect_tree.Node(layer0_effect_id)->parent_id;
+  EXPECT_OPACITY(opacity_id, 1.f, kNoRenderSurface | kHasOpacityAnimation);
+}
+
+TEST_P(PaintArtifactCompositorTest, FilterCreatesRenderSurface) {
+  CompositorFilterOperations filter;
+  filter.AppendBlurFilter(5);
+  auto e1 = CreateFilterEffect(e0(), filter, FloatPoint(),
+                               CompositingReason::kWillChangeCompositingHint);
+  Update(TestPaintArtifact()
+             .Chunk(t0(), c0(), *e1)
+             .RectDrawing(FloatRect(150, 150, 100, 100), Color::kWhite)
+             .Build());
+  ASSERT_EQ(1u, ContentLayerCount());
+  EXPECT_OPACITY(ContentLayerAt(0)->effect_tree_index(), 1.f,
+                 kHasRenderSurface);
+}
+
+TEST_P(PaintArtifactCompositorTest,
+       FilterCompositedAnimationCreatesRenderSurface) {
+  EffectPaintPropertyNode::State state;
+  state.local_transform_space = &t0();
+  state.direct_compositing_reasons = CompositingReason::kActiveFilterAnimation;
+  state.is_running_filter_animation_on_compositor = true;
+  auto e1 = EffectPaintPropertyNode::Create(e0(), std::move(state));
+  Update(TestPaintArtifact()
+             .Chunk(t0(), c0(), *e1)
+             .RectDrawing(FloatRect(150, 150, 100, 100), Color::kWhite)
+             .Build());
+  ASSERT_EQ(1u, ContentLayerCount());
+  EXPECT_OPACITY(ContentLayerAt(0)->effect_tree_index(), 1.f,
+                 kHasRenderSurface | kHasFilterAnimation);
+}
+
+TEST_P(PaintArtifactCompositorTest,
+       FilterNonCompositedAnimationDoesNotCreateRenderSurface) {
+  EffectPaintPropertyNode::State state;
+  state.local_transform_space = &t0();
+  state.direct_compositing_reasons = CompositingReason::kActiveFilterAnimation;
+  auto e1 = EffectPaintPropertyNode::Create(e0(), std::move(state));
+  Update(TestPaintArtifact()
+             .Chunk(t0(), c0(), *e1)
+             .RectDrawing(FloatRect(150, 150, 100, 100), Color::kWhite)
+             .Build());
+  ASSERT_EQ(1u, ContentLayerCount());
+  EXPECT_OPACITY(ContentLayerAt(0)->effect_tree_index(), 1.f, kNoRenderSurface);
+}
+
+TEST_P(PaintArtifactCompositorTest, BackdropFilterCreatesRenderSurface) {
+  CompositorFilterOperations filter;
+  filter.AppendBlurFilter(5);
+  auto e1 =
+      CreateBackdropFilterEffect(e0(), filter, FloatPoint(),
+                                 CompositingReason::kWillChangeCompositingHint);
+  Update(TestPaintArtifact()
+             .Chunk(t0(), c0(), *e1)
+             .RectDrawing(FloatRect(150, 150, 100, 100), Color::kWhite)
+             .Build());
+  ASSERT_EQ(1u, ContentLayerCount());
+  EXPECT_OPACITY(ContentLayerAt(0)->effect_tree_index(), 1.f,
+                 kHasRenderSurface);
+}
+
+TEST_P(PaintArtifactCompositorTest,
+       BackdropFilterCompositedAnimationCreatesRenderSurface) {
+  EffectPaintPropertyNode::State state;
+  state.local_transform_space = &t0();
+  state.direct_compositing_reasons =
+      CompositingReason::kActiveBackdropFilterAnimation;
+  state.is_running_backdrop_filter_animation_on_compositor = true;
+  auto e1 = EffectPaintPropertyNode::Create(e0(), std::move(state));
+  Update(TestPaintArtifact()
+             .Chunk(t0(), c0(), *e1)
+             .RectDrawing(FloatRect(150, 150, 100, 100), Color::kWhite)
+             .Build());
+  ASSERT_EQ(1u, ContentLayerCount());
+  EXPECT_OPACITY(ContentLayerAt(0)->effect_tree_index(), 1.f,
+                 kHasRenderSurface);
+}
+
+TEST_P(PaintArtifactCompositorTest,
+       BackdropFilterNonCompositedAnimationCreatesRenderSurface) {
+  EffectPaintPropertyNode::State state;
+  state.local_transform_space = &t0();
+  state.direct_compositing_reasons =
+      CompositingReason::kActiveBackdropFilterAnimation;
+  auto e1 = EffectPaintPropertyNode::Create(e0(), std::move(state));
+  Update(TestPaintArtifact()
+             .Chunk(t0(), c0(), *e1)
+             .RectDrawing(FloatRect(150, 150, 100, 100), Color::kWhite)
+             .Build());
+  ASSERT_EQ(1u, ContentLayerCount());
+  EXPECT_OPACITY(ContentLayerAt(0)->effect_tree_index(), 1.f, kNoRenderSurface);
+}
+
 TEST_P(PaintArtifactCompositorTest, Non2dAxisAlignedClip) {
   auto rotate = CreateTransform(t0(), TransformationMatrix().Rotate(45));
   auto clip = CreateClip(c0(), *rotate, FloatRoundedRect(50, 50, 50, 50));
diff --git a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
index 9af76e0..e0ec83db 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
@@ -278,7 +278,7 @@
 
   // Set has_potential_animation in case we push property tree during an ongoing
   // animation. This condition should be kept consistent with cc.
-  if (transform_node.HasActiveTransformAnimation())
+  if (transform_node.IsRunningAnimationOnCompositor())
     compositor_node.has_potential_animation = true;
 
   // If this transform is a scroll offset translation, create the associated
@@ -733,7 +733,9 @@
   // a render surface. The render surface status of opacity-only effects will be
   // updated in PaintArtifactCompositor::UpdateRenderSurfaceForEffects().
   if (!next_effect.Filter().IsEmpty() ||
+      next_effect.IsRunningFilterAnimationOnCompositor() ||
       !next_effect.BackdropFilter().IsEmpty() ||
+      next_effect.IsRunningBackdropFilterAnimationOnCompositor() ||
       used_blend_mode != SkBlendMode::kSrcOver)
     effect_node.has_render_surface = true;
 
@@ -771,10 +773,14 @@
 
   // Set has_potential_xxx_animation in case we push property tree during
   // ongoing animations. The conditions should be kept consistent with cc.
-  if (next_effect.HasActiveOpacityAnimation())
+  if (next_effect.IsRunningOpacityAnimationOnCompositor())
     effect_node.has_potential_opacity_animation = true;
-  if (next_effect.HasActiveFilterAnimation())
+  if (next_effect.IsRunningFilterAnimationOnCompositor())
     effect_node.has_potential_filter_animation = true;
+  // TODO(crbug.com/938679): Set effect_node
+  // .has_potential_backdrop_filter_animation when we have it.
+  // if (next_effect.IsRunningBackdropAnimationOnCompositor())
+  //   effect_node.has_potential_backdrop_filter_animation = true;
 
   effect_stack_.emplace_back(current_);
   SetCurrentEffectState(effect_node, CcEffectType::kEffect, next_effect,
diff --git a/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h b/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h
index 5b6d737..8a222f5 100644
--- a/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h
+++ b/third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h
@@ -188,6 +188,11 @@
   bool HasDirectCompositingReasons() const {
     return DirectCompositingReasons() != CompositingReason::kNone;
   }
+
+  // The difference between the following two functions is that the former
+  // is also true for animations that the compositor are not aware of (e.g.
+  // paused animations and worklet animations), while the latter is true only if
+  // the compositor is handling the animation.
   bool HasActiveOpacityAnimation() const {
     return DirectCompositingReasons() &
            CompositingReason::kActiveOpacityAnimation;
diff --git a/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h b/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h
index d74c5d4..1c31dea 100644
--- a/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h
+++ b/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h
@@ -196,6 +196,11 @@
   bool HasDirectCompositingReasons() const {
     return DirectCompositingReasons() != CompositingReason::kNone;
   }
+
+  // The difference between the following two functions is that the former
+  // is also true for animations that the compositor are not aware of (e.g.
+  // paused animations and worklet animations), while the latter is true only if
+  // the compositor is handling the animation.
   bool HasActiveTransformAnimation() const {
     return DirectCompositingReasons() &
            CompositingReason::kActiveTransformAnimation;
diff --git a/third_party/blink/renderer/platform/loader/fetch/fetch_context.cc b/third_party/blink/renderer/platform/loader/fetch/fetch_context.cc
index d942a964..4a3b3cf 100644
--- a/third_party/blink/renderer/platform/loader/fetch/fetch_context.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/fetch_context.cc
@@ -56,7 +56,7 @@
 
 FetchContext::FetchContext()
     : platform_probe_sink_(MakeGarbageCollected<PlatformProbeSink>()) {
-  platform_probe_sink_->addPlatformTraceEvents(
+  platform_probe_sink_->AddPlatformTraceEvents(
       MakeGarbageCollected<PlatformTraceEventsAgent>());
 }
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/raw_resource.cc b/third_party/blink/renderer/platform/loader/fetch/raw_resource.cc
index 1ae0225..5d11a86 100644
--- a/third_party/blink/renderer/platform/loader/fetch/raw_resource.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/raw_resource.cc
@@ -337,14 +337,14 @@
     c->SetSerializedCachedMetadata(this, data, size);
 }
 
-void RawResource::DidSendData(unsigned long long bytes_sent,
-                              unsigned long long total_bytes_to_be_sent) {
+void RawResource::DidSendData(uint64_t bytes_sent,
+                              uint64_t total_bytes_to_be_sent) {
   ResourceClientWalker<RawResourceClient> w(Clients());
   while (RawResourceClient* c = w.Next())
     c->DataSent(this, bytes_sent, total_bytes_to_be_sent);
 }
 
-void RawResource::DidDownloadData(unsigned long long data_length) {
+void RawResource::DidDownloadData(uint64_t data_length) {
   ResourceClientWalker<RawResourceClient> w(Clients());
   while (RawResourceClient* c = w.Next())
     c->DataDownloaded(this, data_length);
diff --git a/third_party/blink/renderer/platform/loader/fetch/raw_resource.h b/third_party/blink/renderer/platform/loader/fetch/raw_resource.h
index 8326e38..93bde4e 100644
--- a/third_party/blink/renderer/platform/loader/fetch/raw_resource.h
+++ b/third_party/blink/renderer/platform/loader/fetch/raw_resource.h
@@ -122,9 +122,9 @@
   void WillNotFollowRedirect() override;
   void ResponseReceived(const ResourceResponse&) override;
   void ResponseBodyReceived(ResponseBodyLoaderDrainableInterface&) override;
-  void DidSendData(unsigned long long bytes_sent,
-                   unsigned long long total_bytes_to_be_sent) override;
-  void DidDownloadData(unsigned long long) override;
+  void DidSendData(uint64_t bytes_sent,
+                   uint64_t total_bytes_to_be_sent) override;
+  void DidDownloadData(uint64_t) override;
   void DidDownloadToBlob(scoped_refptr<BlobDataHandle>) override;
   void ReportResourceTimingToClients(const ResourceTimingInfo&) override;
   bool MatchPreload(const FetchParameters&,
@@ -180,8 +180,8 @@
   //     No callbacks are made after NotifyFinished() or
   //     RemoveClient() is called.
   virtual void DataSent(Resource*,
-                        unsigned long long /* bytesSent */,
-                        unsigned long long /* totalBytesToBeSent */) {}
+                        uint64_t /* bytesSent */,
+                        uint64_t /* totalBytesToBeSent */) {}
   virtual void ResponseBodyReceived(Resource*, BytesConsumer&) {}
   virtual void ResponseReceived(Resource*, const ResourceResponse&) {}
   virtual void SetSerializedCachedMetadata(Resource*, const uint8_t*, size_t) {}
@@ -191,7 +191,7 @@
     return true;
   }
   virtual void RedirectBlocked() {}
-  virtual void DataDownloaded(Resource*, unsigned long long) {}
+  virtual void DataDownloaded(Resource*, uint64_t) {}
   virtual void DidReceiveResourceTiming(Resource*, const ResourceTimingInfo&) {}
   // Called for requests that had DownloadToBlob set to true. Can be called with
   // null if creating the blob failed for some reason (but the download itself
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource.h b/third_party/blink/renderer/platform/loader/fetch/resource.h
index 3a641467..1a72132 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource.h
@@ -342,9 +342,9 @@
   // https://fetch.spec.whatwg.org/#concept-request-origin
   const scoped_refptr<const SecurityOrigin>& GetOrigin() const;
 
-  virtual void DidSendData(unsigned long long /* bytesSent */,
-                           unsigned long long /* totalBytesToBeSent */) {}
-  virtual void DidDownloadData(unsigned long long) {}
+  virtual void DidSendData(uint64_t /* bytesSent */,
+                           uint64_t /* totalBytesToBeSent */) {}
+  virtual void DidDownloadData(uint64_t) {}
   virtual void DidDownloadToBlob(scoped_refptr<BlobDataHandle>) {}
 
   TimeTicks LoadResponseEnd() const { return load_response_end_; }
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher_test.cc b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher_test.cc
index 01b2ebc2..04a895c 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher_test.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher_test.cc
@@ -235,12 +235,12 @@
   auto info = ResourceTimingInfo::Create(fetch_initiator_type_names::kDocument,
                                          CurrentTimeTicks());
   info->AddFinalTransferSize(5);
-  EXPECT_EQ(info->TransferSize(), 5);
+  EXPECT_EQ(info->TransferSize(), static_cast<uint64_t>(5));
   ResourceResponse redirect_response(KURL("https://example.com/original"));
   redirect_response.SetHttpStatusCode(200);
   redirect_response.SetEncodedDataLength(7);
   info->AddRedirect(redirect_response, KURL("https://example.com/redirect"));
-  EXPECT_EQ(info->TransferSize(), 12);
+  EXPECT_EQ(info->TransferSize(), static_cast<uint64_t>(12));
 }
 
 TEST_F(ResourceFetcherTest, VaryOnBack) {
@@ -392,9 +392,7 @@
 
   // No callbacks should be received except for the NotifyFinished() triggered
   // by ResourceLoader::Cancel().
-  void DataSent(Resource*, unsigned long long, unsigned long long) override {
-    ASSERT_TRUE(false);
-  }
+  void DataSent(Resource*, uint64_t, uint64_t) override { ASSERT_TRUE(false); }
   void ResponseReceived(Resource*, const ResourceResponse&) override {
     ASSERT_TRUE(false);
   }
@@ -410,9 +408,7 @@
     ADD_FAILURE();
     return true;
   }
-  void DataDownloaded(Resource*, unsigned long long) override {
-    ASSERT_TRUE(false);
-  }
+  void DataDownloaded(Resource*, uint64_t) override { ASSERT_TRUE(false); }
   void DidReceiveResourceTiming(Resource*, const ResourceTimingInfo&) override {
     ASSERT_TRUE(false);
   }
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
index 5de5fbf6..b68ab6c 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
@@ -869,8 +869,8 @@
   Platform::Current()->ClearCodeCacheEntry(cache_type, resource_->Url());
 }
 
-void ResourceLoader::DidSendData(unsigned long long bytes_sent,
-                                 unsigned long long total_bytes_to_be_sent) {
+void ResourceLoader::DidSendData(uint64_t bytes_sent,
+                                 uint64_t total_bytes_to_be_sent) {
   resource_->DidSendData(bytes_sent, total_bytes_to_be_sent);
 }
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_loader.h b/third_party/blink/renderer/platform/loader/fetch/resource_loader.h
index 3b83852f2..9a0e6458 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_loader.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_loader.h
@@ -124,8 +124,8 @@
       const WebString& new_method,
       const WebURLResponse& passed_redirect_response,
       bool& report_raw_headers) override;
-  void DidSendData(unsigned long long bytes_sent,
-                   unsigned long long total_bytes_to_be_sent) override;
+  void DidSendData(uint64_t bytes_sent,
+                   uint64_t total_bytes_to_be_sent) override;
   void DidReceiveResponse(const WebURLResponse&) override;
   void DidReceiveResponse(const WebURLResponse&,
                           std::unique_ptr<WebDataConsumerHandle>) override;
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h b/third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h
index 7094d25..51b5b2a 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_timing_info.h
@@ -73,10 +73,10 @@
     return redirect_chain_;
   }
 
-  void AddFinalTransferSize(long long encoded_data_length) {
+  void AddFinalTransferSize(uint64_t encoded_data_length) {
     transfer_size_ += encoded_data_length;
   }
-  long long TransferSize() const { return transfer_size_; }
+  uint64_t TransferSize() const { return transfer_size_; }
 
   // The timestamps in PerformanceResourceTiming are measured relative from the
   // time origin. In most cases these timestamps must be positive value, so we
@@ -99,7 +99,7 @@
   KURL initial_url_;
   ResourceResponse final_response_;
   Vector<ResourceResponse> redirect_chain_;
-  long long transfer_size_ = 0;
+  uint64_t transfer_size_ = 0;
   bool has_cross_origin_redirect_ = false;
   bool negative_allowed_ = false;
 
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 0e7977d..9301be6 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1489,6 +1489,10 @@
       status: "test",
     },
     {
+      name: "WebHID",
+      status: "experimental",
+    },
+    {
       name: "WebNFC",
       status: "experimental",
     },
diff --git a/third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h b/third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h
index 978b7a4b..c3ec87da 100644
--- a/third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h
+++ b/third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h
@@ -5,11 +5,15 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_TESTING_BLINK_FUZZER_TEST_SUPPORT_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_TESTING_BLINK_FUZZER_TEST_SUPPORT_H_
 
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
+
 namespace blink {
 
 // Instantiating BlinkFuzzerTestSupport will spin up an environment similar to
 // blink_unittests. It should be statically initialized and leaked in fuzzers.
 class BlinkFuzzerTestSupport {
+  STACK_ALLOCATED();
+
  public:
   // Use this constructor in LLVMFuzzerTestOneInput.
   BlinkFuzzerTestSupport();
diff --git a/third_party/blink/renderer/platform/testing/fuzzed_data_provider.h b/third_party/blink/renderer/platform/testing/fuzzed_data_provider.h
index bafc9e0..47ca6eb8a 100644
--- a/third_party/blink/renderer/platform/testing/fuzzed_data_provider.h
+++ b/third_party/blink/renderer/platform/testing/fuzzed_data_provider.h
@@ -7,6 +7,7 @@
 
 #include "base/macros.h"
 #include "base/test/fuzzed_data_provider.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/cstring.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
@@ -15,6 +16,8 @@
 // This class simply wraps //base/test/fuzzed_data_provider and vends Blink
 // friendly types.
 class FuzzedDataProvider {
+  DISALLOW_NEW();
+
  public:
   FuzzedDataProvider(const uint8_t* bytes, size_t num_bytes);
 
diff --git a/third_party/blink/renderer/platform/testing/histogram_tester.h b/third_party/blink/renderer/platform/testing/histogram_tester.h
index 609a9b79..a9dd3b0 100644
--- a/third_party/blink/renderer/platform/testing/histogram_tester.h
+++ b/third_party/blink/renderer/platform/testing/histogram_tester.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 #include "third_party/blink/renderer/platform/histogram.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 
 namespace base {
 class HistogramTester;
@@ -16,6 +17,8 @@
 
 // Blink interface for base::HistogramTester.
 class HistogramTester {
+  USING_FAST_MALLOC(HistogramTester);
+
  public:
   HistogramTester();
   ~HistogramTester();
diff --git a/third_party/blink/renderer/platform/testing/layer_tree_host_embedder.h b/third_party/blink/renderer/platform/testing/layer_tree_host_embedder.h
index 59b713f0..9a32e74a 100644
--- a/third_party/blink/renderer/platform/testing/layer_tree_host_embedder.h
+++ b/third_party/blink/renderer/platform/testing/layer_tree_host_embedder.h
@@ -11,6 +11,7 @@
 #include "cc/test/test_task_graph_runner.h"
 #include "cc/trees/layer_tree_host.h"
 #include "cc/trees/layer_tree_settings.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 
 namespace blink {
 
@@ -18,6 +19,8 @@
 // for unit tests that need to instantiate only a cc::LayerTreeHost and not the
 // full blink APIs that normally own and embed it.
 class LayerTreeHostEmbedder {
+  USING_FAST_MALLOC(LayerTreeHostEmbedder);
+
  public:
   // Default constructor uses stub clients, and default LayerTreeSettings
   // appropriate for blink unit tests.
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 0db41518..af5b5ae 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
@@ -46,6 +46,20 @@
                              compositing_reasons);
 }
 
+inline scoped_refptr<EffectPaintPropertyNode>
+CreateCompositedAnimatingOpacityEffect(
+    const EffectPaintPropertyNode& parent,
+    float opacity,
+    const ClipPaintPropertyNode* output_clip = nullptr) {
+  EffectPaintPropertyNode::State state;
+  state.local_transform_space = &parent.Unalias().LocalTransformSpace();
+  state.output_clip = output_clip;
+  state.opacity = opacity;
+  state.direct_compositing_reasons = CompositingReason::kActiveOpacityAnimation;
+  state.is_running_opacity_animation_on_compositor = true;
+  return EffectPaintPropertyNode::Create(parent, std::move(state));
+}
+
 inline scoped_refptr<EffectPaintPropertyNode> CreateFilterEffect(
     const EffectPaintPropertyNode& parent,
     const TransformPaintPropertyNode& local_transform_space,
diff --git a/third_party/blink/renderer/platform/testing/scoped_main_thread_overrider.h b/third_party/blink/renderer/platform/testing/scoped_main_thread_overrider.h
index dbe1719..30c4e92 100644
--- a/third_party/blink/renderer/platform/testing/scoped_main_thread_overrider.h
+++ b/third_party/blink/renderer/platform/testing/scoped_main_thread_overrider.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 
 namespace blink {
 
@@ -19,6 +20,8 @@
 // the constructor gets destructed.
 
 class ScopedMainThreadOverrider final {
+  USING_FAST_MALLOC(ScopedMainThreadOverrider);
+
  public:
   explicit ScopedMainThreadOverrider(std::unique_ptr<Thread> main_thread);
   ~ScopedMainThreadOverrider();
diff --git a/third_party/blink/renderer/platform/testing/scoped_mocked_url.h b/third_party/blink/renderer/platform/testing/scoped_mocked_url.h
index 05326b964..37e9516 100644
--- a/third_party/blink/renderer/platform/testing/scoped_mocked_url.h
+++ b/third_party/blink/renderer/platform/testing/scoped_mocked_url.h
@@ -8,6 +8,7 @@
 #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_response.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 
 namespace blink {
 
@@ -17,6 +18,8 @@
 // unregister it on destruction. This prevent mocked URL from leaking to other
 // tests.
 class ScopedMockedURL {
+  STACK_ALLOCATED();
+
  public:
   explicit ScopedMockedURL(const WebURL&);
   virtual ~ScopedMockedURL();
diff --git a/third_party/blink/renderer/platform/testing/scoped_scheduler_overrider.h b/third_party/blink/renderer/platform/testing/scoped_scheduler_overrider.h
index 7f46ea3..fab1157 100644
--- a/third_party/blink/renderer/platform/testing/scoped_scheduler_overrider.h
+++ b/third_party/blink/renderer/platform/testing/scoped_scheduler_overrider.h
@@ -7,6 +7,7 @@
 
 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
 #include "third_party/blink/renderer/platform/testing/scoped_main_thread_overrider.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 
 namespace blink {
 
@@ -16,6 +17,8 @@
 // Overrider. Multi-thread is not supported.
 
 class ScopedSchedulerOverrider final {
+  USING_FAST_MALLOC(ScopedSchedulerOverrider);
+
  public:
   // |scheduler| must be owned by the caller.
   explicit ScopedSchedulerOverrider(ThreadScheduler* scheduler);
diff --git a/third_party/blink/renderer/platform/testing/wtf/scoped_mock_clock.h b/third_party/blink/renderer/platform/testing/wtf/scoped_mock_clock.h
index 6e0580e1..5c1373b 100644
--- a/third_party/blink/renderer/platform/testing/wtf/scoped_mock_clock.h
+++ b/third_party/blink/renderer/platform/testing/wtf/scoped_mock_clock.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_TESTING_WTF_SCOPED_MOCK_CLOCK_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_TESTING_WTF_SCOPED_MOCK_CLOCK_H_
 
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/time.h"
 
 namespace WTF {
@@ -13,6 +14,8 @@
 // stack resets mock time to the zero point for WTF::Time and WTF::TimeTicks.
 // Mock time may only flow forwards, not backwards.
 class ScopedMockClock {
+  USING_FAST_MALLOC(ScopedMockClock);
+
  public:
   ScopedMockClock();
   ~ScopedMockClock();
diff --git a/third_party/blink/renderer/platform/text/layout_locale.h b/third_party/blink/renderer/platform/text/layout_locale.h
index 2ec39dd..f10fda7 100644
--- a/third_party/blink/renderer/platform/text/layout_locale.h
+++ b/third_party/blink/renderer/platform/text/layout_locale.h
@@ -7,6 +7,7 @@
 
 #include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/blink/renderer/platform/text/hyphenation.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
 #include "third_party/blink/renderer/platform/wtf/ref_counted.h"
@@ -23,6 +24,8 @@
 enum class LineBreakIteratorMode { kDefault, kNormal, kStrict, kLoose };
 
 class PLATFORM_EXPORT LayoutLocale : public RefCounted<LayoutLocale> {
+  USING_FAST_MALLOC(LayoutLocale);
+
  public:
   static const LayoutLocale* Get(const AtomicString& locale);
   static const LayoutLocale& GetDefault();
diff --git a/third_party/blink/renderer/platform/text/web_entities.h b/third_party/blink/renderer/platform/text/web_entities.h
index c0288ca..f1a5ab47 100644
--- a/third_party/blink/renderer/platform/text/web_entities.h
+++ b/third_party/blink/renderer/platform/text/web_entities.h
@@ -32,6 +32,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_TEXT_WEB_ENTITIES_H_
 
 #include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
@@ -39,6 +40,8 @@
 
 // FIXME: This class is wrong and needs to be removed!
 class PLATFORM_EXPORT WebEntities {
+  DISALLOW_NEW();
+
  public:
   // &apos;, &percnt;, &nsup;, &supl; are not defined by the HTML standards.
   //  - IE does not support &apos; as an HTML entity (but support it as an XML
diff --git a/third_party/blink/renderer/platform/text/writing_mode_utils_test.cc b/third_party/blink/renderer/platform/text/writing_mode_utils_test.cc
index 8fb0130..73a974f8 100644
--- a/third_party/blink/renderer/platform/text/writing_mode_utils_test.cc
+++ b/third_party/blink/renderer/platform/text/writing_mode_utils_test.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/platform/text/writing_mode_utils.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 
 namespace blink {
 
@@ -173,6 +174,8 @@
 }
 
 class PhysicalValues {
+  STACK_ALLOCATED();
+
  public:
   int Top() const { return top_; }
   int Right() const { return right_; }
@@ -246,6 +249,8 @@
 }
 
 class LogicalValues {
+  STACK_ALLOCATED();
+
  public:
   int InlineStart() const { return inline_start_; }
   int InlineEnd() const { return inline_end_; }
diff --git a/third_party/blink/renderer/platform/weborigin/reporting_service_proxy_ptr_holder.h b/third_party/blink/renderer/platform/weborigin/reporting_service_proxy_ptr_holder.h
index 7b8bc61..5dfa8c8b 100644
--- a/third_party/blink/renderer/platform/weborigin/reporting_service_proxy_ptr_holder.h
+++ b/third_party/blink/renderer/platform/weborigin/reporting_service_proxy_ptr_holder.h
@@ -9,10 +9,13 @@
 #include "third_party/blink/public/platform/interface_provider.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 
 namespace blink {
 
 class ReportingServiceProxyPtrHolder {
+  USING_FAST_MALLOC(ReportingServiceProxyPtrHolder);
+
  public:
   ReportingServiceProxyPtrHolder() {
     Platform::Current()->GetInterfaceProvider()->GetInterface(
diff --git a/third_party/blink/renderer/platform/weborigin/scheme_registry.cc b/third_party/blink/renderer/platform/weborigin/scheme_registry.cc
index d85aa29..5ed5fea 100644
--- a/third_party/blink/renderer/platform/weborigin/scheme_registry.cc
+++ b/third_party/blink/renderer/platform/weborigin/scheme_registry.cc
@@ -26,6 +26,7 @@
 
 #include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
 
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
 #include "third_party/blink/renderer/platform/wtf/thread_specific.h"
 #include "third_party/blink/renderer/platform/wtf/threading.h"
@@ -44,6 +45,8 @@
 };
 
 class URLSchemesRegistry final {
+  USING_FAST_MALLOC(URLSchemesRegistry);
+
  public:
   URLSchemesRegistry()
       :  // For ServiceWorker schemes: HTTP is required because http://localhost
diff --git a/third_party/blink/renderer/platform/weborigin/security_origin.h b/third_party/blink/renderer/platform/weborigin/security_origin.h
index 88f8be6..f09c3e1 100644
--- a/third_party/blink/renderer/platform/weborigin/security_origin.h
+++ b/third_party/blink/renderer/platform/weborigin/security_origin.h
@@ -35,6 +35,7 @@
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/platform/wtf/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "third_party/blink/renderer/platform/wtf/thread_safe_ref_counted.h"
 #include "url/origin.h"
@@ -57,6 +58,8 @@
 //
 // See also: https://html.spec.whatwg.org/C/#concept-origin
 class PLATFORM_EXPORT SecurityOrigin : public RefCounted<SecurityOrigin> {
+  USING_FAST_MALLOC(SecurityOrigin);
+
  public:
   enum class AccessResultDomainDetail {
     kDomainNotRelevant,
diff --git a/third_party/blink/tools/blinkpy/style/checkers/cpp.py b/third_party/blink/tools/blinkpy/style/checkers/cpp.py
index b3442a67..7d3936c 100644
--- a/third_party/blink/tools/blinkpy/style/checkers/cpp.py
+++ b/third_party/blink/tools/blinkpy/style/checkers/cpp.py
@@ -1866,11 +1866,11 @@
     # FIXME: figure out if they're using default arguments in fn proto.
 
     # Check if they're using a precise-width integer type.
-    matched = search(r'\bunsigned short\b', line)
+    matched = search(r'\b((un)?signed\s+)?short\b', line)
     if matched:
         error(line_number, 'runtime/int', 1,
               'Use a precise-width integer type from <stdint.h> or <cstdint>'
-              ' such as uint16_t instead of unsigned short')
+              ' such as uint16_t instead of %s' % matched.group(0))
 
     # Check to see if they're using an conversion function cast.
     # I just try to capture the most common basic types, though there are more.
diff --git a/third_party/blink/tools/blinkpy/style/checkers/cpp_unittest.py b/third_party/blink/tools/blinkpy/style/checkers/cpp_unittest.py
index 3b5a537c..36d83532 100644
--- a/third_party/blink/tools/blinkpy/style/checkers/cpp_unittest.py
+++ b/third_party/blink/tools/blinkpy/style/checkers/cpp_unittest.py
@@ -458,10 +458,11 @@
 
     # Test the integer type.
     def test_precise_width_integer(self):
-        self.assert_lint(
-            'unsigned short a = 1',
-            'Use a precise-width integer type from <stdint.h> or <cstdint> such as uint16_t instead of unsigned short'
-            '  [runtime/int] [1]')
+        errmsg = ('Use a precise-width integer type from <stdint.h> or <cstdint> such as uint16_t instead of %s')
+        self.assert_lint('unsigned short a = 1', errmsg % 'unsigned short  [runtime/int] [1]')
+        self.assert_lint('uint16_t unsignedshort = 1', '')
+        self.assert_lint('signed  short a = 1', errmsg % 'signed  short  [runtime/int] [1]')
+        self.assert_lint('short a = 1', errmsg % 'short  [runtime/int] [1]')
 
     # Test C-style cast cases.
     def test_cstyle_cast(self):
@@ -1508,7 +1509,7 @@
         errmsg = ('Please declare integral type bitfields with either signed or unsigned.  [runtime/bitfields] [5]')
 
         self.assert_lint('int a : 30;', errmsg)
-        self.assert_lint('mutable short a : 14;', errmsg)
+        self.assert_lint('mutable int a : 14;', errmsg)
         self.assert_lint('const char a : 6;', errmsg)
         self.assert_lint('long int a : 30;', errmsg)
         self.assert_lint('int a = 1 ? 0 : 30;', '')
diff --git a/third_party/blink/web_tests/MSANExpectations b/third_party/blink/web_tests/MSANExpectations
index 30ea7c6..7e56cda 100644
--- a/third_party/blink/web_tests/MSANExpectations
+++ b/third_party/blink/web_tests/MSANExpectations
@@ -158,6 +158,7 @@
 crbug.com/856601 [ Linux ] virtual/feature-policy-permissions/external/wpt/mediacapture-streams/MediaDevices-IDL-enumerateDevices.html [ Pass Timeout ]
 crbug.com/856601 [ Linux ] virtual/feature-policy-permissions/external/wpt/mediacapture-streams/idlharness.https.window.html [ Pass Timeout ]
 crbug.com/856601 [ Linux ] virtual/outofblink-cors/external/wpt/fetch/cors-rfc1918/idlharness.tentative.https.any.serviceworker.html [ Pass Timeout ]
+crbug.com/856601 [ Linux ] virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/interfaces-sw.https.html [ Timeout Pass ]
 crbug.com/856601 [ Linux ] virtual/outofblink-cors/external/wpt/service-workers/service-worker/interfaces-sw.https.html [ Timeout Pass ]
 crbug.com/856601 [ Linux ] virtual/service-worker-servicification/external/wpt/service-workers/service-worker/interfaces-sw.https.html [ Timeout Pass ]
 crbug.com/856601 [ Linux ] virtual/video-surface-layer/external/wpt/picture-in-picture/idlharness.window.html [ Pass Failure Timeout ]
diff --git a/third_party/blink/web_tests/SlowTests b/third_party/blink/web_tests/SlowTests
index 069bb621..1bf999c 100644
--- a/third_party/blink/web_tests/SlowTests
+++ b/third_party/blink/web_tests/SlowTests
@@ -699,7 +699,6 @@
 crbug.com/874695 external/wpt/service-workers/service-worker/fetch-response-taint.https.html [ Slow ]
 crbug.com/874695 external/wpt/service-workers/service-worker/navigation-redirect.https.html [ Slow ]
 crbug.com/874695 external/wpt/service-workers/service-worker/performance-timeline.https.html [ Slow ]
-crbug.com/874695 external/wpt/service-workers/service-worker/registration-mime-types.https.html [ Slow ]
 crbug.com/874695 external/wpt/service-workers/service-worker/registration-updateviacache.https.html [ Slow ]
 crbug.com/874695 external/wpt/service-workers/service-worker/skip-waiting-using-registration.https.html [ Slow ]
 crbug.com/874695 external/wpt/service-workers/service-worker/skip-waiting-without-using-registration.https.html [ Slow ]
@@ -1112,7 +1111,6 @@
 crbug.com/874695 virtual/outofblink-cors/external/wpt/service-workers/service-worker/fetch-event-respond-with-body-loaded-in-chunk.https.html [ Slow ]
 crbug.com/874695 virtual/outofblink-cors/external/wpt/service-workers/service-worker/fetch-response-taint.https.html [ Slow ]
 crbug.com/874695 virtual/outofblink-cors/external/wpt/service-workers/service-worker/navigation-redirect.https.html [ Slow ]
-crbug.com/874695 virtual/outofblink-cors/external/wpt/service-workers/service-worker/registration-mime-types.https.html [ Slow ]
 crbug.com/874695 virtual/outofblink-cors/external/wpt/service-workers/service-worker/registration-updateviacache.https.html [ Slow ]
 crbug.com/874695 virtual/outofblink-cors/external/wpt/service-workers/service-worker/skip-waiting-using-registration.https.html [ Slow ]
 crbug.com/874695 virtual/outofblink-cors/external/wpt/service-workers/service-worker/skip-waiting-without-using-registration.https.html [ Slow ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index fb7dda3..8c04e3a7 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -3022,10 +3022,11 @@
 
 # Hit a DCHECK
 crbug.com/918664 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/block-size-with-min-or-max-content-table-1a.html [ Failure Pass ]
+crbug.com/939181 external/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects.html [ Failure Crash ]
+crbug.com/939181 virtual/not-site-per-process/external/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects.html [ Failure Timeout ]
 
 # ====== New tests from wpt-importer added here ======
-crbug.com/626703 [ Mac10.10 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller.https.html [ Failure ]
-crbug.com/626703 [ Mac10.11 ] virtual/threaded/external/wpt/animation-worklet/worklet-animation-with-scroll-timeline-root-scroller.https.html [ Failure ]
+crbug.com/626703 external/wpt/screen-orientation/onchange-event.html [ Timeout ]
 crbug.com/626703 external/wpt/html/semantics/forms/the-fieldset-element/accessibility/fieldset-div-display-contents-manual.html [ Skip ]
 crbug.com/626703 external/wpt/html/semantics/forms/the-fieldset-element/accessibility/role-manual.html [ Skip ]
 crbug.com/626703 external/wpt/html/semantics/forms/the-fieldset-element/accessibility/shadow-dom-manual.html [ Skip ]
@@ -4217,7 +4218,6 @@
 # This fails because CORS check?: Error message is like:
 # Access to XMLHttpRequest at 'foobar://abcd' (redirected from 'http://web-platform.test:8001/xhr/resources/redirect.py?location=foobar://abcd&code=301') from origin 'http://web-platform.test:8001' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.
 crbug.com/924043 virtual/omt-worker-fetch/external/wpt/xhr/send-redirect-bogus-sync.htm [ Timeout ]
-crbug.com/937048 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/interfaces-sw.https.html [ Pass Timeout ]
 
 crbug.com/697971 [ Mac10.12 ] fast/text/selection/flexbox-selection-nested.html [ Skip ]
 crbug.com/697971 [ Mac10.12 ] fast/text/selection/flexbox-selection.html [ Skip ]
@@ -4732,6 +4732,7 @@
 crbug.com/669329 virtual/threaded/http/tests/devtools/tracing/timeline-js/timeline-runtime-stats.js [ Pass Failure Crash ]
 
 crbug.com/799619 [ Debug ] http/tests/devtools/profiler/heap-snapshot-inspect-dom-wrapper.js [ Pass Timeout ]
+crbug.com/939037 [ Mac Linux ] http/tests/devtools/profiler/heap-snapshot-location.js [ Pass Timeout ]
 
 crbug.com/769347 [ Mac ] fast/dom/inert/inert-node-is-uneditable.html [ Failure ]
 
@@ -5983,7 +5984,6 @@
 crbug.com/937416 [ Linux Mac ] http/tests/devtools/resource-tree/resource-tree-htmlimports.js [ Pass Failure ]
 crbug.com/937416 http/tests/devtools/resource-tree/resource-tree-frame-navigate.js [ Pass Failure ]
 crbug.com/937546 [ Win7 ] http/tests/security/xss-DENIED-cross-origin-stack-overflow.html [ Pass Timeout ]
-crbug.com/937544 [ Win7 Mac ] virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/registration-mime-types.https.html [ Pass Timeout ]
 # Sheriff 2019-03-02
 crbug.com/937639 [ Linux Debug ] external/wpt/html/browsers/browsing-the-web/read-media/pageload-image-in-popup.html [ Pass Failure ]
 
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index ba4d91d..01beab7a 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -432,18 +432,18 @@
     "args": ["--enable-print-browser"]
   },
   {
-    "prefix": "high-contrast-mode",
-    "base": "paint/high-contrast-mode/image-filter-all",
+    "prefix": "dark-mode",
+    "base": "paint/dark-mode/image-filter-all",
     "args": ["--blink-settings=highContrastMode=3,highContrastImagePolicy=0"]
   },
   {
-    "prefix": "high-contrast-mode",
-    "base": "paint/high-contrast-mode/image-filter-none",
+    "prefix": "dark-mode",
+    "base": "paint/dark-mode/image-filter-none",
     "args": ["--blink-settings=highContrastMode=3,highContrastImagePolicy=1"]
   },
   {
-    "prefix": "high-contrast-mode",
-    "base": "paint/high-contrast-mode/image-filter-smart",
+    "prefix": "dark-mode",
+    "base": "paint/dark-mode/image-filter-smart",
     "args": ["--blink-settings=highContrastMode=3,highContrastImagePolicy=2"]
   },
   {
diff --git a/third_party/blink/web_tests/animations/skew-notsequential-compositor-expected.png b/third_party/blink/web_tests/animations/skew-notsequential-compositor-expected.png
index 80c2d99f..ffe4f9a 100644
--- a/third_party/blink/web_tests/animations/skew-notsequential-compositor-expected.png
+++ b/third_party/blink/web_tests/animations/skew-notsequential-compositor-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/compositing/masks/broken-mask-expected.html b/third_party/blink/web_tests/compositing/masks/broken-mask-expected.html
new file mode 100644
index 0000000..a50cb14
--- /dev/null
+++ b/third_party/blink/web_tests/compositing/masks/broken-mask-expected.html
@@ -0,0 +1,2 @@
+<!doctype html>
+Passes if there is no red.
diff --git a/third_party/blink/web_tests/compositing/masks/broken-mask.html b/third_party/blink/web_tests/compositing/masks/broken-mask.html
new file mode 100644
index 0000000..52b108c
--- /dev/null
+++ b/third_party/blink/web_tests/compositing/masks/broken-mask.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<style>
+#mask {
+  -webkit-mask-image: url(broken.png);
+  background: red;
+  height: 100px;
+  width: 100px;
+  will-change: transform;
+}
+</style>
+Passes if there is no red.
+<div id=mask></div>
diff --git a/third_party/blink/web_tests/external/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects-expected.txt b/third_party/blink/web_tests/external/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects-expected.txt
deleted file mode 100644
index 770e1bf..0000000
--- a/third_party/blink/web_tests/external/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects-expected.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-This is a testharness.js-based test.
-PASS Basic sanity-checking
-FAIL Only whitelisted properties are accessible cross-origin Blocked a frame with origin "http://web-platform.test:8001" from accessing a cross-origin frame.
-PASS [[GetPrototypeOf]] should return null
-FAIL [[SetPrototypeOf]] should return false assert_throws: proto setter |call| on cross-origin Window function "function() { protoSetter.call(C, new Object()); }" threw object "SecurityError: Blocked a frame with origin "http://web-platform.test:8001" from accessing a cross-origin frame." ("SecurityError") expected object "TypeError" ("TypeError")
-PASS [[IsExtensible]] should return true for cross-origin objects
-FAIL [[PreventExtensions]] should throw for cross-origin objects assert_throws: preventExtensions on cross-origin Window should throw function "function() { Object.preventExtensions(C) }" threw object "SecurityError: Blocked a frame with origin "http://web-platform.test:8001" from accessing a cross-origin frame." ("SecurityError") expected object "TypeError" ("TypeError")
-FAIL [[GetOwnProperty]] - Properties on cross-origin objects should be reported |own| Blocked a frame with origin "http://web-platform.test:8001" from accessing a cross-origin frame.
-FAIL [[GetOwnProperty]] - Property descriptors for cross-origin properties should be set up correctly assert_equals: property descriptor for 0 should be enumerable expected true but got false
-PASS [[GetOwnProperty]] - Subframe named 'then' should shadow the default 'then' value
-PASS [[GetOwnProperty]] - Subframes should be visible cross-origin only if their names don't match the names of cross-origin-exposed IDL properties
-PASS [[GetOwnProperty]] - Should be able to get a property descriptor for an indexed property only if it corresponds to a child window.
-PASS [[Delete]] Should throw on cross-origin objects
-PASS [[DefineOwnProperty]] Should throw for cross-origin objects
-FAIL Can only enumerate safelisted enumerable properties assert_equals: Enumerate all enumerable safelisted cross-origin Window properties expected 2 but got 0
-FAIL [[OwnPropertyKeys]] should return all properties from cross-origin objects assert_array_equals: Object.getOwnPropertyNames() gives the right answer for cross-origin Window lengths differ, expected 16 got 13
-FAIL [[OwnPropertyKeys]] should return the right symbol-named properties for cross-origin objects assert_array_equals: Object.getOwnPropertySymbols() should return the three symbol-named properties that are exposed on a cross-origin Window lengths differ, expected 3 got 0
-FAIL [[OwnPropertyKeys]] should place the symbols after the property names after the subframe indices assert_equals: 'then' property should be added to the end of the string list if not there expected "then" but got "close"
-FAIL [[OwnPropertyKeys]] should not reorder where 'then' appears if it's a named subframe, nor add another copy of 'then' assert_equals: expected "then" but got "postMessage"
-PASS A and B jointly observe the same identity for cross-origin Window and Location
-PASS Cross-origin functions get local Function.prototype
-FAIL Cross-origin Window accessors get local Function.prototype Cannot read property 'name' of undefined
-FAIL Same-origin observers get different functions for cross-origin objects assert_not_equals: same-origin Window functions get their own object got disallowed value function "function () { [native code] }"
-FAIL Same-origin observers get different accessors for cross-origin Window assert_not_equals: different Window accessors per-incumbent script settings object got disallowed value undefined
-FAIL Same-origin observers get different accessors for cross-origin Location Blocked a frame with origin "http://web-platform.test:8001" from accessing a cross-origin frame.
-FAIL {}.toString.call() does the right thing on cross-origin objects assert_equals: expected "[object Object]" but got "[object Location]"
-PASS Resolving a promise with a cross-origin window without a 'then' subframe should work.
-PASS Resolving a promise with a cross-origin window with a 'then' subframe should work.
-PASS Resolving a promise with a cross-origin location should work.
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/registration-mime-types.https.html b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/registration-mime-types.https.html
index 9ae5f095..1d39ecfb 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/registration-mime-types.https.html
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/registration-mime-types.https.html
@@ -1,5 +1,6 @@
 <!DOCTYPE html>
 <title>Service Worker: Registration (MIME types)</title>
+<meta name=timeout content=long>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="resources/test-helpers.sub.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audiocontext-interface/audiocontextoptions.html b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audiocontext-interface/audiocontextoptions.html
index 3a11074..bee1aa83 100644
--- a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audiocontext-interface/audiocontextoptions.html
+++ b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audiocontext-interface/audiocontextoptions.html
@@ -156,6 +156,45 @@
             });
           });
 
+      audit.define(
+          {
+            label: 'test-audiocontextoptions-sampleRate',
+            description:
+                'Test creating contexts with non-default sampleRate values.'
+          },
+          function(task, should) {
+            // A sampleRate of 1 is unlikely to be supported on any browser,
+            // test that this rate is rejected.
+            should(
+                () => {
+                  context = new AudioContext({sampleRate: 1})
+                },
+                'context = new AudioContext({sampleRate: 1})')
+                .throw(DOMException);
+
+            // A sampleRate of 1,000,000 is unlikely to be supported on any
+            // browser, test that this rate is also rejected.
+            should(
+                () => {
+                  context = new AudioContext({sampleRate: 1000000})
+                },
+                'context = new AudioContext({sampleRate: 1000000})')
+                .throw(DOMException);
+
+            should(
+                () => {
+                  context = new AudioContext({sampleRate: 24000})
+                },
+                'context = new AudioContext({sampleRate: 24000})')
+                .notThrow();
+            should(
+                context.sampleRate, 'sampleRate inrange')
+                .beEqualTo(24000);
+
+            context.close();
+            task.done();
+          });
+
       audit.run();
     </script>
   </body>
diff --git a/third_party/blink/web_tests/http/tests/devtools/audits2/audits2-limited-run-expected.txt b/third_party/blink/web_tests/http/tests/devtools/audits2/audits2-limited-run-expected.txt
index 23507051..379e129 100644
--- a/third_party/blink/web_tests/http/tests/devtools/audits2/audits2-limited-run-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/audits2/audits2-limited-run-expected.txt
@@ -13,6 +13,7 @@
 =============== Audits run ===============
 bootup-time
 critical-request-chains
+diagnostics
 dom-size
 efficient-animated-content
 estimated-input-latency
@@ -22,9 +23,13 @@
 first-meaningful-paint
 font-display
 interactive
+main-thread-tasks
 mainthread-work-breakdown
+max-potential-fid
 metrics
 network-requests
+network-rtt
+network-server-latency
 offscreen-images
 redirects
 render-blocking-resources
diff --git a/third_party/blink/web_tests/http/tests/devtools/audits2/audits2-successful-run-expected.txt b/third_party/blink/web_tests/http/tests/devtools/audits2/audits2-successful-run-expected.txt
index 0eeab76..f293516 100644
--- a/third_party/blink/web_tests/http/tests/devtools/audits2/audits2-successful-run-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/audits2/audits2-successful-run-expected.txt
@@ -25,14 +25,13 @@
 Retrieving setup: Scripts
 Retrieving setup: CSSUsage
 Retrieving setup: ViewportDimensions
-Retrieving setup: Manifest
 Retrieving setup: RuntimeExceptions
 Retrieving setup: ChromeConsoleMessages
 Retrieving setup: Accessibility
+Retrieving setup: AnchorElements
 Retrieving setup: ImageElements
 Retrieving setup: LinkElements
 Retrieving setup: MetaElements
-Retrieving setup: AnchorsWithNoRelNoopener
 Retrieving setup: AppCacheManifest
 Retrieving setup: Doctype
 Retrieving setup: DOMStats
@@ -42,25 +41,24 @@
 Retrieving setup: ResponseCompression
 Retrieving setup: TagsBlockingFirstPaint
 Retrieving setup: FontSize
-Retrieving setup: CrawlableLinks
 Retrieving setup: Hreflang
 Retrieving setup: EmbeddedContent
 Retrieving setup: Canonical
 Retrieving setup: RobotsTxt
+Retrieving setup: TapTargets
 Loading page & waiting for onload
 Getting browser version
 Running pass methods
 Retrieving in-page: Scripts
 Retrieving in-page: CSSUsage
 Retrieving in-page: ViewportDimensions
-Retrieving in-page: Manifest
 Retrieving in-page: RuntimeExceptions
 Retrieving in-page: ChromeConsoleMessages
 Retrieving in-page: Accessibility
+Retrieving in-page: AnchorElements
 Retrieving in-page: ImageElements
 Retrieving in-page: LinkElements
 Retrieving in-page: MetaElements
-Retrieving in-page: AnchorsWithNoRelNoopener
 Retrieving in-page: AppCacheManifest
 Retrieving in-page: Doctype
 Retrieving in-page: DOMStats
@@ -70,25 +68,24 @@
 Retrieving in-page: ResponseCompression
 Retrieving in-page: TagsBlockingFirstPaint
 Retrieving in-page: FontSize
-Retrieving in-page: CrawlableLinks
 Retrieving in-page: Hreflang
 Retrieving in-page: EmbeddedContent
 Retrieving in-page: Canonical
 Retrieving in-page: RobotsTxt
+Retrieving in-page: TapTargets
 Retrieving trace
 Retrieving devtoolsLog & network records
 Running afterPass methods
 Retrieving: Scripts
 Retrieving: CSSUsage
 Retrieving: ViewportDimensions
-Retrieving: Manifest
 Retrieving: RuntimeExceptions
 Retrieving: ChromeConsoleMessages
 Retrieving: Accessibility
+Retrieving: AnchorElements
 Retrieving: ImageElements
 Retrieving: LinkElements
 Retrieving: MetaElements
-Retrieving: AnchorsWithNoRelNoopener
 Retrieving: AppCacheManifest
 Retrieving: Doctype
 Retrieving: DOMStats
@@ -98,11 +95,11 @@
 Retrieving: ResponseCompression
 Retrieving: TagsBlockingFirstPaint
 Retrieving: FontSize
-Retrieving: CrawlableLinks
 Retrieving: Hreflang
 Retrieving: EmbeddedContent
 Retrieving: Canonical
 Retrieving: RobotsTxt
+Retrieving: TapTargets
 Resetting state with about:blank
 Running beforePass methods
 Retrieving setup: ServiceWorker
@@ -138,6 +135,7 @@
 Evaluating: Registers a service worker that controls page and start_url
 Evaluating: Current page responds with a 200 when offline
 Evaluating: Has a `<meta name="viewport">` tag with `width` or `initial-scale`
+Computing artifact: ViewportMeta
 Evaluating: Contains some content when JavaScript is not available
 Evaluating: First Contentful Paint
 Computing artifact: FirstContentfulPaint
@@ -154,6 +152,8 @@
 Computing artifact: Screenshots
 Evaluating: Estimated Input Latency
 Computing artifact: EstimatedInputLatency
+Evaluating: Max Potential FID
+Computing artifact: MaxPotentialFID
 Evaluating: No browser errors logged to the console
 Evaluating: Server response times are low (TTFB)
 Computing artifact: MainResource
@@ -182,12 +182,17 @@
 Evaluating: Preconnect to required origins
 Computing artifact: LoadSimulator
 Evaluating: All text remains visible during webfont loads
+Evaluating: Diagnostics
 Evaluating: Network Requests
+Evaluating: Network Round Trip Times
+Evaluating: Server Backend Latencies
+Evaluating: Tasks
 Evaluating: Metrics
 Evaluating: start_url responds with a 200 when offline
 Evaluating: Site works cross-browser
 Evaluating: Page transitions don't feel like they block on the network
 Evaluating: Each page has a URL
+Evaluating: `[accesskey]` values are unique
 Evaluating: `[aria-*]` attributes match their roles
 Evaluating: `[role]`s have all required `[aria-*]` attributes
 Evaluating: Elements with `[role]` that require specific children `[role]`s, are present
@@ -222,7 +227,6 @@
 Evaluating: `[lang]` attributes have a valid value
 Evaluating: `<video>` elements contain a `<track>` element with `[kind="captions"]`
 Evaluating: `<video>` elements contain a `<track>` element with `[kind="description"]`
-Evaluating: `[accesskey]` values are unique
 Evaluating: Custom controls have associated labels
 Evaluating: Custom controls have ARIA roles
 Evaluating: User focus is not accidentally trapped in a region
@@ -240,7 +244,7 @@
 Evaluating: Eliminate render-blocking resources
 Evaluating: Minify CSS
 Evaluating: Minify JavaScript
-Evaluating: Defer unused CSS
+Evaluating: Remove unused CSS
 Evaluating: Serve images in next-gen formats
 Evaluating: Efficiently encode images
 Evaluating: Enable text compression
@@ -264,19 +268,19 @@
 Evaluating: Links have descriptive text
 Evaluating: Page isn’t blocked from indexing
 Evaluating: robots.txt is valid
+Evaluating: Tap targets are sized appropriately
 Evaluating: Document has a valid `hreflang`
 Evaluating: Document avoids plugins
 Evaluating: Document has a valid `rel=canonical`
-Evaluating: Page is mobile friendly
 Evaluating: Structured data is valid
 Generating results...
 
 =============== Lighthouse Results ===============
 URL: http://127.0.0.1:8000/devtools/resources/inspected-page.html
-Version: 4.1.0
+Version: 4.2.0
 
 
-accesskeys: manual
+accesskeys: notApplicable
 appcache-manifest: pass
 aria-allowed-attr: notApplicable
 aria-required-attr: notApplicable
@@ -297,6 +301,7 @@
 custom-controls-roles: manual
 definition-list: notApplicable
 deprecations: pass
+diagnostics: ERROR Something went wrong with recording the trace over your page load. Please run Lighthouse again. (NO_FCP)
 dlitem: notApplicable
 doctype: fail
 document-title: fail
@@ -338,14 +343,17 @@
 listitem: notApplicable
 load-fast-enough-for-pwa: flaky
 logical-tab-order: manual
+main-thread-tasks: ERROR Something went wrong with recording the trace over your page load. Please run Lighthouse again. (NO_FCP)
 mainthread-work-breakdown: ERROR Something went wrong with recording the trace over your page load. Please run Lighthouse again. (NO_FCP)
 managed-focus: manual
+max-potential-fid: ERROR Something went wrong with recording the trace over your page load. Please run Lighthouse again. (NO_FCP)
 meta-description: fail
 meta-refresh: notApplicable
 meta-viewport: notApplicable
 metrics: flaky
-mobile-friendly: manual
 network-requests: informative
+network-rtt: informative
+network-server-latency: informative
 no-document-write: pass
 no-vulnerable-libraries: pass
 notification-on-start: pass
@@ -368,6 +376,7 @@
 splash-screen: fail
 structured-data: manual
 tabindex: notApplicable
+tap-targets: fail
 td-headers-attr: notApplicable
 th-has-data-cells: notApplicable
 themed-omnibox: fail
diff --git a/third_party/blink/web_tests/http/tests/notifications/instrumentation-service-worker.js b/third_party/blink/web_tests/http/tests/notifications/instrumentation-service-worker.js
index d9fa071e..a33ff0f 100644
--- a/third_party/blink/web_tests/http/tests/notifications/instrumentation-service-worker.js
+++ b/third_party/blink/web_tests/http/tests/notifications/instrumentation-service-worker.js
@@ -18,6 +18,21 @@
     return deepCopy(notification);
 }
 
+// Deserializes a trigger object sent via postMessage.
+function deserializeTrigger(trigger) {
+    if (trigger && trigger.timestamp)
+        return new TimestampTrigger(trigger.timestamp);
+    return trigger;
+}
+
+// Deserializes notification options sent via postMessage.
+function deserializeOptions(options) {
+    return {
+        ...options,
+        showTrigger: deserializeTrigger(options.showTrigger),
+    };
+}
+
 // Allows a document to exercise the Notifications API within a service worker by sending commands.
 var messagePort = null;
 
@@ -47,7 +62,7 @@
                 break;
 
             case 'show':
-                registration.showNotification(event.data.title, event.data.options).then(() => {
+                registration.showNotification(event.data.title, deserializeOptions(event.data.options)).then(() => {
                     messagePort.postMessage({ command: event.data.command,
                                               success: true });
                 }, error => {
diff --git a/third_party/blink/web_tests/http/tests/notifications/serviceworker-notification-properties.html b/third_party/blink/web_tests/http/tests/notifications/serviceworker-notification-properties.html
index e83c00e..66eb7a4 100644
--- a/third_party/blink/web_tests/http/tests/notifications/serviceworker-notification-properties.html
+++ b/third_party/blink/web_tests/http/tests/notifications/serviceworker-notification-properties.html
@@ -13,14 +13,33 @@
       // Service Worker, and b) ServiceWorkerRegistration.getNotifications(), both
       // accurately reflect the attributes with which the notification was created.
 
+      // Serializes a trigger object to send via postMessage.
+      // If the given object is not a trigger, return the same object.
+      function serializeTrigger(trigger) {
+          if (trigger instanceof TimestampTrigger)
+              return { timestamp: trigger.timestamp };
+          return trigger;
+      }
+
+      // Serialize options to send via sendMessage and for deep comparison.
+      function serializeOptions(options) {
+          return {
+              ...options,
+              showTrigger: serializeTrigger(options.showTrigger),
+          };
+      }
+
       // Checks that all the properties in expected also exist and are equal in actual.
       function assert_object_is_superset(actual, expected, description) {
           Object.keys(expected).forEach(function(key) {
+              // Serialize fields that are triggers so we can compare them.
+              var fieldActual = serializeTrigger(actual[key]);
+              var fieldExpected = serializeTrigger(expected[key]);
               var fieldDescription = description + ' [field ' + key + ']';
-              if (typeof expected[key] == 'object')
-                  assert_object_equals(actual[key], expected[key], fieldDescription);
+              if (typeof fieldExpected == 'object')
+                  assert_object_equals(fieldActual, fieldExpected, fieldDescription);
               else
-                  assert_equals(actual[key], expected[key], fieldDescription);
+                  assert_equals(fieldActual, fieldExpected, fieldDescription);
           });
       }
 
@@ -52,7 +71,9 @@
                     scalar: true },
                   12.15
               ],
-              actions: []
+              actions: [],
+              // Make sure the notification is immediately triggered by setting the timestamp in the past.
+              showTrigger: new TimestampTrigger(Date.now().valueOf() - 1000),
           };
           // Deliberately add more actions than are supported.
           for (var i = 0; i < 2 * Notification.maxActions; i++) {
@@ -76,7 +97,7 @@
                   command: 'show',
 
                   title: scope,
-                  options: options
+                  options: serializeOptions(options)
               });
 
               // Now limit actions to the number that we expect to be reflected on notifications.
@@ -89,7 +110,7 @@
           }).then(function(data) {
               // (3) Confirm that all properties set on the cloned Notification object are as expected.
               assert_object_is_superset(data.notification, options, 'The Notification object properties must be the same in notificationclick events.');
-              return registration.getNotifications();
+              return registration.getNotifications({includeTriggered: true});
           }).then(function(notifications) {
               // (4) Check that the properties are also set correctly on the non-cloned Notification
               // object from getNotifications.
@@ -109,7 +130,6 @@
               assert_equals(notifications[0].actions, notifications[0].actions, '`actions` attribute equality');
               assert_equals(notifications[0].data, notifications[0].data, '`data` attribute equality');
               assert_equals(notifications[0].vibrate, notifications[0].vibrate, '`vibrate` attribute equality');
-
               assert_equals(notifications[0].showTrigger, notifications[0].showTrigger, '`showTrigger` attribute equality');
               test.done();
           }).catch(unreached_rejection(test));
diff --git a/third_party/blink/web_tests/http/tests/notifications/serviceworkerregistration-service-worker-get-filter-triggers.html b/third_party/blink/web_tests/http/tests/notifications/serviceworkerregistration-service-worker-get-filter-triggers.html
new file mode 100644
index 0000000..3a15af4b
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/notifications/serviceworkerregistration-service-worker-get-filter-triggers.html
@@ -0,0 +1,113 @@
+<!doctype html>
+<html>
+  <head>
+    <title>Notifications: ServiceWorkerRegistration.getNotifications() within a Service Worker with includeTriggered.</title>
+    <script src="../resources/testharness.js"></script>
+    <script src="../resources/testharnessreport.js"></script>
+    <script src="../serviceworker/resources/test-helpers.js"></script>
+    <script src="resources/test-helpers.js"></script>
+  </head>
+  <body>
+    <script>
+      // Tests that the getNotifications() function when used in a Service Worker
+      // return an array of the notifications which were previously scheduled using
+      // the same Service Worker registration id.
+      async_test(function(test) {
+          var scope = 'resources/scope/' + location.pathname,
+              script = 'instrumentation-service-worker.js';
+
+          testRunner.setPermission('notifications', 'granted', location.origin, location.origin);
+
+          var info = null;
+          getActiveServiceWorkerWithMessagePort(test, script, scope).then(function(workerInfo) {
+              info = workerInfo;
+
+              // Display two notifications in the Document.
+              return info.registration.showNotification('Hello, world!', {
+                body: 'First notification',
+                tag: 'banana',
+              });
+          }).then(function() {
+              return info.registration.showNotification('Hello again, world!', {
+                body: 'Second notification',
+                tag: 'strawberry',
+              });
+          }).then(function() {
+              // Schedule two notifications.
+              return info.registration.showNotification('Hello once again, world!', {
+                body: 'Third notification',
+                tag: 'kiwi',
+                showTrigger: new TimestampTrigger(Date.now() + 10000),
+              });
+          }).then(function() {
+              return info.registration.showNotification('Hello another time, world!', {
+                body: 'Fourth notification',
+                tag: 'lemon',
+                showTrigger: new TimestampTrigger(Date.now() + 10000),
+              });
+          }).then(function() {
+              // Request the Service Worker to give us a filtered list of notifications.
+              return sendCommand(info.port, {
+                  command: 'get',
+                  filter: {
+                    tag: 'lemon',
+                    includeTriggered: true,
+                  }
+              });
+          }).then(function(data) {
+              // Confirm that the Service Worker was able to get the one matching notification.
+              assert_true(data.success);
+
+              var notifications = data.notifications;
+
+              assert_equals(notifications.length, 1);
+
+              assert_equals(notifications[0].title, 'Hello another time, world!');
+              assert_equals(notifications[0].body, 'Fourth notification');
+          }).then(function() {
+              // Request a triggered notification by tag without |includeTriggered|.
+              return sendCommand(info.port, {
+                  command: 'get',
+                  filter: {
+                    tag: 'kiwi',
+                  }
+              });
+          }).then(function(data) {
+              // Confirm that the Service Worker was not able to get the notification.
+              assert_true(data.success);
+
+              var notifications = data.notifications;
+
+              assert_equals(notifications.length, 0);
+          }).then(function() {
+          }).then(function() {
+              // Request the Service Worker to give us all notifications.
+              return sendCommand(info.port, {
+                  command: 'get',
+                  filter: {
+                    includeTriggered: true,
+                  }
+              });
+          }).then(function(data) {
+              // Confirm that the Service Worker was able to get all notification.
+              assert_true(data.success);
+
+              assert_equals(data.notifications.length, 4);
+          }).then(function() {
+              // Request the Service Worker to give us all displayed notifications.
+              return sendCommand(info.port, {
+                  command: 'get'
+              });
+          }).then(function(data) {
+              // Confirm that the Service Worker was able to get all displayed notification.
+              assert_true(data.success);
+
+              assert_equals(data.notifications.length, 2);
+
+              test.done();
+          }).catch(unreached_rejection(test));
+
+      }, 'ServiceWorkerRegistration.getNotifications() returns the triggered notifications within a Service Worker with a filter.');
+    </script>
+  </body>
+</html>
diff --git a/third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/gradient-invert-expected.png b/third_party/blink/web_tests/paint/dark-mode/image-filter-all/gradient-invert-expected.png
similarity index 100%
rename from third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/gradient-invert-expected.png
rename to third_party/blink/web_tests/paint/dark-mode/image-filter-all/gradient-invert-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/gradient-invert-expected.txt b/third_party/blink/web_tests/paint/dark-mode/image-filter-all/gradient-invert-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/gradient-invert-expected.txt
rename to third_party/blink/web_tests/paint/dark-mode/image-filter-all/gradient-invert-expected.txt
diff --git a/third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/gradient-invert.html b/third_party/blink/web_tests/paint/dark-mode/image-filter-all/gradient-invert.html
similarity index 100%
rename from third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/gradient-invert.html
rename to third_party/blink/web_tests/paint/dark-mode/image-filter-all/gradient-invert.html
diff --git a/third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/image-invert-expected.png b/third_party/blink/web_tests/paint/dark-mode/image-filter-all/image-invert-expected.png
similarity index 100%
rename from third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/image-invert-expected.png
rename to third_party/blink/web_tests/paint/dark-mode/image-filter-all/image-invert-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/image-invert-expected.txt b/third_party/blink/web_tests/paint/dark-mode/image-filter-all/image-invert-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/image-invert-expected.txt
rename to third_party/blink/web_tests/paint/dark-mode/image-filter-all/image-invert-expected.txt
diff --git a/third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/image-invert.html b/third_party/blink/web_tests/paint/dark-mode/image-filter-all/image-invert.html
similarity index 100%
rename from third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/image-invert.html
rename to third_party/blink/web_tests/paint/dark-mode/image-filter-all/image-invert.html
diff --git a/third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/text-on-backgrounds-expected.png b/third_party/blink/web_tests/paint/dark-mode/image-filter-all/text-on-backgrounds-expected.png
similarity index 100%
rename from third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/text-on-backgrounds-expected.png
rename to third_party/blink/web_tests/paint/dark-mode/image-filter-all/text-on-backgrounds-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/text-on-backgrounds-expected.txt b/third_party/blink/web_tests/paint/dark-mode/image-filter-all/text-on-backgrounds-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/text-on-backgrounds-expected.txt
rename to third_party/blink/web_tests/paint/dark-mode/image-filter-all/text-on-backgrounds-expected.txt
diff --git a/third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/text-on-backgrounds.html b/third_party/blink/web_tests/paint/dark-mode/image-filter-all/text-on-backgrounds.html
similarity index 100%
rename from third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/text-on-backgrounds.html
rename to third_party/blink/web_tests/paint/dark-mode/image-filter-all/text-on-backgrounds.html
diff --git a/third_party/blink/web_tests/paint/high-contrast-mode/image-filter-none/gradient-noinvert-expected.png b/third_party/blink/web_tests/paint/dark-mode/image-filter-none/gradient-noinvert-expected.png
similarity index 100%
rename from third_party/blink/web_tests/paint/high-contrast-mode/image-filter-none/gradient-noinvert-expected.png
rename to third_party/blink/web_tests/paint/dark-mode/image-filter-none/gradient-noinvert-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/paint/high-contrast-mode/image-filter-none/gradient-noinvert-expected.txt b/third_party/blink/web_tests/paint/dark-mode/image-filter-none/gradient-noinvert-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/paint/high-contrast-mode/image-filter-none/gradient-noinvert-expected.txt
rename to third_party/blink/web_tests/paint/dark-mode/image-filter-none/gradient-noinvert-expected.txt
diff --git a/third_party/blink/web_tests/paint/high-contrast-mode/image-filter-none/gradient-noinvert.html b/third_party/blink/web_tests/paint/dark-mode/image-filter-none/gradient-noinvert.html
similarity index 100%
rename from third_party/blink/web_tests/paint/high-contrast-mode/image-filter-none/gradient-noinvert.html
rename to third_party/blink/web_tests/paint/dark-mode/image-filter-none/gradient-noinvert.html
diff --git a/third_party/blink/web_tests/paint/high-contrast-mode/image-filter-none/image-noinvert-expected.png b/third_party/blink/web_tests/paint/dark-mode/image-filter-none/image-noinvert-expected.png
similarity index 100%
rename from third_party/blink/web_tests/paint/high-contrast-mode/image-filter-none/image-noinvert-expected.png
rename to third_party/blink/web_tests/paint/dark-mode/image-filter-none/image-noinvert-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/paint/high-contrast-mode/image-filter-none/image-noinvert-expected.txt b/third_party/blink/web_tests/paint/dark-mode/image-filter-none/image-noinvert-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/paint/high-contrast-mode/image-filter-none/image-noinvert-expected.txt
rename to third_party/blink/web_tests/paint/dark-mode/image-filter-none/image-noinvert-expected.txt
diff --git a/third_party/blink/web_tests/paint/high-contrast-mode/image-filter-none/image-noinvert.html b/third_party/blink/web_tests/paint/dark-mode/image-filter-none/image-noinvert.html
similarity index 100%
rename from third_party/blink/web_tests/paint/high-contrast-mode/image-filter-none/image-noinvert.html
rename to third_party/blink/web_tests/paint/dark-mode/image-filter-none/image-noinvert.html
diff --git a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-all/README.txt b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-all/README.txt
similarity index 68%
rename from third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-all/README.txt
rename to third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-all/README.txt
index 2efd27f..65c317fd 100644
--- a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-all/README.txt
+++ b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-all/README.txt
@@ -1,3 +1,3 @@
-# This suite runs the tests in LayoutTests/paint/high-contrast-mode
+# This suite runs the tests in LayoutTests/paint/dark-mode
 # with --blink-settings="highContrastMode=3,highContrastImagePolicy=0"
 # See the virtual_test_suites() method in tools/blinkpy/web_tests/port/base.py.
diff --git a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-all/gradient-invert-expected.png b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-all/gradient-invert-expected.png
similarity index 100%
rename from third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-all/gradient-invert-expected.png
rename to third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-all/gradient-invert-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/gradient-invert-expected.txt b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-all/gradient-invert-expected.txt
similarity index 100%
copy from third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/gradient-invert-expected.txt
copy to third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-all/gradient-invert-expected.txt
diff --git a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-all/image-invert-expected.png b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-all/image-invert-expected.png
similarity index 100%
rename from third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-all/image-invert-expected.png
rename to third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-all/image-invert-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/image-invert-expected.txt b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-all/image-invert-expected.txt
similarity index 100%
copy from third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/image-invert-expected.txt
copy to third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-all/image-invert-expected.txt
diff --git a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-all/text-on-backgrounds-expected.png b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-all/text-on-backgrounds-expected.png
similarity index 100%
rename from third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-all/text-on-backgrounds-expected.png
rename to third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-all/text-on-backgrounds-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/text-on-backgrounds-expected.txt b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-all/text-on-backgrounds-expected.txt
similarity index 100%
copy from third_party/blink/web_tests/paint/high-contrast-mode/image-filter-all/text-on-backgrounds-expected.txt
copy to third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-all/text-on-backgrounds-expected.txt
diff --git a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-none/README.txt b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-none/README.txt
similarity index 68%
rename from third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-none/README.txt
rename to third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-none/README.txt
index 74ce2f1..767087fb 100644
--- a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-none/README.txt
+++ b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-none/README.txt
@@ -1,3 +1,3 @@
-# This suite runs the tests in LayoutTests/paint/high-contrast-mode
+# This suite runs the tests in LayoutTests/paint/dark-mode
 # with --blink-settings="highContrastMode=3,highContrastImagePolicy=1"
 # See the virtual_test_suites() method in tools/blinkpy/web_tests/port/base.py.
diff --git a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-none/gradient-noinvert-expected.png b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-none/gradient-noinvert-expected.png
similarity index 100%
rename from third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-none/gradient-noinvert-expected.png
rename to third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-none/gradient-noinvert-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/paint/high-contrast-mode/image-filter-none/gradient-noinvert-expected.txt b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-none/gradient-noinvert-expected.txt
similarity index 100%
copy from third_party/blink/web_tests/paint/high-contrast-mode/image-filter-none/gradient-noinvert-expected.txt
copy to third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-none/gradient-noinvert-expected.txt
diff --git a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-none/image-noinvert-expected.png b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-none/image-noinvert-expected.png
similarity index 100%
rename from third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-none/image-noinvert-expected.png
rename to third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-none/image-noinvert-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/paint/high-contrast-mode/image-filter-none/image-noinvert-expected.txt b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-none/image-noinvert-expected.txt
similarity index 100%
copy from third_party/blink/web_tests/paint/high-contrast-mode/image-filter-none/image-noinvert-expected.txt
copy to third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-none/image-noinvert-expected.txt
diff --git a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-smart/README.txt b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-smart/README.txt
similarity index 68%
rename from third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-smart/README.txt
rename to third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-smart/README.txt
index f63db1c..15d882fd 100644
--- a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-smart/README.txt
+++ b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-smart/README.txt
@@ -1,3 +1,3 @@
-# This suite runs the tests in LayoutTests/paint/high-contrast-mode
+# This suite runs the tests in LayoutTests/paint/dark-mode
 # with --blink-settings="highContrastMode=3,highContrastImagePolicy=2"
 # See the virtual_test_suites() method in tools/blinkpy/web_tests/port/base.py.
diff --git a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-smart/gradient-noinvert-expected.png b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-smart/gradient-noinvert-expected.png
similarity index 100%
rename from third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-smart/gradient-noinvert-expected.png
rename to third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-smart/gradient-noinvert-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-smart/gradient-noinvert-expected.txt b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-smart/gradient-noinvert-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-smart/gradient-noinvert-expected.txt
rename to third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-smart/gradient-noinvert-expected.txt
diff --git a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-smart/image-invert-expected.png b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-smart/image-invert-expected.png
similarity index 100%
rename from third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-smart/image-invert-expected.png
rename to third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-smart/image-invert-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-smart/image-invert-expected.txt b/third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-smart/image-invert-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-smart/image-invert-expected.txt
rename to third_party/blink/web_tests/virtual/dark-mode/paint/dark-mode/image-filter-smart/image-invert-expected.txt
diff --git a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-all/gradient-invert-expected.txt b/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-all/gradient-invert-expected.txt
deleted file mode 100644
index 8b13789..0000000
--- a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-all/gradient-invert-expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-all/image-invert-expected.txt b/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-all/image-invert-expected.txt
deleted file mode 100644
index 8b13789..0000000
--- a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-all/image-invert-expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-all/text-on-backgrounds-expected.txt b/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-all/text-on-backgrounds-expected.txt
deleted file mode 100644
index 3b2860a..0000000
--- a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-all/text-on-backgrounds-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-Red on white Green on white Blue on white
-Red on black Green on black Blue on black
-Black on red Black on green Black on blue
-White on red White on green White on blue
diff --git a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-none/gradient-noinvert-expected.txt b/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-none/gradient-noinvert-expected.txt
deleted file mode 100644
index 8b13789..0000000
--- a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-none/gradient-noinvert-expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-none/image-noinvert-expected.txt b/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-none/image-noinvert-expected.txt
deleted file mode 100644
index 8b13789..0000000
--- a/third_party/blink/web_tests/virtual/high-contrast-mode/paint/high-contrast-mode/image-filter-none/image-noinvert-expected.txt
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/third_party/blink/web_tests/virtual/not-site-per-process/external/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects-expected.txt b/third_party/blink/web_tests/virtual/not-site-per-process/external/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects-expected.txt
deleted file mode 100644
index 770e1bf..0000000
--- a/third_party/blink/web_tests/virtual/not-site-per-process/external/wpt/html/browsers/origin/cross-origin-objects/cross-origin-objects-expected.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-This is a testharness.js-based test.
-PASS Basic sanity-checking
-FAIL Only whitelisted properties are accessible cross-origin Blocked a frame with origin "http://web-platform.test:8001" from accessing a cross-origin frame.
-PASS [[GetPrototypeOf]] should return null
-FAIL [[SetPrototypeOf]] should return false assert_throws: proto setter |call| on cross-origin Window function "function() { protoSetter.call(C, new Object()); }" threw object "SecurityError: Blocked a frame with origin "http://web-platform.test:8001" from accessing a cross-origin frame." ("SecurityError") expected object "TypeError" ("TypeError")
-PASS [[IsExtensible]] should return true for cross-origin objects
-FAIL [[PreventExtensions]] should throw for cross-origin objects assert_throws: preventExtensions on cross-origin Window should throw function "function() { Object.preventExtensions(C) }" threw object "SecurityError: Blocked a frame with origin "http://web-platform.test:8001" from accessing a cross-origin frame." ("SecurityError") expected object "TypeError" ("TypeError")
-FAIL [[GetOwnProperty]] - Properties on cross-origin objects should be reported |own| Blocked a frame with origin "http://web-platform.test:8001" from accessing a cross-origin frame.
-FAIL [[GetOwnProperty]] - Property descriptors for cross-origin properties should be set up correctly assert_equals: property descriptor for 0 should be enumerable expected true but got false
-PASS [[GetOwnProperty]] - Subframe named 'then' should shadow the default 'then' value
-PASS [[GetOwnProperty]] - Subframes should be visible cross-origin only if their names don't match the names of cross-origin-exposed IDL properties
-PASS [[GetOwnProperty]] - Should be able to get a property descriptor for an indexed property only if it corresponds to a child window.
-PASS [[Delete]] Should throw on cross-origin objects
-PASS [[DefineOwnProperty]] Should throw for cross-origin objects
-FAIL Can only enumerate safelisted enumerable properties assert_equals: Enumerate all enumerable safelisted cross-origin Window properties expected 2 but got 0
-FAIL [[OwnPropertyKeys]] should return all properties from cross-origin objects assert_array_equals: Object.getOwnPropertyNames() gives the right answer for cross-origin Window lengths differ, expected 16 got 13
-FAIL [[OwnPropertyKeys]] should return the right symbol-named properties for cross-origin objects assert_array_equals: Object.getOwnPropertySymbols() should return the three symbol-named properties that are exposed on a cross-origin Window lengths differ, expected 3 got 0
-FAIL [[OwnPropertyKeys]] should place the symbols after the property names after the subframe indices assert_equals: 'then' property should be added to the end of the string list if not there expected "then" but got "close"
-FAIL [[OwnPropertyKeys]] should not reorder where 'then' appears if it's a named subframe, nor add another copy of 'then' assert_equals: expected "then" but got "postMessage"
-PASS A and B jointly observe the same identity for cross-origin Window and Location
-PASS Cross-origin functions get local Function.prototype
-FAIL Cross-origin Window accessors get local Function.prototype Cannot read property 'name' of undefined
-FAIL Same-origin observers get different functions for cross-origin objects assert_not_equals: same-origin Window functions get their own object got disallowed value function "function () { [native code] }"
-FAIL Same-origin observers get different accessors for cross-origin Window assert_not_equals: different Window accessors per-incumbent script settings object got disallowed value undefined
-FAIL Same-origin observers get different accessors for cross-origin Location Blocked a frame with origin "http://web-platform.test:8001" from accessing a cross-origin frame.
-FAIL {}.toString.call() does the right thing on cross-origin objects assert_equals: expected "[object Object]" but got "[object Location]"
-PASS Resolving a promise with a cross-origin window without a 'then' subframe should work.
-PASS Resolving a promise with a cross-origin window with a 'then' subframe should work.
-PASS Resolving a promise with a cross-origin location should work.
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt b/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt
index 8d09ebc..26ac7bd4 100644
--- a/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt
+++ b/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt
@@ -12,6 +12,7 @@
 fullscreen
 geolocation
 gyroscope
+hid
 layout-animations
 lazyload
 legacy-image-formats
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index 6cb1c315..0ae5b01 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -2356,6 +2356,9 @@
     getter y
     getter z
     method constructor
+interface HID : EventTarget
+    attribute @@toStringTag
+    method constructor
 interface HTMLAllCollection
     attribute @@toStringTag
     getter length
@@ -4823,6 +4826,7 @@
     getter doNotTrack
     getter geolocation
     getter hardwareConcurrency
+    getter hid
     getter idle
     getter keyboard
     getter language
diff --git a/third_party/blink/web_tests/xr/getInputPose_handedness.html b/third_party/blink/web_tests/xr/getInputPose_handedness.html
new file mode 100644
index 0000000..08ebc6a
--- /dev/null
+++ b/third_party/blink/web_tests/xr/getInputPose_handedness.html
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
+<script src="file:///gen/device/vr/public/mojom/vr_service.mojom.js"></script>
+<script src="../external/wpt/resources/chromium/webxr-test.js"></script>
+<script src="../external/wpt/webxr/resources/webxr_test_constants.js"></script>
+<script src="../xr/resources/xr-internal-device-mocking.js"></script>
+<script src="../xr/resources/xr-test-utils.js"></script>
+<canvas id="webgl-canvas"></canvas>
+
+<script>
+
+let testName = "XRInputSources properly communicate their handedness";
+
+let fakeDeviceInitParams = { supportsImmersive: true };
+
+let requestSessionOptions =  [{ mode: 'immersive-vr' }];
+
+let testFunction =
+  (session, t, fakeDeviceController) => new Promise((resolve) => {
+    // Session must have a baseLayer or frame requests will be ignored.
+    session.updateRenderState({ baseLayer: new XRWebGLLayer(session, gl) });
+
+    // Need to have a valid pose or input events don't process.
+    fakeDeviceController.setXRPresentationFrameData(VALID_POSE_MATRIX, [{
+        eye:"left",
+        projectionMatrix: VALID_PROJECTION_MATRIX,
+        viewMatrix: VALID_VIEW_MATRIX
+      }, {
+        eye:"right",
+        projectionMatrix: VALID_PROJECTION_MATRIX,
+        viewMatrix: VALID_VIEW_MATRIX
+      }]);
+
+    let input_source = new MockXRInputSource();
+    input_source.targetRayMode = "tracked-pointer";
+
+    fakeDeviceController.addInputSource(input_source);
+
+    function CheckNone(time, xrFrame) {
+      let source = session.getInputSources()[0];
+
+      t.step( () => {
+        assert_not_equals(source, null);
+        // Handedness should be "none" by default.
+        assert_equals(source.handedness, "none");
+      });
+
+      input_source.handedness = "right"
+
+      session.requestAnimationFrame(CheckRight);
+    }
+
+    function CheckRight(time, xrFrame) {
+      let source = session.getInputSources()[0];
+
+      t.step( () => {
+        assert_not_equals(source, null);
+        // Handedness was set to "right", make sure it propegates.
+        assert_equals(source.handedness, "right");
+      });
+
+      input_source.handedness = "left";
+
+      session.requestAnimationFrame(CheckLeft);
+    }
+
+    function CheckLeft(time, xrFrame) {
+      let source = session.getInputSources()[0];
+
+      t.step( () => {
+        assert_not_equals(source, null);
+        // Handedness was set to "left", make sure it propegates.
+        assert_equals(source.handedness, "left");
+      });
+
+      input_source.handedness = "none";
+
+      session.requestAnimationFrame(CheckNoneAgain);
+    }
+
+    function CheckNoneAgain(time, xrFrame) {
+      let source = session.getInputSources()[0];
+
+      t.step( () => {
+        assert_not_equals(source, null);
+        // Handedness was set to "none" again, make sure it propegates.
+        assert_equals(source.handedness, "none");
+      });
+
+      resolve();
+    }
+
+    // Handedness only updates during an XR frame.
+    session.requestAnimationFrame(CheckNone);
+  });
+
+xr_session_promise_test(
+  testFunction, fakeDeviceInitParams, requestSessionOptions, testName);
+
+</script>
diff --git a/third_party/blink/web_tests/xr/xrInputSource_add_remove.html b/third_party/blink/web_tests/xr/xrInputSource_add_remove.html
index 807bfab..90b6dfb0 100644
--- a/third_party/blink/web_tests/xr/xrInputSource_add_remove.html
+++ b/third_party/blink/web_tests/xr/xrInputSource_add_remove.html
@@ -65,7 +65,7 @@
         t.step( () => {
           assert_equals(input_sources.length, 2);
           assert_equals(input_sources[1].targetRayMode, "gaze");
-          assert_equals(input_sources[1].handedness, "");
+          assert_equals(input_sources[1].handedness, "none");
         });
 
         fakeDeviceController.removeInputSource(input_source_1);
@@ -76,7 +76,7 @@
           t.step( () => {
             assert_equals(input_sources.length, 1);
             assert_equals(input_sources[0].targetRayMode, "gaze");
-            assert_equals(input_sources[0].handedness, "");
+            assert_equals(input_sources[0].handedness, "none");
           });
 
           resolve();
diff --git a/third_party/closure_compiler/externs/accessibility_private.js b/third_party/closure_compiler/externs/accessibility_private.js
index 7bf7732..a919ab1 100644
--- a/third_party/closure_compiler/externs/accessibility_private.js
+++ b/third_party/closure_compiler/externs/accessibility_private.js
@@ -119,6 +119,30 @@
 };
 
 /**
+ * Information about a focus ring.
+ * @typedef {{
+ *   id: ?string,
+ *   rects: !Array<!chrome.accessibilityPrivate.ScreenRect>,
+ *   type: chrome.accessibilityPrivate.FocusType,
+ *   color: string,
+ *   secondaryColor: ?string
+ * }}
+ */
+chrome.accessibilityPrivate.FocusRingInfo;
+
+/** @enum {string} */
+chrome.accessibilityPrivate.FocusType = {
+  // The focus ring used by ChromeVox and Select-to-Speak. Fades from full
+  // opacity to full transparency. Does not support two colors.
+  GLOW: 'glow',
+  // The focus ring used by Switch Access. Supports two-color focus rings.
+  SOLID: 'solid',
+  // Used by Switch Access to indicate the current scope. Supports two-color
+  // focus rings.
+  DASHED: 'dashed',
+}
+
+/**
  * Called to request battery status from Chrome OS system.
  * @param {function(string):void} callback Returns battery description as a
  *     string.
@@ -135,12 +159,11 @@
 
 /**
  * Sets the bounds of the accessibility focus ring.
- * @param {!Array<!chrome.accessibilityPrivate.ScreenRect>} rects Array of
- *     rectangles to draw the accessibility focus ring around.
- * @param {string=} color CSS-style hex color string beginning with # like
- *     #FF9982 or #EEE.
+ * @param {!Array<!chrome.accessibilityPrivate.FocusRingInfo>} focusRings Array of
+ *     focus rings to draw, each with information about the position onscreen,
+ *     display type, and colors to be used.
  */
-chrome.accessibilityPrivate.setFocusRing = function(rects, color) {};
+chrome.accessibilityPrivate.setFocusRings = function(focusRings) {};
 
 /**
  * Sets the bounds of the accessibility highlight.
diff --git a/third_party/ink/README.chromium b/third_party/ink/README.chromium
index 405ce3c..f57a57be2 100644
--- a/third_party/ink/README.chromium
+++ b/third_party/ink/README.chromium
@@ -1,7 +1,7 @@
 Name: Google Ink
 Short Name: ink
 URL: https://github.com/google/ink
-Version: 236729529
+Version: 236852834
 License: Apache 2.0
 Security Critical: yes
 
diff --git a/third_party/ink/build/wasm-threads/glcore_base.js.mem.sha1 b/third_party/ink/build/wasm-threads/glcore_base.js.mem.sha1
index 6aaf19f..53c0e1f 100644
--- a/third_party/ink/build/wasm-threads/glcore_base.js.mem.sha1
+++ b/third_party/ink/build/wasm-threads/glcore_base.js.mem.sha1
@@ -1 +1 @@
-d346855d34df6a757683494be06cad9aac8e1a01
\ No newline at end of file
+941ed55659a63d383ad04b087191cd6f9060b26b
\ No newline at end of file
diff --git a/third_party/ink/build/wasm-threads/glcore_base.wasm.sha1 b/third_party/ink/build/wasm-threads/glcore_base.wasm.sha1
index 714e08a..8879c84 100644
--- a/third_party/ink/build/wasm-threads/glcore_base.wasm.sha1
+++ b/third_party/ink/build/wasm-threads/glcore_base.wasm.sha1
@@ -1 +1 @@
-3e0eaa76ec1238f9cd1cecf7808d700cd9a84924
\ No newline at end of file
+9dbe21d2746b87834de7627d9652f8e6f591e094
\ No newline at end of file
diff --git a/third_party/ink/build/wasm/glcore_base.wasm.sha1 b/third_party/ink/build/wasm/glcore_base.wasm.sha1
index 68dc8071..ab47683 100644
--- a/third_party/ink/build/wasm/glcore_base.wasm.sha1
+++ b/third_party/ink/build/wasm/glcore_base.wasm.sha1
@@ -1 +1 @@
-5310afd49946994abbf109c77d06831143d7f623
\ No newline at end of file
+f5cf27eda4fd26167e9dea8c4ad6e45418f0fd10
\ No newline at end of file
diff --git a/third_party/ink/ink_resources.grd b/third_party/ink/ink_resources.grd
index 3d2e058..4c7a2b9 100644
--- a/third_party/ink/ink_resources.grd
+++ b/third_party/ink/ink_resources.grd
@@ -12,10 +12,8 @@
     <includes>
       <if expr="chromeos">
         <include name="IDR_INK_LIB_BINARY_JS" file="build/ink_lib_binary.js" type="BINDATA" />
-        <include name="IDR_INK_PTHREAD_MAIN_JS" file="build/wasm-threads/pthread-main.js" type="BINDATA" />
-        <include name="IDR_INK_GLCORE_BASE_WASM" file="build/wasm-threads/glcore_base.wasm" compress="gzip" type="BINDATA" />
-        <include name="IDR_INK_GLCORE_BASE_JS_MEM" file="build/wasm-threads/glcore_base.js.mem" compress="gzip" type="BINDATA" />
-        <include name="IDR_INK_GLCORE_WASM_BOOTSTRAP_COMPILED_JS" file="build/wasm-threads/glcore_wasm_bootstrap_compiled.js" type="BINDATA" />
+        <include name="IDR_INK_GLCORE_BASE_WASM" file="build/wasm/glcore_base.wasm" compress="gzip" type="BINDATA" />
+        <include name="IDR_INK_GLCORE_WASM_BOOTSTRAP_COMPILED_JS" file="build/wasm/glcore_wasm_bootstrap_compiled.js" type="BINDATA" />
       </if>
     </includes>
   </release>
diff --git a/third_party/quic_trace/BUILD.gn b/third_party/quic_trace/BUILD.gn
index eb83eeaf..fb23c039 100644
--- a/third_party/quic_trace/BUILD.gn
+++ b/third_party/quic_trace/BUILD.gn
@@ -23,9 +23,7 @@
   sources = [
     "$target_gen_dir/quic_trace.proto",
   ]
-  deps = [
-    ":quic_trace_proto_lite_runtime",
-  ]
+  proto_deps = [ ":quic_trace_proto_lite_runtime" ]
   component_build_force_source_set = true
   testonly = true
 
diff --git a/third_party/sqlite/amalgamation/sqlite3.c b/third_party/sqlite/amalgamation/sqlite3.c
index 7ead39b5..911b658 100644
--- a/third_party/sqlite/amalgamation/sqlite3.c
+++ b/third_party/sqlite/amalgamation/sqlite3.c
@@ -54328,8 +54328,14 @@
       rc = sqlite3OsFileSize(pPager->fd, &nByte);
     }
     if( rc==SQLITE_OK ){
-      pNew = (char *)sqlite3PageMalloc(pageSize);
-      if( !pNew ) rc = SQLITE_NOMEM_BKPT;
+      /* 8 bytes of zeroed overrun space is sufficient so that the b-tree
+      * cell header parser will never run off the end of the allocation */
+      pNew = (char *)sqlite3PageMalloc(pageSize+8);
+      if( !pNew ){
+        rc = SQLITE_NOMEM_BKPT;
+      }else{
+        memset(pNew+pageSize, 0, 8);
+      }
     }
 
     if( rc==SQLITE_OK ){
@@ -68646,7 +68652,7 @@
             sqlite3_free(pCellKey);
             goto moveto_finish;
           }
-          c = xRecordCompare(nCell, pCellKey, pIdxKey);
+          c = sqlite3VdbeRecordCompare(nCell, pCellKey, pIdxKey);
           sqlite3_free(pCellKey);
         }
         assert(
@@ -72676,10 +72682,10 @@
   IntegrityCk *pCheck,  /* Integrity checking context */
   int isFreeList,       /* True for a freelist.  False for overflow page list */
   int iPage,            /* Page number for first page in the list */
-  int N                 /* Expected number of pages in the list */
+  u32 N                 /* Expected number of pages in the list */
 ){
   int i;
-  int expected = N;
+  u32 expected = N;
   int nErrAtStart = pCheck->nErr;
   while( iPage!=0 && pCheck->mxErr ){
     DbPage *pOvflPage;
@@ -72933,7 +72939,7 @@
 
     /* Check the content overflow list */
     if( info.nPayload>info.nLocal ){
-      int nPage;       /* Number of pages on the overflow chain */
+      u32 nPage;       /* Number of pages on the overflow chain */
       Pgno pgnoOvfl;   /* First page of the overflow chain */
       assert( pc + info.nSize - 4 <= usableSize );
       nPage = (info.nPayload - info.nLocal + usableSize - 5)/(usableSize - 4);
@@ -221514,7 +221520,7 @@
 #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */
 
 /************** End of stmt.c ************************************************/
-#if __LINE__!=221517
+#if __LINE__!=221523
 #undef SQLITE_SOURCE_ID
 #define SQLITE_SOURCE_ID      "2019-02-25 16:06:06 bd49a8271d650fa89e446b42e513b595a717b9212c91dd384aab871fc1d0alt2"
 #endif
diff --git a/third_party/sqlite/patches/0001-Modify-default-VFS-to-support-WebDatabase.patch b/third_party/sqlite/patches/0001-Modify-default-VFS-to-support-WebDatabase.patch
index 13c109b..40932de 100644
--- a/third_party/sqlite/patches/0001-Modify-default-VFS-to-support-WebDatabase.patch
+++ b/third_party/sqlite/patches/0001-Modify-default-VFS-to-support-WebDatabase.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: dumi <dumi@chromium.org>
 Date: Mon, 20 Jul 2009 23:40:51 +0000
-Subject: [PATCH 1/7] Modify default VFS to support WebDatabase.
+Subject: [PATCH 01/10] Modify default VFS to support WebDatabase.
 
 The renderer WebDatabase implementation needs to broker certain requests
 to the browser.  This modifies SQLite to allow monkey-patching the VFS
@@ -175,5 +175,5 @@
  ** CAPI3REF: String LIKE Matching
  *
 -- 
-2.21.0.rc2.261.ga7da99ff1b-goog
+2.21.0.352.gf09ad66450-goog
 
diff --git a/third_party/sqlite/patches/0002-Virtual-table-supporting-recovery-of-corrupted-datab.patch b/third_party/sqlite/patches/0002-Virtual-table-supporting-recovery-of-corrupted-datab.patch
index fc8bbab..e05110e 100644
--- a/third_party/sqlite/patches/0002-Virtual-table-supporting-recovery-of-corrupted-datab.patch
+++ b/third_party/sqlite/patches/0002-Virtual-table-supporting-recovery-of-corrupted-datab.patch
@@ -1,7 +1,8 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Scott Hess <shess@chromium.org>
 Date: Sat, 20 Jul 2013 11:42:21 -0700
-Subject: [PATCH 2/7] Virtual table supporting recovery of corrupted databases.
+Subject: [PATCH 02/10] Virtual table supporting recovery of corrupted
+ databases.
 
 "recover" implements a virtual table which uses the SQLite pager layer
 to read table pages and pull out the data which is structurally sound
@@ -3900,5 +3901,5 @@
 +
 +finish_test
 -- 
-2.21.0.rc2.261.ga7da99ff1b-goog
+2.21.0.352.gf09ad66450-goog
 
diff --git a/third_party/sqlite/patches/0003-Custom-shell.c-helpers-to-load-Chromium-s-ICU-data.patch b/third_party/sqlite/patches/0003-Custom-shell.c-helpers-to-load-Chromium-s-ICU-data.patch
index f1a2667e..2396150 100644
--- a/third_party/sqlite/patches/0003-Custom-shell.c-helpers-to-load-Chromium-s-ICU-data.patch
+++ b/third_party/sqlite/patches/0003-Custom-shell.c-helpers-to-load-Chromium-s-ICU-data.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: "tc@google.com" <tc@google.com>
 Date: Tue, 6 Jan 2009 22:39:41 +0000
-Subject: [PATCH 3/7] Custom shell.c helpers to load Chromium's ICU data.
+Subject: [PATCH 03/10] Custom shell.c helpers to load Chromium's ICU data.
 
 History uses fts3 with an icu-based segmenter.  These changes allow building a
 sqlite3 binary for Linux or Windows which can read those files.
@@ -141,5 +141,5 @@
 +  return 1;
 +}
 -- 
-2.21.0.rc2.261.ga7da99ff1b-goog
+2.21.0.352.gf09ad66450-goog
 
diff --git a/third_party/sqlite/patches/0004-fts3-Disable-fts3_tokenizer-and-fts4.patch b/third_party/sqlite/patches/0004-fts3-Disable-fts3_tokenizer-and-fts4.patch
index a385181..f0c5125e 100644
--- a/third_party/sqlite/patches/0004-fts3-Disable-fts3_tokenizer-and-fts4.patch
+++ b/third_party/sqlite/patches/0004-fts3-Disable-fts3_tokenizer-and-fts4.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Scott Hess <shess@chromium.org>
 Date: Tue, 16 Dec 2014 13:02:27 -0800
-Subject: [PATCH 4/7] [fts3] Disable fts3_tokenizer and fts4.
+Subject: [PATCH 04/10] [fts3] Disable fts3_tokenizer and fts4.
 
 fts3_tokenizer allows a SQLite user to specify a pointer to call as a
 function, which has obvious sercurity implications.  Disable fts4 until
@@ -56,5 +56,5 @@
    }
  
 -- 
-2.21.0.rc2.261.ga7da99ff1b-goog
+2.21.0.352.gf09ad66450-goog
 
diff --git a/third_party/sqlite/patches/0005-fuchsia-Use-dot-file-locking-for-sqlite.patch b/third_party/sqlite/patches/0005-fuchsia-Use-dot-file-locking-for-sqlite.patch
index c1b26254..70172d6 100644
--- a/third_party/sqlite/patches/0005-fuchsia-Use-dot-file-locking-for-sqlite.patch
+++ b/third_party/sqlite/patches/0005-fuchsia-Use-dot-file-locking-for-sqlite.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Scott Graham <scottmg@chromium.org>
 Date: Mon, 11 Sep 2017 13:37:46 -0700
-Subject: [PATCH 5/7] fuchsia: Use dot-file locking for sqlite
+Subject: [PATCH 05/10] fuchsia: Use dot-file locking for sqlite
 
 ---
  third_party/sqlite/src/src/os_unix.c | 4 ++++
@@ -23,5 +23,5 @@
      UNIXVFS("unix",          posixIoFinder ),
  #endif
 -- 
-2.21.0.rc2.261.ga7da99ff1b-goog
+2.21.0.352.gf09ad66450-goog
 
diff --git a/third_party/sqlite/patches/0006-Fix-compilation-with-SQLITE_OMIT_WINDOWFUNC.patch b/third_party/sqlite/patches/0006-Fix-compilation-with-SQLITE_OMIT_WINDOWFUNC.patch
index e6a777b..b1f49d81 100644
--- a/third_party/sqlite/patches/0006-Fix-compilation-with-SQLITE_OMIT_WINDOWFUNC.patch
+++ b/third_party/sqlite/patches/0006-Fix-compilation-with-SQLITE_OMIT_WINDOWFUNC.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Victor Costan <pwnall@chromium.org>
 Date: Sun, 10 Feb 2019 13:12:57 -0800
-Subject: [PATCH 6/7] Fix compilation with SQLITE_OMIT_WINDOWFUNC.
+Subject: [PATCH 06/10] Fix compilation with SQLITE_OMIT_WINDOWFUNC.
 
 ---
  third_party/sqlite/src/src/resolve.c | 2 ++
@@ -28,5 +28,5 @@
      /* If this is part of a compound SELECT, check that it has the right
      ** number of expressions in the select list. */
 -- 
-2.21.0.rc2.261.ga7da99ff1b-goog
+2.21.0.352.gf09ad66450-goog
 
diff --git a/third_party/sqlite/patches/0007-Fix-dbfuzz2.c-compilation-errors-on-Windows.patch b/third_party/sqlite/patches/0007-Fix-dbfuzz2.c-compilation-errors-on-Windows.patch
index 8f3cdb47..ad34409 100644
--- a/third_party/sqlite/patches/0007-Fix-dbfuzz2.c-compilation-errors-on-Windows.patch
+++ b/third_party/sqlite/patches/0007-Fix-dbfuzz2.c-compilation-errors-on-Windows.patch
@@ -1,7 +1,7 @@
 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 From: Victor Costan <pwnall@chromium.org>
 Date: Sun, 10 Feb 2019 15:18:43 -0800
-Subject: [PATCH 7/7] Fix dbfuzz2.c compilation errors on Windows.
+Subject: [PATCH 07/10] Fix dbfuzz2.c compilation errors on Windows.
 
 ---
  third_party/sqlite/src/test/dbfuzz2.c | 4 ++++
@@ -39,5 +39,5 @@
      argv[j++] = argv[i];
    }
 -- 
-2.21.0.rc2.261.ga7da99ff1b-goog
+2.21.0.352.gf09ad66450-goog
 
diff --git a/third_party/sqlite/patches/0008-Fix-Heap-buffer-overflow-in-vdbeRecordCompareInt.patch b/third_party/sqlite/patches/0008-Fix-Heap-buffer-overflow-in-vdbeRecordCompareInt.patch
new file mode 100644
index 0000000..456c8b1c
--- /dev/null
+++ b/third_party/sqlite/patches/0008-Fix-Heap-buffer-overflow-in-vdbeRecordCompareInt.patch
@@ -0,0 +1,28 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Darwin Huang <huangdarwin@chromium.org>
+Date: Tue, 5 Mar 2019 13:49:51 -0800
+Subject: [PATCH 08/10] Fix Heap-buffer-overflow in vdbeRecordCompareInt
+
+This backports https://www.sqlite.org/src/info/c1ac00706bae45fe
+
+Bug: 932353
+---
+ third_party/sqlite/src/src/btree.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/third_party/sqlite/src/src/btree.c b/third_party/sqlite/src/src/btree.c
+index 773be1646914..caa45e507da6 100644
+--- a/third_party/sqlite/src/src/btree.c
++++ b/third_party/sqlite/src/src/btree.c
+@@ -5510,7 +5510,7 @@ int sqlite3BtreeMovetoUnpacked(
+             sqlite3_free(pCellKey);
+             goto moveto_finish;
+           }
+-          c = xRecordCompare(nCell, pCellKey, pIdxKey);
++          c = sqlite3VdbeRecordCompare(nCell, pCellKey, pIdxKey);
+           sqlite3_free(pCellKey);
+         }
+         assert(
+--
+2.21.0.352.gf09ad66450-goog
+
diff --git a/third_party/sqlite/patches/0009-fix-heap-buffer-overflow-in-cellsizeptr.patch b/third_party/sqlite/patches/0009-fix-heap-buffer-overflow-in-cellsizeptr.patch
new file mode 100644
index 0000000..f597a3e
--- /dev/null
+++ b/third_party/sqlite/patches/0009-fix-heap-buffer-overflow-in-cellsizeptr.patch
@@ -0,0 +1,36 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Darwin Huang <huangdarwin@chromium.org>
+Date: Tue, 5 Mar 2019 14:13:19 -0800
+Subject: [PATCH 09/10] fix heap-buffer-overflow in cellsizeptr
+
+This backports https://www.sqlite.org/src/info/e7aca0714bc475e0
+
+Bug: 925656
+---
+ third_party/sqlite/src/src/pager.c | 10 ++++++++--
+ 1 file changed, 8 insertions(+), 2 deletions(-)
+
+diff --git a/third_party/sqlite/src/src/pager.c b/third_party/sqlite/src/src/pager.c
+index 35f625d2c03a..efb9155f545d 100644
+--- a/third_party/sqlite/src/src/pager.c
++++ b/third_party/sqlite/src/src/pager.c
+@@ -3786,8 +3786,14 @@ int sqlite3PagerSetPagesize(Pager *pPager, u32 *pPageSize, int nReserve){
+       rc = sqlite3OsFileSize(pPager->fd, &nByte);
+     }
+     if( rc==SQLITE_OK ){
+-      pNew = (char *)sqlite3PageMalloc(pageSize);
+-      if( !pNew ) rc = SQLITE_NOMEM_BKPT;
++      /* 8 bytes of zeroed overrun space is sufficient so that the b-tree
++      * cell header parser will never run off the end of the allocation */
++      pNew = (char *)sqlite3PageMalloc(pageSize+8);
++      if( !pNew ){
++        rc = SQLITE_NOMEM_BKPT;
++      }else{
++        memset(pNew+pageSize, 0, 8);
++      }
+     }
+
+     if( rc==SQLITE_OK ){
+--
+2.21.0.352.gf09ad66450-goog
+
diff --git a/third_party/sqlite/patches/0010-fix-integer-overflow-in-checkList.patch b/third_party/sqlite/patches/0010-fix-integer-overflow-in-checkList.patch
new file mode 100644
index 0000000..e909a7f
--- /dev/null
+++ b/third_party/sqlite/patches/0010-fix-integer-overflow-in-checkList.patch
@@ -0,0 +1,41 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Darwin Huang <huangdarwin@chromium.org>
+Date: Tue, 5 Mar 2019 14:17:05 -0800
+Subject: [PATCH 10/10] fix integer overflow in checkList
+
+This backports https://www.sqlite.org/src/info/05b87e0755638d31
+
+Bug: 925381
+---
+ third_party/sqlite/src/src/btree.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/third_party/sqlite/src/src/btree.c b/third_party/sqlite/src/src/btree.c
+index caa45e507da6..33d63d7b1ce2 100644
+--- a/third_party/sqlite/src/src/btree.c
++++ b/third_party/sqlite/src/src/btree.c
+@@ -9540,10 +9540,10 @@ static void checkList(
+   IntegrityCk *pCheck,  /* Integrity checking context */
+   int isFreeList,       /* True for a freelist.  False for overflow page list */
+   int iPage,            /* Page number for first page in the list */
+-  int N                 /* Expected number of pages in the list */
++  u32 N                 /* Expected number of pages in the list */
+ ){
+   int i;
+-  int expected = N;
++  u32 expected = N;
+   int nErrAtStart = pCheck->nErr;
+   while( iPage!=0 && pCheck->mxErr ){
+     DbPage *pOvflPage;
+@@ -9797,7 +9797,7 @@ static int checkTreePage(
+
+     /* Check the content overflow list */
+     if( info.nPayload>info.nLocal ){
+-      int nPage;       /* Number of pages on the overflow chain */
++      u32 nPage;       /* Number of pages on the overflow chain */
+       Pgno pgnoOvfl;   /* First page of the overflow chain */
+       assert( pc + info.nSize - 4 <= usableSize );
+       nPage = (info.nPayload - info.nLocal + usableSize - 5)/(usableSize - 4);
+--
+2.21.0.352.gf09ad66450-goog
+
diff --git a/third_party/sqlite/src/src/btree.c b/third_party/sqlite/src/src/btree.c
index 773be16..33d63d7 100644
--- a/third_party/sqlite/src/src/btree.c
+++ b/third_party/sqlite/src/src/btree.c
@@ -5510,7 +5510,7 @@
             sqlite3_free(pCellKey);
             goto moveto_finish;
           }
-          c = xRecordCompare(nCell, pCellKey, pIdxKey);
+          c = sqlite3VdbeRecordCompare(nCell, pCellKey, pIdxKey);
           sqlite3_free(pCellKey);
         }
         assert(
@@ -9540,10 +9540,10 @@
   IntegrityCk *pCheck,  /* Integrity checking context */
   int isFreeList,       /* True for a freelist.  False for overflow page list */
   int iPage,            /* Page number for first page in the list */
-  int N                 /* Expected number of pages in the list */
+  u32 N                 /* Expected number of pages in the list */
 ){
   int i;
-  int expected = N;
+  u32 expected = N;
   int nErrAtStart = pCheck->nErr;
   while( iPage!=0 && pCheck->mxErr ){
     DbPage *pOvflPage;
@@ -9797,7 +9797,7 @@
 
     /* Check the content overflow list */
     if( info.nPayload>info.nLocal ){
-      int nPage;       /* Number of pages on the overflow chain */
+      u32 nPage;       /* Number of pages on the overflow chain */
       Pgno pgnoOvfl;   /* First page of the overflow chain */
       assert( pc + info.nSize - 4 <= usableSize );
       nPage = (info.nPayload - info.nLocal + usableSize - 5)/(usableSize - 4);
diff --git a/third_party/sqlite/src/src/pager.c b/third_party/sqlite/src/src/pager.c
index 35f625d..efb9155 100644
--- a/third_party/sqlite/src/src/pager.c
+++ b/third_party/sqlite/src/src/pager.c
@@ -3786,8 +3786,14 @@
       rc = sqlite3OsFileSize(pPager->fd, &nByte);
     }
     if( rc==SQLITE_OK ){
-      pNew = (char *)sqlite3PageMalloc(pageSize);
-      if( !pNew ) rc = SQLITE_NOMEM_BKPT;
+      /* 8 bytes of zeroed overrun space is sufficient so that the b-tree
+      * cell header parser will never run off the end of the allocation */
+      pNew = (char *)sqlite3PageMalloc(pageSize+8);
+      if( !pNew ){
+        rc = SQLITE_NOMEM_BKPT;
+      }else{
+        memset(pNew+pageSize, 0, 8);
+      }
     }
 
     if( rc==SQLITE_OK ){
diff --git a/tools/clang/scripts/generate_compdb.py b/tools/clang/scripts/generate_compdb.py
index 7378093..dffd9df 100755
--- a/tools/clang/scripts/generate_compdb.py
+++ b/tools/clang/scripts/generate_compdb.py
@@ -40,7 +40,8 @@
 
   compdb_text = json.dumps(
       compile_db.ProcessCompileDatabaseIfNeeded(
-          compile_db.GenerateWithNinja(args.p, args.targets)))
+          compile_db.GenerateWithNinja(args.p, args.targets)),
+      indent=2)
   if args.o is None:
     print(compdb_text)
   else:
diff --git a/tools/gritsettings/translation_expectations.pyl b/tools/gritsettings/translation_expectations.pyl
index 72d0dd8..ca46448 100644
--- a/tools/gritsettings/translation_expectations.pyl
+++ b/tools/gritsettings/translation_expectations.pyl
@@ -27,6 +27,7 @@
       "chrome/android/java/strings/android_chrome_strings.grd",
       "chrome/android/features/vr/java/strings/android_chrome_vr_strings.grd",
       "chrome/android/features/media_router/java/strings/android_chrome_media_router_strings.grd",
+      "chrome/android/features/autofill_assistant/java/strings/android_chrome_autofill_assistant_strings.grd",
       "chrome/android/webapk/strings/android_webapk_strings.grd",
       "chrome/app/chromium_strings.grd",
       "chrome/app/generated_resources.grd",
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 61218d5..c9ac4a07 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -5197,6 +5197,11 @@
   <int value="1" label="Used relinker"/>
 </enum>
 
+<enum name="BooleanKnown">
+  <int value="0" label="Unknown"/>
+  <int value="1" label="Known"/>
+</enum>
+
 <enum name="BooleanLatched">
   <int value="0" label="Not latched"/>
   <int value="1" label="Latched"/>
@@ -21835,6 +21840,7 @@
   <int value="40" label="Presentation"/>
   <int value="41" label="Frobulate"/>
   <int value="42" label="Serial"/>
+  <int value="43" label="Hid"/>
 </enum>
 
 <enum name="FeedbackSource">
@@ -25470,6 +25476,27 @@
   <int value="3" label="IGNORED_AS_REDUNDANT"/>
 </enum>
 
+<enum name="GoogleRpcCode">
+  <summary>Canonical error codes for gRPC APIs.</summary>
+  <int value="0" label="OK"/>
+  <int value="1" label="CANCELLED"/>
+  <int value="2" label="UNKNOWN"/>
+  <int value="3" label="INVALID_ARGUMENT"/>
+  <int value="4" label="DEADLINE_EXCEEDED"/>
+  <int value="5" label="NOT_FOUND"/>
+  <int value="6" label="ALREADY_EXISTS"/>
+  <int value="7" label="PERMISSION_DENIED"/>
+  <int value="8" label="RESOURCE_EXHAUSTED"/>
+  <int value="9" label="FAILED_PRECONDITION"/>
+  <int value="10" label="ABORTED"/>
+  <int value="11" label="OUT_OF_RANGE"/>
+  <int value="12" label="UNIMPLEMENTED"/>
+  <int value="13" label="INTERNAL"/>
+  <int value="14" label="UNAVAILABLE"/>
+  <int value="15" label="DATA_LOSS"/>
+  <int value="16" label="UNAUTHENTICATED"/>
+</enum>
+
 <enum name="GoogleServiceAuthError">
   <int value="0" label="NONE"/>
   <int value="1" label="INVALID_GAIA_CREDENTIALS"/>
@@ -27486,6 +27513,28 @@
   <int value="1" label="Some stored passwords from IE7 found."/>
 </enum>
 
+<enum name="ImageAnnotationServiceClientResult">
+  <summary>
+    The result status of a request made to the image annotation service by a
+    client feature.
+  </summary>
+  <int value="0" label="Unknown"/>
+  <int value="1" label="Succeeded"/>
+  <int value="2" label="Canceled"/>
+  <int value="3" label="Failed"/>
+  <int value="4" label="Shutdown"/>
+</enum>
+
+<enum name="ImageAnnotationServiceDescType">
+  <summary>
+    The type of description annotation returned from the image annotation
+    service.
+  </summary>
+  <int value="1" label="OCR"/>
+  <int value="2" label="Label"/>
+  <int value="3" label="Caption"/>
+</enum>
+
 <enum name="ImageCaptureOutcome">
   <int value="0" label="Succeeded using video stream"/>
   <int value="1" label="Succeeded using photo stream"/>
@@ -30913,6 +30962,7 @@
   <int value="-2092067251" label="NativeFilesystemAPI:disabled"/>
   <int value="-2091404586" label="TabStripKeyboardFocus:enabled"/>
   <int value="-2090484194" label="ContextualSearchUrlActions:disabled"/>
+  <int value="-2088555929" label="EnableAppGridGhost:enabled"/>
   <int value="-2088472280" label="KeyboardShortcutViewerApp:disabled"/>
   <int value="-2083998415" label="VrLaunchIntent:enabled"/>
   <int value="-2083195884" label="enable-firewall-hole-punching"/>
@@ -31243,6 +31293,7 @@
   <int value="-1583533050" label="RuntimeHostPermissions:disabled"/>
   <int value="-1581724231" label="ModalPermissionPrompts:enabled"/>
   <int value="-1580376019" label="ShowAllDialogsWithViewsToolkit:enabled"/>
+  <int value="-1578677451" label="EnableAppGridGhost:disabled"/>
   <int value="-1578503491" label="EnableContinueReading:disabled"/>
   <int value="-1578295753" label="UserMediaScreenCapturing:disabled"/>
   <int value="-1575868447" label="RemoveNtpFakebox:enabled"/>
@@ -43497,6 +43548,13 @@
     Other error unrelated to preprocessor. This signals a bug in the feature
     generation process.
   </int>
+  <int value="4" label="Mismatched feature size error">
+    The ExampleProprocessor returned a feature vector with an incorrect size.
+  </int>
+  <int value="5" label="Missing ML Service connection error">
+    The ML Service connection had not been initialized at the time it was
+    needed.
+  </int>
 </enum>
 
 <enum name="PowerMLSmartDimParameterResult">
@@ -45379,6 +45437,16 @@
   <int value="11" label="PARSE_DATA_DECODE_FAILURE"/>
 </enum>
 
+<enum name="QuicDroppedPacketReason">
+  <int value="0" label="INVALID_PUBLIC_HEADER"/>
+  <int value="1" label="VERSION_MISMATCH"/>
+  <int value="2" label="INVALID_VERSION_NEGOTIATION_PACKET"/>
+  <int value="3" label="INVALID_PUBLIC_RESET_PACKET"/>
+  <int value="4" label="INVALID_PACKET_NUMBER"/>
+  <int value="5" label="INVALID_DIVERSIFICATION_NONCE"/>
+  <int value="6" label="DECRYPTION_FAILURE"/>
+</enum>
+
 <enum name="QuicErrorCodes">
   <int value="0" label="NO_ERROR"/>
   <int value="1" label="INTERNAL_ERROR"/>
@@ -49421,6 +49489,12 @@
   <int value="3" label="Fresh index with cache updated since backend start"/>
 </enum>
 
+<enum name="SimulcastApiVersion">
+  <int value="0" label="Simulcast is not used"/>
+  <int value="1" label="Legacy SDP munging"/>
+  <int value="2" label="Spec compliant"/>
+</enum>
+
 <enum name="SinglePageAppNavigationType">
   <int value="0" label="History push state or replace state"/>
   <int value="1" label="Same document backward or forward"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 6d775ecf..08418ebf 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -3861,6 +3861,17 @@
   <summary>The state of a Play Store app search request.</summary>
 </histogram>
 
+<histogram name="Apps.AppListRecommendedImpResultCountAfterOpen" units="shows"
+    expires_after="2019-12-31">
+  <owner>napper@chromium.org</owner>
+  <owner>robsc@chromium.org</owner>
+  <summary>
+    When a user opens an app recommendation result, this is the number of times
+    we showed the result before opening. Number of times shown is reset to 0
+    every 30 days, and is held on device.
+  </summary>
+</histogram>
+
 <histogram name="Apps.AppListRecommendedResponse"
     enum="ReinstallResponseParseResult">
   <owner>napper@chromium.org</owner>
@@ -5293,6 +5304,16 @@
   </summary>
 </histogram>
 
+<histogram name="Ash.InteractiveWindowResize.TimeToPresent" units="ms"
+    expires_after="M77">
+  <owner>mustash-team@google.com</owner>
+  <summary>
+    Time between when the size of a window changes during an interactive window
+    resize and the results are drawn (presented) on screen. This is recorded for
+    each movement of the mouse/pointer that results in a resize.
+  </summary>
+</histogram>
+
 <histogram name="Ash.Login.Lock.AuthMethod.Switched"
     enum="AuthMethodSwitchType">
   <owner>jdufault@google.com</owner>
@@ -9570,6 +9591,35 @@
   </summary>
 </histogram>
 
+<histogram name="AutoScreenBrightness.DailyUserAdjustment.Atlas" units="count"
+    expires_after="2019-12-31">
+  <owner>jiameng@chromium.org</owner>
+  <summary>
+    Number of times that a user has made brightness adjustments on an Atlas
+    device with an ambient sensor that is supported by the auto screen
+    brightness model. Reported daily. The count is accumulated through the day,
+    spanning reboots, and sent once the system clock indicates that a full day
+    or more has passed since the last report. If the system is suspended or off
+    for more than a day, the current count will be reported immediately the next
+    time the system boots, but the skipped days will not be reported. Chrome OS
+    only.
+  </summary>
+</histogram>
+
+<histogram name="AutoScreenBrightness.DailyUserAdjustment.Eve" units="count"
+    expires_after="2019-12-31">
+  <owner>jiameng@chromium.org</owner>
+  <summary>
+    Number of times that a user has made brightness adjustments on an Eve device
+    with an ambient sensor that is supported by the auto screen brightness
+    model. Reported daily. The count is accumulated through the day, spanning
+    reboots, and sent once the system clock indicates that a full day or more
+    has passed since the last report. If the system is suspended or off for more
+    than a day, the current count will be reported immediately the next time the
+    system boots, but the skipped days will not be reported. Chrome OS only.
+  </summary>
+</histogram>
+
 <histogram name="AutoScreenBrightness.DailyUserAdjustment.NoAls" units="count"
     expires_after="2019-12-31">
   <owner>jiameng@chromium.org</owner>
@@ -42157,6 +42207,180 @@
   <summary>The time it takes to open a hyphenation dictionary file.</summary>
 </histogram>
 
+<histogram name="ImageAnnotationService.AccessibilityV1.CacheHit"
+    enum="BooleanCacheHit" expires_after="2019-06-04">
+  <owner>amoylan@chromium.org</owner>
+  <owner>martis@chromium.org</owner>
+  <summary>
+    For each image processed by the image annotation service, records whether or
+    not results for the image already existed in the client-side cache. A hit
+    means that image data was not transmitted to the image annotation server.
+  </summary>
+</histogram>
+
+<histogram name="ImageAnnotationService.AccessibilityV1.ClientResult"
+    enum="ImageAnnotationServiceClientResult" expires_after="2019-06-04">
+  <owner>amoylan@chromium.org</owner>
+  <owner>martis@chromium.org</owner>
+  <summary>
+    For each request made by a client feature to the image annotation service,
+    reports the final status of the service response.
+  </summary>
+</histogram>
+
+<histogram base="true" name="ImageAnnotationService.AccessibilityV1.Confidence"
+    units="%" expires_after="2019-06-04">
+<!-- Name completed by histogram_suffixes name="ImageAnnotationServiceAnnotationType" -->
+
+  <owner>amoylan@chromium.org</owner>
+  <owner>martis@chromium.org</owner>
+  <summary>
+    For each annotation of the given type returned by the image annotation
+    server, records the confidence (as a percentage) produced by the ML model
+    for the annotation.
+  </summary>
+</histogram>
+
+<histogram name="ImageAnnotationService.AccessibilityV1.DescType"
+    enum="ImageAnnotationServiceDescType" expires_after="2019-06-04">
+  <owner>amoylan@chromium.org</owner>
+  <owner>martis@chromium.org</owner>
+  <summary>
+    For each annotation backend run by the image annotation server, records the
+    gRPC status reported.
+  </summary>
+</histogram>
+
+<histogram base="true" name="ImageAnnotationService.AccessibilityV1.Empty"
+    enum="BooleanEmpty" expires_after="2019-06-04">
+<!-- Name completed by histogram_suffixes name="ImageAnnotationServiceAnnotationType" -->
+
+  <owner>amoylan@chromium.org</owner>
+  <owner>martis@chromium.org</owner>
+  <summary>
+    For each annotation of the given type returned by the image annotation
+    server, records whether or not the text of the annotation was empty. This is
+    sometimes valid, e.g. for text extraction (OCR) on an image that does not
+    contain any text.
+  </summary>
+</histogram>
+
+<histogram name="ImageAnnotationService.AccessibilityV1.EncodedJpegSizeKB"
+    units="KB" expires_after="2019-06-04">
+  <owner>amoylan@chromium.org</owner>
+  <owner>martis@chromium.org</owner>
+  <summary>
+    For each image sent from a client feature to the image annotation service,
+    reports the image's final size (in kilobytes) after resizing and reencoding.
+  </summary>
+</histogram>
+
+<histogram name="ImageAnnotationService.AccessibilityV1.EngineKnown"
+    enum="BooleanKnown" expires_after="2019-06-04">
+  <owner>amoylan@chromium.org</owner>
+  <owner>martis@chromium.org</owner>
+  <summary>
+    For each annotation backend run by the image annotation server, records
+    whether or not the backend is recognised by this version of Chrome.
+  </summary>
+</histogram>
+
+<histogram name="ImageAnnotationService.AccessibilityV1.JsonParseSuccess"
+    enum="BooleanSuccess" expires_after="2019-06-04">
+  <owner>amoylan@chromium.org</owner>
+  <owner>martis@chromium.org</owner>
+  <summary>
+    For each non-empty response sent from the image annotation server, records
+    whether or not JSON data were successfully parsed from the response body.
+  </summary>
+</histogram>
+
+<histogram name="ImageAnnotationService.AccessibilityV1.PixelFetchSuccess"
+    enum="BooleanSuccess" expires_after="2019-06-04">
+  <owner>amoylan@chromium.org</owner>
+  <owner>martis@chromium.org</owner>
+  <summary>
+    For each pixel fetch request sent from the image annotation service to a
+    client feature, records whether or not encoded pixel data were successfully
+    returned to the service.
+  </summary>
+</histogram>
+
+<histogram name="ImageAnnotationService.AccessibilityV1.ServerHttpResponseCode"
+    enum="HttpResponseCode" expires_after="2019-06-04">
+  <owner>amoylan@chromium.org</owner>
+  <owner>martis@chromium.org</owner>
+  <summary>
+    For each HTTP response from the image annotation server, reports the
+    associated HTTP code.
+  </summary>
+</histogram>
+
+<histogram name="ImageAnnotationService.AccessibilityV1.ServerLatencyMs"
+    units="ms" expires_after="2019-06-04">
+  <owner>amoylan@chromium.org</owner>
+  <owner>martis@chromium.org</owner>
+  <summary>
+    For each HTTP response from the image annotation server, reports the time
+    elapsed (in milliseconds) since the corresponding request was sent.
+  </summary>
+</histogram>
+
+<histogram name="ImageAnnotationService.AccessibilityV1.ServerNetError"
+    enum="NetErrorCodes" expires_after="2019-06-04">
+  <owner>amoylan@chromium.org</owner>
+  <owner>martis@chromium.org</owner>
+  <summary>
+    For each HTTP request sent to the image annotation server, reports the net
+    error (or OK status) of the request.
+  </summary>
+</histogram>
+
+<histogram name="ImageAnnotationService.AccessibilityV1.ServerRequestSizeKB"
+    units="KB" expires_after="2019-06-04">
+  <owner>amoylan@chromium.org</owner>
+  <owner>martis@chromium.org</owner>
+  <summary>
+    For each HTTP request sent to the image annotation server, reports the size
+    of the request. Measured in kilobytes, since a request can contain the pixel
+    data of multiple images.
+  </summary>
+</histogram>
+
+<histogram
+    name="ImageAnnotationService.AccessibilityV1.ServerResponseSizeBytes"
+    units="bytes" expires_after="2019-06-04">
+  <owner>amoylan@chromium.org</owner>
+  <owner>martis@chromium.org</owner>
+  <summary>
+    For each HTTP response from the image annotation server, reports the
+    response size in bytes.
+  </summary>
+</histogram>
+
+<histogram name="ImageAnnotationService.AccessibilityV1.SourcePixelCount"
+    units="pixels" expires_after="2019-06-04">
+  <owner>amoylan@chromium.org</owner>
+  <owner>martis@chromium.org</owner>
+  <summary>
+    For each image sent from a client feature to the image annotation service,
+    reports the image's original (i.e. pre-scaling) area in pixels. Images with
+    pixel count above a certain threshold will be resized prior to transmission.
+  </summary>
+</histogram>
+
+<histogram base="true" name="ImageAnnotationService.AccessibilityV1.Status"
+    enum="GoogleRpcCode" expires_after="2019-06-04">
+<!-- Name completed by histogram_suffixes name="ImageAnnotationServiceAnnotationType" -->
+
+  <owner>amoylan@chromium.org</owner>
+  <owner>martis@chromium.org</owner>
+  <summary>
+    For each annotation backend run by the image annotation server, records the
+    gRPC status reported.
+  </summary>
+</histogram>
+
 <histogram name="ImageLoader.Client.Cache.HitMiss" enum="BooleanCacheHit"
     expires_after="2019-01-01">
   <owner>chromeos-files-app@google.com</owner>
@@ -63455,6 +63679,14 @@
   </summary>
 </histogram>
 
+<histogram name="Net.QuicDroppedPacketReason" enum="QuicDroppedPacketReason">
+  <owner>rch@chromium.org</owner>
+  <summary>
+    The reason a QUIC packet could not be processed, logged for each dropped
+    packet.
+  </summary>
+</histogram>
+
 <histogram name="Net.QuicEphemeralPortsSuggested">
   <obsolete>
     Deprecated as of 04/2016.
@@ -131239,6 +131471,38 @@
   </summary>
 </histogram>
 
+<histogram name="WebRTC.PeerConnection.Simulcast.ApplyLocalDescription"
+    enum="SimulcastApiVersion">
+  <owner>amithi@chromium.org</owner>
+  <summary>
+    Was simulcast applied to the local description and with which API surface.
+  </summary>
+</histogram>
+
+<histogram name="WebRTC.PeerConnection.Simulcast.ApplyRemoteDescription"
+    enum="SimulcastApiVersion">
+  <owner>amithi@chromium.org</owner>
+  <summary>
+    Was simulcast applied to the remote description and with which API surface.
+  </summary>
+</histogram>
+
+<histogram name="WebRTC.PeerConnection.Simulcast.Disabled">
+  <owner>amithi@chromium.org</owner>
+  <summary>
+    Simulcast was disabled because it is not supported by the remote party. This
+    is a counter that can take on only a single value (1).
+  </summary>
+</histogram>
+
+<histogram name="WebRTC.PeerConnection.Simulcast.NumberOfSendEncodings">
+  <owner>amithi@chromium.org</owner>
+  <summary>
+    Counts the number of send encodings given to PeerConnection::AddTransceiver.
+    This histogram will be used to understand if and how the API is used.
+  </summary>
+</histogram>
+
 <histogram name="WebRTC.PeerConnection.SrtcpUnprotectError"
     enum="SrtpErrorCode">
   <owner>steveanton@chromium.org</owner>
@@ -138176,6 +138440,24 @@
   <affected-histogram name="PLT.BeginToFinish_NormalLoad"/>
 </histogram_suffixes>
 
+<histogram_suffixes name="ImageAnnotationServiceAnnotationType" separator="."
+    ordering="prefix">
+  <suffix name="DescCaption"
+      label="Caption annotations from the description engine"/>
+  <suffix name="DescLabel"
+      label="Label annotations from the description engine"/>
+  <suffix name="DescOcr"
+      label="OCR (text extraction) annotations from the description engine"/>
+  <suffix name="DescUnknown"
+      label="Annotations of an unknown type from the description engine"/>
+  <suffix name="Ocr"
+      label="OCR (text extraction) annotations from the specialized OCR
+             engine"/>
+  <affected-histogram name="ImageAnnotationService.AccessibilityV1.Confidence"/>
+  <affected-histogram name="ImageAnnotationService.AccessibilityV1.Empty"/>
+  <affected-histogram name="ImageAnnotationService.AccessibilityV1.Status"/>
+</histogram_suffixes>
+
 <histogram_suffixes name="ImageDecoderFileTypes" separator=".">
   <obsolete>
     Deprecated as of 11/29/2016.
diff --git a/tools/perf/cli_tools/pinpoint_cli/__init__.py b/tools/perf/cli_tools/pinpoint_cli/__init__.py
new file mode 100644
index 0000000..1adf20d2
--- /dev/null
+++ b/tools/perf/cli_tools/pinpoint_cli/__init__.py
@@ -0,0 +1,3 @@
+# 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.
diff --git a/tools/perf/cli_tools/pinpoint_cli/commands.py b/tools/perf/cli_tools/pinpoint_cli/commands.py
new file mode 100644
index 0000000..c2ce907
--- /dev/null
+++ b/tools/perf/cli_tools/pinpoint_cli/commands.py
@@ -0,0 +1,62 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import csv
+import json
+import ntpath
+import posixpath
+import sys
+
+from cli_tools.pinpoint_cli import histograms_df
+from cli_tools.pinpoint_cli import job_results
+from core.services import isolate_service
+from core.services import pinpoint_service
+
+
+def StartJobFromConfig(config_path):
+  """Start a pinpoint job based on a config file."""
+  src = sys.stdin if config_path == '-' else open(config_path)
+  with src as f:
+    config = json.load(f)
+
+  if not isinstance(config, dict):
+    raise ValueError('Invalid job config')
+
+  response = pinpoint_service.NewJob(**config)
+  print 'Started:', response['jobUrl']
+
+
+def CheckJobStatus(job_ids):
+  for job_id in job_ids:
+    job = pinpoint_service.Job(job_id)
+    print '%s: %s' % (job_id, job['status'].lower())
+
+
+def DownloadJobResultsAsCsv(job_ids, only_differences, output_file):
+  """Download the perf results of a job as a csv file."""
+  with open(output_file, 'wb') as f:
+    writer = csv.writer(f)
+    writer.writerow(('job_id', 'change', 'isolate') + histograms_df.COLUMNS)
+    num_rows = 0
+    for job_id in job_ids:
+      job = pinpoint_service.Job(job_id, with_state=True)
+      os_path = _OsPathFromJob(job)
+      results_file = os_path.join(
+          job['arguments']['benchmark'], 'perf_results.json')
+      print 'Fetching results for %s job %s:' % (job['status'].lower(), job_id)
+      for change_id, isolate_hash in job_results.IterTestOutputIsolates(
+          job, only_differences):
+        print '- isolate: %s ...' % isolate_hash
+        histograms = isolate_service.RetrieveFile(isolate_hash, results_file)
+        for row in histograms_df.IterRows(json.loads(histograms)):
+          writer.writerow((job_id, change_id, isolate_hash) + row)
+          num_rows += 1
+  print 'Wrote data from %d histograms in %s.' % (num_rows, output_file)
+
+
+def _OsPathFromJob(job):
+  if job['arguments']['configuration'].lower().startswith('win'):
+    return ntpath
+  else:
+    return posixpath
diff --git a/tools/perf/cli_tools/pinpoint_cli/histograms_df.py b/tools/perf/cli_tools/pinpoint_cli/histograms_df.py
new file mode 100644
index 0000000..3887515
--- /dev/null
+++ b/tools/perf/cli_tools/pinpoint_cli/histograms_df.py
@@ -0,0 +1,56 @@
+# 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.
+
+from core.external_modules import pandas
+
+from tracing.value import histogram_set
+from tracing.value.diagnostics import generic_set
+
+
+_PROPERTIES = (
+    ('name', 'name'),
+    ('unit', 'unit'),
+    ('mean', 'average'),
+    ('stdev', 'standard_deviation'),
+    ('count', 'num_values')
+)
+_DIAGNOSTICS = (
+    ('run_label', 'labels'),
+    ('benchmark', 'benchmarks'),
+    ('story', 'stories'),
+    ('benchmark_start', 'benchmarkStart'),
+    ('device_id', 'deviceIds'),
+    ('trace_url', 'traceUrls')
+)
+COLUMNS = tuple(key for key, _ in _PROPERTIES) + tuple(
+    key for key, _ in _DIAGNOSTICS)
+
+
+def _DiagnosticValueToStr(value):
+  if value is None:
+    return ''
+  elif isinstance(value, generic_set.GenericSet):
+    return ','.join(str(v) for v in value)
+  else:
+    return str(value)
+
+
+def IterRows(histogram_dicts):
+  """Iterate over histogram dicts yielding rows for a DataFrame or csv."""
+  histograms = histogram_set.HistogramSet()
+  histograms.ImportDicts(histogram_dicts)
+  for hist in histograms:
+    row = [getattr(hist, name) for _, name in _PROPERTIES]
+    row.extend(
+        _DiagnosticValueToStr(hist.diagnostics.get(name))
+        for _, name in _DIAGNOSTICS)
+    yield tuple(row)
+
+
+def DataFrame(histogram_dicts):
+  """Turn a list of histogram dicts into a pandas DataFrame."""
+  df = pandas.DataFrame.from_records(
+      IterRows(histogram_dicts), columns=COLUMNS)
+  df['benchmark_start'] = pandas.to_datetime(df['benchmark_start'])
+  return df
diff --git a/tools/perf/cli_tools/pinpoint_cli/histograms_df_test.py b/tools/perf/cli_tools/pinpoint_cli/histograms_df_test.py
new file mode 100644
index 0000000..e3d955f
--- /dev/null
+++ b/tools/perf/cli_tools/pinpoint_cli/histograms_df_test.py
@@ -0,0 +1,90 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import unittest
+
+from cli_tools.pinpoint_cli import histograms_df
+from core.external_modules import pandas
+
+from tracing.value import histogram
+from tracing.value import histogram_set
+from tracing.value.diagnostics import date_range
+from tracing.value.diagnostics import generic_set
+
+
+def TestHistogram(name, units, values, **kwargs):
+  def DiagnosticValue(value):
+    if isinstance(value, (int, long)):
+      return date_range.DateRange(value)
+    elif isinstance(value, list):
+      return generic_set.GenericSet(value)
+    else:
+      raise NotImplementedError(type(value))
+
+  hist = histogram.Histogram(name, units)
+  hist.diagnostics.update(
+      (key, DiagnosticValue(value)) for key, value in kwargs.iteritems())
+  for value in values:
+    hist.AddSample(value)
+  return hist
+
+
+@unittest.skipIf(pandas is None, 'pandas not available')
+class TestHistogramsDf(unittest.TestCase):
+  def testIterRows(self):
+    run1 = {'benchmarkStart': 1234567890000, 'labels': ['run1'],
+            'benchmarks': ['system_health'], 'deviceIds': ['device1']}
+    # Second run on same device ten minutes later.
+    run2 = {'benchmarkStart': 1234567890000 + 600000, 'labels': ['run2'],
+            'benchmarks': ['system_health'], 'deviceIds': ['device1']}
+    hists = histogram_set.HistogramSet([
+        TestHistogram('startup', 'ms', [8, 10, 12], stories=['story1'],
+                      traceUrls=['http://url/to/trace1'], **run1),
+        TestHistogram('memory', 'sizeInBytes', [256], stories=['story2'],
+                      traceUrls=['http://url/to/trace2'], **run1),
+        TestHistogram('memory', 'sizeInBytes', [512], stories=['story2'],
+                      traceUrls=['http://url/to/trace3'], **run2),
+    ])
+
+    expected = [
+        ('startup', 'ms', 10.0, 2.0, 3, 'run1', 'system_health',
+         'story1', '2009-02-13 23:31:30', 'device1', 'http://url/to/trace1'),
+        ('memory', 'sizeInBytes', 256.0, 0.0, 1, 'run1', 'system_health',
+         'story2', '2009-02-13 23:31:30', 'device1', 'http://url/to/trace2'),
+        ('memory', 'sizeInBytes', 512.0, 0.0, 1, 'run2', 'system_health',
+         'story2', '2009-02-13 23:41:30', 'device1', 'http://url/to/trace3'),
+    ]
+    self.assertItemsEqual(histograms_df.IterRows(hists.AsDicts()), expected)
+
+  def testDataFrame(self):
+    run1 = {'benchmarkStart': 1234567890000, 'labels': ['run1'],
+            'benchmarks': ['system_health'], 'deviceIds': ['device1']}
+    # Second run on same device ten minutes later.
+    run2 = {'benchmarkStart': 1234567890000 + 600000, 'labels': ['run2'],
+            'benchmarks': ['system_health'], 'deviceIds': ['device1']}
+    hists = histogram_set.HistogramSet([
+        TestHistogram('startup', 'ms', [8, 10, 12], stories=['story1'],
+                      traceUrls=['http://url/to/trace1'], **run1),
+        TestHistogram('memory', 'sizeInBytes', [256], stories=['story2'],
+                      traceUrls=['http://url/to/trace2'], **run1),
+        TestHistogram('memory', 'sizeInBytes', [384], stories=['story2'],
+                      traceUrls=['http://url/to/trace3'], **run2),
+    ])
+    df = histograms_df.DataFrame(hists.AsDicts())
+
+    # Poke at the data frame and check a few known facts about our fake data:
+    # It has 3 histograms.
+    self.assertEqual(len(df), 3)
+    # The benchmark has two stories.
+    self.assertItemsEqual(df['story'].unique(), ['story1', 'story2'])
+    # We recorded three traces.
+    self.assertEqual(len(df['trace_url'].unique()), 3)
+    # All benchmarks ran on the same device.
+    self.assertEqual(len(df['device_id'].unique()), 1)
+    # There is a memory regression between runs 1 and 2.
+    memory = df.set_index(['name', 'run_label']).loc['memory']['mean']
+    self.assertEqual(memory['run2'] - memory['run1'], 128.0)
+    # Ten minutes passed between the two benchmark runs.
+    self.assertEqual(df['benchmark_start'].max() - df['benchmark_start'].min(),
+                     pandas.Timedelta('10 minutes'))
diff --git a/tools/perf/cli_tools/pinpoint_cli/job_results.py b/tools/perf/cli_tools/pinpoint_cli/job_results.py
new file mode 100644
index 0000000..88e3abd1
--- /dev/null
+++ b/tools/perf/cli_tools/pinpoint_cli/job_results.py
@@ -0,0 +1,44 @@
+# 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.
+
+
+def ChangeToStr(change):
+  """Turn a pinpoint change dict into a string id."""
+  change_id = ','.join(
+      '{repository}@{git_hash}'.format(**commit)
+      for commit in change['commits'])
+  if 'patch' in change:
+    change_id += '+' + change['patch']['url']
+  return change_id
+
+
+def IterTestOutputIsolates(job, only_differences=False):
+  """Iterate over test execution results for all changes tested in the job.
+
+  Args:
+    job: A pinpoint job dict with state.
+
+  Yields:
+    (change_id, isolate_hash) pairs for each completed test execution found in
+    the job.
+  """
+  quests = job['quests']
+  for change_state in job['state']:
+    if only_differences and not any(
+        v == 'different' for v in change_state['comparisons'].itervalues()):
+      continue
+    change_id = ChangeToStr(change_state['change'])
+    for attempt in change_state['attempts']:
+      executions = dict(zip(quests, attempt['executions']))
+      if 'Test' not in executions:
+        continue
+      test_run = executions['Test']
+      if not test_run['completed']:
+        continue
+      try:
+        isolate_hash = next(
+            d['value'] for d in test_run['details'] if d['key'] == 'isolate')
+      except StopIteration:
+        continue
+      yield change_id, isolate_hash
diff --git a/tools/perf/cli_tools/pinpoint_cli/job_results_test.py b/tools/perf/cli_tools/pinpoint_cli/job_results_test.py
new file mode 100644
index 0000000..241c09f
--- /dev/null
+++ b/tools/perf/cli_tools/pinpoint_cli/job_results_test.py
@@ -0,0 +1,99 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import unittest
+
+from cli_tools.pinpoint_cli import job_results
+
+
+def Change(*args, **kwargs):
+  commits = []
+  for arg in args:
+    repository, git_hash = arg.split('@')
+    commits.append({'repository': repository, 'git_hash': git_hash})
+  change = {'commits': commits}
+  patch = kwargs.pop('patch', None)
+  if patch is not None:
+    change['patch'] = {'url': patch}
+  return change
+
+
+def Execution(**kwargs):
+  execution = {'completed': kwargs.pop('completed', True)}
+  execution['details'] = [
+      {'key': k, 'value': v} for k, v in kwargs.iteritems()]
+  return execution
+
+
+class TestJobResults(unittest.TestCase):
+  def testChangeToStr(self):
+    self.assertEqual(
+        job_results.ChangeToStr(Change('src@1234')),
+        'src@1234')
+    self.assertEqual(
+        job_results.ChangeToStr(
+            Change('src@1234', 'v8@4567', patch='crrev.com/c/123')),
+        'src@1234,v8@4567+crrev.com/c/123')
+
+  def testIterTestOutputIsolates(self):
+    job = {
+        'quests': ['Build', 'Test', 'Get results'],
+        'state': [
+            {
+                'change': Change('src@1234'),
+                'attempts': [
+                    {
+                        'executions': [
+                            Execution(),  # Build
+                            Execution(isolate='results1'),  # Test
+                            Execution()  # Get results
+                        ]
+                    },
+                    {
+                        'executions': [
+                            Execution(),  # Build
+                            Execution(),  # Test (completed but failed)
+                        ]
+                    },
+                    {
+                        'executions': [
+                            Execution(),  # Build
+                            Execution(isolate='results3'),  # Test
+                            Execution(completed=False)  # Get results
+                        ]
+                    }
+                ]
+            },
+            {
+                'change': Change('src@1234', patch='crrev.com/c/123'),
+                'attempts': [
+                    {
+                        'executions': [
+                            Execution(),  # Build
+                            Execution(isolate='results4'),  # Test
+                            Execution()  # Get results
+                        ]
+                    },
+                    {
+                        'executions': [
+                            Execution(),  # Build
+                            Execution(completed=False)  # Test
+                        ]
+                    },
+                    {
+                        'executions': [
+                            Execution(completed=False)  # Build
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+    self.assertSequenceEqual(
+        list(job_results.IterTestOutputIsolates(job)),
+        [
+            ('src@1234', 'results1'),
+            ('src@1234', 'results3'),
+            ('src@1234+crrev.com/c/123', 'results4')
+        ])
diff --git a/tools/perf/cli_tools/soundwave/__init__.py b/tools/perf/cli_tools/soundwave/__init__.py
new file mode 100644
index 0000000..1adf20d2
--- /dev/null
+++ b/tools/perf/cli_tools/soundwave/__init__.py
@@ -0,0 +1,3 @@
+# 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.
diff --git a/tools/perf/cli_tools/soundwave/commands.py b/tools/perf/cli_tools/soundwave/commands.py
new file mode 100644
index 0000000..61e9cc7
--- /dev/null
+++ b/tools/perf/cli_tools/soundwave/commands.py
@@ -0,0 +1,157 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import json
+import logging
+import sqlite3
+
+from core import cli_utils
+from core.external_modules import pandas
+from core.services import dashboard_service
+from cli_tools.soundwave import pandas_sqlite
+from cli_tools.soundwave import studies
+from cli_tools.soundwave import tables
+from cli_tools.soundwave import worker_pool
+
+
+def _FetchBugsWorker(args):
+  con = sqlite3.connect(args.database_file, timeout=10)
+
+  def Process(bug_id):
+    bugs = tables.bugs.DataFrameFromJson([dashboard_service.Bugs(bug_id)])
+    pandas_sqlite.InsertOrReplaceRecords(con, 'bugs', bugs)
+
+  worker_pool.Process = Process
+
+
+def FetchAlertsData(args):
+  params = {
+      'test_suite': args.benchmark,
+      'min_timestamp': cli_utils.DaysAgoToTimestamp(args.days)
+  }
+  if args.sheriff != 'all':
+    params['sheriff'] = args.sheriff
+
+  with tables.DbSession(args.database_file) as con:
+    # Get alerts.
+    num_alerts = 0
+    bug_ids = set()
+    # TODO: This loop may be slow when fetching thousands of alerts, needs a
+    # better progress indicator.
+    for data in dashboard_service.IterAlerts(**params):
+      alerts = tables.alerts.DataFrameFromJson(data)
+      pandas_sqlite.InsertOrReplaceRecords(con, 'alerts', alerts)
+      num_alerts += len(alerts)
+      bug_ids.update(alerts['bug_id'].unique())
+    print '%d alerts found!' % num_alerts
+
+    # Get set of bugs associated with those alerts.
+    bug_ids.discard(0)  # A bug_id of 0 means untriaged.
+    print '%d bugs found!' % len(bug_ids)
+
+    # Filter out bugs already in cache.
+    if args.use_cache:
+      known_bugs = set(
+          b for b in bug_ids if tables.bugs.Get(con, b) is not None)
+      if known_bugs:
+        print '(skipping %d bugs already in the database)' % len(known_bugs)
+        bug_ids.difference_update(known_bugs)
+
+  # Use worker pool to fetch bug data.
+  total_seconds = worker_pool.Run(
+      'Fetching data of %d bugs: ' % len(bug_ids),
+      _FetchBugsWorker, args, bug_ids)
+  print '[%.1f bugs per second]' % (len(bug_ids) / total_seconds)
+
+
+def _IterStaleTestPaths(con, test_paths):
+  """Iterate over test_paths yielding only those with stale or absent data.
+
+  A test_path is considered to be stale if the most recent data point we have
+  for it in the db is more than a day older.
+  """
+  a_day_ago = pandas.Timestamp.utcnow() - pandas.Timedelta(days=1)
+  a_day_ago = a_day_ago.tz_convert(tz=None)
+
+  for test_path in test_paths:
+    latest = tables.timeseries.GetMostRecentPoint(con, test_path)
+    if latest is None or latest['timestamp'] < a_day_ago:
+      yield test_path
+
+
+def _FetchTimeseriesWorker(args):
+  con = sqlite3.connect(args.database_file, timeout=10)
+  min_timestamp = cli_utils.DaysAgoToTimestamp(args.days)
+
+  def Process(test_path):
+    try:
+      if isinstance(test_path, tables.timeseries.Key):
+        params = test_path.AsApiParams()
+        params['min_timestamp'] = min_timestamp
+        data = dashboard_service.Timeseries2(**params)
+      else:
+        data = dashboard_service.Timeseries(test_path, days=args.days)
+    except KeyError:
+      logging.info('Timeseries not found: %s', test_path)
+      return
+
+    timeseries = tables.timeseries.DataFrameFromJson(test_path, data)
+    pandas_sqlite.InsertOrReplaceRecords(con, 'timeseries', timeseries)
+
+  worker_pool.Process = Process
+
+
+def _ReadTimeseriesFromFile(filename):
+  with open(filename, 'r') as f:
+    data = json.load(f)
+  return [tables.timeseries.Key.FromDict(ts) for ts in data]
+
+
+def FetchTimeseriesData(args):
+  def _MatchesAllFilters(test_path):
+    return all(f in test_path for f in args.filters)
+
+  with tables.DbSession(args.database_file) as con:
+    # Get test_paths.
+    if args.benchmark is not None:
+      test_paths = dashboard_service.ListTestPaths(
+          args.benchmark, sheriff=args.sheriff)
+    elif args.input_file is not None:
+      test_paths = _ReadTimeseriesFromFile(args.input_file)
+    elif args.study is not None:
+      test_paths = list(args.study.IterTestPaths())
+    else:
+      raise ValueError('No source for test paths specified')
+
+    # Apply --filter's to test_paths.
+    if args.filters:
+      test_paths = filter(_MatchesAllFilters, test_paths)
+    num_found = len(test_paths)
+    print '%d test paths found!' % num_found
+
+    # Filter out test_paths already in cache.
+    if args.use_cache:
+      test_paths = list(_IterStaleTestPaths(con, test_paths))
+      num_skipped = num_found - len(test_paths)
+      if num_skipped:
+        print '(skipping %d test paths already in the database)' % num_skipped
+
+  # Use worker pool to fetch test path data.
+  total_seconds = worker_pool.Run(
+      'Fetching data of %d timeseries: ' % len(test_paths),
+      _FetchTimeseriesWorker, args, test_paths)
+  print '[%.1f test paths per second]' % (len(test_paths) / total_seconds)
+
+  if args.output_csv is not None:
+    print
+    print 'Post-processing data for study ...'
+    dfs = []
+    with tables.DbSession(args.database_file) as con:
+      for test_path in test_paths:
+        df = tables.timeseries.GetTimeSeries(con, test_path)
+        dfs.append(df)
+    df = studies.PostProcess(pandas.concat(dfs, ignore_index=True))
+    with cli_utils.OpenWrite(args.output_csv) as f:
+      df.to_csv(f, index=False)
+    print 'Wrote timeseries data to:', args.output_csv
diff --git a/tools/perf/cli_tools/soundwave/pandas_sqlite.py b/tools/perf/cli_tools/soundwave/pandas_sqlite.py
new file mode 100644
index 0000000..59b9a154
--- /dev/null
+++ b/tools/perf/cli_tools/soundwave/pandas_sqlite.py
@@ -0,0 +1,84 @@
+# 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.
+
+"""
+Helper methods for dealing with a SQLite database with pandas.
+"""
+
+from core.external_modules import pandas
+
+
+def DataFrame(column_types, index=None, rows=None):
+  """Create a DataFrame with given column types as index.
+
+  Unlike usual pandas DataFrame constructors, this allows to have explicitly
+  typed column values, even when no rows of data are provided. And, when such
+  data is available, values are explicitly casted, instead of letting pandas
+  guess a type.
+
+  Args:
+    column_types: A sequence of (name, dtype) pairs to define the columns.
+    index: An optional column name or sequence of column names to use as index
+      of the frame.
+    rows: An optional sequence of rows of data.
+  """
+  if rows:
+    cols = zip(*rows)
+    assert len(cols) == len(column_types)
+    cols = (list(vs) for vs in cols)
+  else:
+    cols = (None for _ in column_types)
+  df = pandas.DataFrame()
+  for (column, dtype), values in zip(column_types, cols):
+    df[column] = pandas.Series(values, dtype=dtype)
+  if index is not None:
+    index = [index] if isinstance(index, basestring) else list(index)
+    df.set_index(index, inplace=True)
+  return df
+
+
+def CreateTableIfNotExists(con, name, frame):
+  """Create a new empty table, if it doesn't already exist.
+
+  Args:
+    con: A sqlite connection object.
+    name: Name of SQL table to create.
+    frame: A DataFrame used to infer the schema of the table; the index of the
+      DataFrame is set as PRIMARY KEY of the table.
+  """
+  keys = [k for k in frame.index.names if k is not None]
+  if not keys:
+    keys = None
+  db = pandas.io.sql.SQLiteDatabase(con)
+  table = pandas.io.sql.SQLiteTable(
+      name, db, frame=frame, index=keys is not None, keys=keys,
+      if_exists='append')
+  table.create()
+
+
+def _InsertOrReplaceStatement(name, keys):
+  columns = ','.join(keys)
+  values = ','.join('?' for _ in keys)
+  return 'INSERT OR REPLACE INTO %s(%s) VALUES (%s)' % (name, columns, values)
+
+
+def InsertOrReplaceRecords(con, name, frame):
+  """Insert or replace records from a DataFrame into a SQLite database.
+
+  Assumes that the table already exists. Any new records with a matching
+  PRIMARY KEY, usually the frame.index, will replace existing records.
+
+  Args:
+    con: A sqlite connection object.
+    name: Name of SQL table.
+    frame: DataFrame with records to write.
+  """
+  db = pandas.io.sql.SQLiteDatabase(con)
+  table = pandas.io.sql.SQLiteTable(
+      name, db, frame=frame, index=True, if_exists='append')
+  assert table.exists()
+  keys, data = table.insert_data()
+  insert_statement = _InsertOrReplaceStatement(name, keys)
+  with db.run_transaction() as c:
+    c.executemany(insert_statement, zip(*data))
diff --git a/tools/perf/cli_tools/soundwave/pandas_sqlite_test.py b/tools/perf/cli_tools/soundwave/pandas_sqlite_test.py
new file mode 100644
index 0000000..648f420
--- /dev/null
+++ b/tools/perf/cli_tools/soundwave/pandas_sqlite_test.py
@@ -0,0 +1,73 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import sqlite3
+import unittest
+
+from cli_tools.soundwave import pandas_sqlite
+from core.external_modules import pandas
+
+
+@unittest.skipIf(pandas is None, 'pandas not available')
+class TestPandasSQLite(unittest.TestCase):
+  def testCreateTableIfNotExists_newTable(self):
+    df = pandas_sqlite.DataFrame(
+        [('bug_id', int), ('summary', str), ('status', str)], index='bug_id')
+    con = sqlite3.connect(':memory:')
+    try:
+      self.assertFalse(pandas.io.sql.has_table('bugs', con))
+      pandas_sqlite.CreateTableIfNotExists(con, 'bugs', df)
+      self.assertTrue(pandas.io.sql.has_table('bugs', con))
+    finally:
+      con.close()
+
+  def testCreateTableIfNotExists_alreadyExists(self):
+    df = pandas_sqlite.DataFrame(
+        [('bug_id', int), ('summary', str), ('status', str)], index='bug_id')
+    con = sqlite3.connect(':memory:')
+    try:
+      self.assertFalse(pandas.io.sql.has_table('bugs', con))
+      pandas_sqlite.CreateTableIfNotExists(con, 'bugs', df)
+      self.assertTrue(pandas.io.sql.has_table('bugs', con))
+      # It's fine to call a second time.
+      pandas_sqlite.CreateTableIfNotExists(con, 'bugs', df)
+      self.assertTrue(pandas.io.sql.has_table('bugs', con))
+    finally:
+      con.close()
+
+  def testInsertOrReplaceRecords_tableNotExistsRaises(self):
+    column_types = (('bug_id', int), ('summary', str), ('status', str))
+    rows = [(123, 'Some bug', 'Started'), (456, 'Another bug', 'Assigned')]
+    df = pandas_sqlite.DataFrame(column_types, index='bug_id', rows=rows)
+    con = sqlite3.connect(':memory:')
+    try:
+      with self.assertRaises(AssertionError):
+        pandas_sqlite.InsertOrReplaceRecords(con, 'bugs', df)
+    finally:
+      con.close()
+
+  def testInsertOrReplaceRecords_existingRecords(self):
+    column_types = (('bug_id', int), ('summary', str), ('status', str))
+    rows1 = [(123, 'Some bug', 'Started'), (456, 'Another bug', 'Assigned')]
+    df1 = pandas_sqlite.DataFrame(column_types, index='bug_id', rows=rows1)
+    rows2 = [(123, 'Some bug', 'Fixed'), (789, 'A new bug', 'Untriaged')]
+    df2 = pandas_sqlite.DataFrame(column_types, index='bug_id', rows=rows2)
+    con = sqlite3.connect(':memory:')
+    try:
+      pandas_sqlite.CreateTableIfNotExists(con, 'bugs', df1)
+
+      # Write first data frame to database.
+      pandas_sqlite.InsertOrReplaceRecords(con, 'bugs', df1)
+      df = pandas.read_sql('SELECT * FROM bugs', con, index_col='bug_id')
+      self.assertEqual(len(df), 2)
+      self.assertEqual(df.loc[123]['status'], 'Started')
+
+      # Write second data frame to database.
+      pandas_sqlite.InsertOrReplaceRecords(con, 'bugs', df2)
+      df = pandas.read_sql('SELECT * FROM bugs', con, index_col='bug_id')
+      self.assertEqual(len(df), 3)  # Only one extra record added.
+      self.assertEqual(df.loc[123]['status'], 'Fixed')  # Bug is now fixed.
+      self.assertItemsEqual(df.index, (123, 456, 789))
+    finally:
+      con.close()
diff --git a/tools/perf/cli_tools/soundwave/studies/__init__.py b/tools/perf/cli_tools/soundwave/studies/__init__.py
new file mode 100644
index 0000000..72b4e19
--- /dev/null
+++ b/tools/perf/cli_tools/soundwave/studies/__init__.py
@@ -0,0 +1,36 @@
+# 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.
+
+from cli_tools.soundwave.studies import health_study
+from cli_tools.soundwave.studies import v8_study
+from core.external_modules import pandas
+
+
+_STUDIES = {'health': health_study, 'v8': v8_study}
+
+NAMES = sorted(_STUDIES)
+
+
+def GetStudy(study):
+  return _STUDIES[study]
+
+
+def PostProcess(df):
+  # Snap stories on the same test run to the same timestamp.
+  df['timestamp'] = df.groupby(
+      ['test_suite', 'bot', 'point_id'])['timestamp'].transform('min')
+
+  # We use all runs on the latest day for each quarter as reference.
+  df['quarter'] = df['timestamp'].dt.to_period('Q')
+  df['reference'] = df['timestamp'].dt.date == df.groupby(
+      'quarter')['timestamp'].transform('max').dt.date
+
+  # Change unit for values in ms to seconds.
+  # TODO: Get and use unit information from the dashboard instead of trying to
+  # guess by the measurement name.
+  is_ms_unit = (df['measurement'].str.startswith('timeTo') |
+                df['measurement'].str.endswith(':duration'))
+  df.loc[is_ms_unit, 'value'] = df['value'] / 1000
+
+  return df
diff --git a/tools/perf/cli_tools/soundwave/studies/health_study.py b/tools/perf/cli_tools/soundwave/studies/health_study.py
new file mode 100644
index 0000000..eb5711a4
--- /dev/null
+++ b/tools/perf/cli_tools/soundwave/studies/health_study.py
@@ -0,0 +1,45 @@
+# 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.
+
+CLOUD_PATH = 'gs://chome-health-tvdata/datasets/health_study.csv'
+
+OVERALL_PSS = ('memory:{browser}:all_processes:reported_by_os:system_memory'
+               ':proportional_resident_size_avg')
+
+BATTERY = [
+    'power.typical_10_mobile',
+    'application_energy_consumption_mwh'
+]
+
+STARTUP_BY_BROWSER = {
+    'chrome': [
+        'startup.mobile',
+        'first_contentful_paint_time_avg',
+        'intent_coldish_bbc'
+    ],
+    'webview': [
+        'system_health.webview_startup',
+        'webview_startup_wall_time_avg',
+        'load_chrome/load_chrome_blank'
+    ]
+}
+
+
+def IterSystemHealthBots():
+  yield 'ChromiumPerf/android-go-perf'
+  yield 'ChromiumPerfFyi/android-go_webview-perf'
+
+
+def GetBrowserFromBot(bot):
+  return 'webview' if 'webview' in bot else 'chrome'
+
+
+def IterTestPaths():
+  for bot in IterSystemHealthBots():
+    browser = GetBrowserFromBot(bot)
+    overall_pss = OVERALL_PSS.format(browser=browser)
+    for story_group in ('foreground', 'background'):
+      yield '/'.join([bot, 'memory.top_10_mobile', overall_pss, story_group])
+    yield '/'.join([bot] + BATTERY)
+    yield '/'.join([bot] + STARTUP_BY_BROWSER[browser])
diff --git a/tools/perf/cli_tools/soundwave/studies/v8_study.py b/tools/perf/cli_tools/soundwave/studies/v8_study.py
new file mode 100644
index 0000000..5a09749
--- /dev/null
+++ b/tools/perf/cli_tools/soundwave/studies/v8_study.py
@@ -0,0 +1,48 @@
+# 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.
+
+from core.services import dashboard_service
+from cli_tools.soundwave.tables import timeseries
+
+
+CLOUD_PATH = 'gs://chome-health-tvdata/datasets/v8_report.csv'
+
+ANDROID_GO = 'ChromiumPerf:android-go-perf'
+V8_EFFECTIVE_SIZE = (
+    'memory:chrome:renderer_processes:reported_by_chrome:v8:effective_size')
+
+
+TEST_SUITES = {
+    'system_health.memory_mobile': [
+        V8_EFFECTIVE_SIZE],
+    'system_health.common_mobile': [
+        'timeToFirstContentfulPaint', 'timeToFirstMeaningfulPaint',
+        'timeToInteractive'],
+    'v8.browsing_mobile': [
+        'Total:duration', 'V8-Only:duration', V8_EFFECTIVE_SIZE]
+}
+
+
+def GetEmergingMarketStories():
+  description = dashboard_service.Describe('system_health.memory_mobile')
+  return description['caseTags']['emerging_market']
+
+
+def IterTestPaths():
+  # We want to track emerging market stories only.
+  test_cases = GetEmergingMarketStories()
+
+  for test_suite, measurements in TEST_SUITES.iteritems():
+    # v8.browsing_mobile only runs 'browse:*' stories, while other benchmarks
+    # run all of them.
+    browse_only = 'browsing' in test_suite
+    for test_case in test_cases:
+      if browse_only and not test_case.startswith('browse:'):
+        continue
+      for measurement in measurements:
+        yield timeseries.Key(
+            test_suite=test_suite,
+            measurement=measurement,
+            bot=ANDROID_GO,
+            test_case=test_case)
diff --git a/tools/perf/cli_tools/soundwave/tables/__init__.py b/tools/perf/cli_tools/soundwave/tables/__init__.py
new file mode 100644
index 0000000..e8528f3
--- /dev/null
+++ b/tools/perf/cli_tools/soundwave/tables/__init__.py
@@ -0,0 +1,44 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import contextlib
+import os
+
+import sqlite3
+
+from cli_tools.soundwave import pandas_sqlite
+from cli_tools.soundwave.tables import alerts
+from cli_tools.soundwave.tables import bugs
+from cli_tools.soundwave.tables import timeseries
+
+
+@contextlib.contextmanager
+def DbSession(filename):
+  """Context manage a session with a database connection.
+
+  Ensures that tables have been initialized.
+  """
+  if filename != ':memory:':
+    parent_dir = os.path.dirname(filename)
+    if not os.path.exists(parent_dir):
+      os.makedirs(parent_dir)
+  con = sqlite3.connect(filename)
+  try:
+    # Tell sqlite to use a write-ahead log, which drastically increases its
+    # concurrency capabilities. This helps prevent 'database is locked'
+    # exceptions when we have many workers writing to a single database. This
+    # mode is sticky, so we only need to set it once and future connections
+    # will automatically use the log. More details are available at
+    # https://www.sqlite.org/wal.html.
+    con.execute('PRAGMA journal_mode=WAL')
+    _CreateTablesIfNeeded(con)
+    yield con
+  finally:
+    con.close()
+
+
+def _CreateTablesIfNeeded(con):
+  """Creates soundwave tables in the database, if they don't already exist."""
+  for m in (alerts, bugs, timeseries):
+    pandas_sqlite.CreateTableIfNotExists(con, m.TABLE_NAME, m.DataFrame())
diff --git a/tools/perf/cli_tools/soundwave/tables/alerts.py b/tools/perf/cli_tools/soundwave/tables/alerts.py
new file mode 100644
index 0000000..521512d
--- /dev/null
+++ b/tools/perf/cli_tools/soundwave/tables/alerts.py
@@ -0,0 +1,71 @@
+# 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.
+
+from cli_tools.soundwave import pandas_sqlite
+
+
+TABLE_NAME = 'alerts'
+COLUMN_TYPES = (
+    ('key', str),  # unique datastore key ('agxzfmNocm9tZXBlcmZyFAsS')
+    ('timestamp', 'datetime64[ns]'),  # when the alert was created
+    ('test_suite', str),  # benchmark name ('loading.mobile')
+    ('measurement', str),  # metric name ('timeToFirstContentfulPaint')
+    ('bot', str),  # master/builder name ('ChromiumPerf.android-nexus5')
+    ('test_case', str),  # story name ('Wikipedia')
+    ('start_revision', str),  # git hash or commit position before anomaly
+    ('end_revision', str),  # git hash or commit position after anomaly
+    ('median_before_anomaly', 'float64'),  # median of values before anomaly
+    ('median_after_anomaly', 'float64'),  # median of values after anomaly
+    ('units', str),  # unit in which values are masured ('ms')
+    ('improvement', bool),  # whether anomaly is an improvement or regression
+    ('bug_id', 'int64'),  # crbug id associated with this alert, 0 if missing
+    ('status', str),  # one of 'ignored', 'invalid', 'triaged', 'untriaged'
+    ('bisect_status', str),  # one of 'started', 'falied', 'completed'
+)
+COLUMNS = tuple(c for c, _ in COLUMN_TYPES)
+INDEX = COLUMNS[0]
+
+
+_CODE_TO_STATUS = {
+    -2: 'ignored',
+    -1: 'invalid',
+    None: 'untriaged',
+    # Any positive integer represents a bug_id and maps to a 'triaged' status.
+}
+
+
+def DataFrame(rows=None):
+  return pandas_sqlite.DataFrame(COLUMN_TYPES, index=INDEX, rows=rows)
+
+
+def _RowFromJson(data):
+  """Turn json data from an alert into a tuple with values for that record."""
+  data = data.copy()  # Do not modify the original dict.
+
+  # Name fields using newer dashboard nomenclature.
+  data['test_suite'] = data.pop('testsuite')
+  raw_test = data.pop('test')
+  if '/' in raw_test:
+    data['measurement'], data['test_case'] = raw_test.split('/', 1)
+  else:
+    # Alert was on a summary metric, i.e. a summary of the measurement across
+    # multiple test cases. Therefore, no test_case is associated with it.
+    data['measurement'], data['test_case'] = raw_test, None
+  data['bot'] = '/'.join([data.pop('master'), data.pop('bot')])
+
+  # Separate bug_id from alert status.
+  data['status'] = _CODE_TO_STATUS.get(data['bug_id'], 'triaged')
+  if data['status'] == 'triaged':
+    assert data['bug_id'] > 0
+  else:
+    # pandas cannot hold both int and None values in the same series, if so the
+    # type is coerced into float; to prevent this we use 0 to denote untriaged
+    # alerts with no bug_id assigned.
+    data['bug_id'] = 0
+
+  return tuple(data[k] for k in COLUMNS)
+
+
+def DataFrameFromJson(data):
+  return DataFrame([_RowFromJson(d) for d in data['anomalies']])
diff --git a/tools/perf/cli_tools/soundwave/tables/alerts_test.py b/tools/perf/cli_tools/soundwave/tables/alerts_test.py
new file mode 100644
index 0000000..09c15ea8
--- /dev/null
+++ b/tools/perf/cli_tools/soundwave/tables/alerts_test.py
@@ -0,0 +1,101 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import datetime
+import unittest
+
+from cli_tools.soundwave import tables
+from core.external_modules import pandas
+
+
+@unittest.skipIf(pandas is None, 'pandas not available')
+class TestAlerts(unittest.TestCase):
+  def testDataFrameFromJson(self):
+    data = {
+        'anomalies': [
+            {
+                'key': 'abc123',
+                'timestamp': '2009-02-13T23:31:30.000',
+                'testsuite': 'loading.mobile',
+                'test': 'timeToFirstInteractive/Google',
+                'master': 'ChromiumPerf',
+                'bot': 'android-nexus5',
+                'start_revision': 12345,
+                'end_revision': 12543,
+                'median_before_anomaly': 2037.18,
+                'median_after_anomaly': 2135.540,
+                'units': 'ms',
+                'improvement': False,
+                'bug_id': 55555,
+                'bisect_status': 'started',
+            },
+            {
+                'key': 'xyz567',
+                'timestamp': '2009-02-13T23:31:30.000',
+                'testsuite': 'loading.mobile',
+                'test': 'timeToFirstInteractive/Wikipedia',
+                'master': 'ChromiumPerf',
+                'bot': 'android-nexus5',
+                'start_revision': 12345,
+                'end_revision': 12543,
+                'median_before_anomaly': 2037.18,
+                'median_after_anomaly': 2135.540,
+                'units': 'ms',
+                'improvement': False,
+                'bug_id': None,
+                'bisect_status': 'started',
+            }
+        ]
+    }
+    alerts = tables.alerts.DataFrameFromJson(data)
+    self.assertEqual(len(alerts), 2)
+
+    alert = alerts.loc['abc123']
+    self.assertEqual(alert['timestamp'], datetime.datetime(
+        year=2009, month=2, day=13, hour=23, minute=31, second=30))
+    self.assertEqual(alert['bot'], 'ChromiumPerf/android-nexus5')
+    self.assertEqual(alert['test_suite'], 'loading.mobile')
+    self.assertEqual(alert['test_case'], 'Google')
+    self.assertEqual(alert['measurement'], 'timeToFirstInteractive')
+    self.assertEqual(alert['bug_id'], 55555)
+    self.assertEqual(alert['status'], 'triaged')
+
+    # We expect bug_id's to be integers.
+    self.assertTrue(pandas.api.types.is_integer_dtype(alerts['bug_id'].dtype))
+
+    # Missing bug_id's become 0.
+    self.assertEqual(alerts.loc['xyz567']['bug_id'], 0)
+
+  def testDataFrameFromJson_withSummaryMetric(self):
+    data = {
+        'anomalies': [
+            {
+                'key': 'abc123',
+                'timestamp': '2009-02-13T23:31:30.000',
+                'testsuite': 'loading.mobile',
+                'test': 'timeToFirstInteractive',
+                'master': 'ChromiumPerf',
+                'bot': 'android-nexus5',
+                'start_revision': 12345,
+                'end_revision': 12543,
+                'median_before_anomaly': 2037.18,
+                'median_after_anomaly': 2135.540,
+                'units': 'ms',
+                'improvement': False,
+                'bug_id': 55555,
+                'bisect_status': 'started',
+            }
+        ]
+    }
+    alerts = tables.alerts.DataFrameFromJson(data)
+    self.assertEqual(len(alerts), 1)
+
+    alert = alerts.loc['abc123']
+    self.assertEqual(alert['measurement'], 'timeToFirstInteractive')
+    self.assertIsNone(alert['test_case'])
+
+  def testDataFrameFromJson_noAlerts(self):
+    data = {'anomalies': []}
+    alerts = tables.alerts.DataFrameFromJson(data)
+    self.assertEqual(len(alerts), 0)
diff --git a/tools/perf/cli_tools/soundwave/tables/bugs.py b/tools/perf/cli_tools/soundwave/tables/bugs.py
new file mode 100644
index 0000000..9e5f5b158
--- /dev/null
+++ b/tools/perf/cli_tools/soundwave/tables/bugs.py
@@ -0,0 +1,60 @@
+# 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.
+
+from cli_tools.soundwave import pandas_sqlite
+from core.external_modules import pandas
+
+
+TABLE_NAME = 'bugs'
+COLUMN_TYPES = (
+    ('id', 'int64'),  # crbug number identifying this issue
+    ('summary', unicode),  # issue title ('1%-5% regression in loading ...')
+    ('published', 'datetime64[ns]'),  # when the issue got created
+    ('updated', 'datetime64[ns]'),  # when the issue got last updated
+    ('state', str),  # usually either 'open' or 'closed'
+    ('status', str),  # current state of the bug ('Assigned', 'Fixed', etc.)
+    ('author', str),  # email of user who created the issue
+    ('owner', str),  # email of user who currently owns the issue
+    ('cc', str),  # comma-separated list of users cc'ed into the issue
+    ('components', str),  # comma-separated list of components ('Blink>Loader')
+    ('labels', str),  # comma-separated list of labels ('Type-Bug-Regression')
+)
+COLUMNS = tuple(c for c, _ in COLUMN_TYPES)
+DATE_COLUMNS = tuple(c for c, t in COLUMN_TYPES if t == 'datetime64[ns]')
+INDEX = COLUMNS[0]
+
+
+def DataFrame(rows=None):
+  return pandas_sqlite.DataFrame(COLUMN_TYPES, index=INDEX, rows=rows)
+
+
+def _CommaSeparate(values):
+  assert isinstance(values, list)
+  if values:
+    return ','.join(values)
+  else:
+    return None
+
+
+def DataFrameFromJson(data):
+  rows = []
+  for row in data:
+    row = row['bug'].copy()
+    for key in ('cc', 'components', 'labels'):
+      row[key] = _CommaSeparate(row[key])
+    rows.append(tuple(row[k] for k in COLUMNS))
+
+  return DataFrame(rows)
+
+
+def Get(con, bug_id):
+  """Find the record for a bug_id in the given database connection.
+
+  Returns:
+    A pandas.Series with the record if found, or None otherwise.
+  """
+  df = pandas.read_sql(
+      'SELECT * FROM %s WHERE id=?' % TABLE_NAME, con, params=(bug_id,),
+      index_col=INDEX, parse_dates=DATE_COLUMNS)
+  return df.loc[bug_id] if len(df) else None
diff --git a/tools/perf/cli_tools/soundwave/tables/bugs_test.py b/tools/perf/cli_tools/soundwave/tables/bugs_test.py
new file mode 100644
index 0000000..9d4831d
--- /dev/null
+++ b/tools/perf/cli_tools/soundwave/tables/bugs_test.py
@@ -0,0 +1,46 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import datetime
+import unittest
+
+from cli_tools.soundwave import tables
+from core.external_modules import pandas
+
+
+@unittest.skipIf(pandas is None, 'pandas not available')
+class TestBugs(unittest.TestCase):
+  def testDataFrameFromJson(self):
+    data = [
+        {
+            'bug': {
+                'id': 12345,
+                'summary': u'1%-\u221e% regression in loading at 123:125',
+                'published': '2018-04-09T17:01:09',
+                'updated': '2018-04-12T06:38:34',
+                'state': 'closed',
+                'status': 'Fixed',
+                'author': 'foo@chromium.org',
+                'owner': 'bar@chromium.org',
+                'cc': ['baz@chromium.org', 'foo@chromium.org'],
+                'components': [],
+                'labels': ['Perf-Regression', 'Foo>Label'],
+            }
+        }
+    ]
+
+    bugs = tables.bugs.DataFrameFromJson(data)
+    self.assertEqual(len(bugs), 1)
+
+    bug = bugs.loc[12345]  # Get bug by id.
+    self.assertEqual(bug['published'], datetime.datetime(
+        year=2018, month=4, day=9, hour=17, minute=1, second=9))
+    self.assertEqual(bug['status'], 'Fixed')
+    self.assertEqual(bug['cc'], 'baz@chromium.org,foo@chromium.org')
+    self.assertEqual(bug['components'], None)
+
+  def testDataFrameFromJson_noBugs(self):
+    data = []
+    bugs = tables.bugs.DataFrameFromJson(data)
+    self.assertEqual(len(bugs), 0)
diff --git a/tools/perf/cli_tools/soundwave/tables/timeseries.py b/tools/perf/cli_tools/soundwave/tables/timeseries.py
new file mode 100644
index 0000000..df70566
--- /dev/null
+++ b/tools/perf/cli_tools/soundwave/tables/timeseries.py
@@ -0,0 +1,175 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import collections
+
+from cli_tools.soundwave import pandas_sqlite
+from core.external_modules import pandas
+
+
+TABLE_NAME = 'timeseries'
+COLUMN_TYPES = (
+    # Index columns.
+    ('test_suite', str),  # benchmark name ('loading.mobile')
+    ('measurement', str),  # metric name ('timeToFirstContentfulPaint')
+    ('bot', str),  # master/builder name ('ChromiumPerf.android-nexus5')
+    ('test_case', str),  # story name ('Wikipedia')
+    ('point_id', 'int64'),  # monotonically increasing id for time series axis
+    # Other columns.
+    ('value', 'float64'),  # value recorded for test_path at given point_id
+    ('timestamp', 'datetime64[ns]'),  # when the value got stored on dashboard
+    ('commit_pos', 'int64'),  # chromium commit position
+    ('chromium_rev', str),  # git hash of chromium revision
+    ('clank_rev', str),  # git hash of clank revision
+    ('trace_url', str),  # URL to a sample trace.
+    ('units', str),  # unit of measurement (e.g. 'ms', 'bytes')
+    ('improvement_direction', str),  # good direction ('up', 'down', 'unknown')
+)
+COLUMNS = tuple(c for c, _ in COLUMN_TYPES)
+INDEX = COLUMNS[:5]
+
+# Copied from https://goo.gl/DzGYpW.
+_CODE_TO_IMPROVEMENT_DIRECTION = {
+    0: 'up',
+    1: 'down',
+}
+
+
+TEST_PATH_PARTS = (
+    'master', 'builder', 'test_suite', 'measurement', 'test_case')
+
+# Query template to find all data points of a given test_path (i.e. fixed
+# test_suite, measurement, bot, and test_case values).
+_QUERY_TIME_SERIES = (
+    'SELECT * FROM %s WHERE %s'
+    % (TABLE_NAME, ' AND '.join('%s=?' % c for c in INDEX[:-1])))
+
+
+# Required columns to request from /timeseries2 API.
+_TIMESERIES2_COLS = [
+    'revision',
+    'revisions',
+    'avg',
+    'timestamp',
+    'annotations']
+
+
+class Key(collections.namedtuple('Key', INDEX[:-1])):
+  """Uniquely identifies a single timeseries."""
+
+  @classmethod
+  def FromDict(cls, *args, **kwargs):
+    kwargs = dict(*args, **kwargs)
+    kwargs.setdefault('test_case', '')  # test_case is optional.
+    return cls(**kwargs)
+
+  def AsDict(self):
+    return dict(zip(self._fields, self))
+
+  def AsApiParams(self):
+    """Return a dict with params for a /timeseries2 API request."""
+    params = self.AsDict()
+    if not params['test_case']:
+      del params['test_case']  # test_case is optional.
+    params['columns'] = ','.join(_TIMESERIES2_COLS)
+    return params
+
+
+def DataFrame(rows=None):
+  return pandas_sqlite.DataFrame(COLUMN_TYPES, index=INDEX, rows=rows)
+
+
+def _ParseIntValue(value, on_error=-1):
+  # Try to parse as int and, in case of error, return a pre-defined value.
+  try:
+    return int(value)
+  except StandardError:
+    return on_error
+
+
+def _ParseConfigFromTestPath(test_path):
+  if isinstance(test_path, Key):
+    return test_path.AsDict()
+
+  values = test_path.split('/', len(TEST_PATH_PARTS) - 1)
+  if len(values) < len(TEST_PATH_PARTS):
+    values.append('')  # Possibly missing test_case.
+  if len(values) != len(TEST_PATH_PARTS):
+    raise ValueError(test_path)
+  config = dict(zip(TEST_PATH_PARTS, values))
+  config['bot'] = '%s/%s' % (config.pop('master'), config.pop('builder'))
+  return config
+
+
+def DataFrameFromJson(test_path, data):
+  if isinstance(test_path, Key):
+    return _DataFrameFromJsonV2(test_path, data)
+  else:
+    # TODO(crbug.com/907121): Remove when we can switch entirely to v2.
+    return _DataFrameFromJsonV1(test_path, data)
+
+
+def _DataFrameFromJsonV2(ts_key, data):
+  rows = []
+  for point in data['data']:
+    point = dict(zip(_TIMESERIES2_COLS, point))
+    rows.append(ts_key + (
+        point['revision'],  # point_id
+        point['avg'],  # value
+        point['timestamp'],  # timestamp
+        _ParseIntValue(point['revisions']['r_commit_pos']),  # commit_pos
+        point['revisions'].get('r_chromium'),  # chromium_rev
+        point['revisions'].get('r_clank'),  # clank_rev
+        point['annotations'].get('a_tracing_uri'),  # trace_url
+        data['units'],  # units
+        data['improvement_direction'],  # improvement_direction
+    ))
+  return DataFrame(rows)
+
+
+def _DataFrameFromJsonV1(test_path, data):
+  assert test_path == data['test_path']
+  config = _ParseConfigFromTestPath(data['test_path'])
+  config['improvement_direction'] = _CODE_TO_IMPROVEMENT_DIRECTION.get(
+      data['improvement_direction'], 'unknown')
+  timeseries = data['timeseries']
+  # The first element in timeseries list contains header with column names.
+  header = timeseries[0]
+  rows = []
+
+  # Remaining elements contain the values for each row.
+  for values in timeseries[1:]:
+    row = config.copy()
+    row.update(zip(header, values))
+    row['point_id'] = row['revision']
+    row['commit_pos'] = _ParseIntValue(row['r_commit_pos'])
+    row['chromium_rev'] = row.get('r_chromium')
+    row['clank_rev'] = row.get('r_clank', None)
+    rows.append(tuple(row.get(k) for k in COLUMNS))
+
+  return DataFrame(rows)
+
+
+def GetTimeSeries(con, test_path, extra_cond=None):
+  """Get the records for all data points on the given test_path.
+
+  Returns:
+    A pandas.DataFrame with all records found.
+  """
+  config = _ParseConfigFromTestPath(test_path)
+  params = tuple(config[c] for c in INDEX[:-1])
+  query = _QUERY_TIME_SERIES
+  if extra_cond is not None:
+    query = ' '.join([query, extra_cond])
+  return pandas.read_sql(query, con, params=params, parse_dates=['timestamp'])
+
+
+def GetMostRecentPoint(con, test_path):
+  """Find the record for the most recent data point on the given test_path.
+
+  Returns:
+    A pandas.Series with the record if found, or None otherwise.
+  """
+  df = GetTimeSeries(con, test_path, 'ORDER BY timestamp DESC LIMIT 1')
+  return df.iloc[0] if not df.empty else None
diff --git a/tools/perf/cli_tools/soundwave/tables/timeseries_test.py b/tools/perf/cli_tools/soundwave/tables/timeseries_test.py
new file mode 100644
index 0000000..c7f0d5e
--- /dev/null
+++ b/tools/perf/cli_tools/soundwave/tables/timeseries_test.py
@@ -0,0 +1,247 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import datetime
+import unittest
+
+from cli_tools.soundwave import pandas_sqlite
+from cli_tools.soundwave import tables
+from core.external_modules import pandas
+
+
+def SamplePoint(point_id, value, timestamp=None, missing_commit_pos=False):
+  """Build a sample point as returned by timeseries2 API."""
+  revisions = {
+      'r_commit_pos': str(point_id),
+      'r_chromium': 'chromium@%d' % point_id,
+  }
+  annotations = {
+      'a_tracing_uri': 'http://example.com/trace/%d' % point_id
+  }
+
+  if timestamp is None:
+    timestamp = datetime.datetime.utcfromtimestamp(
+        1234567890 + 60 * point_id).isoformat()
+  if missing_commit_pos:
+    # Some data points have a missing commit position.
+    revisions['r_commit_pos'] = None
+  return [
+      point_id,
+      revisions,
+      value,
+      timestamp,
+      annotations,
+  ]
+
+
+class TestKey(unittest.TestCase):
+  def testKeyFromDict_typical(self):
+    key1 = tables.timeseries.Key.FromDict({
+        'test_suite': 'loading.mobile',
+        'bot': 'ChromiumPerf:android-nexus5',
+        'measurement': 'timeToFirstInteractive',
+        'test_case': 'Wikipedia'})
+    key2 = tables.timeseries.Key(
+        test_suite='loading.mobile',
+        measurement='timeToFirstInteractive',
+        bot='ChromiumPerf:android-nexus5',
+        test_case='Wikipedia')
+    self.assertEqual(key1, key2)
+
+  def testKeyFromDict_defaultTestCase(self):
+    key1 = tables.timeseries.Key.FromDict({
+        'test_suite': 'loading.mobile',
+        'bot': 'ChromiumPerf:android-nexus5',
+        'measurement': 'timeToFirstInteractive'})
+    key2 = tables.timeseries.Key(
+        test_suite='loading.mobile',
+        measurement='timeToFirstInteractive',
+        bot='ChromiumPerf:android-nexus5',
+        test_case='')
+    self.assertEqual(key1, key2)
+
+  def testKeyFromDict_invalidArgsRaises(self):
+    with self.assertRaises(TypeError):
+      tables.timeseries.Key.FromDict({
+          'test_suite': 'loading.mobile',
+          'bot': 'ChromiumPerf:android-nexus5'})
+
+
+@unittest.skipIf(pandas is None, 'pandas not available')
+class TestTimeSeries(unittest.TestCase):
+  def testDataFrameFromJsonV1(self):
+    test_path = ('ChromiumPerf/android-nexus5/loading.mobile'
+                 '/timeToFirstInteractive/PageSet/Google')
+    data = {
+        'test_path': test_path,
+        'improvement_direction': 1,
+        'timeseries': [
+            ['revision', 'value', 'timestamp', 'r_commit_pos', 'r_chromium'],
+            [547397, 2300.3, '2018-04-01T14:16:32.000', '547397', 'adb123'],
+            [547398, 2750.9, '2018-04-01T18:24:04.000', '547398', 'cde456'],
+            [547423, 2342.2, '2018-04-02T02:19:00.000', '547423', 'fab789'],
+            # Some timeseries have a missing commit position.
+            [547836, 2402.5, '2018-04-02T02:20:00.000', None, 'acf147'],
+        ]
+    }
+
+    timeseries = tables.timeseries.DataFrameFromJson(test_path, data)
+    # Check the integrity of the index: there should be no duplicates.
+    self.assertFalse(timeseries.index.duplicated().any())
+    self.assertEqual(len(timeseries), 4)
+
+    # Check values on the first point of the series.
+    point = timeseries.reset_index().iloc[0]
+    self.assertEqual(point['test_suite'], 'loading.mobile')
+    self.assertEqual(point['measurement'], 'timeToFirstInteractive')
+    self.assertEqual(point['bot'], 'ChromiumPerf/android-nexus5')
+    self.assertEqual(point['test_case'], 'PageSet/Google')
+    self.assertEqual(point['improvement_direction'], 'down')
+    self.assertEqual(point['point_id'], 547397)
+    self.assertEqual(point['value'], 2300.3)
+    self.assertEqual(point['timestamp'], datetime.datetime(
+        year=2018, month=4, day=1, hour=14, minute=16, second=32))
+    self.assertEqual(point['commit_pos'], 547397)
+    self.assertEqual(point['chromium_rev'], 'adb123')
+    self.assertEqual(point['clank_rev'], None)
+
+  def testDataFrameFromJsonV2(self):
+    test_path = tables.timeseries.Key(
+        test_suite='loading.mobile',
+        measurement='timeToFirstInteractive',
+        bot='ChromiumPerf:android-nexus5',
+        test_case='Wikipedia')
+    data = {
+        'improvement_direction': 'down',
+        'units': 'ms',
+        'data': [
+            SamplePoint(547397, 2300.3, timestamp='2018-04-01T14:16:32.000'),
+            SamplePoint(547398, 2750.9),
+            SamplePoint(547423, 2342.2),
+            SamplePoint(547836, 2402.5, missing_commit_pos=True),
+        ]
+    }
+
+    timeseries = tables.timeseries.DataFrameFromJson(test_path, data)
+    # Check the integrity of the index: there should be no duplicates.
+    self.assertFalse(timeseries.index.duplicated().any())
+    self.assertEqual(len(timeseries), 4)
+
+    # Check values on the first point of the series.
+    point = timeseries.reset_index().iloc[0]
+    self.assertEqual(point['test_suite'], 'loading.mobile')
+    self.assertEqual(point['measurement'], 'timeToFirstInteractive')
+    self.assertEqual(point['bot'], 'ChromiumPerf:android-nexus5')
+    self.assertEqual(point['test_case'], 'Wikipedia')
+    self.assertEqual(point['improvement_direction'], 'down')
+    self.assertEqual(point['units'], 'ms')
+    self.assertEqual(point['point_id'], 547397)
+    self.assertEqual(point['value'], 2300.3)
+    self.assertEqual(point['timestamp'], datetime.datetime(
+        year=2018, month=4, day=1, hour=14, minute=16, second=32))
+    self.assertEqual(point['commit_pos'], 547397)
+    self.assertEqual(point['chromium_rev'], 'chromium@547397')
+    self.assertEqual(point['clank_rev'], None)
+
+  def testDataFrameFromJson_withSummaryMetric(self):
+    test_path = tables.timeseries.Key(
+        test_suite='loading.mobile',
+        measurement='timeToFirstInteractive',
+        bot='ChromiumPerf:android-nexus5',
+        test_case='')
+    data = {
+        'improvement_direction': 'down',
+        'units': 'ms',
+        'data': [
+            SamplePoint(547397, 2300.3),
+            SamplePoint(547398, 2750.9),
+        ],
+    }
+
+    timeseries = tables.timeseries.DataFrameFromJson(
+        test_path, data).reset_index()
+    self.assertTrue((timeseries['test_case'] == '').all())
+
+  def testGetTimeSeries(self):
+    test_path = tables.timeseries.Key(
+        test_suite='loading.mobile',
+        measurement='timeToFirstInteractive',
+        bot='ChromiumPerf:android-nexus5',
+        test_case='Wikipedia')
+    data = {
+        'improvement_direction': 'down',
+        'units': 'ms',
+        'data': [
+            SamplePoint(547397, 2300.3),
+            SamplePoint(547398, 2750.9),
+            SamplePoint(547423, 2342.2),
+        ]
+    }
+
+    timeseries_in = tables.timeseries.DataFrameFromJson(test_path, data)
+    with tables.DbSession(':memory:') as con:
+      pandas_sqlite.InsertOrReplaceRecords(con, 'timeseries', timeseries_in)
+      timeseries_out = tables.timeseries.GetTimeSeries(con, test_path)
+      # Both DataFrame's should be equal, except the one we get out of the db
+      # does not have an index defined.
+      timeseries_in = timeseries_in.reset_index()
+      self.assertTrue(timeseries_in.equals(timeseries_out))
+
+  def testGetTimeSeries_withSummaryMetric(self):
+    test_path = tables.timeseries.Key(
+        test_suite='loading.mobile',
+        measurement='timeToFirstInteractive',
+        bot='ChromiumPerf:android-nexus5',
+        test_case='')
+    data = {
+        'improvement_direction': 'down',
+        'units': 'ms',
+        'data': [
+            SamplePoint(547397, 2300.3),
+            SamplePoint(547398, 2750.9),
+            SamplePoint(547423, 2342.2),
+        ]
+    }
+
+    timeseries_in = tables.timeseries.DataFrameFromJson(test_path, data)
+    with tables.DbSession(':memory:') as con:
+      pandas_sqlite.InsertOrReplaceRecords(con, 'timeseries', timeseries_in)
+      timeseries_out = tables.timeseries.GetTimeSeries(con, test_path)
+      # Both DataFrame's should be equal, except the one we get out of the db
+      # does not have an index defined.
+      timeseries_in = timeseries_in.reset_index()
+      self.assertTrue(timeseries_in.equals(timeseries_out))
+
+  def testGetMostRecentPoint_success(self):
+    test_path = tables.timeseries.Key(
+        test_suite='loading.mobile',
+        measurement='timeToFirstInteractive',
+        bot='ChromiumPerf:android-nexus5',
+        test_case='Wikipedia')
+    data = {
+        'improvement_direction': 'down',
+        'units': 'ms',
+        'data': [
+            SamplePoint(547397, 2300.3),
+            SamplePoint(547398, 2750.9),
+            SamplePoint(547423, 2342.2),
+        ]
+    }
+
+    timeseries = tables.timeseries.DataFrameFromJson(test_path, data)
+    with tables.DbSession(':memory:') as con:
+      pandas_sqlite.InsertOrReplaceRecords(con, 'timeseries', timeseries)
+      point = tables.timeseries.GetMostRecentPoint(con, test_path)
+      self.assertEqual(point['point_id'], 547423)
+
+  def testGetMostRecentPoint_empty(self):
+    test_path = tables.timeseries.Key(
+        test_suite='loading.mobile',
+        measurement='timeToFirstInteractive',
+        bot='ChromiumPerf:android-nexus5',
+        test_case='Wikipedia')
+
+    with tables.DbSession(':memory:') as con:
+      point = tables.timeseries.GetMostRecentPoint(con, test_path)
+      self.assertIsNone(point)
diff --git a/tools/perf/cli_tools/soundwave/worker_pool.py b/tools/perf/cli_tools/soundwave/worker_pool.py
new file mode 100644
index 0000000..ff035a3
--- /dev/null
+++ b/tools/perf/cli_tools/soundwave/worker_pool.py
@@ -0,0 +1,82 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Use a pool of workers to concurrently process a sequence of items.
+
+Example usage:
+
+    from soundwave import worker_pool
+
+    def MyWorker(args):
+      # This is called once for each worker to initialize it.
+
+      def Process(item):
+        # This will be called once for each item processed by this worker.
+
+      # Hook up the Process function so the worker_pool module can find it.
+      worker_pool.Process = Process
+
+    args.processes = 10  # Set number of processes to be used by the pool.
+    worker_pool.Run('This might take a while: ', MyWorker, args, items)
+"""
+import logging
+import multiprocessing
+import sys
+
+from core.external_modules import pandas
+
+
+# Worker implementations override this value
+Process = NotImplemented  # pylint: disable=invalid-name
+
+
+def ProgressIndicator(label, iterable, stream=None):
+  if stream is None:
+    stream = sys.stdout
+  stream.write(label)
+  stream.flush()
+  for _ in iterable:
+    stream.write('.')
+    stream.flush()
+  stream.write('\n')
+  stream.flush()
+
+
+def Run(label, worker, args, items, stream=None):
+  """Use a pool of workers to concurrently process a sequence of items.
+
+  Args:
+    label: A string displayed by the progress indicator when the job starts.
+    worker: A function with the worker implementation. See example above.
+    args: An argparse.Namespace() object used to initialize the workers. The
+        value of args.processes is the number of processes used by the pool.
+    items: An iterable with items to process by the pool of workers.
+    stream: A file-like object for the progress indicator output, defaults to
+        sys.stdout.
+
+  Returns:
+    Total time in seconds spent by the pool to process all items.
+  """
+  pool = multiprocessing.Pool(
+      processes=args.processes, initializer=worker, initargs=(args,))
+  time_started = pandas.Timestamp.utcnow()
+  try:
+    ProgressIndicator(label, pool.imap_unordered(_Worker, items), stream=stream)
+    time_finished = pandas.Timestamp.utcnow()
+  finally:
+    # Ensure resources (e.g. db connections from workers) are freed up.
+    pool.terminate()
+    pool.join()
+  return (time_finished - time_started).total_seconds()
+
+
+def _Worker(item):
+  try:
+    Process(item)  # pylint: disable=not-callable
+  except KeyboardInterrupt:
+    pass
+  except:
+    logging.exception('Worker failed with exception')
+    raise
diff --git a/tools/perf/cli_tools/soundwave/worker_pool_test.py b/tools/perf/cli_tools/soundwave/worker_pool_test.py
new file mode 100644
index 0000000..47197b3d
--- /dev/null
+++ b/tools/perf/cli_tools/soundwave/worker_pool_test.py
@@ -0,0 +1,50 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import argparse
+import os
+import shutil
+import sqlite3
+import tempfile
+import unittest
+
+from cli_tools.soundwave import pandas_sqlite
+from cli_tools.soundwave import worker_pool
+from core.external_modules import pandas
+
+
+def TestWorker(args):
+  con = sqlite3.connect(args.database_file)
+
+  def Process(item):
+    # Add item to the database.
+    df = pandas.DataFrame({'item': [item]})
+    df.to_sql('items', con, index=False, if_exists='append')
+
+  worker_pool.Process = Process
+
+
+@unittest.skipIf(pandas is None, 'pandas not available')
+class TestWorkerPool(unittest.TestCase):
+  def testWorkerPoolRun(self):
+    tempdir = tempfile.mkdtemp()
+    try:
+      args = argparse.Namespace()
+      args.database_file = os.path.join(tempdir, 'test.db')
+      args.processes = 3
+      schema = pandas_sqlite.DataFrame([('item', int)])
+      items = range(20)  # We'll write these in the database.
+      con = sqlite3.connect(args.database_file)
+      try:
+        pandas_sqlite.CreateTableIfNotExists(con, 'items', schema)
+        with open(os.devnull, 'w') as devnull:
+          worker_pool.Run(
+              'Processing:', TestWorker, args, items, stream=devnull)
+        df = pandas.read_sql('SELECT * FROM items', con)
+        # Check all of our items were written.
+        self.assertItemsEqual(df['item'], items)
+      finally:
+        con.close()
+    finally:
+      shutil.rmtree(tempdir)
diff --git a/tools/perf/cli_tools/update_wpr/update_wpr.py b/tools/perf/cli_tools/update_wpr/update_wpr.py
index 2f8d00e..be33cf2 100644
--- a/tools/perf/cli_tools/update_wpr/update_wpr.py
+++ b/tools/perf/cli_tools/update_wpr/update_wpr.py
@@ -18,12 +18,9 @@
 import webbrowser
 
 from core import cli_helpers
-from core import path_util
-
-path_util.AddSoundwaveToPath()
-from services import luci_auth  # pylint: disable=import-error
-from services import pinpoint_service  # pylint: disable=import-error
-from services import request  # pylint: disable=import-error
+from core.services import luci_auth
+from core.services import pinpoint_service
+from core.services import request
 
 
 SRC_ROOT = os.path.abspath(
diff --git a/tools/perf/cli_tools/update_wpr/update_wpr_unittest.py b/tools/perf/cli_tools/update_wpr/update_wpr_unittest.py
index 65a82bf..489c754 100644
--- a/tools/perf/cli_tools/update_wpr/update_wpr_unittest.py
+++ b/tools/perf/cli_tools/update_wpr/update_wpr_unittest.py
@@ -9,11 +9,8 @@
 
 import mock
 
-from core import path_util
 from cli_tools.update_wpr import update_wpr
-
-path_util.AddSoundwaveToPath()
-from services import request  # pylint: disable=import-error
+from core.services import request
 
 
 WPR_UPDATER = 'cli_tools.update_wpr.update_wpr.'
@@ -325,7 +322,7 @@
         WPR_UPDATER + 'WprUpdater._GetBranchIssueUrl',
         return_value='<issue-url>').start()
     new_job = mock.patch(
-        'services.pinpoint_service.NewJob',
+        'core.services.pinpoint_service.NewJob',
         return_value={'jobUrl': '<url>'}).start()
     self.assertEqual(
         self.wpr_updater.StartPinpointJobs(),
@@ -348,8 +345,9 @@
         return_value='<issue-url>').start()
     self.wpr_updater.device_id = '<serial>'
     new_job = mock.patch(
-        'services.pinpoint_service.NewJob', side_effect=request.ServerError(
-          mock.Mock(), mock.Mock(status=500), '')).start()
+        'core.services.pinpoint_service.NewJob',
+        side_effect=request.ServerError(
+            mock.Mock(), mock.Mock(status=500), '')).start()
     self.assertEqual(
         self.wpr_updater.StartPinpointJobs(['<config>']), ([], ['<config>']))
     new_job.assert_called_once_with(
diff --git a/tools/perf/core/cli_utils.py b/tools/perf/core/cli_utils.py
new file mode 100644
index 0000000..44b2967
--- /dev/null
+++ b/tools/perf/core/cli_utils.py
@@ -0,0 +1,34 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+import datetime
+import logging
+
+from core import gsutil
+
+
+def VerboseLevel(count):
+  if count == 0:
+    return logging.WARNING
+  elif count == 1:
+    return logging.INFO
+  else:
+    return logging.DEBUG
+
+
+def ConfigureLogging(verbose_count):
+  logging.basicConfig(level=VerboseLevel(verbose_count))
+
+
+def OpenWrite(filepath):
+  """Open file for writing, optionally supporting cloud storage paths."""
+  if filepath.startswith('gs://'):
+    return gsutil.OpenWrite(filepath)
+  else:
+    return open(filepath, 'w')
+
+
+def DaysAgoToTimestamp(num_days):
+  """Return an ISO formatted timestamp for a number of days ago."""
+  timestamp = datetime.datetime.utcnow() - datetime.timedelta(days=num_days)
+  return timestamp.isoformat()
diff --git a/tools/perf/core/gsutil.py b/tools/perf/core/gsutil.py
new file mode 100644
index 0000000..d75e754
--- /dev/null
+++ b/tools/perf/core/gsutil.py
@@ -0,0 +1,33 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import contextlib
+import os
+import shutil
+import subprocess
+import tempfile
+
+
+GSUTIL_BIN = 'gsutil'
+
+
+def Copy(source_path, dest_path):
+  subprocess.check_call([GSUTIL_BIN, 'cp', source_path, dest_path])
+
+
+@contextlib.contextmanager
+def OpenWrite(cloudpath):
+  """Allows to "open" a cloud storage path for writing.
+
+  Works by opening a local temporary file for writing, then copying the file
+  to cloud storage when writing is done.
+  """
+  tempdir = tempfile.mkdtemp()
+  try:
+    localpath = os.path.join(tempdir, os.path.basename(cloudpath))
+    with open(localpath, 'w') as f:
+      yield f
+    Copy(localpath, cloudpath)
+  finally:
+    shutil.rmtree(tempdir)
diff --git a/tools/perf/core/path_util.py b/tools/perf/core/path_util.py
index 39c5ae4..11cec5d 100644
--- a/tools/perf/core/path_util.py
+++ b/tools/perf/core/path_util.py
@@ -41,12 +41,6 @@
   return os.path.join(GetChromiumSrcDir(), 'build', 'android')
 
 
-def GetSoundwaveDir():
-  return os.path.join(
-      GetChromiumSrcDir(), 'third_party', 'catapult', 'experimental',
-      'soundwave')
-
-
 def AddTelemetryToPath():
   telemetry_path = GetTelemetryDir()
   if telemetry_path not in sys.path:
@@ -66,13 +60,6 @@
     sys.path.insert(1, py_utils_dir)
 
 
-def AddSoundwaveToPath():
-  AddPyUtilsToPath()  # needed by some soundwave scripts
-  soundwave_services_path = GetSoundwaveDir()
-  if soundwave_services_path not in sys.path:
-    sys.path.insert(1, soundwave_services_path)
-
-
 def GetWprDir():
   return os.path.join(
       GetChromiumSrcDir(), 'third_party', 'catapult', 'telemetry',
diff --git a/tools/perf/core/services/__init__.py b/tools/perf/core/services/__init__.py
new file mode 100644
index 0000000..1adf20d2
--- /dev/null
+++ b/tools/perf/core/services/__init__.py
@@ -0,0 +1,3 @@
+# 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.
diff --git a/tools/perf/core/services/buildbucket_service.py b/tools/perf/core/services/buildbucket_service.py
new file mode 100644
index 0000000..4d36f6d
--- /dev/null
+++ b/tools/perf/core/services/buildbucket_service.py
@@ -0,0 +1,66 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Make requests to the Buildbucket RPC API.
+
+For more details on the API see: go/buildbucket-rpc
+"""
+
+from core.services import request
+
+
+SERVICE_URL = 'https://cr-buildbucket.appspot.com/prpc/buildbucket.v2.Builds/'
+
+
+def Request(method, **kwargs):
+  """Send a request to some buildbucket service method."""
+  kwargs.setdefault('use_auth', True)
+  kwargs.setdefault('method', 'POST')
+  kwargs.setdefault('content_type', 'json')
+  kwargs.setdefault('accept', 'json')
+  return request.Request(SERVICE_URL + method, **kwargs)
+
+
+def GetBuild(project, bucket, builder, build_number):
+  """Get the status of a build by its build number.
+
+  Args:
+    project: The LUCI project name (e.g. 'chromium').
+    bucket: The LUCI bucket name (e.g. 'ci' or 'try').
+    builder: The builder name (e.g. 'linux_chromium_rel_ng').
+    build_number: An int with the build number to get.
+  """
+  return Request('GetBuild', data={
+      'builder': {
+          'project': project,
+          'bucket': bucket,
+          'builder': builder,
+      },
+      'buildNumber': build_number,
+  })
+
+
+def GetBuilds(project, bucket, builder, only_completed=True):
+  """Get a list of recent builds from a given builder.
+
+  Args:
+    project: The LUCI project name (e.g. 'chromium').
+    bucket: The LUCI bucket name (e.g. 'ci' or 'try').
+    builder: The builder name (e.g. 'linux_chromium_rel_ng').
+    only_completed: An optional bool to indicate whehter builds that have
+      not yet finished should be included in the results. The default is to
+      include only completed builds.
+  """
+  data = {
+      'predicate': {
+          'builder': {
+              'project': project,
+              'bucket': bucket,
+              'builder': builder
+          }
+      }
+  }
+  if only_completed:
+    data['predicate']['status'] = 'ENDED_MASK'
+  return Request('SearchBuilds', data=data)
diff --git a/tools/perf/core/services/buildbucket_service_test.py b/tools/perf/core/services/buildbucket_service_test.py
new file mode 100644
index 0000000..02946b4
--- /dev/null
+++ b/tools/perf/core/services/buildbucket_service_test.py
@@ -0,0 +1,69 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import unittest
+
+import mock
+
+from core.services import buildbucket_service
+
+
+class TestBuildbucketApi(unittest.TestCase):
+  def setUp(self):
+    self.mock_request = mock.patch('core.services.request.Request').start()
+    self.mock_request.return_value = 'OK'
+
+  def tearDown(self):
+    mock.patch.stopall()
+
+  def testGetBuild(self):
+    self.assertEqual(buildbucket_service.GetBuild(
+        'chromium', 'try', 'linux_chromium_rel_ng', 227278), 'OK')
+    self.mock_request.assert_called_once_with(
+        buildbucket_service.SERVICE_URL + 'GetBuild', method='POST',
+        use_auth=True, content_type='json', accept='json',
+        data={
+            'builder': {
+                'project': 'chromium',
+                'bucket': 'try',
+                'builder': 'linux_chromium_rel_ng',
+            },
+            'buildNumber': 227278
+        })
+
+  def testGetBuilds(self):
+    self.assertEqual(buildbucket_service.GetBuilds(
+        'chromium', 'try', 'linux_chromium_rel_ng'), 'OK')
+    self.mock_request.assert_called_once_with(
+        buildbucket_service.SERVICE_URL + 'SearchBuilds', method='POST',
+        use_auth=True, content_type='json', accept='json',
+        data={
+            'predicate': {
+                'builder': {
+                    'project': 'chromium',
+                    'bucket': 'try',
+                    'builder': 'linux_chromium_rel_ng',
+                },
+                'status': 'ENDED_MASK'
+            }
+        })
+
+  def testGetBuildsIncludeUnfinished(self):
+    self.assertEqual(
+        buildbucket_service.GetBuilds(
+            'chromium', 'try', 'linux_chromium_rel_ng',
+            only_completed=False),
+        'OK')
+    self.mock_request.assert_called_once_with(
+        buildbucket_service.SERVICE_URL + 'SearchBuilds', method='POST',
+        use_auth=True, content_type='json', accept='json',
+        data={
+            'predicate': {
+                'builder': {
+                    'project': 'chromium',
+                    'bucket': 'try',
+                    'builder': 'linux_chromium_rel_ng',
+                }
+            }
+        })
diff --git a/tools/perf/core/services/dashboard_service.py b/tools/perf/core/services/dashboard_service.py
new file mode 100644
index 0000000..0bd39a6
--- /dev/null
+++ b/tools/perf/core/services/dashboard_service.py
@@ -0,0 +1,140 @@
+# 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.
+
+"""Make requests to the Chrome Perf Dashboard API.
+
+For more details on the API see:
+https://chromium.googlesource.com/catapult.git/+/HEAD/dashboard/dashboard/api/README.md
+"""
+
+import urllib
+
+from core.services import request
+
+SERVICE_URL = 'https://chromeperf.appspot.com/api'
+
+
+def Request(endpoint, **kwargs):
+  """Send a request to some dashboard service endpoint."""
+  kwargs.setdefault('use_auth', True)
+  kwargs.setdefault('method', 'POST')
+  kwargs.setdefault('accept', 'json')
+  return request.Request(SERVICE_URL + endpoint, **kwargs)
+
+
+def Describe(test_suite):
+  """Obtain information about a given test_suite.
+
+  Args:
+    test_suite: A string with the name of the test suite.
+
+  Returns:
+    A dict with information about: bots, caseTags, cases, and measurements.
+  """
+  return Request('/describe', params={'test_suite': test_suite})
+
+
+def Timeseries2(**kwargs):
+  """Get timeseries data for a particular test path.
+
+  Args:
+    test_suite: A string with the test suite or benchmark name.
+    measurement: A string with the metric name, e.g. timeToFirstContentfulPaint.
+    bot: A string with the bot name, usually of the form 'master:builder'.
+    columns: A string with a comma separated list of column names to retrieve;
+      may contain: revision, avg, std, count, max, min, sum, revisions,
+      timestamp, alert, histogram, diagnostics.
+    test_case: An optional string with the name of a test case or story.
+    **kwargs: For other options and full details see the API docs.
+
+  Returns:
+    A dict with timeseries data, alerts, Histograms, and SparseDiagnostics.
+
+  Raises:
+    TypeError if any required arguments are missing.
+    KeyError if the timeseries is not found.
+  """
+  for col in ('test_suite', 'measurement', 'bot', 'columns'):
+    if col not in kwargs:
+      raise TypeError('Missing required argument: %s' % col)
+  try:
+    return Request('/timeseries2', params=kwargs)
+  except request.ClientError as exc:
+    if exc.response.status == 404:
+      raise KeyError('Timeseries not found')
+    raise  # Re-raise the original exception.
+
+
+def Timeseries(test_path, days=30):
+  """Get timeseries for the given test path.
+
+  TODO(crbug.com/907121): Remove when no longer needed.
+
+  Args:
+    test_path: test path to get timeseries for.
+    days: Number of days to get data points for.
+
+  Returns:
+    A dict with timeseries data for the given test_path
+
+  Raises:
+    KeyError if the test_path is not found.
+  """
+  try:
+    return Request(
+        '/timeseries/%s' % urllib.quote(test_path), params={'num_days': days})
+  except request.ClientError as exc:
+    if 'Invalid test_path' in exc.json['error']:
+      raise KeyError(test_path)
+    else:
+      raise
+
+
+def ListTestPaths(test_suite, sheriff):
+  """Lists test paths for the given test_suite.
+
+  TODO(crbug.com/907121): Remove when no longer needed.
+
+  Args:
+    test_suite: String with test suite to get paths for.
+    sheriff: Include only test paths monitored by the given sheriff rotation,
+        use 'all' to return all test paths regardless of rotation.
+
+  Returns:
+    A list of test paths. Ex. ['TestPath1', 'TestPath2']
+  """
+  return Request(
+      '/list_timeseries/%s' % test_suite, params={'sheriff': sheriff})
+
+
+def Bugs(bug_id):
+  """Get all the information about a given bug id."""
+  return Request('/bugs/%d' % bug_id)
+
+
+def IterAlerts(**kwargs):
+  """Returns alerts matching the supplied query parameters.
+
+  The response for the dashboard may be returned in multiple chunks, this
+  function will take care of following `next_cursor`s in responses and
+  iterate over all the chunks.
+
+  Args:
+    test_suite: Match alerts on a given test suite (benchmark).
+    sheriff: Match only alerts of a given sheriff rotation.
+    min_timestamp, max_timestamp: Match only alerts on a given time range.
+    limit: Max number of responses per chunk (defaults to 1000).
+    **kwargs: See API docs for other possible query params.
+
+  Yields:
+    Data for all the matching alerts in chunks.
+  """
+  kwargs.setdefault('limit', 1000)
+  while True:
+    response = Request('/alerts', params=kwargs)
+    yield response
+    if 'next_cursor' in response:
+      kwargs['cursor'] = response['next_cursor']
+    else:
+      return
diff --git a/tools/perf/core/services/dashboard_service_test.py b/tools/perf/core/services/dashboard_service_test.py
new file mode 100644
index 0000000..208ba44
--- /dev/null
+++ b/tools/perf/core/services/dashboard_service_test.py
@@ -0,0 +1,115 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import unittest
+
+import httplib2
+import mock
+
+from core.services import dashboard_service
+from core.services import request
+
+
+def TestResponse(code, content):
+  def Request(url, *args, **kwargs):
+    del args  # Unused.
+    del kwargs  # Unused.
+    response = httplib2.Response({'status': str(code)})
+    if code != 200:
+      raise request.BuildRequestError(url, response, content)
+    else:
+      return content
+  return Request
+
+
+class TestDashboardApi(unittest.TestCase):
+  def setUp(self):
+    self.mock_request = mock.patch('core.services.request.Request').start()
+    self.mock_request.side_effect = TestResponse(200, 'OK')
+
+  def tearDown(self):
+    mock.patch.stopall()
+
+  def testDescribe(self):
+    self.assertEqual(dashboard_service.Describe('my_test'), 'OK')
+    self.mock_request.assert_called_once_with(
+        dashboard_service.SERVICE_URL + '/describe', method='POST',
+        params={'test_suite': 'my_test'}, use_auth=True, accept='json')
+
+  def testListTestPaths(self):
+    self.assertEqual(
+        dashboard_service.ListTestPaths('my_test', 'a_rotation'), 'OK')
+    self.mock_request.assert_called_once_with(
+        dashboard_service.SERVICE_URL + '/list_timeseries/my_test',
+        method='POST', params={'sheriff': 'a_rotation'}, use_auth=True,
+        accept='json')
+
+  def testTimeseries2(self):
+    response = dashboard_service.Timeseries2(
+        test_suite='loading.mobile',
+        measurement='timeToFirstContenrfulPaint',
+        bot='ChromiumPerf:androd-go-perf',
+        columns='revision,avg')
+    self.assertEqual(response, 'OK')
+    self.mock_request.assert_called_once_with(
+        dashboard_service.SERVICE_URL + '/timeseries2',
+        params={'test_suite': 'loading.mobile',
+                'measurement': 'timeToFirstContenrfulPaint',
+                'bot': 'ChromiumPerf:androd-go-perf',
+                'columns': 'revision,avg'},
+        method='POST', use_auth=True, accept='json')
+
+  def testTimeseries2_notFoundRaisesKeyError(self):
+    self.mock_request.side_effect = TestResponse(404, 'Not found')
+    with self.assertRaises(KeyError):
+      dashboard_service.Timeseries2(
+          test_suite='loading.mobile',
+          measurement='timeToFirstContenrfulPaint',
+          bot='ChromiumPerf:androd-go-perf',
+          columns='revision,avg')
+
+  def testTimeseries2_missingArgsRaisesTypeError(self):
+    with self.assertRaises(TypeError):
+      dashboard_service.Timeseries2(
+          test_suite='loading.mobile',
+          measurement='timeToFirstContenrfulPaint')
+
+  def testTimeseries(self):
+    response = dashboard_service.Timeseries('some test path')
+    self.assertEqual(response, 'OK')
+    self.mock_request.assert_called_once_with(
+        dashboard_service.SERVICE_URL + '/timeseries/some%20test%20path',
+        params={'num_days': 30}, method='POST', use_auth=True, accept='json')
+
+  def testTimeseries_notFoundRaisesKeyError(self):
+    self.mock_request.side_effect = TestResponse(
+        400, '{"error": "Invalid test_path"}')
+    with self.assertRaises(KeyError):
+      dashboard_service.Timeseries('some test path')
+
+  def testBugs(self):
+    self.assertEqual(dashboard_service.Bugs(123), 'OK')
+    self.mock_request.assert_called_once_with(
+        dashboard_service.SERVICE_URL + '/bugs/123', method='POST',
+        use_auth=True, accept='json')
+
+  def testIterAlerts(self):
+    pages = {'page1': {'data': 'foo', 'next_cursor': 'page2'},
+             'page2': {'data': 'bar'}}
+
+    def RequestStub(endpoint, method=None, params=None, **kwargs):
+      del kwargs  # Unused.
+      self.assertEqual(endpoint, dashboard_service.SERVICE_URL + '/alerts')
+      self.assertEqual(method, 'POST')
+      self.assertDictContainsSubset(
+          {'test_suite': 'loading.mobile', 'limit': 1000}, params)
+      cursor = params.get('cursor', 'page1')
+      return pages[cursor]
+
+    self.mock_request.side_effect = RequestStub
+    response = [
+        resp['data']
+        for resp in dashboard_service.IterAlerts(test_suite='loading.mobile')]
+    self.assertEqual(response, ['foo', 'bar'])
+    self.assertEqual(self.mock_request.call_count, 2)
diff --git a/tools/perf/core/services/isolate_service.py b/tools/perf/core/services/isolate_service.py
new file mode 100644
index 0000000..28a44ad
--- /dev/null
+++ b/tools/perf/core/services/isolate_service.py
@@ -0,0 +1,67 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import base64
+import json
+import os
+import zlib
+
+from core.services import request
+
+
+SERVICE_URL = 'https://chrome-isolated.appspot.com/_ah/api/isolateservice/v1'
+CACHE_DIR = os.path.abspath(os.path.join(
+    os.path.dirname(__file__), '..', '..', '_cached_data', 'isolates'))
+
+
+def Request(endpoint, **kwargs):
+  """Send a request to some isolate service endpoint."""
+  kwargs.setdefault('use_auth', True)
+  kwargs.setdefault('accept', 'json')
+  return request.Request(SERVICE_URL + endpoint, **kwargs)
+
+
+def Retrieve(digest):
+  """Retrieve the content stored at some isolate digest."""
+  return zlib.decompress(RetrieveCompressed(digest))
+
+
+def RetrieveFile(digest, filename):
+  """Retrieve a particular filename from an isolate container."""
+  container = json.loads(Retrieve(digest))
+  return Retrieve(container['files'][filename]['h'])
+
+
+def RetrieveCompressed(digest):
+  """Retrieve the compressed content stored at some isolate digest.
+
+  Responses are cached locally to speed up retrieving content multiple times
+  for the same digest.
+  """
+  cache_file = os.path.join(CACHE_DIR, digest)
+  if os.path.exists(cache_file):
+    with open(cache_file, 'rb') as f:
+      return f.read()
+  else:
+    if not os.path.isdir(CACHE_DIR):
+      os.makedirs(CACHE_DIR)
+    content = _RetrieveCompressed(digest)
+    with open(cache_file, 'wb') as f:
+      f.write(content)
+    return content
+
+
+def _RetrieveCompressed(digest):
+  """Retrieve the compressed content stored at some isolate digest."""
+  data = Request(
+      '/retrieve', method='POST', content_type='json',
+      data={'namespace': {'namespace': 'default-gzip'}, 'digest': digest})
+
+  if 'url' in data:
+    return request.Request(data['url'])
+  if 'content' in data:
+    return base64.b64decode(data['content'])
+  else:
+    raise NotImplementedError(
+        'Isolate %s in unknown format %s' % (digest, json.dumps(data)))
diff --git a/tools/perf/core/services/isolate_service_test.py b/tools/perf/core/services/isolate_service_test.py
new file mode 100644
index 0000000..150845b
--- /dev/null
+++ b/tools/perf/core/services/isolate_service_test.py
@@ -0,0 +1,77 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import base64
+import json
+import os
+import shutil
+import tempfile
+import unittest
+import zlib
+
+import mock
+
+from core.services import isolate_service
+
+
+def ContentResponse(content):
+  return [{'content': base64.b64encode(zlib.compress(content))}]
+
+
+def UrlResponse(url, content):
+  return [{'url': url}, zlib.compress(content)]
+
+
+class TestIsolateApi(unittest.TestCase):
+  def setUp(self):
+    self.temp_dir = tempfile.mkdtemp()
+    mock.patch('core.services.isolate_service.CACHE_DIR', os.path.join(
+        self.temp_dir, 'isolate_cache')).start()
+    self.mock_request = mock.patch('core.services.request.Request').start()
+
+  def tearDown(self):
+    shutil.rmtree(self.temp_dir)
+    mock.patch.stopall()
+
+  def testRetrieve_content(self):
+    self.mock_request.side_effect = ContentResponse('OK!')
+    self.assertEqual(isolate_service.Retrieve('hash'), 'OK!')
+
+  def testRetrieve_fromUrl(self):
+    self.mock_request.side_effect = UrlResponse('http://get/response', 'OK!')
+    self.assertEqual(isolate_service.Retrieve('hash'), 'OK!')
+
+  def testRetrieveCompressed_content(self):
+    self.mock_request.side_effect = ContentResponse('OK!')
+    self.assertEqual(
+        isolate_service.RetrieveCompressed('hash'), zlib.compress('OK!'))
+
+  def testRetrieveCompressed_fromUrl(self):
+    self.mock_request.side_effect = UrlResponse('http://get/response', 'OK!')
+    self.assertEqual(
+        isolate_service.RetrieveCompressed('hash'), zlib.compress('OK!'))
+
+  def testRetrieveCompressed_usesCache(self):
+    self.mock_request.side_effect = ContentResponse('OK!')
+    self.assertEqual(
+        isolate_service.RetrieveCompressed('hash'), zlib.compress('OK!'))
+    self.assertEqual(
+        isolate_service.RetrieveCompressed('hash'), zlib.compress('OK!'))
+    # We retrieve the same hash twice, but the request is only made once.
+    self.assertEqual(self.mock_request.call_count, 1)
+
+  def testRetrieveFile_succeeds(self):
+    self.mock_request.side_effect = (
+        ContentResponse(json.dumps({'files': {'foo': {'h': 'hash2'}}})) +
+        UrlResponse('http://get/file/contents', 'nice!'))
+
+    self.assertEqual(isolate_service.RetrieveFile('hash1', 'foo'), 'nice!')
+
+  def testRetrieveFile_fails(self):
+    self.mock_request.side_effect = (
+        ContentResponse(json.dumps({'files': {'foo': {'h': 'hash2'}}})) +
+        UrlResponse('http://get/file/contents', 'nice!'))
+
+    with self.assertRaises(KeyError):
+      isolate_service.RetrieveFile('hash1', 'bar')  # File not in isolate.
diff --git a/tools/perf/core/services/luci_auth.py b/tools/perf/core/services/luci_auth.py
new file mode 100644
index 0000000..0fe4c68
--- /dev/null
+++ b/tools/perf/core/services/luci_auth.py
@@ -0,0 +1,48 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import re
+import subprocess
+import sys
+
+
+_RE_INFO_USER_EMAIL = r'Logged in as (?P<email>\S+)\.$'
+
+
+class AuthorizationError(Exception):
+  pass
+
+
+def _RunCommand(command):
+  try:
+    return subprocess.check_output(
+        ['luci-auth', command], stderr=subprocess.STDOUT,
+        universal_newlines=True)
+  except subprocess.CalledProcessError as exc:
+    raise AuthorizationError(exc.output.strip())
+
+
+def CheckLoggedIn():
+  """Check that the user is currently logged in.
+
+  Otherwise sys.exit immediately with the error message from luci-auth
+  instructing the user how to log in.
+  """
+  try:
+    GetAccessToken()
+  except AuthorizationError as exc:
+    sys.exit(exc.message)
+
+
+def GetAccessToken():
+  """Get an access token to make requests on behalf of the logged in user."""
+  return _RunCommand('token').rstrip()
+
+
+def GetUserEmail():
+  """Get the email address of the currently logged in user."""
+  output = _RunCommand('info')
+  m = re.match(_RE_INFO_USER_EMAIL, output, re.MULTILINE)
+  assert m, 'Failed to parse luci-auth info output.'
+  return m.group('email')
diff --git a/tools/perf/core/services/luci_auth_test.py b/tools/perf/core/services/luci_auth_test.py
new file mode 100644
index 0000000..8bd54fe
--- /dev/null
+++ b/tools/perf/core/services/luci_auth_test.py
@@ -0,0 +1,59 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import subprocess
+import unittest
+
+import mock
+
+from core.services import luci_auth
+
+
+class TestLuciAuth(unittest.TestCase):
+  def setUp(self):
+    self.check_output = mock.patch('subprocess.check_output').start()
+
+  def _MockSubprocessOutput(self, output, return_code=0):
+    if not return_code:
+      self.check_output.return_value = output
+    else:
+      def SideEffect(cmd, *args, **kwargs):
+        del args  # Unused.
+        del kwargs  # Unused.
+        raise subprocess.CalledProcessError(return_code, cmd, output=output)
+      self.check_output.side_effect = SideEffect
+
+  def tearDown(self):
+    mock.patch.stopall()
+
+  @mock.patch('sys.exit')
+  def testCheckLoggedIn_success(self, sys_exit):
+    self._MockSubprocessOutput('access-token')
+    self.check_output.return_value = 'access-token'
+    luci_auth.CheckLoggedIn()
+    self.assertFalse(sys_exit.mock_calls)
+
+  @mock.patch('sys.exit')
+  def testCheckLoggedIn_failure(self, sys_exit):
+    self._MockSubprocessOutput('Not logged in.', return_code=1)
+    luci_auth.CheckLoggedIn()
+    sys_exit.assert_called_once_with('Not logged in.')
+
+  def testGetAccessToken_success(self):
+    self._MockSubprocessOutput('access-token')
+    self.assertEqual(luci_auth.GetAccessToken(), 'access-token')
+
+  def testGetAccessToken_failure(self):
+    self._MockSubprocessOutput('Not logged in.', return_code=1)
+    with self.assertRaises(luci_auth.AuthorizationError):
+      luci_auth.GetAccessToken()
+
+  def testGetUserEmail(self):
+    self._MockSubprocessOutput(
+        'Logged in as someone@example.com.\n'
+        'OAuth token details:\n'
+        '  Client ID: abcd1234foo.bar.example.com\n'
+        '  Scopes:\n'
+        '    https://www.example.com/auth/userinfo.email\n')
+    self.assertEqual(luci_auth.GetUserEmail(), 'someone@example.com')
diff --git a/tools/perf/core/services/pinpoint_service.py b/tools/perf/core/services/pinpoint_service.py
new file mode 100644
index 0000000..c418384
--- /dev/null
+++ b/tools/perf/core/services/pinpoint_service.py
@@ -0,0 +1,38 @@
+# 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.
+
+from core.services import luci_auth
+from core.services import request
+
+
+SERVICE_URL = 'https://pinpoint-dot-chromeperf.appspot.com/api'
+
+
+def Request(endpoint, **kwargs):
+  """Send a request to some pinpoint endpoint."""
+  kwargs.setdefault('use_auth', True)
+  kwargs.setdefault('accept', 'json')
+  return request.Request(SERVICE_URL + endpoint, **kwargs)
+
+
+def Job(job_id, with_state=False, with_tags=False):
+  """Get job information from its id."""
+  params = []
+  if with_state:
+    params.append(('o', 'STATE'))
+  if with_tags:
+    params.append(('o', 'TAGS'))
+  return Request('/job/%s' % job_id, params=params)
+
+
+def Jobs():
+  """List jobs for the authenticated user."""
+  return Request('/jobs')
+
+
+def NewJob(**kwargs):
+  """Create a new pinpoint job."""
+  if 'user' not in kwargs:
+    kwargs['user'] = luci_auth.GetUserEmail()
+  return Request('/new', method='POST', data=kwargs)
diff --git a/tools/perf/core/services/pinpoint_service_test.py b/tools/perf/core/services/pinpoint_service_test.py
new file mode 100644
index 0000000..c5ed68a
--- /dev/null
+++ b/tools/perf/core/services/pinpoint_service_test.py
@@ -0,0 +1,48 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import unittest
+
+import mock
+
+from core.services import pinpoint_service
+
+
+class TestPinpointService(unittest.TestCase):
+  def setUp(self):
+    self.get_user_email = mock.patch(
+        'core.services.luci_auth.GetUserEmail').start()
+    self.get_user_email.return_value = 'user@example.com'
+    self.mock_request = mock.patch('core.services.request.Request').start()
+    self.mock_request.return_value = 'OK'
+
+  def tearDown(self):
+    mock.patch.stopall()
+
+  def testJob(self):
+    self.assertEqual(pinpoint_service.Job('1234'), 'OK')
+    self.mock_request.assert_called_once_with(
+        pinpoint_service.SERVICE_URL + '/job/1234', params=[], use_auth=True,
+        accept='json')
+
+  def testJob_withState(self):
+    self.assertEqual(pinpoint_service.Job('1234', with_state=True), 'OK')
+    self.mock_request.assert_called_once_with(
+        pinpoint_service.SERVICE_URL + '/job/1234', params=[('o', 'STATE')],
+        use_auth=True, accept='json')
+
+  def testJobs(self):
+    self.mock_request.return_value = ['job1', 'job2', 'job3']
+    self.assertEqual(pinpoint_service.Jobs(), ['job1', 'job2', 'job3'])
+    self.mock_request.assert_called_once_with(
+        pinpoint_service.SERVICE_URL + '/jobs', use_auth=True, accept='json')
+
+  def testNewJob(self):
+    self.assertEqual(pinpoint_service.NewJob(
+        name='test_job', configuration='some_config'), 'OK')
+    self.mock_request.assert_called_once_with(
+        pinpoint_service.SERVICE_URL + '/new', method='POST',
+        data={'name': 'test_job', 'configuration': 'some_config',
+              'user': 'user@example.com'},
+        use_auth=True, accept='json')
diff --git a/tools/perf/core/services/request.py b/tools/perf/core/services/request.py
new file mode 100644
index 0000000..fda439b
--- /dev/null
+++ b/tools/perf/core/services/request.py
@@ -0,0 +1,152 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import json
+import logging
+import urllib
+
+import httplib2
+
+from py_utils import retry_util  # pylint: disable=import-error
+
+from core.services import luci_auth
+
+
+# Some services pad JSON responses with a security prefix to prevent against
+# XSSI attacks. If found, the prefix is stripped off before attempting to parse
+# a JSON response.
+# See e.g.: https://gerrit-review.googlesource.com/Documentation/rest-api.html#output
+JSON_SECURITY_PREFIX = ")]}'"
+
+
+class RequestError(OSError):
+  """Exception class for errors while making a request."""
+  def __init__(self, request, response, content):
+    self.request = request
+    self.response = response
+    self.content = content
+    message = u'%s returned HTTP Error %d: %s' % (
+        self.request, self.response.status, self.error_message)
+    # Note: the message is a unicode object, possibly with special characters,
+    # so it needs to be turned into a str as expected by the constructor of
+    # the base class.
+    super(RequestError, self).__init__(message.encode('utf-8'))
+
+  def __reduce__(self):
+    # Method needed to make the exception pickleable [1], otherwise it causes
+    # the multiprocess pool to hang when raised by a worker [2].
+    # [1]: https://stackoverflow.com/a/36342588
+    # [2]: https://github.com/uqfoundation/multiprocess/issues/33
+    return (type(self), (self.request, self.response, self.content))
+
+  @property
+  def json(self):
+    """Attempt to load the content as a json object."""
+    try:
+      return json.loads(self.content)
+    except StandardError:
+      return None
+
+  @property
+  def error_message(self):
+    """Returns a unicode object with the error message found in the content."""
+    try:
+      # Try to find error message within json content.
+      return self.json['error']
+    except StandardError:
+      # Otherwise fall back to entire content itself, converting str to unicode.
+      return self.content.decode('utf-8')
+
+
+class ClientError(RequestError):
+  """Exception for 4xx HTTP client errors."""
+  pass
+
+
+class ServerError(RequestError):
+  """Exception for 5xx HTTP server errors."""
+  pass
+
+
+def BuildRequestError(request, response, content):
+  """Build the correct RequestError depending on the response status."""
+  if response['status'].startswith('4'):
+    error = ClientError
+  elif response['status'].startswith('5'):
+    error = ServerError
+  else:  # Fall back to the base class.
+    error = RequestError
+  return error(request, response, content)
+
+
+@retry_util.RetryOnException(ServerError, retries=3)
+def Request(url, method='GET', params=None, data=None, accept=None,
+            content_type='urlencoded', use_auth=False, retries=None):
+  """Perform an HTTP request of a given resource.
+
+  Args:
+    url: A string with the URL to request.
+    method: A string with the HTTP method to perform, e.g. 'GET' or 'POST'.
+    params: An optional dict or sequence of key, value pairs to be added as
+      a query to the url.
+    data: An optional dict or sequence of key, value pairs to send as payload
+      data in the body of the request.
+    accept: An optional string to specify the expected response format.
+      Currently only 'json' is supported, which attempts to parse the response
+      content as json. If omitted, the default is to return the raw response
+      content as a string.
+    content_type: A string specifying how to encode the payload data,
+      can be either 'urlencoded' (default) or 'json'.
+    use_auth: A boolean indecating whether to send authorized requests, if True
+      luci-auth is used to get an access token for the logged in user.
+    retries: Number of times to retry the request in case of ServerError. Note,
+      the request is _not_ retried if the response is a ClientError.
+
+  Returns:
+    A string with the content of the response when it has a successful status.
+
+  Raises:
+    A ClientError if the response has a 4xx status, or ServerError if the
+    response has a 5xx status.
+  """
+  del retries  # Handled by the decorator.
+
+  if params:
+    url = '%s?%s' % (url, urllib.urlencode(params))
+
+  body = None
+  headers = {}
+
+  if accept == 'json':
+    headers['Accept'] = 'application/json'
+  elif accept is not None:
+    raise NotImplementedError('Invalid accept format: %s' % accept)
+
+  if data is not None:
+    if content_type == 'json':
+      body = json.dumps(data, sort_keys=True, separators=(',', ':'))
+      headers['Content-Type'] = 'application/json'
+    elif content_type == 'urlencoded':
+      body = urllib.urlencode(data)
+      headers['Content-Type'] = 'application/x-www-form-urlencoded'
+    else:
+      raise NotImplementedError('Invalid content type: %s' % content_type)
+  else:
+    headers['Content-Length'] = '0'
+
+  if use_auth:
+    headers['Authorization'] = 'Bearer %s' % luci_auth.GetAccessToken()
+
+  logging.info('Making API request: %s', url)
+  http = httplib2.Http()
+  response, content = http.request(
+      url, method=method, body=body, headers=headers)
+  if response.status != 200:
+    raise BuildRequestError(url, response, content)
+
+  if accept == 'json':
+    if content[:4] == JSON_SECURITY_PREFIX:
+      content = content[4:]  # Strip off security prefix if found.
+    content = json.loads(content)
+  return content
diff --git a/tools/perf/core/services/request_test.py b/tools/perf/core/services/request_test.py
new file mode 100644
index 0000000..d05f778
--- /dev/null
+++ b/tools/perf/core/services/request_test.py
@@ -0,0 +1,126 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import json
+import pickle
+import unittest
+
+import httplib2
+import mock
+
+from core.services import request
+
+
+def Response(code, content):
+  return httplib2.Response({'status': str(code)}), content
+
+
+class TestRequest(unittest.TestCase):
+  def setUp(self):
+    self.http = mock.Mock()
+    mock.patch('httplib2.Http', return_value=self.http).start()
+    mock.patch('time.sleep').start()
+
+  def tearDown(self):
+    mock.patch.stopall()
+
+  def testRequest_simple(self):
+    self.http.request.return_value = Response(200, 'OK!')
+    self.assertEqual(request.Request('http://example.com/'), 'OK!')
+    self.http.request.assert_called_once_with(
+        'http://example.com/', method='GET', body=None, headers=mock.ANY)
+
+  def testRequest_acceptJson(self):
+    self.http.request.return_value = Response(200, '{"code": "ok!"}')
+    self.assertEqual(
+        request.Request('http://example.com/', accept='json'), {'code': 'ok!'})
+    self.http.request.assert_called_once_with(
+        'http://example.com/', method='GET', body=None, headers=mock.ANY)
+
+  def testRequest_acceptJsonWithSecurityPrefix(self):
+    self.http.request.return_value = Response(200, ')]}\'{"code": "ok!"}')
+    self.assertEqual(
+        request.Request('http://example.com/', accept='json'), {'code': 'ok!'})
+    self.http.request.assert_called_once_with(
+        'http://example.com/', method='GET', body=None, headers=mock.ANY)
+
+  def testRequest_postWithParams(self):
+    self.http.request.return_value = Response(200, 'OK!')
+    self.assertEqual(request.Request(
+        'http://example.com/', params={'q': 'foo'}, method='POST'), 'OK!')
+    self.http.request.assert_called_once_with(
+        'http://example.com/?q=foo', method='POST', body=None, headers=mock.ANY)
+
+  def testRequest_postWithData(self):
+    self.http.request.return_value = Response(200, 'OK!')
+    self.assertEqual(request.Request(
+        'http://example.com/', data={'q': 'foo'}, method='POST'), 'OK!')
+    self.http.request.assert_called_once_with(
+        'http://example.com/', method='POST', body='q=foo', headers=mock.ANY)
+
+  def testRequest_postWithJsonData(self):
+    self.http.request.return_value = Response(200, 'OK!')
+    self.assertEqual(request.Request(
+        'http://example.com/', data={'q': 'foo'}, content_type='json',
+        method='POST'), 'OK!')
+    self.http.request.assert_called_once_with(
+        'http://example.com/', method='POST', body='{"q":"foo"}',
+        headers=mock.ANY)
+
+  def testRequest_retryOnServerError(self):
+    self.http.request.side_effect = [
+        Response(500, 'Oops. Something went wrong!'),
+        Response(200, 'All is now OK.')
+    ]
+    self.assertEqual(request.Request('http://example.com/'), 'All is now OK.')
+
+  def testRequest_failOnClientError(self):
+    self.http.request.side_effect = [
+        Response(400, 'Bad request!'),
+        Response(200, 'This is not called.')
+    ]
+    with self.assertRaises(request.ClientError):
+      request.Request('http://example.com/')
+
+  @mock.patch('core.services.luci_auth.GetAccessToken')
+  def testRequest_withLuciAuth(self, get_access_token):
+    get_access_token.return_value = 'access-token'
+    self.http.request.return_value = Response(200, 'OK!')
+    self.assertEqual(
+        request.Request('http://example.com/', use_auth=True), 'OK!')
+    self.http.request.assert_called_once_with(
+        'http://example.com/', method='GET', body=None, headers={
+            'Content-Length': '0',
+            'Authorization': 'Bearer access-token'})
+
+
+class TestRequestErrors(unittest.TestCase):
+  def testClientErrorPickleable(self):
+    error = request.ClientError(
+        'api', *Response(400, 'You made a bad request!'))
+    error = pickle.loads(pickle.dumps(error))
+    self.assertIsInstance(error, request.ClientError)
+    self.assertEqual(error.request, 'api')
+    self.assertEqual(error.response.status, 400)
+    self.assertEqual(error.content, 'You made a bad request!')
+
+  def testServerErrorPickleable(self):
+    error = request.ServerError(
+        'api', *Response(500, 'Oops, I had a problem!'))
+    error = pickle.loads(pickle.dumps(error))
+    self.assertIsInstance(error, request.ServerError)
+    self.assertEqual(error.request, 'api')
+    self.assertEqual(error.response.status, 500)
+    self.assertEqual(error.content, 'Oops, I had a problem!')
+
+  def testJsonErrorMessageToString(self):
+    message = u'Something went wrong. That\u2019s all we know.'
+    error = request.ServerError(
+        '/endpoint', *Response(500, json.dumps({'error': message})))
+    self.assertIn('Something went wrong.', str(error))
+
+  def testErrorMessageToString(self):
+    content = u'Something went wrong. That\u2019s all we know.'.encode('utf-8')
+    error = request.ServerError('/endpoint', *Response(500, content))
+    self.assertIn('Something went wrong.', str(error))
diff --git a/tools/perf/examples/pinpoint_cli/bisect_job.json b/tools/perf/examples/pinpoint_cli/bisect_job.json
new file mode 100644
index 0000000..30ba37e
--- /dev/null
+++ b/tools/perf/examples/pinpoint_cli/bisect_job.json
@@ -0,0 +1,16 @@
+{
+  "name": "Example bisect job",
+  "bug_id": "893896",
+  "target": "performance_test_suite",
+  "configuration": "android-go-perf",
+  "benchmark": "memory.top_10_mobile",
+  "comparison_mode": "performance",
+  "story": "http.m.youtube.com.results.q.science",
+  "chart": "memory:chrome:all_processes:reported_by_os:system_memory:private_footprint_size",
+  "tir_label": "foreground",
+  "trace": "http_m_youtube_com_results_q_science",
+  "statistic": "avg",
+  "repository": "chromium",
+  "start_git_hash": "daa361c6e17be19c30de3896efeb63ca0034e9b8",
+  "end_git_hash": "30588560782b21f7910332d49796948a3b482c19"
+}
diff --git a/tools/perf/examples/pinpoint_cli/try_job.json b/tools/perf/examples/pinpoint_cli/try_job.json
new file mode 100644
index 0000000..a51757ac
--- /dev/null
+++ b/tools/perf/examples/pinpoint_cli/try_job.json
@@ -0,0 +1,11 @@
+{
+  "name": "Try job to test my change",
+  "target": "performance_test_suite",
+  "configuration": "android-go-perf",
+  "benchmark": "system_health.memory_mobile",
+  "extra_test_args": "--story-tag-filter emerging_market",
+  "patch": "https://chromium-review.googlesource.com/c/v8/v8/+/1278496",
+  "repository": "chromium",
+  "start_git_hash": "HEAD",
+  "end_git_hash": "HEAD"
+}
diff --git a/tools/perf/examples/soundwave/startup_timeseries.json b/tools/perf/examples/soundwave/startup_timeseries.json
new file mode 100644
index 0000000..ea99b39
--- /dev/null
+++ b/tools/perf/examples/soundwave/startup_timeseries.json
@@ -0,0 +1,8 @@
+[
+  {
+    "test_suite": "startup.mobile",
+    "measurement": "first_contentful_paint_time",
+    "bot": "ChromiumPerf:android-go-perf",
+    "test_case": "intent_coldish_bbc"
+  }
+]
diff --git a/tools/perf/export_csv b/tools/perf/export_csv
new file mode 100755
index 0000000..bd87d348
--- /dev/null
+++ b/tools/perf/export_csv
@@ -0,0 +1,57 @@
+#!/usr/bin/env vpython
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import argparse
+import contextlib
+import csv
+import os
+import sqlite3
+import sys
+
+
+DEFAULT_DATABASE_PATH = os.path.abspath(os.path.join(
+    os.path.dirname(__file__), '_cached_data', 'soundwave', 'soundwave.db'))
+
+
+@contextlib.contextmanager
+def OutputStream(filename):
+  if filename is None or filename == '-':
+    yield sys.stdout
+  else:
+    with open(filename, 'w') as f:
+      yield f
+
+
+def EncodeUnicode(v):
+  return v.encode('utf-8') if isinstance(v, unicode) else v
+
+
+def main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument(
+      'table', help='Name of a table to export')
+  parser.add_argument(
+      '--database-file', default=DEFAULT_DATABASE_PATH,
+      help='File path for database where to store data.')
+  parser.add_argument(
+      '--output', '-o',
+      help='Where to write the csv output, defaults to stdout.')
+  args = parser.parse_args()
+
+  con = sqlite3.connect(args.database_file)
+  try:
+    cur = con.execute('SELECT * FROM %s' % args.table)
+    header = [c[0] for c in cur.description]
+    with OutputStream(args.output) as out:
+      writer = csv.writer(out)
+      writer.writerow(header)
+      for row in cur:
+        writer.writerow([EncodeUnicode(v) for v in row])
+  finally:
+    con.close()
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/tools/perf/pinpoint_cli b/tools/perf/pinpoint_cli
new file mode 100755
index 0000000..6222893
--- /dev/null
+++ b/tools/perf/pinpoint_cli
@@ -0,0 +1,72 @@
+#!/usr/bin/env vpython
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import argparse
+import json
+import os
+import sys
+
+from core import path_util
+path_util.AddPyUtilsToPath()
+path_util.AddTracingToPath()
+
+from core import cli_utils
+from core import external_modules
+from cli_tools.pinpoint_cli import commands
+from core.services import luci_auth
+
+
+def main():
+  parser = argparse.ArgumentParser()
+  parser.add_argument(
+      '-v', '--verbose', action='count', default=0,
+      help='Increase verbosity level')
+  subparsers = parser.add_subparsers(dest='action')
+  subparsers.required = True
+
+  subparser = subparsers.add_parser(
+      'status', help='check the status of some pinpoint jobs')
+  subparser.add_argument(
+      'job_ids', metavar='JOB_ID', nargs='+',
+      help='one or more pinpoint job ids')
+
+  subparser = subparsers.add_parser(
+      'get-csv', help='download the perf results from jobs as a csv file')
+  subparser.add_argument(
+      '--only-differences', action='store_true',
+      help='on bisect jobs, only get data for changes immediately before/after'
+           ' differences in the comparison metric.')
+  subparser.add_argument(
+      '--output', metavar='OUTPUT_CSV', default='job_results.csv',
+      help='path to a file where to store perf results as a csv file'
+           ' (default: %(default)s)')
+  subparser.add_argument(
+      'job_ids', metavar='JOB_ID', nargs='+',
+      help='one or more pinpoint job ids')
+
+  subparser = subparsers.add_parser(
+      'start-job', help='start a new pinpoint job')
+  subparser.add_argument(
+      'config_path', metavar='CONFIG_PATH',
+      help='path to a json file with a pinpoint job configuration (see'
+           " examples/pinpoint_cli directory) or '-' to read it from stdin")
+  args = parser.parse_args()
+  cli_utils.ConfigureLogging(args.verbose)
+
+  luci_auth.CheckLoggedIn()
+  if args.action == 'status':
+    return commands.CheckJobStatus(args.job_ids)
+  elif args.action == 'get-csv':
+    return commands.DownloadJobResultsAsCsv(
+        args.job_ids, args.only_differences, args.output)
+  elif args.action == 'start-job':
+    return commands.StartJobFromConfig(args.config_path)
+  else:
+    raise NotImplementedError(args.action)
+
+
+if __name__ == '__main__':
+  external_modules.RequireModules()
+  sys.exit(main())
diff --git a/tools/perf/soundwave b/tools/perf/soundwave
new file mode 100755
index 0000000..a438e7e6
--- /dev/null
+++ b/tools/perf/soundwave
@@ -0,0 +1,100 @@
+#!/usr/bin/env vpython
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import argparse
+import os
+import sys
+
+from core import path_util
+path_util.AddPyUtilsToPath()
+
+from core import cli_utils
+from core import external_modules
+from core.services import luci_auth
+from cli_tools.soundwave import commands
+from cli_tools.soundwave import studies
+
+
+DEFAULT_DATABASE_PATH = os.path.abspath(os.path.join(
+    os.path.dirname(__file__), '_cached_data', 'soundwave', 'soundwave.db'))
+
+
+def main():
+  parser = argparse.ArgumentParser()
+  # Default args for all actions.
+  parser.add_argument(
+      '-s', '--sheriff', default='Chromium Perf Sheriff',
+      help='Only get data for this sheriff rotation, default: "%(default)s". '
+           'You can use the special value "all" to disable filtering by '
+           'sheriff rotation.')
+  parser.add_argument(
+      '-d', '--days', default=30, type=int,
+      help='Number of days to collect data for (default: %(default)s)')
+  parser.add_argument(
+      '--continue', action='store_true', dest='use_cache',
+      help='Skip refreshing some data for elements already in local db.')
+  parser.add_argument(
+      '--processes', type=int, default=40,
+      help='Number of concurrent processes to use for fetching data.')
+  parser.add_argument(
+      '--database-file', default=DEFAULT_DATABASE_PATH,
+      help='File path for database where to store data.')
+  parser.add_argument(
+      '-v', '--verbose', action='count', default=0,
+      help='Increase verbosity level')
+  subparsers = parser.add_subparsers(dest='action')
+  subparsers.required = True
+  # Subparser args for fetching alerts data.
+  subparser = subparsers.add_parser('alerts')
+  subparser.add_argument(
+      '-b', '--benchmark', required=True,
+      help='Fetch alerts for this benchmark.')
+  # Subparser args for fetching timeseries data.
+  subparser = subparsers.add_parser('timeseries')
+  group = subparser.add_mutually_exclusive_group(required=True)
+  group.add_argument(
+      '-b', '--benchmark', help='Fetch timeseries for this benchmark.')
+  group.add_argument(
+      '--study', choices=studies.NAMES,
+      help='Fetch timeseries needed for a specific study.')
+  group.add_argument(
+      '-i', '--input-file',
+      help='Fetch timeseries listed in this json file (see e.g.'
+           ' examples/soundwave directory).')
+  subparser.add_argument(
+      '-f', '--filters', action='append',
+      help='Only get data for timeseries whose path contains all the given '
+           'substrings.')
+  group = subparser.add_mutually_exclusive_group()
+  group.add_argument(
+      '--output-csv', metavar='PATH',
+      help='Export the timeseries data to a csv file, the PATH given may be '
+           'either a local or a cloud storage (i.e. gs://...) path.')
+  group.add_argument(
+      '--upload-csv', action='store_true',
+      help='Export the timeseries data to the default cloud storage path for '
+           'a given --study.')
+
+  args = parser.parse_args()
+
+  cli_utils.ConfigureLogging(args.verbose)
+  luci_auth.CheckLoggedIn()
+  if args.action == 'alerts':
+    commands.FetchAlertsData(args)
+  elif args.action == 'timeseries':
+    if args.study is not None:
+      args.study = studies.GetStudy(args.study)
+      if args.upload_csv:
+        args.output_csv = args.study.CLOUD_PATH
+    elif args.upload_csv:
+      return 'ERROR: --upload-csv also requires a --study to be specified'
+    commands.FetchTimeseriesData(args)
+  else:
+    raise NotImplementedError(args.action)
+
+
+if __name__ == '__main__':
+  external_modules.RequireModules()
+  sys.exit(main())
diff --git a/tools/perf/update_wpr b/tools/perf/update_wpr
index 54b4886..74d12e1 100755
--- a/tools/perf/update_wpr
+++ b/tools/perf/update_wpr
@@ -5,6 +5,9 @@
 
 import sys
 
+from core import path_util
+path_util.AddPyUtilsToPath()
+
 from cli_tools import update_wpr
 
 
diff --git a/ui/accessibility/ax_enums.mojom b/ui/accessibility/ax_enums.mojom
index 24db5efb..9ca117e6 100644
--- a/ui/accessibility/ax_enums.mojom
+++ b/ui/accessibility/ax_enums.mojom
@@ -558,6 +558,9 @@
 
   kHasPopup,
 
+  // Image annotation status, of type ImageAnnotationStatus.
+  kImageAnnotationStatus,
+
    // Indicates if a form control has invalid input or
    // if an element has an aria-invalid attribute.
   kInvalidState,
@@ -577,9 +580,6 @@
    // Focus traversal in views and Android.
   kPreviousFocusId,
   kNextFocusId,
-
-   // Image annotation status, of type ImageAnnotationStatus.
-  kImageAnnotationStatus,
 };
 
 enum FloatAttribute {
diff --git a/ui/android/java/src/org/chromium/ui/DropdownPopupWindowImpl.java b/ui/android/java/src/org/chromium/ui/DropdownPopupWindowImpl.java
index b65dd316..f756a4a5 100644
--- a/ui/android/java/src/org/chromium/ui/DropdownPopupWindowImpl.java
+++ b/ui/android/java/src/org/chromium/ui/DropdownPopupWindowImpl.java
@@ -148,8 +148,8 @@
         }
         mAnchoredPopupWindow.show();
         mListView.setDividerHeight(0);
-        ApiCompatibilityUtils.setLayoutDirection(
-                mListView, mRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
+        int layoutDirection = mRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR;
+        mListView.setLayoutDirection(layoutDirection);
         if (!wasShowing) {
             mListView.setContentDescription(mDescription);
             mListView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
diff --git a/ui/android/java/src/org/chromium/ui/DropdownPopupWindowJellyBean.java b/ui/android/java/src/org/chromium/ui/DropdownPopupWindowJellyBean.java
index feb2259..cc0cf0a9 100644
--- a/ui/android/java/src/org/chromium/ui/DropdownPopupWindowJellyBean.java
+++ b/ui/android/java/src/org/chromium/ui/DropdownPopupWindowJellyBean.java
@@ -20,7 +20,6 @@
 import android.widget.ListView;
 import android.widget.PopupWindow;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.Log;
 
 import java.lang.reflect.Method;
@@ -128,8 +127,8 @@
         boolean wasShowing = mListPopupWindow.isShowing();
         mListPopupWindow.show();
         mListPopupWindow.getListView().setDividerHeight(0);
-        ApiCompatibilityUtils.setLayoutDirection(mListPopupWindow.getListView(),
-                mRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
+        int layoutDirection = mRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR;
+        mListPopupWindow.getListView().setLayoutDirection(layoutDirection);
         if (!wasShowing) {
             mListPopupWindow.getListView().setContentDescription(mDescription);
             mListPopupWindow.getListView().sendAccessibilityEvent(
diff --git a/ui/android/java/src/org/chromium/ui/base/LocalizationUtils.java b/ui/android/java/src/org/chromium/ui/base/LocalizationUtils.java
index d9fb9ef..f41bf20f 100644
--- a/ui/android/java/src/org/chromium/ui/base/LocalizationUtils.java
+++ b/ui/android/java/src/org/chromium/ui/base/LocalizationUtils.java
@@ -7,7 +7,6 @@
 import android.content.res.Configuration;
 import android.view.View;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.LocaleUtils;
 import org.chromium.base.VisibleForTesting;
@@ -55,8 +54,8 @@
         if (sIsLayoutRtl == null) {
             Configuration configuration =
                     ContextUtils.getApplicationContext().getResources().getConfiguration();
-            sIsLayoutRtl = Boolean.valueOf(ApiCompatibilityUtils.getLayoutDirection(configuration)
-                    == View.LAYOUT_DIRECTION_RTL);
+            sIsLayoutRtl = Boolean.valueOf(
+                    configuration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
         }
 
         return sIsLayoutRtl.booleanValue();
diff --git a/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java b/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
index 2d1df8e6..1c773bb 100644
--- a/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
+++ b/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
@@ -19,7 +19,6 @@
 import android.text.TextUtils;
 import android.webkit.MimeTypeMap;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.ContentUriUtils;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
@@ -468,8 +467,7 @@
         public Uri doInBackground() {
             try {
                 Context context = ContextUtils.getApplicationContext();
-                return ApiCompatibilityUtils.getUriForImageCaptureFile(
-                        getFileForImageCapture(context));
+                return ContentUriUtils.getContentUriFromFile(getFileForImageCapture(context));
             } catch (IOException e) {
                 Log.e(TAG, "Cannot retrieve content uri from file", e);
                 return null;
diff --git a/ui/android/java/src/org/chromium/ui/base/ViewAndroidDelegate.java b/ui/android/java/src/org/chromium/ui/base/ViewAndroidDelegate.java
index 69cc4769..2d7151fb 100644
--- a/ui/android/java/src/org/chromium/ui/base/ViewAndroidDelegate.java
+++ b/ui/android/java/src/org/chromium/ui/base/ViewAndroidDelegate.java
@@ -16,7 +16,6 @@
 import android.widget.FrameLayout.LayoutParams;
 import android.widget.ImageView;
 
-import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.ObserverList;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
@@ -144,7 +143,7 @@
         int heightInt = Math.round(height);
         int startMargin;
 
-        if (ApiCompatibilityUtils.isLayoutRtl(containerView)) {
+        if (containerView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
             startMargin = containerView.getMeasuredWidth() - Math.round(width + x);
         } else {
             startMargin = leftMargin;
diff --git a/ui/android/java/src/org/chromium/ui/widget/ViewRectProvider.java b/ui/android/java/src/org/chromium/ui/widget/ViewRectProvider.java
index 89740dd..d4eff9c 100644
--- a/ui/android/java/src/org/chromium/ui/widget/ViewRectProvider.java
+++ b/ui/android/java/src/org/chromium/ui/widget/ViewRectProvider.java
@@ -9,8 +9,6 @@
 import android.view.View;
 import android.view.ViewTreeObserver;
 
-import org.chromium.base.ApiCompatibilityUtils;
-
 /**
  * Provides a {@Rect} for the location of a {@View} in its window, see
  * {@link View#getLocationOnScreen(int[])}.
@@ -132,7 +130,7 @@
 
         // Account for the padding.
         if (!mIncludePadding) {
-            boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(mView);
+            boolean isRtl = mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
             mRect.left +=
                     isRtl ? ViewCompat.getPaddingEnd(mView) : ViewCompat.getPaddingStart(mView);
             mRect.right -=
diff --git a/ui/aura/mus/client_surface_embedder.cc b/ui/aura/mus/client_surface_embedder.cc
index 9d77ab80..2b1de085 100644
--- a/ui/aura/mus/client_surface_embedder.cc
+++ b/ui/aura/mus/client_surface_embedder.cc
@@ -10,6 +10,21 @@
 #include "ui/gfx/geometry/dip_util.h"
 
 namespace aura {
+namespace {
+
+// Returns the target visibility respecting the window hierarchy. It returns
+// true only when the target visibility of the window and all its ancestors are
+// true. This can check if the window is going to be drawn without the effect
+// of LayerAnimator.
+bool GetTargetVisibility(aura::Window* window) {
+  if (!window)
+    return false;
+  while (window && window->TargetVisibility())
+    window = window->parent();
+  return window == nullptr;
+}
+
+}  // namespace
 
 ClientSurfaceEmbedder::ClientSurfaceEmbedder(Window* window)
     : window_(window),
@@ -20,6 +35,7 @@
   // The frame provided by the parent window->layer() needs to show through
   // the surface layer.
   surface_layer_owner_->layer()->SetFillsBoundsOpaquely(false);
+  surface_layer_owner_->layer()->SetVisible(GetTargetVisibility(window_));
 
   window_->layer()->Add(surface_layer_owner_->layer());
 
@@ -27,9 +43,12 @@
   // this is the case with window decorations provided by Window Manager.
   // This content should appear underneath the content of the embedded client.
   window_->layer()->StackAtTop(surface_layer_owner_->layer());
+  window_->AddObserver(this);
 }
 
-ClientSurfaceEmbedder::~ClientSurfaceEmbedder() = default;
+ClientSurfaceEmbedder::~ClientSurfaceEmbedder() {
+  window_->RemoveObserver(this);
+}
 
 void ClientSurfaceEmbedder::SetSurfaceId(const viz::SurfaceId& surface_id) {
   // Set the background to transparent to avoid a flash of color before the
@@ -48,4 +67,10 @@
   return id ? *id : viz::SurfaceId();
 }
 
+void ClientSurfaceEmbedder::OnWindowVisibilityChanged(Window* window,
+                                                      bool visible) {
+  if (window->Contains(window_))
+    surface_layer_owner_->layer()->SetVisible(GetTargetVisibility(window_));
+}
+
 }  // namespace aura
diff --git a/ui/aura/mus/client_surface_embedder.h b/ui/aura/mus/client_surface_embedder.h
index e3a809d5..331b576 100644
--- a/ui/aura/mus/client_surface_embedder.h
+++ b/ui/aura/mus/client_surface_embedder.h
@@ -9,6 +9,7 @@
 
 #include "base/macros.h"
 #include "ui/aura/aura_export.h"
+#include "ui/aura/window_observer.h"
 
 namespace ui {
 class LayerOwner;
@@ -23,11 +24,12 @@
 class Window;
 
 // Used to display content from another client. The other client is rendering
-// to viz using the SurfaceId supplied to SetSurfaceId().
-class AURA_EXPORT ClientSurfaceEmbedder {
+// to viz using the SurfaceId supplied to SetSurfaceId(). This class can't
+// outlive the window.
+class AURA_EXPORT ClientSurfaceEmbedder : public WindowObserver {
  public:
   explicit ClientSurfaceEmbedder(Window* window);
-  ~ClientSurfaceEmbedder();
+  ~ClientSurfaceEmbedder() override;
 
   // Sets the current SurfaceId *and* updates the size of the layer to match
   // that of the window.
@@ -37,6 +39,9 @@
   viz::SurfaceId GetSurfaceId() const;
 
  private:
+  // aura::WindowObserver:
+  void OnWindowVisibilityChanged(Window* window, bool visible) override;
+
   // The window which embeds the client.
   Window* window_;
 
diff --git a/ui/aura/mus/window_tree_host_mus.h b/ui/aura/mus/window_tree_host_mus.h
index 4d17b48f..0d96a6a 100644
--- a/ui/aura/mus/window_tree_host_mus.h
+++ b/ui/aura/mus/window_tree_host_mus.h
@@ -127,11 +127,14 @@
       const viz::LocalSurfaceIdAllocation& local_surface_id_allocation =
           viz::LocalSurfaceIdAllocation()) override;
 
+  bool in_set_bounds_from_server() const { return in_set_bounds_from_server_; }
+
  private:
   int64_t display_id_;
 
   WindowTreeHostMusDelegate* delegate_;
 
+  // If true, the server initiated the bounds change.
   bool in_set_bounds_from_server_ = false;
 
   std::unique_ptr<InputMethodMus> input_method_;
diff --git a/ui/aura/window_tree_host_platform.cc b/ui/aura/window_tree_host_platform.cc
index ede54de7..c28e70b 100644
--- a/ui/aura/window_tree_host_platform.cc
+++ b/ui/aura/window_tree_host_platform.cc
@@ -238,7 +238,8 @@
       // other external states are updated correctly, instead of just changing
       // |current_cursor_| here.
       cursor_client->SetCursor(ui::CursorType::kNone);
-      DCHECK_EQ(ui::CursorType::kNone, current_cursor_.native_type());
+      DCHECK(cursor_client->IsCursorLocked() ||
+             ui::CursorType::kNone == current_cursor_.native_type());
     }
   }
 }
diff --git a/ui/base/BUILD.gn b/ui/base/BUILD.gn
index 33dc6528..be0c397 100644
--- a/ui/base/BUILD.gn
+++ b/ui/base/BUILD.gn
@@ -11,6 +11,7 @@
 import("//build/util/branding.gni")
 import("//testing/test.gni")
 import("//tools/grit/grit_rule.gni")
+import("//ui/base/mpris/features.gni")
 import("//ui/base/ui_features.gni")
 import("//ui/ozone/ozone.gni")
 
@@ -1050,6 +1051,10 @@
     ]
   }
 
+  if (use_mpris) {
+    deps += [ "//ui/base/mpris:unit_tests" ]
+  }
+
   if (is_chromeos) {
     deps += [
       "//chromeos",
diff --git a/ui/base/ime/BUILD.gn b/ui/base/ime/BUILD.gn
index d2e351d..2dcd725d 100644
--- a/ui/base/ime/BUILD.gn
+++ b/ui/base/ime/BUILD.gn
@@ -197,6 +197,7 @@
   if (is_chromeos) {
     deps += [
       "//chromeos",
+      "//chromeos/ime:gencode",
       "//services/ws/public/cpp/input_devices",
       "//ui/base/ime/chromeos/public/interfaces",
       "//ui/chromeos/strings",
diff --git a/ui/base/ime/chromeos/component_extension_ime_manager.cc b/ui/base/ime/chromeos/component_extension_ime_manager.cc
index 803ac9ec..44d53e8 100644
--- a/ui/base/ime/chromeos/component_extension_ime_manager.cc
+++ b/ui/base/ime/chromeos/component_extension_ime_manager.cc
@@ -12,62 +12,13 @@
 #include "base/stl_util.h"
 #include "base/strings/string_util.h"
 #include "chromeos/constants/chromeos_switches.h"
+#include "chromeos/ime/input_methods.h"
 #include "ui/base/ime/chromeos/extension_ime_util.h"
 
 namespace chromeos {
 
 namespace {
 
-// The whitelist for enabling extension based xkb keyboards at login session.
-const char* kLoginLayoutWhitelist[] = {
-  "be",
-  "br",
-  "ca",
-  "ca(eng)",
-  "ca(multix)",
-  "ch",
-  "ch(fr)",
-  "cz",
-  "cz(qwerty)",
-  "de",
-  "de(neo)",
-  "dk",
-  "ee",
-  "es",
-  "es(cat)",
-  "fi",
-  "fr",
-  "fr(bepo)",
-  "fr(oss)",
-  "gb(dvorak)",
-  "gb(extd)",
-  "hr",
-  "hu",
-  "ie",
-  "is",
-  "it",
-  "jp",
-  "latam",
-  "lt",
-  "lv(apostrophe)",
-  "mt",
-  "no",
-  "pl",
-  "pt",
-  "ro",
-  "se",
-  "si",
-  "tr",
-  "us",
-  "us(altgr-intl)",
-  "us(colemak)",
-  "us(dvorak)",
-  "us(dvp)",
-  "us(intl)",
-  "us(workman)",
-  "us(workman-intl)"
-};
-
 // Gets the input method category according to the given input method id.
 // This is used for sorting a list of input methods.
 int GetInputMethodCategory(const std::string& id) {
@@ -116,8 +67,9 @@
 }
 
 ComponentExtensionIMEManager::ComponentExtensionIMEManager() {
-  for (size_t i = 0; i < base::size(kLoginLayoutWhitelist); ++i) {
-    login_layout_set_.insert(kLoginLayoutWhitelist[i]);
+  for (size_t i = 0; i < base::size(input_method::kInputMethods); ++i) {
+    if (input_method::kInputMethods[i].is_login_keyboard)
+      login_layout_set_.insert(input_method::kInputMethods[i].xkb_layout_id);
   }
 }
 
diff --git a/ui/base/ime/win/tsf_text_store.cc b/ui/base/ime/win/tsf_text_store.cc
index bc855ee..0ebd90b 100644
--- a/ui/base/ime/win/tsf_text_store.cc
+++ b/ui/base/ime/win/tsf_text_store.cc
@@ -219,7 +219,8 @@
 
   status->dwDynamicFlags = 0;
   // We don't support hidden text.
-  status->dwStaticFlags = TS_SS_NOHIDDENTEXT;
+  // TODO(IME): Remove TS_SS_TRANSITORY to support Korean reconversion
+  status->dwStaticFlags = TS_SS_TRANSITORY | TS_SS_NOHIDDENTEXT;
 
   return S_OK;
 }
@@ -1201,7 +1202,6 @@
     const base::string16& new_committed_string = string_buffer_document_.substr(
         new_committed_string_offset, new_committed_string_size);
     text_input_client_->InsertText(new_committed_string);
-    text_input_client_->SetEditableSelectionRange(selection_);
   }
 }
 
diff --git a/ui/base/ime/win/tsf_text_store_unittest.cc b/ui/base/ime/win/tsf_text_store_unittest.cc
index 52bc6b8..4fc9685 100644
--- a/ui/base/ime/win/tsf_text_store_unittest.cc
+++ b/ui/base/ime/win/tsf_text_store_unittest.cc
@@ -387,7 +387,8 @@
   TS_STATUS status = {};
   EXPECT_EQ(S_OK, text_store_->GetStatus(&status));
   EXPECT_EQ(0u, status.dwDynamicFlags);
-  EXPECT_EQ((ULONG)(TS_SS_NOHIDDENTEXT), status.dwStaticFlags);
+  EXPECT_EQ((ULONG)(TS_SS_TRANSITORY | TS_SS_NOHIDDENTEXT),
+            status.dwStaticFlags);
 }
 
 TEST_F(TSFTextStoreTest, QueryInsertTest) {
diff --git a/components/mpris/BUILD.gn b/ui/base/mpris/BUILD.gn
similarity index 94%
rename from components/mpris/BUILD.gn
rename to ui/base/mpris/BUILD.gn
index 0dd4235..5d75a52 100644
--- a/components/mpris/BUILD.gn
+++ b/ui/base/mpris/BUILD.gn
@@ -3,7 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/buildflag_header.gni")
-import("//components/mpris/features.gni")
+import("//ui/base/mpris/features.gni")
 
 buildflag_header("buildflags") {
   header = "buildflags.h"
diff --git a/components/mpris/DEPS b/ui/base/mpris/DEPS
similarity index 100%
rename from components/mpris/DEPS
rename to ui/base/mpris/DEPS
diff --git a/components/mpris/OWNERS b/ui/base/mpris/OWNERS
similarity index 100%
rename from components/mpris/OWNERS
rename to ui/base/mpris/OWNERS
diff --git a/components/mpris/features.gni b/ui/base/mpris/features.gni
similarity index 100%
rename from components/mpris/features.gni
rename to ui/base/mpris/features.gni
diff --git a/components/mpris/mpris_service.cc b/ui/base/mpris/mpris_service.cc
similarity index 98%
rename from components/mpris/mpris_service.cc
rename to ui/base/mpris/mpris_service.cc
index 0e674a5..f04e80e 100644
--- a/components/mpris/mpris_service.cc
+++ b/ui/base/mpris/mpris_service.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/mpris/mpris_service.h"
+#include "ui/base/mpris/mpris_service.h"
 
 #include <memory>
 #include <utility>
@@ -13,13 +13,13 @@
 #include "base/unguessable_token.h"
 #include "base/values.h"
 #include "components/dbus/dbus_thread_linux.h"
-#include "components/mpris/mpris_service_observer.h"
 #include "dbus/bus.h"
 #include "dbus/exported_object.h"
 #include "dbus/message.h"
 #include "dbus/object_path.h"
 #include "dbus/property.h"
 #include "dbus/values_util.h"
+#include "ui/base/mpris/mpris_service_observer.h"
 
 namespace mpris {
 
diff --git a/components/mpris/mpris_service.h b/ui/base/mpris/mpris_service.h
similarity index 96%
rename from components/mpris/mpris_service.h
rename to ui/base/mpris/mpris_service.h
index 149d21c..03f66fe 100644
--- a/components/mpris/mpris_service.h
+++ b/ui/base/mpris/mpris_service.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_MPRIS_MPRIS_SERVICE_H_
-#define COMPONENTS_MPRIS_MPRIS_SERVICE_H_
+#ifndef UI_BASE_MPRIS_MPRIS_SERVICE_H_
+#define UI_BASE_MPRIS_MPRIS_SERVICE_H_
 
 #include <string>
 
@@ -137,4 +137,4 @@
 
 }  // namespace mpris
 
-#endif  // COMPONENTS_MPRIS_MPRIS_SERVICE_H_
+#endif  // UI_BASE_MPRIS_MPRIS_SERVICE_H_
diff --git a/components/mpris/mpris_service_observer.h b/ui/base/mpris/mpris_service_observer.h
similarity index 83%
rename from components/mpris/mpris_service_observer.h
rename to ui/base/mpris/mpris_service_observer.h
index 3a269b9..cbcbed8e0 100644
--- a/components/mpris/mpris_service_observer.h
+++ b/ui/base/mpris/mpris_service_observer.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_MPRIS_MPRIS_SERVICE_OBSERVER_H_
-#define COMPONENTS_MPRIS_MPRIS_SERVICE_OBSERVER_H_
+#ifndef UI_BASE_MPRIS_MPRIS_SERVICE_OBSERVER_H_
+#define UI_BASE_MPRIS_MPRIS_SERVICE_OBSERVER_H_
 
 #include "base/component_export.h"
 #include "base/observer_list_types.h"
@@ -29,4 +29,4 @@
 
 }  // namespace mpris
 
-#endif  // COMPONENTS_MPRIS_MPRIS_SERVICE_OBSERVER_H_
+#endif  // UI_BASE_MPRIS_MPRIS_SERVICE_OBSERVER_H_
diff --git a/components/mpris/mpris_service_unittest.cc b/ui/base/mpris/mpris_service_unittest.cc
similarity index 98%
rename from components/mpris/mpris_service_unittest.cc
rename to ui/base/mpris/mpris_service_unittest.cc
index 28e4464d..9e679d2 100644
--- a/components/mpris/mpris_service_unittest.cc
+++ b/ui/base/mpris/mpris_service_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/mpris/mpris_service.h"
+#include "ui/base/mpris/mpris_service.h"
 
 #include <memory>
 
@@ -11,12 +11,12 @@
 #include "base/run_loop.h"
 #include "base/test/scoped_task_environment.h"
 #include "components/dbus/dbus_thread_linux.h"
-#include "components/mpris/mpris_service_observer.h"
 #include "dbus/message.h"
 #include "dbus/mock_bus.h"
 #include "dbus/mock_exported_object.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/mpris/mpris_service_observer.h"
 
 using ::testing::_;
 using ::testing::Invoke;
diff --git a/ui/chromeos/events/event_rewriter_chromeos.cc b/ui/chromeos/events/event_rewriter_chromeos.cc
index d4fec6da..ea4ba92 100644
--- a/ui/chromeos/events/event_rewriter_chromeos.cc
+++ b/ui/chromeos/events/event_rewriter_chromeos.cc
@@ -65,8 +65,7 @@
   const char* pref_name;
   EventRewriterChromeOS::MutableKeyState result;
 } kModifierRemappings[] = {
-    {// kModifierRemappingCtrl references this entry by index.
-     ui::EF_CONTROL_DOWN,
+    {ui::EF_CONTROL_DOWN,
      ui::chromeos::ModifierKey::kControlKey,
      prefs::kLanguageRemapControlKeyTo,
      {ui::EF_CONTROL_DOWN, ui::DomCode::CONTROL_LEFT, ui::DomKey::CONTROL,
@@ -108,13 +107,8 @@
      ui::chromeos::ModifierKey::kAssistantKey,
      prefs::kLanguageRemapAssistantKeyTo,
      {ui::EF_NONE, ui::DomCode::LAUNCH_ASSISTANT, ui::DomKey::LAUNCH_ASSISTANT,
-      ui::VKEY_ASSISTANT}},
-    {ui::EF_NONE,
-     ui::chromeos::ModifierKey::kNumModifierKeys,
-     prefs::kLanguageRemapDiamondKeyTo,
-     {ui::EF_NONE, ui::DomCode::F15, ui::DomKey::F15, ui::VKEY_F15}}};
+      ui::VKEY_ASSISTANT}}};
 
-const ModifierRemapping* kModifierRemappingCtrl = &kModifierRemappings[0];
 const ModifierRemapping* kModifierRemappingNeoMod3 = &kModifierRemappings[1];
 
 // Gets a remapped key for |pref_name| key. For example, to find out which
@@ -166,11 +160,6 @@
   return GetRemappedKey(pref_name, delegate);
 }
 
-bool HasDiamondKey() {
-  return base::CommandLine::ForCurrentProcess()->HasSwitch(
-      ::chromeos::switches::kHasChromeOSDiamondKey);
-}
-
 bool IsISOLevel5ShiftUsedByCurrentInputMethod() {
   // Since both German Neo2 XKB layout and Caps Lock depend on Mod3Mask,
   // it's not possible to make both features work. For now, we don't remap
@@ -749,22 +738,6 @@
   const ModifierRemapping* remapped_key = nullptr;
   // Remapping based on DomKey.
   switch (incoming.key) {
-    // On Chrome OS, F15 (XF86XK_Launch6) with NumLock (Mod2Mask) is sent
-    // when Diamond key is pressed.
-    case ui::DomKey::F15:
-      // When diamond key is not available, the configuration UI for Diamond
-      // key is not shown. Therefore, ignore the kLanguageRemapDiamondKeyTo
-      // syncable pref.
-      if (HasDiamondKey())
-        remapped_key =
-            GetRemappedKey(prefs::kLanguageRemapDiamondKeyTo, delegate_);
-      // Default behavior of F15 is Control, even if --has-chromeos-diamond-key
-      // is absent, according to unit test comments.
-      if (!remapped_key) {
-        DCHECK_EQ(ui::VKEY_CONTROL, kModifierRemappingCtrl->result.key_code);
-        remapped_key = kModifierRemappingCtrl;
-      }
-      break;
     case ui::DomKey::ALT_GRAPH:
       // The Neo2 codes modifiers such that CapsLock appears as VKEY_ALTGR,
       // but AltGraph (right Alt) also appears as VKEY_ALTGR in Neo2,
diff --git a/ui/chromeos/events/pref_names.cc b/ui/chromeos/events/pref_names.cc
index 54bf8bb..b4f16ea 100644
--- a/ui/chromeos/events/pref_names.cc
+++ b/ui/chromeos/events/pref_names.cc
@@ -27,8 +27,6 @@
     "settings.language.remap_backspace_key_to";
 const char kLanguageRemapAssistantKeyTo[] =
     "settings.language.xkb_remap_assistant_key_to";
-const char kLanguageRemapDiamondKeyTo[] =
-    "settings.language.remap_diamond_key_to";
 const char kLanguageRemapExternalCommandKeyTo[] =
     "settings.language.remap_external_command_key_to";
 const char kLanguageRemapExternalMetaKeyTo[] =
diff --git a/ui/chromeos/events/pref_names.h b/ui/chromeos/events/pref_names.h
index a84e62b..a2183c44 100644
--- a/ui/chromeos/events/pref_names.h
+++ b/ui/chromeos/events/pref_names.h
@@ -17,7 +17,6 @@
 extern const char kLanguageRemapEscapeKeyTo[];
 extern const char kLanguageRemapBackspaceKeyTo[];
 extern const char kLanguageRemapAssistantKeyTo[];
-extern const char kLanguageRemapDiamondKeyTo[];
 extern const char kLanguageRemapExternalCommandKeyTo[];
 extern const char kLanguageRemapExternalMetaKeyTo[];
 
diff --git a/ui/chromeos/resources/default_100_percent/fingerprint/fingerprint_scanner_laptop.png b/ui/chromeos/resources/default_100_percent/fingerprint/fingerprint_scanner_laptop.png
new file mode 100644
index 0000000..ac9fada
--- /dev/null
+++ b/ui/chromeos/resources/default_100_percent/fingerprint/fingerprint_scanner_laptop.png
Binary files differ
diff --git a/ui/chromeos/resources/default_200_percent/fingerprint/fingerprint_scanner_laptop.png b/ui/chromeos/resources/default_200_percent/fingerprint/fingerprint_scanner_laptop.png
new file mode 100644
index 0000000..4bfc30b6
--- /dev/null
+++ b/ui/chromeos/resources/default_200_percent/fingerprint/fingerprint_scanner_laptop.png
Binary files differ
diff --git a/ui/chromeos/resources/ui_chromeos_resources.grd b/ui/chromeos/resources/ui_chromeos_resources.grd
index 0147a127..cb7aa2b 100644
--- a/ui/chromeos/resources/ui_chromeos_resources.grd
+++ b/ui/chromeos/resources/ui_chromeos_resources.grd
@@ -96,7 +96,8 @@
       <!-- Fingerprint images. -->
       <structure type="chrome_scaled_image" name="IDR_LOGIN_FINGERPRINT_ICON_ANIMATION" file="fingerprint/fingerprint.png" />
       <structure type="chrome_scaled_image" name="IDR_LOGIN_FINGERPRINT_ENROLLMENT_COMPLETE_ANIMATION" file="fingerprint/tick.png" />
-      <structure type="chrome_scaled_image" name="IDR_LOGIN_FINGERPRINT_SCANNER_ANIMATION" file="fingerprint/fingerprint_scanner.png" />
+      <structure type="chrome_scaled_image" name="IDR_LOGIN_FINGERPRINT_SCANNER_TABLET_ANIMATION" file="fingerprint/fingerprint_scanner.png" />
+      <structure type="chrome_scaled_image" name="IDR_LOGIN_FINGERPRINT_SCANNER_LAPTOP_ANIMATION" file="fingerprint/fingerprint_scanner_laptop.png" />
 
     </structures>
   </release>
diff --git a/ui/compositor/test/test_utils.cc b/ui/compositor/test/test_utils.cc
index 50f48fd3..77dacd8 100644
--- a/ui/compositor/test/test_utils.cc
+++ b/ui/compositor/test/test_utils.cc
@@ -4,7 +4,10 @@
 
 #include "ui/compositor/test/test_utils.h"
 
+#include "base/run_loop.h"
+#include "base/test/bind_test_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/compositor/compositor.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/transform.h"
 
@@ -36,4 +39,13 @@
   EXPECT_FLOAT_EQ(lhs.height(), rhs.height());
 }
 
+void WaitForNextFrameToBePresented(ui::Compositor* compositor) {
+  base::RunLoop runloop;
+  compositor->RequestPresentationTimeForNextFrame(base::BindLambdaForTesting(
+      [&runloop](const gfx::PresentationFeedback& feedback) {
+        runloop.QuitClosure().Run();
+      }));
+  runloop.Run();
+}
+
 }  // namespace ui
diff --git a/ui/compositor/test/test_utils.h b/ui/compositor/test/test_utils.h
index 9f2163b644..863f39c 100644
--- a/ui/compositor/test/test_utils.h
+++ b/ui/compositor/test/test_utils.h
@@ -12,10 +12,15 @@
 
 namespace ui {
 
+class Compositor;
+
 void CheckApproximatelyEqual(const gfx::Transform& lhs,
                              const gfx::Transform& rhs);
 void CheckApproximatelyEqual(const gfx::Rect& lhs, const gfx::Rect& rhs);
 
+// Runs a RunLoop until the next frame is presented.
+void WaitForNextFrameToBePresented(ui::Compositor* compositor);
+
 }  // namespace ui
 
 #endif  // UI_COMPOSITOR_TEST_TEST_UTILS_H_
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 e1cd989e..3396058 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
@@ -415,6 +415,22 @@
     return false;
   }
 
+  /**
+   * Removes the entry.
+   * @param {!Entry|FilesAppEntry} entry to be removed.
+   * This method is specific to EntryList and VolumeEntry instance.
+   * @return {boolean} if entry was removed.
+   */
+  removeChildEntry(entry) {
+    const childIndex =
+        this.children_.findIndex(childEntry => childEntry === entry);
+    if (childIndex !== -1) {
+      this.children_.splice(childIndex, 1);
+      return true;
+    }
+    return false;
+  }
+
   /** @override */
   getNativeEntry() {
     return null;
@@ -670,6 +686,22 @@
     }
     return false;
   }
+
+  /**
+   * Removes the entry.
+   * @param {!Entry|FilesAppEntry} entry to be removed.
+   * This method is specific to EntryList and VolumeEntry instance.
+   * @return {boolean} if entry was removed.
+   */
+  removeChildEntry(entry) {
+    const childIndex =
+        this.children_.findIndex(childEntry => childEntry === entry);
+    if (childIndex !== -1) {
+      this.children_.splice(childIndex, 1);
+      return true;
+    }
+    return false;
+  }
 }
 
 /**
diff --git a/ui/file_manager/file_manager/common/js/files_app_entry_types_unittest.js b/ui/file_manager/file_manager/common/js/files_app_entry_types_unittest.js
index 2a9e674..d7572b7 100644
--- a/ui/file_manager/file_manager/common/js/files_app_entry_types_unittest.js
+++ b/ui/file_manager/file_manager/common/js/files_app_entry_types_unittest.js
@@ -103,7 +103,7 @@
 
 /**
  * Tests EntryList's methods addEntry, findIndexByVolumeInfo,
- * removeByVolumeType, removeByRootType.
+ * removeByVolumeType, removeByRootType, removeChildEntry.
  */
 function testEntryFindIndex() {
   const entryList =
@@ -140,11 +140,17 @@
   entryList.addEntry(fakeEntry);
   assertTrue(entryList.removeByRootType(VolumeManagerCommon.RootType.CROSTINI));
   assertEquals(1, entryList.getUIChildren().length);
+
+  // Test removeChildEntry.
+  assertTrue(entryList.removeChildEntry(entryList.getUIChildren()[0]));
+  assertEquals(0, entryList.getUIChildren().length);
+  // Nothing left to remove.
+  assertFalse(entryList.removeChildEntry(/** @type {Entry} */ ({})));
 }
 
 /**
  * Tests VolumeEntry's methods findIndexByVolumeInfo, removeByVolumeType,
- * removeByRootType.
+ * removeByRootType, removeChildEntry.
  * @suppress {accessControls} to be able to access private properties.
  */
 function testVolumeEntryFindIndex() {
@@ -187,6 +193,12 @@
   assertTrue(
       volumeEntry.removeByRootType(VolumeManagerCommon.RootType.CROSTINI));
   assertEquals(1, volumeEntry.children_.length);
+
+  // Test removeChildEntry.
+  assertTrue(volumeEntry.removeChildEntry(volumeEntry.getUIChildren()[0]));
+  assertEquals(0, volumeEntry.getUIChildren().length);
+  // Nothing left to remove.
+  assertFalse(volumeEntry.removeChildEntry(/** @type {Entry} */ ({})));
 }
 
 /** Tests method EntryList.getMetadata. */
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 6523c7e..0835190 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
@@ -31,7 +31,7 @@
  * Extracts entry on which command event was dispatched.
  *
  * @param {EventTarget} element Element which is the command event's target.
- * @return {Entry} Entry of the found node.
+ * @return {Entry|FakeEntry} Entry of the found node.
  */
 CommandUtil.getCommandEntry = element => {
   const entries = CommandUtil.getCommandEntries(element);
@@ -272,7 +272,8 @@
 };
 
 /**
- * If entry is fake/invalid/root, we don't show menu items for regular entries.
+ * If entry is fake/invalid/root, we don't show menu items intended for regular
+ * entries.
  * @param {!VolumeManager} volumeManager
  * @param {(!Entry|!FakeEntry)} entry Entry or a fake entry.
  * @return {boolean} True if we should show the menu items for regular entries.
@@ -293,15 +294,8 @@
     return false;
   }
 
-  // If the file is /Downloads within My files, hide context menu entries.
-  if (util.isMyFilesVolumeEnabled() &&
-      volumeInfo.volumeType === VolumeManagerCommon.RootType.DOWNLOADS &&
-      entry.fullPath === '/Downloads') {
-    return false;
-  }
-
-  // If the entry is root entry of its volume (but not a team drive root), hide
-  // context menu entries.
+  // If the entry is root entry of its volume (but not a team drive root),
+  // hide context menu entries.
   if (CommandUtil.isRootEntry(volumeManager, entry) &&
       !util.isTeamDriveRoot(entry)) {
     return false;
@@ -315,6 +309,38 @@
 };
 
 /**
+ * If entry is MyFiles/Downloads, we don't allow cut/delete/rename.
+ * @param {!VolumeManager} volumeManager
+ * @param {(Entry|FakeEntry)} entry Entry or a fake entry.
+ * @return {boolean}
+ */
+CommandUtil.isDownloads = (volumeManager, entry) => {
+  if (!entry) {
+    return false;
+  }
+  if (util.isFakeEntry(entry)) {
+    return false;
+  }
+
+  // If the entry is not a valid entry.
+  if (!volumeManager) {
+    return false;
+  }
+
+  const volumeInfo = volumeManager.getVolumeInfo(entry);
+  if (!volumeInfo) {
+    return false;
+  }
+
+  if (util.isMyFilesVolumeEnabled() &&
+      volumeInfo.volumeType === VolumeManagerCommon.RootType.DOWNLOADS &&
+      entry.fullPath === '/Downloads') {
+    return true;
+  }
+  return false;
+};
+
+/**
  * Returns whether all of the given entries have the given capability.
  *
  * @param {!Array<Entry>} entries List of entries to check capabilities for.
@@ -1033,6 +1059,8 @@
     },
 
     /**
+     * Returns True if any entry belongs to a read-only volume or is
+     * MyFiles>Downloads.
      * @param {!Array<!Entry>} entries
      * @param {!CommandHandlerDeps} fileManager
      * @return {boolean} True if entries contain read only entry.
@@ -1040,7 +1068,8 @@
     containsReadOnlyEntry_: function(entries, fileManager) {
       return entries.some(entry => {
         const locationInfo = fileManager.volumeManager.getLocationInfo(entry);
-        return locationInfo && locationInfo.isReadOnly;
+        return (locationInfo && locationInfo.isReadOnly) ||
+            CommandUtil.isDownloads(fileManager.volumeManager, entry);
       });
     }
   };
@@ -1194,10 +1223,13 @@
    * @param {!CommandHandlerDeps} fileManager CommandHandlerDeps to use.
    */
   execute: function(event, fileManager) {
+    const entry = CommandUtil.getCommandEntry(event.target);
+    if (CommandUtil.isDownloads(fileManager.volumeManager, entry)) {
+      return;
+    }
     if (event.target instanceof DirectoryTree ||
         event.target instanceof DirectoryItem) {
       let isRemovableRoot = false;
-      const entry = CommandUtil.getCommandEntry(event.target);
       let volumeInfo = null;
       if (entry) {
         volumeInfo = fileManager.volumeManager.getVolumeInfo(entry);
@@ -1265,7 +1297,9 @@
     const entries = CommandUtil.getCommandEntries(renameTarget);
     if (entries.length === 0 ||
         !CommandUtil.shouldShowMenuItemsForEntry(
-            fileManager.volumeManager, entries[0])) {
+            fileManager.volumeManager, entries[0]) ||
+        entries.some(
+            CommandUtil.isDownloads.bind(null, fileManager.volumeManager))) {
       event.canExecute = false;
       event.command.setHidden(true);
       return;
diff --git a/ui/file_manager/file_manager/foreground/js/file_transfer_controller.js b/ui/file_manager/file_manager/foreground/js/file_transfer_controller.js
index 45631a9..092350c 100644
--- a/ui/file_manager/file_manager/foreground/js/file_transfer_controller.js
+++ b/ui/file_manager/file_manager/foreground/js/file_transfer_controller.js
@@ -1427,19 +1427,25 @@
     if (!selectedItem) {
       return false;
     }
+    const entry = selectedItem.entry;
 
-    if (!this.shouldShowCommandFor_(selectedItem.entry)) {
+    if (!this.shouldShowCommandFor_(entry)) {
       command.setHidden(true);
       return false;
     }
 
+    // For MyFiles/Downloads we only allow copy.
+    if (isMove && this.isDownloads_(entry)) {
+      return false;
+    }
+
     // Cut is unavailable on Team Drive roots.
-    if (util.isTeamDriveRoot(selectedItem.entry)) {
+    if (util.isTeamDriveRoot(entry)) {
       return false;
     }
 
     const metadata = this.metadataModel_.getCache(
-        [selectedItem.entry], ['canCopy', 'canDelete']);
+        [entry], ['canCopy', 'canDelete']);
     assert(metadata.length === 1);
 
     if (!isMove) {
@@ -1447,7 +1453,7 @@
     }
 
     // We need to check source volume is writable for move operation.
-    const volumeInfo = this.volumeManager_.getVolumeInfo(selectedItem.entry);
+    const volumeInfo = this.volumeManager_.getVolumeInfo(entry);
     return !volumeInfo.isReadOnly && metadata[0].canCopy !== false &&
         metadata[0].canDelete !== false;
   }
@@ -1461,6 +1467,12 @@
     return false;
   }
 
+  // For MyFiles/Downloads we only allow copy.
+  if (isMove &&
+      this.selectionHandler_.selection.entries.some(this.isDownloads_, this)) {
+    return false;
+  }
+
   return isMove ? this.canCutOrDrag_() : this.canCopyOrDrag_() ;
 };
 
@@ -1820,3 +1832,26 @@
     }
   }, 100);
 };
+
+/**
+ * Returns True if entry is MyFiles>Downloads.
+ * @param {(!Entry|!FakeEntry)} entry Entry or a fake entry.
+ * @return {boolean}
+ */
+FileTransferController.prototype.isDownloads_ = function(entry) {
+  if (util.isFakeEntry(entry)) {
+    return false;
+  }
+
+  const volumeInfo = this.volumeManager_.getVolumeInfo(entry);
+  if (!volumeInfo) {
+    return false;
+  }
+
+  if (util.isMyFilesVolumeEnabled() &&
+      volumeInfo.volumeType === VolumeManagerCommon.RootType.DOWNLOADS &&
+      entry.fullPath === '/Downloads') {
+    return true;
+  }
+  return false;
+};
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 1a4868a..2de9579 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
@@ -659,29 +659,42 @@
 
       // Multiple partitions found.
       let removableModel;
+      let removableEntry;
       if (this.removableModels_.has(devicePath)) {
         // Removable model has been seen before. Use the same reference.
         removableModel = this.removableModels_.get(devicePath);
+        removableEntry = removableModel.entry;
       } 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(
+        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));
-          }
+      }
+
+      // Remove partitions that aren't available anymore.
+      const existingVolumeIds =
+          new Set(removableGroup.map(p => p.volumeInfo.volumeId));
+      for (const partition of removableEntry.getUIChildren()) {
+        if (!existingVolumeIds.has(partition.volumeInfo.volumeId)) {
+          removableEntry.removeChildEntry(partition);
         }
       }
+
+      // 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);
     }
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 92d08b6..7cdc5ff 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
@@ -478,3 +478,51 @@
       }),
       callback);
 }
+
+/**
+ * Tests that adding a new partition to the same grouped USB will add the
+ * partition to the grouping.
+ */
+function testMultipleUsbPartitionsGrouping() {
+  const shortcutListModel = new MockFolderShortcutDataModel([]);
+  const recentItem = null;
+  const volumeManager = new MockVolumeManager();
+
+  // Use same device path so the partitions are grouped.
+  volumeManager.volumeInfoList.add(MockVolumeManager.createMockVolumeInfo(
+      VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:partition1',
+      'partition1', 'device/path/1'));
+  volumeManager.volumeInfoList.add(MockVolumeManager.createMockVolumeInfo(
+      VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:partition2',
+      'partition2', 'device/path/1'));
+  volumeManager.volumeInfoList.add(MockVolumeManager.createMockVolumeInfo(
+      VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:partition3',
+      'partition3', 'device/path/1'));
+
+  const model = new NavigationListModel(
+      volumeManager, shortcutListModel, recentItem, directoryModel);
+
+  // Check that the common root shows 3 partitions.
+  let groupedUsbs = /** @type NavigationModelFakeItem */ (model.item(2));
+  assertEquals('External Drive', groupedUsbs.label);
+  assertEquals(3, groupedUsbs.entry.getUIChildren().length);
+
+  // Add a 4th partition, which triggers NavigationListModel to recalculate.
+  volumeManager.volumeInfoList.add(MockVolumeManager.createMockVolumeInfo(
+      VolumeManagerCommon.VolumeType.REMOVABLE, 'removable:partition4',
+      'partition4', 'device/path/1'));
+
+  // Check that the common root shows 4 partitions.
+  groupedUsbs = /** @type NavigationModelFakeItem */ (model.item(2));
+  assertEquals('External Drive', groupedUsbs.label);
+  assertEquals(4, groupedUsbs.entry.getUIChildren().length);
+
+  // Remove the 4th partition, which triggers NavigationListModel to
+  // recalculate.
+  volumeManager.volumeInfoList.remove('removable:partition4');
+
+  // Check that the common root shows 3 partitions.
+  groupedUsbs = /** @type NavigationModelFakeItem */ (model.item(2));
+  assertEquals('External Drive', groupedUsbs.label);
+  assertEquals(3, groupedUsbs.entry.getUIChildren().length);
+}
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 c4e7bd9..2036972 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
@@ -800,8 +800,6 @@
 function EntryListItem(rootType, modelItem, tree) {
   const item =
       /** @type {EntryListItem} */ (new DirectoryItem(modelItem.label, tree));
-  // Get the original label id defined by TreeItem, before overwriting
-  // prototype.
   item.__proto__ = EntryListItem.prototype;
   if (window.IN_TEST) {
     item.setAttribute('dir-type', 'EntryListItem');
@@ -815,6 +813,16 @@
 
   if (rootType === VolumeManagerCommon.RootType.REMOVABLE) {
     item.setupEjectButton_(item.rowElement);
+
+    // For removable add menus for roots to be able to unmount, format, etc.
+    if (tree.contextMenuForRootItems) {
+      item.setContextMenu_(tree.contextMenuForRootItems);
+    }
+  } else {
+    // For MyFiles allow normal file operations menus.
+    if (tree.contextMenuForSubitems) {
+      item.setContextMenu_(tree.contextMenuForSubitems);
+    }
   }
 
   const icon = queryRequiredElement('.icon', item);
@@ -828,11 +836,6 @@
   icon.classList.add('item-icon');
   icon.setAttribute('root-type-icon', rootType);
 
-  // Sets up context menu of the item.
-  if (tree.contextMenuForRootItems) {
-    item.setContextMenu_(tree.contextMenuForRootItems);
-  }
-
   // Populate children of this volume.
   item.updateSubDirectories(false /* recursive */);
 
@@ -915,7 +918,7 @@
   const onSuccess = (entries) => {
     this.entries_ = entries;
     this.updateSubElementsFromList(recursive);
-    if (this.entries_.length > 0) {
+    if (this.entries_.length > 0 && this.selected) {
       this.expanded = true;
     }
     opt_successCallback && opt_successCallback();
diff --git a/ui/file_manager/file_manager/foreground/js/ui/file_table_list_unittest.js b/ui/file_manager/file_manager/foreground/js/ui/file_table_list_unittest.js
index 51deaeab..d7302002 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/file_table_list_unittest.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/file_table_list_unittest.js
@@ -79,6 +79,10 @@
     bubbles: true,
     composed: true,
     key: keyName,
+    // Get keyCode for key like A, B but not for Escape, Arrow, etc.
+    // A==65, B==66, etc.
+    keyCode: (keyName && keyName.length === 1) ? keyName.charCodeAt(0) :
+                                                 undefined,
   };
 }
 
@@ -136,7 +140,7 @@
       assertFalse(item.id === tableList.getAttribute('aria-activedescendant'));
     }
   }
- 
+
   // FileTableList always allows multiple selection.
   assertEquals('true', tableList.getAttribute('aria-multiselectable'));
 
@@ -173,10 +177,10 @@
   // listItem2 should be focused but not selected.
   assertItemIsTheLead(listItem2);
   assertItemIsSelected(listItem2, false);
- 
+
   // Only one item is selected: multiple selection should be inactive.
   assertFalse(sm.getCheckSelectMode());
-  
+
   // Ctrl+Space selects the focused item.
   tableList.dispatchEvent(new KeyboardEvent('keydown', ctrlAndKey(' ')));
   // Multiple selection mode should now be activated.
@@ -214,3 +218,82 @@
         'item ' + i + ' should not have selected attr');
   }
 }
+
+function testKeyboardOperations() {
+  // Render the FileTable on |element|.
+  const fullPage = true;
+  FileTable.decorate(
+      element, metadataModel, volumeManager, historyLoader, fullPage);
+
+  // Overwrite the selectionModel of the FileTable class (since events
+  // would be handled by cr.ui.ListSelectionModel otherwise).
+  const sm = new FileListSelectionModel();
+  const table = /** @type {FileTable} */ (element);
+  table.selectionModel = sm;
+
+  // Add FileTableList file entries, then draw and focus the table list.
+  const entries = [
+      new FakeEntry('entry1-label', VolumeManagerCommon.RootType.CROSTINI),
+      new FakeEntry('entry2-label', VolumeManagerCommon.RootType.CROSTINI),
+      new FakeEntry('entry3-label', VolumeManagerCommon.RootType.CROSTINI),
+  ];
+  const dataModel = new FileListModel(metadataModel);
+  dataModel.splice(0, 0, ...entries);
+  const tableList = /** @type {FileTableList} */ (element.list);
+  tableList.dataModel = dataModel;
+  tableList.redraw();
+  tableList.focus();
+
+  // Home key selects the first item (index 0).
+  tableList.dispatchEvent(new KeyboardEvent('keydown', key('Home')));
+  // Only 1 item selected.
+  assertEquals(1, sm.selectedIndexes.length);
+  // Index 0 should be selected and focused.
+  assertEquals(0, sm.selectedIndexes[0]);
+
+  // End key selects the last item (index 2).
+  tableList.dispatchEvent(new KeyboardEvent('keydown', key('End')));
+  // Only 1 item selected.
+  assertEquals(1, sm.selectedIndexes.length);
+  // Index 2 should be selected and focused.
+  assertEquals(2, sm.selectedIndexes[0]);
+
+  // Ctrl+A key selects all items.
+  tableList.dispatchEvent(new KeyboardEvent('keydown', ctrlAndKey('A')));
+  // All 3 items are selected.
+  assertEquals(3, sm.selectedIndexes.length);
+  assertEquals(0, sm.selectedIndexes[0]);
+  assertEquals(1, sm.selectedIndexes[1]);
+  assertEquals(2, sm.selectedIndexes[2]);
+
+  // Escape key selects all items.
+  tableList.dispatchEvent(new KeyboardEvent('keydown', key('Escape')));
+  // All 3 items are selected.
+  assertEquals(0, sm.selectedIndexes.length);
+
+  // Home key selects the first item (index 0).
+  tableList.dispatchEvent(new KeyboardEvent('keydown', key('Home')));
+  assertEquals(1, sm.selectedIndexes.length);
+  assertEquals(0, sm.selectedIndexes[0]);
+
+  // ArrowDown moves and selects next item.
+  tableList.dispatchEvent(new KeyboardEvent('keydown', key('ArrowDown')));
+  // Only index 1 should be selected.
+  assertEquals(1, sm.selectedIndexes.length);
+  assertEquals(1, sm.selectedIndexes[0]);
+
+  // ArrowUp moves and selects previous item.
+  tableList.dispatchEvent(new KeyboardEvent('keydown', key('ArrowUp')));
+  // Only index 0 should be selected.
+  assertEquals(1, sm.selectedIndexes.length);
+  assertEquals(0, sm.selectedIndexes[0]);
+
+  // ArrowLeft and ArrowRight aren't really implemented.
+  tableList.dispatchEvent(new KeyboardEvent('keydown', key('ArrowLeft')));
+  // Selected item remains the same.
+  assertEquals(1, sm.selectedIndexes.length);
+  assertEquals(0, sm.selectedIndexes[0]);
+  tableList.dispatchEvent(new KeyboardEvent('keydown', key('ArrowRight')));
+  assertEquals(1, sm.selectedIndexes.length);
+  assertEquals(0, sm.selectedIndexes[0]);
+}
diff --git a/ui/file_manager/integration_tests/file_manager/context_menu.js b/ui/file_manager/integration_tests/file_manager/context_menu.js
index 181a035f..d82f6ae 100644
--- a/ui/file_manager/integration_tests/file_manager/context_menu.js
+++ b/ui/file_manager/integration_tests/file_manager/context_menu.js
@@ -603,8 +603,30 @@
 /**
  * Checks that mutating context menu items are not present for a root within
  * My files.
+ * @param {string} itemName Name of item inside MyFiles that should be checked.
+ * @param {!Object<string, boolean>} commandStates Commands that should be
+ *     enabled for the checked item.
  */
-async function checkMyFilesRootItemContextMenu(itemName) {
+async function checkMyFilesRootItemContextMenu(itemName, commandStates) {
+  const validCmds = {
+    'copy': true,
+    'cut': true,
+    'delete': true,
+    'rename': true,
+    'zip-selection': true,
+  };
+
+  const enabledCmds = [];
+  const disabledCmds = [];
+  for (let [cmd, enabled] of Object.entries(commandStates)) {
+    chrome.test.assertTrue(cmd in validCmds, cmd + ' is not a valid command.');
+    if (enabled) {
+      enabledCmds.push(cmd);
+    } else {
+      disabledCmds.push(cmd);
+    }
+  }
+
   // Open FilesApp on Downloads.
   const appId =
       await setupAndWaitUntilReady(RootPath.DOWNLOADS, [ENTRIES.photos], []);
@@ -636,11 +658,17 @@
   // Wait for the context menu to appear.
   await remoteCall.waitForElement(appId, '#file-context-menu:not([hidden])');
 
-  // Check that the commands are neither visible nor enabled.
-  for (const commandId
-           of ['delete', 'copy', 'cut', 'zip-selection', 'rename']) {
+  // Check the enabled commands.
+  for (const commandId of enabledCmds) {
     let query = `#file-context-menu:not([hidden]) [command="#${
-        commandId}"][disabled][hidden]`;
+        commandId}"]:not([disabled])`;
+    await remoteCall.waitForElement(appId, query);
+  }
+
+  // Check the disabled commands.
+  for (const commandId of disabledCmds) {
+    let query =
+        `#file-context-menu:not([hidden]) [command="#${commandId}"][disabled]`;
     await remoteCall.waitForElement(appId, query);
   }
 
@@ -654,7 +682,14 @@
  * files.
  */
 testcase.checkDownloadsContextMenu = () => {
-  return checkMyFilesRootItemContextMenu('Downloads');
+  const commands = {
+    copy: true,
+    cut: false,
+    delete: false,
+    rename: false,
+    'zip-selection': true,
+  };
+  return checkMyFilesRootItemContextMenu('Downloads', commands);
 };
 
 /**
@@ -662,7 +697,14 @@
  * files.
  */
 testcase.checkPlayFilesContextMenu = () => {
-  return checkMyFilesRootItemContextMenu('Play files');
+  const commands = {
+    copy: false,
+    cut: false,
+    delete: false,
+    rename: false,
+    'zip-selection': false,
+  };
+  return checkMyFilesRootItemContextMenu('Play files', commands);
 };
 
 /**
@@ -670,7 +712,14 @@
  * My files.
  */
 testcase.checkLinuxFilesContextMenu = () => {
-  return checkMyFilesRootItemContextMenu('Linux files');
+  const commands = {
+    copy: false,
+    cut: false,
+    delete: false,
+    rename: false,
+    'zip-selection': false,
+  };
+  return checkMyFilesRootItemContextMenu('Linux files', commands);
 };
 
 /**
@@ -754,4 +803,4 @@
       appId,
       '#directory-tree-context-menu:not([hidden]) ' +
           '[command="#new-folder"]:not([hidden])');
-};
\ No newline at end of file
+};
diff --git a/ui/file_manager/integration_tests/file_manager/directory_tree_context_menu.js b/ui/file_manager/integration_tests/file_manager/directory_tree_context_menu.js
index 74d58b1..17e480c 100644
--- a/ui/file_manager/integration_tests/file_manager/directory_tree_context_menu.js
+++ b/ui/file_manager/integration_tests/file_manager/directory_tree_context_menu.js
@@ -605,9 +605,14 @@
 testcase.dirContextMenuMyFiles = async () => {
   const myFilesMenus = [
     ['#share-with-linux', true],
+    ['#new-folder', true],
   ];
   const downloadsMenus = [
+    ['#cut', false],
+    ['#copy', true],
+    ['#paste-into-folder', false],
     ['#share-with-linux', true],
+    ['#delete', false],
     ['#new-folder', true],
   ];
   const photosMenus = [
@@ -630,7 +635,7 @@
 
   // Check the context menu is on desired state for MyFiles.
   await checkContextMenu(
-      appId, myFilesQuery, myFilesMenus, true /* rootMenu */);
+      appId, myFilesQuery, myFilesMenus, false /* rootMenu */);
 
   // Check the context menu for MyFiles>Downloads.
   await checkContextMenu(
diff --git a/ui/file_manager/integration_tests/file_manager/metadata.js b/ui/file_manager/integration_tests/file_manager/metadata.js
index 3618e06..b4a7bb0f 100644
--- a/ui/file_manager/integration_tests/file_manager/metadata.js
+++ b/ui/file_manager/integration_tests/file_manager/metadata.js
@@ -121,7 +121,7 @@
   // Navigate 2 folders deep, because navigating in directory tree might
   // trigger further metadata fetches.
   await remoteCall.navigateWithDirectoryTree(
-      appId, '/root/photos1/folder1', 'My Drive');
+      appId, '/root/photos1/folder1', 'My Drive', 'drive');
 
   // Fetch the metadata stats.
   const metadataStats =
@@ -216,11 +216,12 @@
 
   // Open Files app on Drive.
   const appId = await setupAndWaitUntilReady(RootPath.DRIVE, entries, entries);
+  console.log('setupAndWaitUntilReady finished!');
 
   // Navigate only 1 folder deep,which is slightly different from
   // metadatatDrive test.
   await remoteCall.navigateWithDirectoryTree(
-      appId, '/root/folder1', 'My Drive');
+      appId, '/root/folder1', 'My Drive', 'drive');
 
   // Wait for the metadata stats to reach the desired count.
   // File list component, doesn't display all files at once for performance
@@ -287,7 +288,7 @@
 
   // Navigate to Team Drives root.
   await remoteCall.navigateWithDirectoryTree(
-      appId, '/team_drives', 'Team Drives');
+      appId, '/team_drives', 'Team Drives', 'drive');
 
   // Expand Team Drives, because expanding might need metadata.
   const expandIcon = teamDriveTreeItem + ' > .tree-row > .expand-icon';
diff --git a/ui/file_manager/integration_tests/file_manager/my_files.js b/ui/file_manager/integration_tests/file_manager/my_files.js
index aab3fb6..a06ea6d 100644
--- a/ui/file_manager/integration_tests/file_manager/my_files.js
+++ b/ui/file_manager/integration_tests/file_manager/my_files.js
@@ -294,3 +294,40 @@
       appId, expectedRows2,
       {ignoreFileSize: true, ignoreLastModifiedTime: true});
 };
+
+/**
+ * Tests that MyFiles doesn't expand when it isn't selected.
+ */
+testcase.myFilesExpandWhenSelected = async () => {
+  // Open Files app on local Downloads.
+  const appId = await setupAndWaitUntilReady(
+      RootPath.DOWNLOADS, [ENTRIES.photos], [ENTRIES.beautiful]);
+
+  // Collapse MyFiles.
+  const myFiles = '#directory-tree [entry-label="My files"]';
+  let expandIcon = myFiles + '[expanded] > .tree-row > .expand-icon';
+  await remoteCall.waitAndClickElement(appId, expandIcon);
+  await remoteCall.waitForElement(appId, myFiles + ':not([expanded])');
+
+  // Expand Google Drive.
+  const driveGrandRoot = '#directory-tree [entry-label="Google Drive"]';
+  expandIcon = driveGrandRoot + ' > .tree-row > .expand-icon';
+  await remoteCall.waitAndClickElement(appId, expandIcon);
+
+  // Wait for its subtree to expand and display its children.
+  const expandedSubItems =
+      driveGrandRoot + ' > .tree-children[expanded] > .tree-item';
+  await remoteCall.waitForElement(appId, expandedSubItems);
+
+  // Click on My Drive
+  const myDrive = '#directory-tree [entry-label="My Drive"]';
+  await remoteCall.waitAndClickElement(appId, myDrive);
+
+  // Wait for My Drive to selected.
+  await remoteCall.waitForFiles(
+      appId, [ENTRIES.beautiful.getExpectedRow()],
+      {ignoreFileSize: true, ignoreLastModifiedTime: true});
+
+  // Check that MyFiles is still collapsed.
+  await remoteCall.waitForElement(appId, myFiles + ':not([expanded])');
+};
diff --git a/ui/file_manager/integration_tests/file_manager/recents.js b/ui/file_manager/integration_tests/file_manager/recents.js
index 6a66f650..69c387cd 100644
--- a/ui/file_manager/integration_tests/file_manager/recents.js
+++ b/ui/file_manager/integration_tests/file_manager/recents.js
@@ -14,22 +14,8 @@
   await remoteCall.waitForFiles(appId, files);
 
   // Select all the files and check that the delete button isn't visible.
-  // First, wait for the gear menu button to appear.
-  await remoteCall.waitForElement(appId, '#gear-button');
-
-  // Click the gear menu button.
-  chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
-      'fakeMouseClick', appId, ['#gear-button']));
-  // Check: #select-all command is shown, and enabled (there are files).
-  await remoteCall.waitForElement(
-      appId,
-      '#gear-menu:not([hidden]) cr-menu-item' +
-          '[command=\'#select-all\']' +
-          ':not([disabled]):not([hidden])');
-
-  // Click on the #gear-menu-select-all item.
-  chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
-      'fakeMouseClick', appId, ['#gear-menu-select-all']));
+  const ctrlA = ['#file-list', 'a', true, false, false];
+  await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlA);
 
   // Check: the file-list should be selected.
   await remoteCall.waitForElement(appId, '#file-list li[selected]');
diff --git a/ui/native_theme/common_theme.cc b/ui/native_theme/common_theme.cc
index 2017e90c..8d7e0fa 100644
--- a/ui/native_theme/common_theme.cc
+++ b/ui/native_theme/common_theme.cc
@@ -89,6 +89,10 @@
         return gfx::kGoogleBlue300;
       case NativeTheme::kColorId_HighlightedMenuItemBackgroundColor:
         return SkColorSetRGB(0x32, 0x36, 0x39);
+      case NativeTheme::kColorId_MenuItemAlertBackgroundColorMax:
+        return SkColorSetA(gfx::kGoogleGrey100, 0x1A);
+      case NativeTheme::kColorId_MenuItemAlertBackgroundColorMin:
+        return SkColorSetA(gfx::kGoogleGrey100, 0x4D);
       case NativeTheme::kColorId_TreeBackground:
         return gfx::kGoogleGrey800;
       case NativeTheme::kColorId_TreeText:
diff --git a/ui/ozone/platform/drm/gpu/drm_gpu_display_manager_unittest.cc b/ui/ozone/platform/drm/gpu/drm_gpu_display_manager_unittest.cc
index 92c477d..e53eb9d 100644
--- a/ui/ozone/platform/drm/gpu/drm_gpu_display_manager_unittest.cc
+++ b/ui/ozone/platform/drm/gpu/drm_gpu_display_manager_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "ui/ozone/platform/drm/gpu/drm_gpu_display_manager.h"
 
+#include <xf86drm.h>
 #include <string>
 #include <unordered_map>
 #include <utility>
@@ -76,7 +77,9 @@
   // reallocation. It can help to test the code block of crtc reallocation.
   for (auto connector_iter = connector_resources_.rbegin();
        connector_iter != connector_resources_.rend(); connector_iter++) {
-    drmModeConnectorPtr connector = new drmModeConnector;
+    drmModeConnectorPtr connector = nullptr;
+    connector = static_cast<drmModeConnectorPtr>(drmMalloc(sizeof(*connector)));
+    DCHECK(connector);
     connector->connector_id = *connector_iter;
     connector->encoders = nullptr;
     connector->prop_values = nullptr;
@@ -85,7 +88,8 @@
 
     drmModeCrtcPtr crtc = nullptr;
     if (crtc_iter != crtc_resources_.rend()) {
-      crtc = new drmModeCrtc;
+      crtc = static_cast<drmModeCrtcPtr>(drmMalloc(sizeof(*crtc)));
+      DCHECK(crtc);
       crtc->crtc_id = *crtc_iter;
       intermediate_mappings_[connector->connector_id] = crtc->crtc_id;
       crtc_iter++;
@@ -213,13 +217,7 @@
       screen_manager_.get(), device_manager_.get()));
 }
 
-// Crashes on ChromeOS ASan LSan. https://crbug.com/937638.
-#if defined(OS_CHROMEOS)
-#define MAYBE_GetDisplays DISABLED_GetDisplays
-#else
-#define MAYBE_GetDisplays GetDisplays
-#endif
-TEST_F(DrmGpuDisplayManagerTest, MAYBE_GetDisplays) {
+TEST_F(DrmGpuDisplayManagerTest, GetDisplays) {
   const uint32_t display_id1 = 1, display_id2 = 2, display_id3 = 3;
 
   // Connect two displays with device then update |hardware_infos_|.
diff --git a/ui/views/mus/desktop_window_tree_host_mus.cc b/ui/views/mus/desktop_window_tree_host_mus.cc
index d29d80d..887cfbd3 100644
--- a/ui/views/mus/desktop_window_tree_host_mus.cc
+++ b/ui/views/mus/desktop_window_tree_host_mus.cc
@@ -1031,7 +1031,8 @@
     const gfx::Rect& bounds,
     const viz::LocalSurfaceIdAllocation& local_surface_id_allocation) {
   gfx::Rect final_bounds = bounds;
-  if (bounds_in_dip().size() != bounds.size()) {
+  // If the server initiated the bounds change, then we need to honor it.
+  if (!in_set_bounds_from_server() && bounds_in_dip().size() != bounds.size()) {
     gfx::Size size = bounds.size();
     size.SetToMax(native_widget_delegate_->GetMinimumSize());
     const gfx::Size max_size = native_widget_delegate_->GetMaximumSize();
@@ -1048,8 +1049,10 @@
     const viz::LocalSurfaceIdAllocation& local_surface_id_allocation) {
   // NOTE: in typical usage SetBounds() is called and not this, but as
   // WindowTreeHost exposes SetBoundsInPixels() this function may be called too.
+  // If the server initiated the bounds change, then we need to honor it.
   gfx::Rect final_bounds_in_pixels = bounds_in_pixels;
-  if (GetBoundsInPixels().size() != bounds_in_pixels.size()) {
+  if (!in_set_bounds_from_server() &&
+      GetBoundsInPixels().size() != bounds_in_pixels.size()) {
     gfx::Size size = bounds_in_pixels.size();
     size.SetToMax(gfx::ConvertSizeToPixel(
         GetScaleFactor(), native_widget_delegate_->GetMinimumSize()));
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 d6cea1c..d39534a 100644
--- a/ui/views/mus/desktop_window_tree_host_mus_unittest.cc
+++ b/ui/views/mus/desktop_window_tree_host_mus_unittest.cc
@@ -21,6 +21,7 @@
 #include "ui/aura/mus/window_mus.h"
 #include "ui/aura/mus/window_tree_client.h"
 #include "ui/aura/mus/window_tree_client_test_observer.h"
+#include "ui/aura/mus/window_tree_host_mus.h"
 #include "ui/aura/test/mus/change_completion_waiter.h"
 #include "ui/aura/test/mus/test_window_tree.h"
 #include "ui/aura/test/mus/window_tree_client_test_api.h"
@@ -1164,4 +1165,23 @@
   EXPECT_EQ(bounds, widget->GetNativeWindow()->GetBoundsInScreen());
 }
 
+TEST_F(DesktopWindowTreeHostMusTest, ServerBoundsChangeIngoresMinMax) {
+  gfx::Size min_size(100, 100);
+  gfx::Size max_size(200, 200);
+  auto* delegate = new StaticSizedWidgetDelegate(min_size, max_size);
+  std::unique_ptr<Widget> widget = CreateWidget(delegate);
+  widget->Show();
+
+  // Setting the bounds to a size bigger than max should result in going to
+  // max.
+  widget->SetBounds(gfx::Rect(0, 0, 250, 250));
+  EXPECT_EQ(gfx::Size(200, 200), widget->GetWindowBoundsInScreen().size());
+
+  // Changes to the bounds from the server should not consider the min/max.
+  const gfx::Rect server_bounds(1, 2, 250, 251);
+  static_cast<aura::WindowTreeHostMus*>(widget->GetNativeWindow()->GetHost())
+      ->SetBoundsFromServer(server_bounds, viz::LocalSurfaceIdAllocation());
+  EXPECT_EQ(server_bounds, widget->GetWindowBoundsInScreen());
+}
+
 }  // namespace views
diff --git a/ui/webui/resources/cr_elements/shared_vars_css.html b/ui/webui/resources/cr_elements/shared_vars_css.html
index 6f72fe2..bd690c0 100644
--- a/ui/webui/resources/cr_elements/shared_vars_css.html
+++ b/ui/webui/resources/cr_elements/shared_vars_css.html
@@ -181,7 +181,7 @@
       padding: 0 var(--cr-section-padding);
     }
 
-    --cr-centered-card-container-vertical-margin: 21px;
+    --cr-section-vertical-margin: 21px;
 
     --cr-centered-card-max-width: 680px;
     --cr-centered-card-container: {
@@ -195,6 +195,17 @@
       width: 96%;
     }
 
+    --cr-card-external-title: {
+        color: var(--cr-primary-text-color);
+        font-size: 108%;
+        font-weight: 400;
+        letter-spacing: .25px;
+        margin-bottom: 12px;
+        margin-top: var(--cr-section-vertical-margin);
+        padding-bottom: 4px;
+        padding-top: 8px;
+    }
+
     --cr-text-elide: {
       overflow: hidden;
       text-overflow: ellipsis;